Convert signal.js and preload.js to Typescript

This commit is contained in:
Scott Nonnenberg 2022-06-13 14:39:35 -07:00 committed by GitHub
parent e18510e41c
commit 2464e0a9c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 2113 additions and 1848 deletions

View file

@ -167,7 +167,13 @@ module.exports = {
overrides: [
{
files: ['ts/**/*.ts', 'ts/**/*.tsx', 'app/**/*.ts'],
files: [
'ts/**/*.ts',
'ts/**/*.tsx',
'app/**/*.ts',
'sticker-creator/**/*.ts',
'sticker-creator/**/*.tsx',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
@ -186,26 +192,6 @@ module.exports = {
],
rules: typescriptRules,
},
{
files: ['sticker-creator/**/*.ts', 'sticker-creator/**/*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './sticker-creator/tsconfig.json',
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'airbnb-typescript-prettier',
],
rules: typescriptRules,
},
{
files: ['**/*.stories.tsx', 'ts/build/**', 'ts/test-*/**'],
rules: {

View file

@ -27,7 +27,7 @@ process.env.NODE_CONFIG_DIR = join(__dirname, '..', 'config');
if (getEnvironment() === Environment.Production) {
// harden production config against the local env
process.env.NODE_CONFIG = '';
process.env.NODE_CONFIG_STRICT_MODE = 'true';
process.env.NODE_CONFIG_STRICT_MODE = '';
process.env.HOSTNAME = '';
process.env.NODE_APP_INSTANCE = '';
process.env.ALLOW_CONFIG_MUTATIONS = '';

View file

@ -42,12 +42,14 @@ import { createSupportUrl } from '../ts/util/createSupportUrl';
import { missingCaseError } from '../ts/util/missingCaseError';
import { strictAssert } from '../ts/util/assert';
import { consoleLogger } from '../ts/util/consoleLogger';
import type { ThemeSettingType } from '../ts/types/Storage.d';
import type { ThemeSettingType } from '../ts/types/StorageUIKeys';
import { ThemeType } from '../ts/types/Util';
import './startup_config';
import type { ConfigType } from './config';
import type { RendererConfigType } from '../ts/types/RendererConfig';
import { rendererConfigSchema } from '../ts/types/RendererConfig';
import config from './config';
import {
Environment,
@ -349,25 +351,29 @@ function getLocale(): LocaleType {
return locale;
}
function prepareFileUrl(
type PrepareUrlOptions = { forCalling?: boolean; forCamera?: boolean };
async function prepareFileUrl(
pathSegments: ReadonlyArray<string>,
moreKeys?: undefined | Record<string, unknown>
): string {
options: PrepareUrlOptions = {}
): Promise<string> {
const filePath = join(...pathSegments);
const fileUrl = pathToFileURL(filePath);
return prepareUrl(fileUrl, moreKeys);
return prepareUrl(fileUrl, options);
}
function prepareUrl(
async function prepareUrl(
url: URL,
moreKeys?: undefined | Record<string, unknown>
): string {
return setUrlSearchParams(url, {
{ forCalling, forCamera }: PrepareUrlOptions = {}
): Promise<string> {
const theme = await getResolvedThemeSetting();
const urlParams: RendererConfigType = {
name: packageJson.productName,
locale: locale ? locale.name : undefined,
locale: getLocale().name,
version: app.getVersion(),
buildCreation: config.get<number | undefined>('buildCreation'),
buildExpiration: config.get<number | undefined>('buildExpiration'),
buildCreation: config.get<number>('buildCreation'),
buildExpiration: config.get<number>('buildExpiration'),
serverUrl: config.get<string>('serverUrl'),
storageUrl: config.get<string>('storageUrl'),
updatesUrl: config.get<string>('updatesUrl'),
@ -377,29 +383,52 @@ function prepareUrl(
config.get<string | null>('directoryEnclaveId') || undefined,
directoryTrustAnchor:
config.get<string | null>('directoryTrustAnchor') || undefined,
directoryV2Url: config.get<string>('directoryV2Url'),
directoryV2PublicKey: config.get<string>('directoryV2PublicKey'),
directoryV2CodeHashes: config.get<string>('directoryV2CodeHashes'),
directoryV2Url: config.get<string | null>('directoryV2Url') || undefined,
directoryV2PublicKey:
config.get<string | null>('directoryV2PublicKey') || undefined,
directoryV2CodeHashes:
config.get<Array<string> | null>('directoryV2CodeHashes') || undefined,
cdnUrl0: config.get<ConfigType>('cdn').get<string>('0'),
cdnUrl2: config.get<ConfigType>('cdn').get<string>('2'),
certificateAuthority: config.get<string>('certificateAuthority'),
environment: enableCI ? 'production' : getEnvironment(),
enableCI: enableCI ? 'true' : '',
node_version: process.versions.node,
environment: enableCI ? Environment.Production : getEnvironment(),
enableCI,
nodeVersion: process.versions.node,
hostname: os.hostname(),
appInstance: process.env.NODE_APP_INSTANCE,
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
appInstance: process.env.NODE_APP_INSTANCE || undefined,
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy || undefined,
contentProxyUrl: config.get<string>('contentProxyUrl'),
sfuUrl: config.get('sfuUrl'),
reducedMotionSetting: animationSettings.prefersReducedMotion ? 'true' : '',
reducedMotionSetting: animationSettings.prefersReducedMotion,
serverPublicParams: config.get<string>('serverPublicParams'),
serverTrustRoot: config.get<string>('serverTrustRoot'),
theme,
appStartInitialSpellcheckSetting,
userDataPath: app.getPath('userData'),
homePath: app.getPath('home'),
crashDumpsPath: app.getPath('crashDumps'),
...moreKeys,
}).href;
// Only used by the main window
isMainWindowFullScreen: Boolean(mainWindow?.isFullScreen()),
// Only for tests
argv: JSON.stringify(process.argv),
// Only for permission popup window
forCalling: Boolean(forCalling),
forCamera: Boolean(forCamera),
};
const parsed = rendererConfigSchema.safeParse(urlParams);
if (!parsed.success) {
throw new Error(
`prepareUrl: Failed to parse renderer config ${JSON.stringify(
parsed.error.flatten()
)}`
);
}
return setUrlSearchParams(url, { config: JSON.stringify(parsed.data) }).href;
}
async function handleUrl(event: Electron.Event, rawTarget: string) {
@ -607,7 +636,9 @@ async function createWindow() {
contextIsolation: false,
preload: join(
__dirname,
usePreloadBundle ? '../preload.bundle.js' : '../preload.js'
usePreloadBundle
? '../preload.bundle.js'
: '../ts/windows/main/preload.js'
),
spellcheck: await getSpellCheckSetting(),
backgroundThrottling: isThrottlingEnabled,
@ -738,22 +769,10 @@ async function createWindow() {
// This is a fallback in case we drop an event for some reason.
setInterval(setWindowFocus, 10000);
const moreKeys = {
isFullScreen: String(Boolean(mainWindow.isFullScreen())),
resolvedTheme: await getResolvedThemeSetting(),
};
if (getEnvironment() === Environment.Test) {
mainWindow.loadURL(
prepareFileUrl([__dirname, '../test/index.html'], {
...moreKeys,
argv: JSON.stringify(process.argv),
})
);
mainWindow.loadURL(await prepareFileUrl([__dirname, '../test/index.html']));
} else {
mainWindow.loadURL(
prepareFileUrl([__dirname, '../background.html'], moreKeys)
);
mainWindow.loadURL(await prepareFileUrl([__dirname, '../background.html']));
}
if (!enableCI && config.get<boolean>('openDevTools')) {
@ -1039,7 +1058,7 @@ function setupAsStandalone() {
}
let screenShareWindow: BrowserWindow | undefined;
function showScreenShareWindow(sourceName: string) {
async function showScreenShareWindow(sourceName: string) {
if (screenShareWindow) {
screenShareWindow.showInactive();
return;
@ -1078,7 +1097,9 @@ function showScreenShareWindow(sourceName: string) {
handleCommonWindowEvents(screenShareWindow);
screenShareWindow.loadURL(prepareFileUrl([__dirname, '../screenShare.html']));
screenShareWindow.loadURL(
await prepareFileUrl([__dirname, '../screenShare.html'])
);
screenShareWindow.on('closed', () => {
screenShareWindow = undefined;
@ -1128,7 +1149,7 @@ async function showAbout() {
handleCommonWindowEvents(aboutWindow, titleBarOverlay);
aboutWindow.loadURL(prepareFileUrl([__dirname, '../about.html']));
aboutWindow.loadURL(await prepareFileUrl([__dirname, '../about.html']));
aboutWindow.on('closed', () => {
aboutWindow = undefined;
@ -1175,7 +1196,7 @@ async function showSettingsWindow() {
handleCommonWindowEvents(settingsWindow, titleBarOverlay);
settingsWindow.loadURL(prepareFileUrl([__dirname, '../settings.html']));
settingsWindow.loadURL(await prepareFileUrl([__dirname, '../settings.html']));
settingsWindow.on('closed', () => {
settingsWindow = undefined;
@ -1254,7 +1275,7 @@ async function showStickerCreator() {
)
: prepareFileUrl([__dirname, '../sticker-creator/dist/index.html']);
stickerCreatorWindow.loadURL(appUrl);
stickerCreatorWindow.loadURL(await appUrl);
stickerCreatorWindow.on('closed', () => {
stickerCreatorWindow = undefined;
@ -1283,7 +1304,6 @@ async function showDebugLogWindow() {
const titleBarOverlay = await getTitleBarOverlay();
const theme = await getThemeSetting();
const options = {
width: 700,
height: 500,
@ -1315,7 +1335,7 @@ async function showDebugLogWindow() {
handleCommonWindowEvents(debugLogWindow, titleBarOverlay);
debugLogWindow.loadURL(
prepareFileUrl([__dirname, '../debug_log.html'], { theme })
await prepareFileUrl([__dirname, '../debug_log.html'])
);
debugLogWindow.on('closed', () => {
@ -1346,7 +1366,6 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
return;
}
const theme = await getThemeSetting();
const size = mainWindow.getSize();
const options = {
width: Math.min(400, size[0]),
@ -1374,8 +1393,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
handleCommonWindowEvents(permissionsPopupWindow);
permissionsPopupWindow.loadURL(
prepareFileUrl([__dirname, '../permissions_popup.html'], {
theme,
await prepareFileUrl([__dirname, '../permissions_popup.html'], {
forCalling,
forCamera,
})
@ -1629,7 +1647,7 @@ app.on('ready', async () => {
const backgroundColor = await getBackgroundColor({ ephemeralOnly: true });
// eslint-disable-next-line more/no-then
Promise.race([sqlInitPromise, timeout]).then(maybeTimeout => {
Promise.race([sqlInitPromise, timeout]).then(async maybeTimeout => {
if (maybeTimeout !== 'timeout') {
return;
}
@ -1665,7 +1683,7 @@ app.on('ready', async () => {
loadingWindow = undefined;
});
loadingWindow.loadURL(prepareFileUrl([__dirname, '../loading.html']));
loadingWindow.loadURL(await prepareFileUrl([__dirname, '../loading.html']));
});
try {

View file

@ -175,10 +175,6 @@
src="ts/backbone/reliable_trigger.js"
></script>
<script
type="text/javascript"
src="js/views/react_wrapper_view.js"
></script>
<script
type="text/javascript"
src="ts/shims/showConfirmationDialog.js"

View file

@ -1,16 +0,0 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
module.exports = {
env: {
browser: false,
commonjs: true,
node: false,
},
globals: {
console: true,
},
parserOptions: {
sourceType: 'module',
},
};

View file

@ -1,409 +0,0 @@
// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// The idea with this file is to make it webpackable for the style guide
const Backbone = require('../../ts/backbone');
const Crypto = require('../../ts/Crypto');
const Curve = require('../../ts/Curve');
const {
start: conversationControllerStart,
} = require('../../ts/ConversationController');
const Data = require('../../ts/sql/Client').default;
const EmojiLib = require('../../ts/components/emoji/lib');
const Groups = require('../../ts/groups');
const GroupChange = require('../../ts/groupChange');
const OS = require('../../ts/OS');
const Stickers = require('../../ts/types/Stickers');
const RemoteConfig = require('../../ts/RemoteConfig');
const Util = require('../../ts/util');
// Components
const {
AttachmentList,
} = require('../../ts/components/conversation/AttachmentList');
const { ChatColorPicker } = require('../../ts/components/ChatColorPicker');
const {
ConfirmationDialog,
} = require('../../ts/components/ConfirmationDialog');
const {
ContactModal,
} = require('../../ts/components/conversation/ContactModal');
const { Emojify } = require('../../ts/components/conversation/Emojify');
const {
MessageDetail,
} = require('../../ts/components/conversation/MessageDetail');
const { Quote } = require('../../ts/components/conversation/Quote');
const {
StagedLinkPreview,
} = require('../../ts/components/conversation/StagedLinkPreview');
const {
DisappearingTimeDialog,
} = require('../../ts/components/DisappearingTimeDialog');
const {
SystemTraySettingsCheckboxes,
} = require('../../ts/components/conversation/SystemTraySettingsCheckboxes');
// State
const {
createChatColorPicker,
} = require('../../ts/state/roots/createChatColorPicker');
const {
createConversationDetails,
} = require('../../ts/state/roots/createConversationDetails');
const { createApp } = require('../../ts/state/roots/createApp');
const {
createForwardMessageModal,
} = require('../../ts/state/roots/createForwardMessageModal');
const {
createGroupLinkManagement,
} = require('../../ts/state/roots/createGroupLinkManagement');
const {
createGroupV1MigrationModal,
} = require('../../ts/state/roots/createGroupV1MigrationModal');
const {
createGroupV2JoinModal,
} = require('../../ts/state/roots/createGroupV2JoinModal');
const { createLeftPane } = require('../../ts/state/roots/createLeftPane');
const {
createMessageDetail,
} = require('../../ts/state/roots/createMessageDetail');
const {
createConversationNotificationsSettings,
} = require('../../ts/state/roots/createConversationNotificationsSettings');
const {
createGroupV2Permissions,
} = require('../../ts/state/roots/createGroupV2Permissions');
const {
createPendingInvites,
} = require('../../ts/state/roots/createPendingInvites');
const {
createSafetyNumberViewer,
} = require('../../ts/state/roots/createSafetyNumberViewer');
const {
createStickerManager,
} = require('../../ts/state/roots/createStickerManager');
const {
createStickerPreviewModal,
} = require('../../ts/state/roots/createStickerPreviewModal');
const {
createShortcutGuideModal,
} = require('../../ts/state/roots/createShortcutGuideModal');
const { createStore } = require('../../ts/state/createStore');
const appDuck = require('../../ts/state/ducks/app');
const callingDuck = require('../../ts/state/ducks/calling');
const conversationsDuck = require('../../ts/state/ducks/conversations');
const emojisDuck = require('../../ts/state/ducks/emojis');
const expirationDuck = require('../../ts/state/ducks/expiration');
const itemsDuck = require('../../ts/state/ducks/items');
const linkPreviewsDuck = require('../../ts/state/ducks/linkPreviews');
const networkDuck = require('../../ts/state/ducks/network');
const searchDuck = require('../../ts/state/ducks/search');
const stickersDuck = require('../../ts/state/ducks/stickers');
const updatesDuck = require('../../ts/state/ducks/updates');
const userDuck = require('../../ts/state/ducks/user');
const conversationsSelectors = require('../../ts/state/selectors/conversations');
const searchSelectors = require('../../ts/state/selectors/search');
// Types
const AttachmentType = require('../../ts/types/Attachment');
const VisualAttachment = require('../../ts/types/VisualAttachment');
const MessageType = require('../../ts/types/Message2');
const { UUID } = require('../../ts/types/UUID');
const { Address } = require('../../ts/types/Address');
const { QualifiedAddress } = require('../../ts/types/QualifiedAddress');
// Processes / Services
const {
initializeGroupCredentialFetcher,
} = require('../../ts/services/groupCredentialFetcher');
const {
initializeNetworkObserver,
} = require('../../ts/services/networkObserver');
const {
initializeUpdateListener,
} = require('../../ts/services/updateListener');
const { calling } = require('../../ts/services/calling');
const {
enableStorageService,
eraseAllStorageServiceState,
runStorageServiceSyncJob,
storageServiceUploadJob,
} = require('../../ts/services/storage');
function initializeMigrations({
userDataPath,
getRegionCode,
Attachments,
Type,
VisualType,
logger,
}) {
if (!Attachments) {
return null;
}
const {
createAbsolutePathGetter,
createReader,
createWriterForExisting,
createWriterForNew,
createDoesExist,
getAvatarsPath,
getDraftPath,
getPath,
getStickersPath,
getBadgesPath,
getTempPath,
openFileInFolder,
saveAttachmentToDisk,
} = Attachments;
const {
getImageDimensions,
makeImageThumbnail,
makeObjectUrl,
makeVideoScreenshot,
revokeObjectUrl,
} = VisualType;
const attachmentsPath = getPath(userDataPath);
const readAttachmentData = createReader(attachmentsPath);
const loadAttachmentData = Type.loadData(readAttachmentData);
const loadContactData = MessageType.loadContactData(loadAttachmentData);
const loadPreviewData = MessageType.loadPreviewData(loadAttachmentData);
const loadQuoteData = MessageType.loadQuoteData(loadAttachmentData);
const loadStickerData = MessageType.loadStickerData(loadAttachmentData);
const getAbsoluteAttachmentPath = createAbsolutePathGetter(attachmentsPath);
const deleteOnDisk = Attachments.createDeleter(attachmentsPath);
const writeNewAttachmentData = createWriterForNew(attachmentsPath);
const copyIntoAttachmentsDirectory =
Attachments.copyIntoAttachmentsDirectory(attachmentsPath);
const doesAttachmentExist = createDoesExist(attachmentsPath);
const stickersPath = getStickersPath(userDataPath);
const writeNewStickerData = createWriterForNew(stickersPath);
const getAbsoluteStickerPath = createAbsolutePathGetter(stickersPath);
const deleteSticker = Attachments.createDeleter(stickersPath);
const readStickerData = createReader(stickersPath);
const badgesPath = getBadgesPath(userDataPath);
const getAbsoluteBadgeImageFilePath = createAbsolutePathGetter(badgesPath);
const writeNewBadgeImageFileData = createWriterForNew(badgesPath, '.svg');
const tempPath = getTempPath(userDataPath);
const getAbsoluteTempPath = createAbsolutePathGetter(tempPath);
const writeNewTempData = createWriterForNew(tempPath);
const deleteTempFile = Attachments.createDeleter(tempPath);
const readTempData = createReader(tempPath);
const copyIntoTempDirectory =
Attachments.copyIntoAttachmentsDirectory(tempPath);
const draftPath = getDraftPath(userDataPath);
const getAbsoluteDraftPath = createAbsolutePathGetter(draftPath);
const writeNewDraftData = createWriterForNew(draftPath);
const deleteDraftFile = Attachments.createDeleter(draftPath);
const readDraftData = createReader(draftPath);
const avatarsPath = getAvatarsPath(userDataPath);
const getAbsoluteAvatarPath = createAbsolutePathGetter(avatarsPath);
const writeNewAvatarData = createWriterForNew(avatarsPath);
const deleteAvatar = Attachments.createDeleter(avatarsPath);
return {
attachmentsPath,
copyIntoAttachmentsDirectory,
copyIntoTempDirectory,
deleteAttachmentData: deleteOnDisk,
deleteAvatar,
deleteDraftFile,
deleteExternalMessageFiles: MessageType.deleteAllExternalFiles({
deleteAttachmentData: Type.deleteData(deleteOnDisk),
deleteOnDisk,
}),
deleteSticker,
deleteTempFile,
doesAttachmentExist,
getAbsoluteAttachmentPath,
getAbsoluteAvatarPath,
getAbsoluteBadgeImageFilePath,
getAbsoluteDraftPath,
getAbsoluteStickerPath,
getAbsoluteTempPath,
loadAttachmentData,
loadContactData,
loadMessage: MessageType.createAttachmentLoader(loadAttachmentData),
loadPreviewData,
loadQuoteData,
loadStickerData,
openFileInFolder,
readAttachmentData,
readDraftData,
readStickerData,
readTempData,
saveAttachmentToDisk,
processNewAttachment: attachment =>
MessageType.processNewAttachment(attachment, {
writeNewAttachmentData,
getAbsoluteAttachmentPath,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
}),
processNewSticker: stickerData =>
MessageType.processNewSticker(stickerData, {
writeNewStickerData,
getAbsoluteStickerPath,
getImageDimensions,
logger,
}),
processNewEphemeralSticker: stickerData =>
MessageType.processNewSticker(stickerData, {
writeNewStickerData: writeNewTempData,
getAbsoluteStickerPath: getAbsoluteTempPath,
getImageDimensions,
logger,
}),
upgradeMessageSchema: (message, options = {}) => {
const { maxVersion } = options;
return MessageType.upgradeSchema(message, {
writeNewAttachmentData,
getRegionCode,
getAbsoluteAttachmentPath,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
maxVersion,
getAbsoluteStickerPath,
writeNewStickerData,
});
},
writeMessageAttachments: MessageType.createAttachmentDataWriter({
writeExistingAttachmentData: createWriterForExisting(attachmentsPath),
logger,
}),
writeNewAttachmentData: createWriterForNew(attachmentsPath),
writeNewAvatarData,
writeNewDraftData,
writeNewBadgeImageFileData,
};
}
exports.setup = (options = {}) => {
const { Attachments, userDataPath, getRegionCode, logger } = options;
const Migrations = initializeMigrations({
userDataPath,
getRegionCode,
Attachments,
Type: AttachmentType,
VisualType: VisualAttachment,
logger,
});
const Components = {
AttachmentList,
ChatColorPicker,
ConfirmationDialog,
ContactModal,
Emojify,
MessageDetail,
Quote,
StagedLinkPreview,
DisappearingTimeDialog,
SystemTraySettingsCheckboxes,
};
const Roots = {
createApp,
createChatColorPicker,
createConversationDetails,
createForwardMessageModal,
createGroupLinkManagement,
createGroupV1MigrationModal,
createGroupV2JoinModal,
createGroupV2Permissions,
createLeftPane,
createMessageDetail,
createConversationNotificationsSettings,
createPendingInvites,
createSafetyNumberViewer,
createShortcutGuideModal,
createStickerManager,
createStickerPreviewModal,
};
const Ducks = {
app: appDuck,
calling: callingDuck,
conversations: conversationsDuck,
emojis: emojisDuck,
expiration: expirationDuck,
items: itemsDuck,
linkPreviews: linkPreviewsDuck,
network: networkDuck,
updates: updatesDuck,
user: userDuck,
search: searchDuck,
stickers: stickersDuck,
};
const Selectors = {
conversations: conversationsSelectors,
search: searchSelectors,
};
const Services = {
calling,
enableStorageService,
eraseAllStorageServiceState,
initializeGroupCredentialFetcher,
initializeNetworkObserver,
initializeUpdateListener,
runStorageServiceSyncJob,
storageServiceUploadJob,
};
const State = {
createStore,
Roots,
Ducks,
Selectors,
};
const Types = {
Message: MessageType,
// Mostly for debugging
UUID,
Address,
QualifiedAddress,
};
return {
Backbone,
Components,
Crypto,
Curve,
conversationControllerStart,
Data,
EmojiLib,
Groups,
GroupChange,
Migrations,
OS,
RemoteConfig,
Services,
State,
Stickers,
Types,
Util,
};
};

View file

@ -147,7 +147,7 @@
"react-blurhash": "0.1.2",
"react-contextmenu": "2.11.0",
"react-dom": "17.0.2",
"react-dropzone": "10.1.7",
"react-dropzone": "10.2.2",
"react-hot-loader": "4.13.0",
"react-measure": "2.3.0",
"react-popper": "2.3.0",

View file

@ -1,522 +0,0 @@
// Copyright 2017-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global Whisper, window */
/* eslint-disable global-require */
const preloadStartTime = Date.now();
let preloadEndTime = 0;
try {
const electron = require('electron');
const semver = require('semver');
const _ = require('lodash');
const { strictAssert } = require('./ts/util/assert');
const { parseIntWithFallback } = require('./ts/util/parseIntWithFallback');
const { UUIDKind } = require('./ts/types/UUID');
const { ThemeType } = require('./ts/types/Util');
// It is important to call this as early as possible
const { SignalContext } = require('./ts/windows/context');
window.i18n = SignalContext.i18n;
const { getEnvironment, Environment } = require('./ts/environment');
const ipc = electron.ipcRenderer;
const config = require('url').parse(window.location.toString(), true).query;
const log = require('./ts/logging/log');
let title = config.name;
if (getEnvironment() !== Environment.Production) {
title += ` - ${getEnvironment()}`;
}
if (config.appInstance) {
title += ` - ${config.appInstance}`;
}
// Flags for testing
window.GV2_ENABLE_SINGLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_STATE_PROCESSING = true;
window.GV2_ENABLE_PRE_JOIN_FETCH = true;
window.GV2_MIGRATION_DISABLE_ADD = false;
window.GV2_MIGRATION_DISABLE_INVITE = false;
window.RETRY_DELAY = false;
window.platform = process.platform;
window.getTitle = () => title;
window.getLocale = () => config.locale;
window.getEnvironment = getEnvironment;
window.getAppInstance = () => config.appInstance;
window.getVersion = () => config.version;
window.getBuildCreation = () => parseIntWithFallback(config.buildCreation, 0);
window.getExpiration = () => {
const sixtyDays = 60 * 86400 * 1000;
const remoteBuildExpiration = window.storage.get('remoteBuildExpiration');
const localBuildExpiration = window.Events.getAutoDownloadUpdate()
? config.buildExpiration
: config.buildExpiration - sixtyDays;
if (remoteBuildExpiration) {
return remoteBuildExpiration < config.buildExpiration
? remoteBuildExpiration
: localBuildExpiration;
}
return localBuildExpiration;
};
window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.getServerPublicParams = () => config.serverPublicParams;
window.getSfuUrl = () => config.sfuUrl;
window.isBehindProxy = () => Boolean(config.proxyUrl);
window.getAutoLaunch = () => {
return ipc.invoke('get-auto-launch');
};
window.setAutoLaunch = value => {
return ipc.invoke('set-auto-launch', value);
};
window.isBeforeVersion = (toCheck, baseVersion) => {
try {
return semver.lt(toCheck, baseVersion);
} catch (error) {
log.error(
`isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
error && error.stack ? error.stack : error
);
return true;
}
};
window.isAfterVersion = (toCheck, baseVersion) => {
try {
return semver.gt(toCheck, baseVersion);
} catch (error) {
log.error(
`isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
error && error.stack ? error.stack : error
);
return true;
}
};
window.setBadgeCount = count => ipc.send('set-badge-count', count);
let connectStartTime = 0;
window.logAuthenticatedConnect = () => {
if (connectStartTime === 0) {
connectStartTime = Date.now();
}
};
window.logAppLoadedEvent = ({ processedCount }) =>
ipc.send('signal-app-loaded', {
preloadTime: preloadEndTime - preloadStartTime,
connectTime: connectStartTime - preloadEndTime,
processedCount,
});
// We never do these in our code, so we'll prevent it everywhere
window.open = () => null;
// Playwright uses `eval` for `.evaluate()` API
if (!config.enableCI && config.environment !== 'test') {
// eslint-disable-next-line no-eval, no-multi-assign
window.eval = global.eval = () => null;
}
window.drawAttention = () => {
log.info('draw attention');
ipc.send('draw-attention');
};
window.showWindow = () => {
log.info('show window');
ipc.send('show-window');
};
window.titleBarDoubleClick = () => {
ipc.send('title-bar-double-click');
};
window.setAutoHideMenuBar = autoHide =>
ipc.send('set-auto-hide-menu-bar', autoHide);
window.setMenuBarVisibility = visibility =>
ipc.send('set-menu-bar-visibility', visibility);
window.updateSystemTraySetting = (
systemTraySetting /* : Readonly<SystemTraySetting> */
) => {
ipc.send('update-system-tray-setting', systemTraySetting);
};
window.restart = () => {
log.info('restart');
ipc.send('restart');
};
window.shutdown = () => {
log.info('shutdown');
ipc.send('shutdown');
};
window.showDebugLog = () => {
log.info('showDebugLog');
ipc.send('show-debug-log');
};
window.closeAbout = () => ipc.send('close-about');
window.readyForUpdates = () => ipc.send('ready-for-updates');
window.updateTrayIcon = unreadCount =>
ipc.send('update-tray-icon', unreadCount);
ipc.on('additional-log-data-request', async event => {
const ourConversation = window.ConversationController.getOurConversation();
const ourCapabilities = ourConversation
? ourConversation.get('capabilities')
: undefined;
const remoteConfig = window.storage.get('remoteConfig') || {};
let statistics;
try {
statistics = await window.Signal.Data.getStatisticsForLogging();
} catch (error) {
statistics = {};
}
const ourUuid = window.textsecure.storage.user.getUuid();
const ourPni = window.textsecure.storage.user.getUuid(UUIDKind.PNI);
event.sender.send('additional-log-data-response', {
capabilities: ourCapabilities || {},
remoteConfig: _.mapValues(remoteConfig, ({ value, enabled }) => {
const enableString = enabled ? 'enabled' : 'disabled';
const valueString = value && value !== 'TRUE' ? ` ${value}` : '';
return `${enableString}${valueString}`;
}),
statistics,
user: {
deviceId: window.textsecure.storage.user.getDeviceId(),
e164: window.textsecure.storage.user.getNumber(),
uuid: ourUuid && ourUuid.toString(),
pni: ourPni && ourPni.toString(),
conversationId: ourConversation && ourConversation.id,
},
});
});
ipc.on('set-up-as-new-device', () => {
Whisper.events.trigger('setupAsNewDevice');
});
ipc.on('set-up-as-standalone', () => {
Whisper.events.trigger('setupAsStandalone');
});
ipc.on('challenge:response', (_event, response) => {
Whisper.events.trigger('challengeResponse', response);
});
ipc.on('power-channel:suspend', () => {
Whisper.events.trigger('powerMonitorSuspend');
});
ipc.on('power-channel:resume', () => {
Whisper.events.trigger('powerMonitorResume');
});
ipc.on('power-channel:lock-screen', () => {
Whisper.events.trigger('powerMonitorLockScreen');
});
ipc.on('window:set-window-stats', (_event, stats) => {
if (!Whisper.events) {
return;
}
Whisper.events.trigger('setWindowStats', stats);
});
ipc.on('window:set-menu-options', (_event, options) => {
if (!Whisper.events) {
return;
}
Whisper.events.trigger('setMenuOptions', options);
});
window.sendChallengeRequest = request =>
ipc.send('challenge:request', request);
{
let isFullScreen = config.isFullScreen === 'true';
window.isFullScreen = () => isFullScreen;
// This is later overwritten.
window.onFullScreenChange = _.noop;
ipc.on('full-screen-change', (_event, isFull) => {
isFullScreen = Boolean(isFull);
window.onFullScreenChange(isFullScreen);
});
}
if (config.resolvedTheme === 'light') {
window.initialTheme = ThemeType.light;
} else if (config.resolvedTheme === 'dark') {
window.initialTheme = ThemeType.dark;
}
// Settings-related events
window.showSettings = () => ipc.send('show-settings');
window.showPermissionsPopup = (forCalling, forCamera) =>
ipc.invoke('show-permissions-popup', forCalling, forCamera);
ipc.on('show-keyboard-shortcuts', () => {
window.Events.showKeyboardShortcuts();
});
ipc.on('add-dark-overlay', () => {
window.Events.addDarkOverlay();
});
ipc.on('remove-dark-overlay', () => {
window.Events.removeDarkOverlay();
});
require('./ts/windows/preload');
window.getBuiltInImages = () =>
new Promise((resolve, reject) => {
ipc.once('get-success-built-in-images', (_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
});
ipc.send('get-built-in-images');
});
ipc.on('delete-all-data', async () => {
const { deleteAllData } = window.Events;
if (!deleteAllData) {
return;
}
try {
await deleteAllData();
} catch (error) {
log.error('delete-all-data: error', error && error.stack);
}
});
ipc.on('show-sticker-pack', (_event, info) => {
const { packId, packKey } = info;
const { showStickerPack } = window.Events;
if (showStickerPack) {
showStickerPack(packId, packKey);
}
});
ipc.on('show-group-via-link', (_event, info) => {
const { hash } = info;
const { showGroupViaLink } = window.Events;
if (showGroupViaLink) {
showGroupViaLink(hash);
}
});
ipc.on('show-conversation-via-signal.me', (_event, info) => {
const { hash } = info;
strictAssert(typeof hash === 'string', 'Got an invalid hash over IPC');
const { showConversationViaSignalDotMe } = window.Events;
if (showConversationViaSignalDotMe) {
showConversationViaSignalDotMe(hash);
}
});
ipc.on('unknown-sgnl-link', () => {
const { unknownSignalLink } = window.Events;
if (unknownSignalLink) {
unknownSignalLink();
}
});
ipc.on('install-sticker-pack', (_event, info) => {
const { packId, packKey } = info;
const { installStickerPack } = window.Events;
if (installStickerPack) {
installStickerPack(packId, packKey);
}
});
ipc.on('get-ready-for-shutdown', async () => {
const { shutdown } = window.Events || {};
if (!shutdown) {
log.error('preload shutdown handler: shutdown method not found');
ipc.send('now-ready-for-shutdown');
return;
}
try {
await shutdown();
ipc.send('now-ready-for-shutdown');
} catch (error) {
ipc.send(
'now-ready-for-shutdown',
error && error.stack ? error.stack : error
);
}
});
ipc.on('show-release-notes', () => {
const { showReleaseNotes } = window.Events;
if (showReleaseNotes) {
showReleaseNotes();
}
});
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');
// We pull these dependencies in now, from here, because they have Node.js dependencies
if (config.proxyUrl) {
log.info('Using provided proxy url');
}
window.nodeSetImmediate = setImmediate;
window.Backbone = require('backbone');
window.textsecure = require('./ts/textsecure').default;
window.WebAPI = window.textsecure.WebAPI.initialize({
url: config.serverUrl,
storageUrl: config.storageUrl,
updatesUrl: config.updatesUrl,
directoryVersion: parseInt(config.directoryVersion, 10),
directoryUrl: config.directoryUrl,
directoryEnclaveId: config.directoryEnclaveId,
directoryTrustAnchor: config.directoryTrustAnchor,
directoryV2Url: config.directoryV2Url,
directoryV2PublicKey: config.directoryV2PublicKey,
directoryV2CodeHashes: (config.directoryV2CodeHashes || '').split(','),
cdnUrlObject: {
0: config.cdnUrl0,
2: config.cdnUrl2,
},
certificateAuthority: config.certificateAuthority,
contentProxyUrl: config.contentProxyUrl,
proxyUrl: config.proxyUrl,
version: config.version,
});
const { imageToBlurHash } = require('./ts/util/imageToBlurHash');
const { ActiveWindowService } = require('./ts/services/ActiveWindowService');
window.imageToBlurHash = imageToBlurHash;
window.libphonenumber =
require('google-libphonenumber').PhoneNumberUtil.getInstance();
window.libphonenumber.PhoneNumberFormat =
require('google-libphonenumber').PhoneNumberFormat;
const activeWindowService = new ActiveWindowService();
activeWindowService.initialize(window.document, ipc);
window.isActive = activeWindowService.isActive.bind(activeWindowService);
window.registerForActive =
activeWindowService.registerForActive.bind(activeWindowService);
window.unregisterForActive =
activeWindowService.unregisterForActive.bind(activeWindowService);
window.Accessibility = {
reducedMotionSetting: Boolean(config.reducedMotionSetting),
};
window.React = require('react');
window.ReactDOM = require('react-dom');
window.moment = require('moment');
require('moment/min/locales.min');
window.PQueue = require('p-queue').default;
const Signal = require('./js/modules/signal');
const Attachments = require('./ts/windows/attachments');
const { locale } = config;
window.moment.updateLocale(locale, {
relativeTime: {
s: window.i18n('timestamp_s'),
m: window.i18n('timestamp_m'),
h: window.i18n('timestamp_h'),
},
});
window.moment.locale(locale);
const userDataPath = SignalContext.getPath('userData');
window.baseAttachmentsPath = Attachments.getPath(userDataPath);
window.baseStickersPath = Attachments.getStickersPath(userDataPath);
window.baseTempPath = Attachments.getTempPath(userDataPath);
window.baseDraftPath = Attachments.getDraftPath(userDataPath);
const { addSensitivePath } = require('./ts/util/privacy');
addSensitivePath(window.baseAttachmentsPath);
if (config.crashDumpsPath) {
addSensitivePath(config.crashDumpsPath);
}
window.Signal = Signal.setup({
Attachments,
userDataPath,
getRegionCode: () => window.storage.get('regionCode'),
logger: log,
});
if (config.enableCI) {
const { CI } = require('./ts/CI');
window.CI = new CI(title);
}
// these need access to window.Signal:
require('./ts/models/messages');
require('./ts/models/conversations');
require('./ts/backbone/views/whisper_view');
require('./ts/views/conversation_view');
require('./ts/views/inbox_view');
require('./ts/SignalProtocolStore');
require('./ts/background');
window.addEventListener('contextmenu', e => {
const editable = e.target.closest(
'textarea, input, [contenteditable="true"]'
);
const link = e.target.closest('a');
const selection = Boolean(window.getSelection().toString());
const image = e.target.closest('.Lightbox img');
if (!editable && !selection && !link && !image) {
e.preventDefault();
}
});
if (config.environment === 'test') {
require('./preload_test');
}
log.info('preload complete');
} catch (error) {
/* eslint-disable no-console */
if (console._log) {
console._log('preload error!', error.stack);
} else {
console.log('preload error!', error.stack);
}
/* eslint-enable no-console */
throw error;
}
preloadEndTime = Date.now();

View file

@ -1,26 +0,0 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global window */
const { ipcRenderer } = require('electron');
const fastGlob = require('fast-glob');
window.assert = require('chai').assert;
// This is a hack to let us run TypeScript tests in the renderer process. See the
// code in `test/index.html`.
window.test = {
onComplete(info) {
return ipcRenderer.invoke('ci:test-electron:done', info);
},
prepareTests() {
fastGlob
.sync('./ts/test-{both,electron}/**/*_test.js', {
absolute: true,
cwd: __dirname,
})
.forEach(require);
},
};

View file

@ -80,6 +80,6 @@ esbuild.build({
esbuild.build({
...bundleDefaults,
mainFields: ['browser', 'main'],
entryPoints: [path.join(ROOT_DIR, 'preload.js')],
entryPoints: [path.join(ROOT_DIR, 'ts/windows/main/preload.ts')],
outfile: path.join(ROOT_DIR, 'preload.bundle.js'),
});

View file

@ -1,7 +1,7 @@
// Copyright 2019-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import React from 'react';
import * as styles from './AppStage.scss';
import { history } from '../../util/history';
import { Button } from '../../elements/Button';
@ -49,11 +49,15 @@ export const AppStage: React.ComponentType<Props> = props => {
const i18n = useI18n();
const handleNext = React.useCallback(() => {
history.push(next);
if (next) {
history.push(next);
}
}, [next]);
const handlePrev = React.useCallback(() => {
history.push(prev);
if (prev) {
history.push(prev);
}
}, [prev]);
const addMoreCount = stickersDuck.useAddMoreCount();
@ -71,7 +75,9 @@ export const AppStage: React.ComponentType<Props> = props => {
) : null}
{addMoreCount > 0 ? (
<Text secondary>
{i18n('StickerCreator--DropStage--addMore', [addMoreCount])}
{i18n('StickerCreator--DropStage--addMore', [
addMoreCount.toString(),
])}
</Text>
) : null}
{next || onNext ? (

View file

@ -85,7 +85,7 @@ export const MetaStage: React.ComponentType = () => {
<Text>{i18n('StickerCreator--MetaStage--Field--cover--help')}</Text>
<div className={styles.coverContainer}>
<div {...getRootProps()} className={coverFrameClass}>
{cover.src ? (
{cover?.src ? (
<img
className={styles.coverImage}
src={cover.src}

View file

@ -8,6 +8,8 @@ import { action } from '@storybook/addon-actions';
import { StoryRow } from '../elements/StoryRow';
import { StickerFrame } from './StickerFrame';
import type { EmojiPickDataType } from '../../ts/components/emoji/EmojiPicker';
export default {
title: 'Sticker Creator/components',
};
@ -15,11 +17,13 @@ export default {
export const _StickerFrame = (): JSX.Element => {
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
const showGuide = boolean('show guide', true);
const mode = select('mode', [null, 'removable', 'pick-emoji', 'add'], null);
const mode = select('mode', ['removable', 'pick-emoji', 'add'], 'add');
const onRemove = action('onRemove');
const onDrop = action('onDrop');
const [skinTone, setSkinTone] = React.useState(0);
const [emoji, setEmoji] = React.useState(undefined);
const [emoji, setEmoji] = React.useState<EmojiPickDataType | undefined>(
undefined
);
return (
<StoryRow top>

View file

@ -94,6 +94,14 @@ export const StickerFrame = React.memo(
const handlePickEmoji = React.useCallback(
(emoji: EmojiPickDataType) => {
if (!id) {
return;
}
if (!onPickEmoji) {
throw new Error(
'StickerFrame/handlePickEmoji: onPickEmoji was not provided!'
);
}
onPickEmoji({ id, emoji });
setEmojiPickerOpen(false);
},
@ -101,6 +109,14 @@ export const StickerFrame = React.memo(
);
const handleRemove = React.useCallback(() => {
if (!id) {
return;
}
if (!onRemove) {
throw new Error(
'StickerFrame/handleRemove: onRemove was not provided!'
);
}
onRemove(id);
}, [onRemove, id]);
@ -238,7 +254,7 @@ export const StickerFrame = React.memo(
</button>
)}
</PopperReference>
{emojiPickerOpen && emojiPopperRoot
{emojiPickerOpen && onSetSkinTone && emojiPopperRoot
? createPortal(
<Popper placement="bottom-start">
{({ ref, style }) => (

View file

@ -16,8 +16,10 @@ import { useI18n } from '../util/i18n';
const queue = new PQueue({ concurrency: 3, timeout: 1000 * 60 * 2 });
type SmartStickerFrameProps = Omit<StickerFrameProps, 'id'> & { id: string };
const SmartStickerFrame = SortableElement(
({ id, showGuide, mode }: StickerFrameProps) => {
({ id, showGuide, mode }: SmartStickerFrameProps) => {
const data = stickersDuck.useStickerData(id);
const actions = stickersDuck.useStickerActions();
const image = data.imageData ? data.imageData.src : undefined;

View file

@ -5,7 +5,7 @@ import * as React from 'react';
import { last, noop } from 'lodash';
import { Toast } from '../elements/Toast';
export type Props = React.HTMLProps<HTMLDivElement> & {
export type Props = React.HTMLAttributes<HTMLDivElement> & {
loaf: Array<{ id: number; text: string }>;
onDismiss: () => unknown;
};

View file

@ -1,11 +1,12 @@
// Copyright 2019-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import * as classnames from 'classnames';
import React from 'react';
import classnames from 'classnames';
import * as styles from './Button.scss';
export type Props = React.HTMLProps<HTMLButtonElement> & {
export type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & {
pill?: boolean;
primary?: boolean;
};

View file

@ -3,9 +3,11 @@
import * as React from 'react';
import type { FileWithPath } from 'react-dropzone';
import * as styles from './DropZone.scss';
import { useI18n } from '../util/i18n';
import { useStickerDropzone } from '../util/useStickerDropzone';
import { isNotNil } from '../../ts/util/isNotNil';
export type Props = {
readonly inner?: boolean;
@ -32,7 +34,7 @@ export const DropZone: React.ComponentType<Props> = props => {
const handleDrop = React.useCallback(
(files: ReadonlyArray<FileWithPath>) => {
onDrop(files.map(({ path }) => path));
onDrop(files.map(({ path }) => path).filter(isNotNil));
},
[onDrop]
);

View file

@ -34,7 +34,9 @@ export const MessageMeta = React.memo((props: Props) => {
<path d="M6.003 1H6a5.06 5.06 0 00-.5.025V.02A6.08 6.08 0 016 0h.003A6 6 0 0112 6h-1a5 5 0 00-4.997-5zM3.443.572l.502.87a5.06 5.06 0 00-.866.5l-.502-.87a6.08 6.08 0 01.866-.5z" />
</g>
</svg>
<div className={itemClass}>{i18n('minutesAgo', [props.minutesAgo])}</div>
<div className={itemClass}>
{i18n('minutesAgo', [props.minutesAgo.toString()])}
</div>
<svg width={18} height={12} className={itemClass}>
<defs>
<path

View file

@ -2,10 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import * as classnames from 'classnames';
import classnames from 'classnames';
import * as styles from './ProgressBar.scss';
export type Props = Pick<React.HTMLProps<HTMLDivElement>, 'className'> & {
export type Props = Pick<React.HTMLAttributes<HTMLDivElement>, 'className'> & {
readonly count: number;
readonly total: number;
};

View file

@ -5,7 +5,7 @@ import * as React from 'react';
import classNames from 'classnames';
import * as styles from './Toast.scss';
export type Props = React.HTMLProps<HTMLButtonElement> & {
export type Props = React.HTMLAttributes<HTMLButtonElement> & {
children: React.ReactNode;
};

View file

@ -2,20 +2,21 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import * as classnames from 'classnames';
import classnames from 'classnames';
import * as styles from './Typography.scss';
export type Props = {
children: React.ReactNode;
};
export type HeadingProps = React.HTMLProps<HTMLHeadingElement>;
export type ParagraphProps = React.HTMLProps<HTMLParagraphElement> & {
export type HeadingProps = React.HTMLAttributes<HTMLHeadingElement>;
export type ParagraphProps = React.HTMLAttributes<HTMLParagraphElement> & {
center?: boolean;
wide?: boolean;
secondary?: boolean;
};
export type SpanProps = React.HTMLProps<HTMLSpanElement>;
export type SpanProps = React.HTMLAttributes<HTMLSpanElement>;
export const H1 = React.memo(
({ children, className, ...rest }: Props & HeadingProps) => (

View file

@ -1,29 +1,37 @@
// Copyright 2019-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global window */
const { ipcRenderer: ipc } = require('electron');
const sharp = require('sharp');
const pify = require('pify');
const { readFile } = require('fs');
const config = require('url').parse(window.location.toString(), true).query;
const { noop, uniqBy } = require('lodash');
const pMap = require('p-map');
import PQueue from 'p-queue';
import Backbone from 'backbone';
import { ipcRenderer as ipc } from 'electron';
import sharp from 'sharp';
import pify from 'pify';
import { readFile } from 'fs';
import { noop, uniqBy } from 'lodash';
// It is important to call this as early as possible
const { SignalContext } = require('../ts/windows/context');
import { SignalContext } from '../ts/windows/context';
window.i18n = SignalContext.i18n;
const {
import {
deriveStickerPackKey,
encryptAttachment,
getRandomBytes,
} = require('../ts/Crypto');
const Bytes = require('../ts/Bytes');
const { SignalService: Proto } = require('../ts/protobuf');
const { getEnvironment } = require('../ts/environment');
const { createSetting } = require('../ts/util/preload');
} from '../ts/Crypto';
import * as Bytes from '../ts/Bytes';
import { SignalService as Proto } from '../ts/protobuf';
import { getEnvironment } from '../ts/environment';
import { createSetting } from '../ts/util/preload';
import * as Attachments from '../ts/windows/attachments';
import * as Signal from '../ts/signal';
import { textsecure } from '../ts/textsecure';
import { initialize as initializeWebAPI } from '../ts/textsecure/WebAPI';
import { getAnimatedPngDataIfExists } from '../ts/util/getAnimatedPngDataIfExists';
const { config } = SignalContext;
window.i18n = SignalContext.i18n;
const STICKER_SIZE = 512;
const MIN_STICKER_DIMENSION = 10;
@ -32,9 +40,10 @@ const MAX_STICKER_BYTE_LENGTH = 300 * 1024;
window.ROOT_PATH = window.location.href.startsWith('file') ? '../../' : '/';
window.getEnvironment = getEnvironment;
window.getVersion = () => config.version;
window.PQueue = require('p-queue').default;
window.Backbone = require('backbone');
window.getVersion = () => window.SignalContext.config.version;
window.PQueue = PQueue;
window.Backbone = Backbone;
window.localeMessages = ipc.sendSync('locale-data');
@ -42,27 +51,27 @@ require('../ts/SignalProtocolStore');
SignalContext.log.info('sticker-creator starting up...');
const Signal = require('../js/modules/signal');
window.Signal = Signal.setup({});
window.textsecure = require('../ts/textsecure').default;
const { initialize: initializeWebAPI } = require('../ts/textsecure/WebAPI');
const {
getAnimatedPngDataIfExists,
} = require('../ts/util/getAnimatedPngDataIfExists');
window.Signal = Signal.setup({
Attachments,
getRegionCode: () => {
throw new Error('Sticker Creator preload: Not implemented!');
},
logger: SignalContext.log,
userDataPath: SignalContext.config.userDataPath,
});
window.textsecure = textsecure;
const WebAPI = initializeWebAPI({
url: config.serverUrl,
storageUrl: config.storageUrl,
updatesUrl: config.updatesUrl,
directoryVersion: parseInt(config.directoryVersion, 10),
directoryVersion: config.directoryVersion,
directoryUrl: config.directoryUrl,
directoryEnclaveId: config.directoryEnclaveId,
directoryTrustAnchor: config.directoryTrustAnchor,
directoryV2Url: config.directoryV2Url,
directoryV2PublicKey: config.directoryV2PublicKey,
directoryV2CodeHashes: (config.directoryV2CodeHashes || '').split(','),
directoryV2CodeHashes: config.directoryV2CodeHashes,
cdnUrlObject: {
0: config.cdnUrl0,
2: config.cdnUrl2,
@ -73,13 +82,17 @@ const WebAPI = initializeWebAPI({
version: config.version,
});
function processStickerError(message, i18nKey) {
function processStickerError(message: string, i18nKey: string): Error {
const result = new Error(message);
result.errorMessageI18nKey = i18nKey;
return result;
}
window.processStickerImage = async path => {
window.processStickerImage = async (path: string | undefined) => {
if (!path) {
throw new Error(`Path ${path} is not valid!`);
}
const imgBuffer = await pify(readFile)(path);
const sharpImg = sharp(imgBuffer);
const meta = await sharpImg.metadata();
@ -170,7 +183,8 @@ window.encryptAndUpload = async (
const oldUsernameItem = await window.Signal.Data.getItemById('number_id');
const passwordItem = await window.Signal.Data.getItemById('password');
if (!oldUsernameItem || !passwordItem) {
const username = usernameItem?.value || oldUsernameItem?.value;
if (!username || !passwordItem?.value) {
const { message } =
window.localeMessages['StickerCreator--Authentication--error'];
@ -182,8 +196,6 @@ window.encryptAndUpload = async (
throw new Error(message);
}
const { value: username } = usernameItem;
const { value: oldUsername } = oldUsernameItem;
const { value: password } = passwordItem;
const packKey = getRandomBytes(32);
@ -191,7 +203,7 @@ window.encryptAndUpload = async (
const iv = getRandomBytes(16);
const server = WebAPI.connect({
username: username || oldUsername,
username,
password,
useWebSocket: false,
});
@ -207,7 +219,9 @@ window.encryptAndUpload = async (
manifestProto.stickers = stickers.map(({ emoji }, id) => {
const s = new Proto.StickerPack.Sticker();
s.id = id;
s.emoji = emoji;
if (emoji) {
s.emoji = emoji;
}
return s;
});
@ -222,14 +236,13 @@ window.encryptAndUpload = async (
encryptionKey,
iv
);
const encryptedStickers = await pMap(
uniqueStickers,
({ imageData }) => encrypt(imageData.buffer, encryptionKey, iv),
{
concurrency: 3,
timeout: 1000 * 60 * 2,
const encryptedStickers = uniqueStickers.map(({ imageData }) => {
if (!imageData?.buffer) {
throw new Error('encryptStickers: Missing image data on sticker');
}
);
return encrypt(imageData.buffer, encryptionKey, iv);
});
const packId = await server.putStickers(
encryptedManifest,
@ -244,8 +257,12 @@ window.encryptAndUpload = async (
return { packId, key: hexKey };
};
async function encrypt(data, key, iv) {
const { ciphertext } = await encryptAttachment(data, key, iv);
function encrypt(
data: Uint8Array,
key: Uint8Array,
iv: Uint8Array
): Uint8Array {
const { ciphertext } = encryptAttachment(data, key, iv);
return ciphertext;
}

View file

@ -10,20 +10,12 @@ import { history } from './util/history';
import { store } from './store';
import { I18n } from './util/i18n';
declare global {
// We want to extend `window` here.
// eslint-disable-next-line no-restricted-syntax
interface Window {
localeMessages: { [key: string]: { message: string } };
}
}
const { localeMessages } = window;
const { localeMessages, SignalContext } = window;
const ColdRoot = () => (
<ReduxProvider store={store}>
<Router history={history}>
<I18n messages={localeMessages}>
<I18n messages={localeMessages} locale={SignalContext.config.locale}>
<App />
</I18n>
</Router>

View file

@ -8,7 +8,16 @@ import type { Draft } from 'redux-ts-utils';
import { createAction, handleAction, reduceReducers } from 'redux-ts-utils';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { clamp, find, isNumber, pull, remove, take, uniq } from 'lodash';
import {
clamp,
find,
isNumber,
isString,
pull,
remove,
take,
uniq,
} from 'lodash';
import type { SortEnd } from 'react-sortable-hoc';
import { bindActionCreators } from 'redux';
import arrayMove from 'array-move';
@ -20,6 +29,7 @@ import type {
} from '../../util/preload';
import type { EmojiPickDataType } from '../../../ts/components/emoji/EmojiPicker';
import { convertShortName } from '../../../ts/components/emoji/lib';
import { isNotNil } from '../../../ts/util/isNotNil';
export const initializeStickers = createAction<Array<string>>(
'stickers/initializeStickers'
@ -39,7 +49,7 @@ export const setPackMeta = createAction<PackMetaData>('stickers/setPackMeta');
export const addToast = createAction<{
key: string;
subs?: Array<number | string>;
subs?: Array<string>;
}>('stickers/addToast');
export const dismissToast = createAction<void>('stickers/dismissToast');
@ -57,7 +67,7 @@ type StateStickerData = {
type StateToastData = {
key: string;
subs?: Array<number | string>;
subs?: Array<string>;
};
export type State = {
@ -149,14 +159,21 @@ export const reducer = reduceReducers<State>(
return oldToast;
}
const newToast = { key, subs: [0] };
const newToast = { key, subs: ['0'] };
state.toasts.push(newToast);
return newToast;
})();
if (toast.subs && isNumber(toast.subs[0])) {
toast.subs[0] = (toast.subs[0] || 0) + 1;
const previousSub = toast?.subs?.[0];
if (toast && isString(previousSub)) {
const previousCount = parseInt(previousSub, 10);
const newCount = Number.isFinite(previousCount)
? previousCount + 1
: 1;
toast.subs = toast.subs || [];
toast.subs[0] = newCount.toString();
}
}
}
@ -276,7 +293,7 @@ export const useAddMoreCount = (): number =>
const selectOrderedData = createSelector(
({ stickers }: AppState) => stickers.order,
({ stickers }) => stickers.data,
({ stickers }: AppState) => stickers.data,
(order, data) =>
order.map(id => ({
...data[id],
@ -290,8 +307,10 @@ const selectOrderedData = createSelector(
export const useSelectOrderedData = (): Array<StickerData> =>
useSelector(selectOrderedData);
const selectOrderedImagePaths = createSelector(selectOrderedData, data =>
data.map(({ imageData }) => imageData.src)
const selectOrderedImagePaths = createSelector(
selectOrderedData,
(data: Array<StickerData>) =>
data.map(({ imageData }) => imageData?.src).filter(isNotNil)
);
export const useOrderedImagePaths = (): Array<string> =>

View file

@ -1,9 +0,0 @@
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"lib": ["dom", "es2017"],
"jsx": "react",
"rootDir": "."
}
}

View file

@ -2,81 +2,89 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import type { LocalizerType, ReplacementValuesType } from '../../ts/types/Util';
export type I18nFn = (
key: string,
substitutions?: Array<string | number> | ReplacementValuesType
) => string;
const placeholder = () => 'NO LOCALE LOADED';
placeholder.getLocale = () => 'none';
export type ReplacementValuesType = {
[key: string]: string | number;
};
const I18nContext = React.createContext<I18nFn>(() => 'NO LOCALE LOADED');
const I18nContext = React.createContext<LocalizerType>(placeholder);
export type I18nProps = {
children: React.ReactNode;
locale: string;
messages: { [key: string]: { message: string } };
};
export const I18n = ({ messages, children }: I18nProps): JSX.Element => {
const getMessage = React.useCallback<I18nFn>(
(key, substitutions) => {
if (Array.isArray(substitutions) && substitutions.length > 1) {
throw new Error(
'Array syntax is not supported with more than one placeholder'
export const I18n = ({
messages,
locale,
children,
}: I18nProps): JSX.Element => {
const callback = (key: string, substitutions?: ReplacementValuesType) => {
if (Array.isArray(substitutions) && substitutions.length > 1) {
throw new Error(
'Array syntax is not supported with more than one placeholder'
);
}
const stringInfo = messages[key];
if (!stringInfo) {
window.SignalContext.log.warn(
`getMessage: No string found for key ${key}`
);
return '';
}
const { message } = stringInfo;
if (!substitutions) {
return message;
}
if (Array.isArray(substitutions)) {
return substitutions.reduce(
(result, substitution) =>
result.toString().replace(/\$.+?\$/, substitution.toString()),
message
) as string;
}
const FIND_REPLACEMENTS = /\$([^$]+)\$/g;
let match = FIND_REPLACEMENTS.exec(message);
let builder = '';
let lastTextIndex = 0;
while (match) {
if (lastTextIndex < match.index) {
builder += message.slice(lastTextIndex, match.index);
}
const placeholderName = match[1];
const value = substitutions[placeholderName];
if (!value) {
// eslint-disable-next-line no-console
console.error(
`i18n: Value not provided for placeholder ${placeholderName} in key '${key}'`
);
}
builder += value || '';
const { message } = messages[key];
if (!substitutions) {
return message;
}
if (Array.isArray(substitutions)) {
return substitutions.reduce(
(result, substitution) =>
result.toString().replace(/\$.+?\$/, substitution.toString()),
message
) as string;
}
lastTextIndex = FIND_REPLACEMENTS.lastIndex;
match = FIND_REPLACEMENTS.exec(message);
}
const FIND_REPLACEMENTS = /\$([^$]+)\$/g;
if (lastTextIndex < message.length) {
builder += message.slice(lastTextIndex);
}
let match = FIND_REPLACEMENTS.exec(message);
let builder = '';
let lastTextIndex = 0;
return builder;
};
callback.getLocale = () => locale;
while (match) {
if (lastTextIndex < match.index) {
builder += message.slice(lastTextIndex, match.index);
}
const placeholderName = match[1];
const value = substitutions[placeholderName];
if (!value) {
// eslint-disable-next-line no-console
console.error(
`i18n: Value not provided for placeholder ${placeholderName} in key '${key}'`
);
}
builder += value || '';
lastTextIndex = FIND_REPLACEMENTS.lastIndex;
match = FIND_REPLACEMENTS.exec(message);
}
if (lastTextIndex < message.length) {
builder += message.slice(lastTextIndex);
}
return builder;
},
[messages]
);
const getMessage = React.useCallback<LocalizerType>(callback, [messages]);
return (
<I18nContext.Provider value={getMessage}>{children}</I18nContext.Provider>
);
};
export const useI18n = (): I18nFn => React.useContext(I18nContext);
export const useI18n = (): LocalizerType => React.useContext(I18nContext);

View file

@ -19,7 +19,9 @@ export type StickerImageData = {
meta: Metadata;
};
type ProcessStickerImageFn = (path: string) => Promise<StickerImageData>;
type ProcessStickerImageFn = (
path: string | undefined
) => Promise<StickerImageData>;
export type StickerData = { imageData?: StickerImageData; emoji?: string };
export type PackMetaData = { packId: string; key: string };
@ -27,7 +29,7 @@ export type PackMetaData = { packId: string; key: string };
export type EncryptAndUploadFn = (
manifest: { title: string; author: string },
stickers: Array<StickerData>,
cover: StickerImageData,
cover: StickerImageData | undefined,
onProgress?: () => unknown
) => Promise<PackMetaData>;

View file

@ -83,32 +83,11 @@
</script>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="../js/database.js" data-cover></script>
<script type="text/javascript" src="../js/libphonenumber-util.js"></script>
<script
type="text/javascript"
src="../js/expiring_messages.js"
data-cover
></script>
<script
type="text/javascript"
src="../js/expiring_tap_to_view_messages.js"
data-cover
></script>
<script
type="text/javascript"
src="../js/views/react_wrapper_view.js"
></script>
<script type="text/javascript" src="views/whisper_view_test.js"></script>
<script type="text/javascript">
window.Signal.conversationControllerStart();
window.test.prepareTests();
delete window.test.prepareTests;
window.testUtilities.prepareTests();
delete window.testUtilities.prepareTests;
!(function () {
const passed = [];
@ -126,7 +105,9 @@
});
});
runner.on('end', () => window.test.onComplete({ passed, failed }));
runner.on('end', () =>
window.testUtilities.onComplete({ passed, failed })
);
}
}

View file

@ -52,6 +52,7 @@ import { QualifiedAddress } from './types/QualifiedAddress';
import * as log from './logging/log';
import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
import * as Errors from './types/errors';
import MessageSender from './textsecure/SendMessage';
const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds
@ -1386,7 +1387,7 @@ export class SignalProtocolStore extends EventsMixin {
// Enqueue a null message with newly-created session
await singleProtoJobQueue.add(
window.textsecure.messaging.getNullMessage({
MessageSender.getNullMessage({
uuid: uuid.toString(),
})
);

View file

@ -149,6 +149,8 @@ import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
import { getInitialState } from './state/getInitialState';
import { conversationJobQueue } from './jobs/conversationJobQueue';
import { SeenStatus } from './MessageSeenStatus';
import MessageSender from './textsecure/SendMessage';
import type AccountManager from './textsecure/AccountManager';
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
@ -237,7 +239,11 @@ export async function startApp(): Promise<void> {
},
async sendChallengeResponse(data) {
await window.textsecure.messaging.sendChallengeResponse(data);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('sendChallengeResponse: messaging is not available!');
}
await messaging.sendChallengeResponse(data);
},
onChallengeFailed() {
@ -532,11 +538,14 @@ export async function startApp(): Promise<void> {
}
return server.getSocketStatus();
};
let accountManager: typeof window.textsecure.AccountManager;
let accountManager: AccountManager;
window.getAccountManager = () => {
if (accountManager) {
return accountManager;
}
if (!server) {
throw new Error('getAccountManager: server is not available!');
}
accountManager = new window.textsecure.AccountManager(server);
accountManager.addEventListener('registration', () => {
@ -1675,9 +1684,7 @@ export async function startApp(): Promise<void> {
}
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getRequestKeySyncMessage()
);
await singleProtoJobQueue.add(MessageSender.getRequestKeySyncMessage());
} catch (error) {
log.error(
'runStorageService: Failed to queue sync message',
@ -1875,7 +1882,6 @@ export async function startApp(): Promise<void> {
strictAssert(messageReceiver, 'MessageReceiver not initialized');
const syncRequest = new window.textsecure.SyncRequest(
window.textsecure.messaging,
messageReceiver,
timeoutMillis
);
@ -2173,7 +2179,6 @@ export async function startApp(): Promise<void> {
}
if (firstRun === true && deviceId !== 1) {
const { messaging } = window.textsecure;
const hasThemeSetting = Boolean(window.storage.get('theme-setting'));
if (
!hasThemeSetting &&
@ -2214,11 +2219,13 @@ export async function startApp(): Promise<void> {
try {
await Promise.all([
singleProtoJobQueue.add(
messaging.getRequestConfigurationSyncMessage()
MessageSender.getRequestConfigurationSyncMessage()
),
singleProtoJobQueue.add(MessageSender.getRequestBlockSyncMessage()),
singleProtoJobQueue.add(MessageSender.getRequestGroupSyncMessage()),
singleProtoJobQueue.add(
MessageSender.getRequestContactSyncMessage()
),
singleProtoJobQueue.add(messaging.getRequestBlockSyncMessage()),
singleProtoJobQueue.add(messaging.getRequestGroupSyncMessage()),
singleProtoJobQueue.add(messaging.getRequestContactSyncMessage()),
runStorageService(),
]);
} catch (error) {
@ -2270,7 +2277,7 @@ export async function startApp(): Promise<void> {
log.info('firstRun: requesting stickers', operations.length);
try {
await singleProtoJobQueue.add(
messaging.getStickerPackSync(operations)
MessageSender.getStickerPackSync(operations)
);
} catch (error) {
log.error(
@ -2345,7 +2352,7 @@ export async function startApp(): Promise<void> {
window.waitForEmptyEventQueue = waitForEmptyEventQueue;
async function onEmpty() {
const { storage, messaging } = window.textsecure;
const { storage } = window.textsecure;
await Promise.all([
window.waitForAllBatchers(),
@ -2454,7 +2461,7 @@ export async function startApp(): Promise<void> {
if (!pniIdentity) {
log.info('Requesting PNI identity sync');
await singleProtoJobQueue.add(
messaging.getRequestPniIdentitySyncMessage()
MessageSender.getRequestPniIdentitySyncMessage()
);
}
}

View file

@ -88,7 +88,14 @@ function getUrlsToDownload(): Array<string> {
async function downloadBadgeImageFile(url: string): Promise<string> {
await waitForOnline(navigator, window, { timeout: 1 * MINUTE });
const imageFileData = await window.textsecure.server.getBadgeImageFile(url);
const { server } = window.textsecure;
if (!server) {
throw new Error(
'downloadBadgeImageFile: window.textsecure.server is not available!'
);
}
const imageFileData = await server.getBadgeImageFile(url);
const localPath = await window.Signal.Migrations.writeNewBadgeImageFileData(
imageFileData
);

View file

@ -10,9 +10,9 @@ import type { AudioDevice } from 'ringrtc';
import type { MediaDeviceSettings } from '../types/Calling';
import type {
ZoomFactorType,
ThemeSettingType,
NotificationSettingType,
} from '../types/Storage.d';
import type { ThemeSettingType } from '../types/StorageUIKeys';
import { Button, ButtonVariant } from './Button';
import { ChatColorPicker } from './ChatColorPicker';
import { Checkbox } from './Checkbox';

View file

@ -129,6 +129,10 @@ export const StandaloneRegistration = ({
}
document.location.href = getChallengeURL();
if (!window.Signal.challengeHandler) {
setError('Captcha handler is not ready!');
return;
}
const token = await window.Signal.challengeHandler.requestCaptcha();
try {

View file

@ -1,6 +1,8 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { z } from 'zod';
import { makeEnumParser } from './util/enum';
// Many places rely on this enum being a string.
@ -11,6 +13,8 @@ export enum Environment {
Test = 'test',
}
export const environmentSchema = z.nativeEnum(Environment);
let environment: undefined | Environment;
export function getEnvironment(): Environment {

View file

@ -33,6 +33,7 @@ import { missingCaseError } from '../util/missingCaseError';
import { explodePromise } from '../util/explodePromise';
import type { Job } from './Job';
import type { ParsedJob } from './types';
import type SendMessage from '../textsecure/SendMessage';
// Note: generally, we only want to add to this list. If you do need to change one of
// these values, you'll likely need to write a database migration.
@ -118,10 +119,11 @@ export type ConversationQueueJobData = z.infer<
export type ConversationQueueJobBundle = {
isFinalAttempt: boolean;
log: LoggerType;
messaging: SendMessage;
shouldContinue: boolean;
timeRemaining: number;
timestamp: number;
log: LoggerType;
};
const MAX_RETRY_TIME = durations.DAY;
@ -143,6 +145,10 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
insert?: (job: ParsedJob<ConversationQueueJobData>) => Promise<void>
): Promise<Job<ConversationQueueJobData>> {
const { conversationId } = data;
strictAssert(
window.Signal.challengeHandler,
'conversationJobQueue.add: Missing challengeHandler!'
);
window.Signal.challengeHandler.maybeSolve(conversationId);
return super.add(data, insert);
@ -232,7 +238,7 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
break;
}
if (window.Signal.challengeHandler.isRegistered(conversationId)) {
if (window.Signal.challengeHandler?.isRegistered(conversationId)) {
log.info(
'captcha challenge is pending for this conversation; waiting at most 5m...'
);
@ -290,7 +296,13 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
throw missingCaseError(verificationData);
}
const jobBundle = {
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging interface is not available!');
}
const jobBundle: ConversationQueueJobBundle = {
messaging,
isFinalAttempt,
shouldContinue,
timeRemaining,
@ -348,7 +360,7 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
}
untrustedUuids.push(uuid);
} else if (toProcess instanceof SendMessageChallengeError) {
window.Signal.challengeHandler.register(
window.Signal.challengeHandler?.register(
{
conversationId,
createdAt: Date.now(),

View file

@ -39,6 +39,7 @@ export async function sendDeleteForEveryone(
conversation: ConversationModel,
{
isFinalAttempt,
messaging,
shouldContinue,
timestamp,
timeRemaining,
@ -108,7 +109,7 @@ export async function sendDeleteForEveryone(
try {
if (isMe(conversation.attributes)) {
const proto = await window.textsecure.messaging.getContentMessage({
const proto = await messaging.getContentMessage({
deletedForEveryoneTimestamp: targetTimestamp,
profileKey,
recipients: conversation.getRecipients(),
@ -120,7 +121,7 @@ export async function sendDeleteForEveryone(
);
await handleMessageSend(
window.textsecure.messaging.sendSyncMessage({
messaging.sendSyncMessage({
encodedDataMessage: Proto.DataMessage.encode(
proto.dataMessage
).finish(),

View file

@ -24,6 +24,7 @@ export async function sendDirectExpirationTimerUpdate(
conversation: ConversationModel,
{
isFinalAttempt,
messaging,
shouldContinue,
timeRemaining,
timestamp,
@ -75,7 +76,7 @@ export async function sendDirectExpirationTimerUpdate(
const sendType = 'expirationTimerUpdate';
const flags = Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
const proto = await window.textsecure.messaging.getContentMessage({
const proto = await messaging.getContentMessage({
expireTimer,
flags,
profileKey,
@ -95,7 +96,7 @@ export async function sendDirectExpirationTimerUpdate(
try {
if (isMe(conversation.attributes)) {
await handleMessageSend(
window.textsecure.messaging.sendSyncMessage({
messaging.sendSyncMessage({
encodedDataMessage: Proto.DataMessage.encode(
proto.dataMessage
).finish(),

View file

@ -36,6 +36,7 @@ export async function sendNormalMessage(
conversation: ConversationModel,
{
isFinalAttempt,
messaging,
shouldContinue,
timeRemaining,
log,
@ -166,7 +167,7 @@ export async function sendNormalMessage(
// We're sending to Note to Self or a 'lonely group' with just us in it
log.info('sending sync message only');
const dataMessage = await window.textsecure.messaging.getDataMessage({
const dataMessage = await messaging.getDataMessage({
attachments,
body,
contact,
@ -261,7 +262,7 @@ export async function sendNormalMessage(
}
log.info('sending direct message');
innerPromise = window.textsecure.messaging.sendMessageToIdentifier({
innerPromise = messaging.sendMessageToIdentifier({
attachments,
contact,
contentHint: ContentHint.RESENDABLE,

View file

@ -64,6 +64,7 @@ export async function sendProfileKey(
conversation: ConversationModel,
{
isFinalAttempt,
messaging,
shouldContinue,
timestamp,
timeRemaining,
@ -123,13 +124,13 @@ export async function sendProfileKey(
return;
}
const proto = await window.textsecure.messaging.getContentMessage({
const proto = await messaging.getContentMessage({
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
profileKey,
recipients: conversation.getRecipients(),
timestamp,
});
sendPromise = window.textsecure.messaging.sendIndividualProto({
sendPromise = messaging.sendIndividualProto({
contentHint,
identifier: conversation.getSendTarget(),
options: sendOptions,

View file

@ -40,6 +40,7 @@ export async function sendReaction(
conversation: ConversationModel,
{
isFinalAttempt,
messaging,
shouldContinue,
timeRemaining,
log,
@ -169,7 +170,7 @@ export async function sendReaction(
if (recipientIdentifiersWithoutMe.length === 0) {
log.info('sending sync reaction message only');
const dataMessage = await window.textsecure.messaging.getDataMessage({
const dataMessage = await messaging.getDataMessage({
attachments: [],
expireTimer,
groupV2: conversation.getGroupV2Info({
@ -217,7 +218,7 @@ export async function sendReaction(
}
log.info('sending direct reaction message');
promise = window.textsecure.messaging.sendMessageToIdentifier({
promise = messaging.sendMessageToIdentifier({
identifier: recipientIdentifiersWithoutMe[0],
messageText: undefined,
attachments: [],

View file

@ -13,6 +13,7 @@ import { isRecord } from '../../util/isRecord';
import { commonShouldJobContinue } from './commonShouldJobContinue';
import { handleCommonJobRequestError } from './handleCommonJobRequestError';
import { missingCaseError } from '../../util/missingCaseError';
import type SendMessage from '../../textsecure/SendMessage';
const CHUNK_SIZE = 100;
@ -122,25 +123,24 @@ export async function runSyncJob({
syncMessage: true,
});
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
let doSync:
| typeof window.textsecure.messaging.syncReadMessages
| typeof window.textsecure.messaging.syncView
| typeof window.textsecure.messaging.syncViewOnceOpen;
| SendMessage['syncReadMessages']
| SendMessage['syncView']
| SendMessage['syncViewOnceOpen'];
switch (type) {
case SyncTypeList.View:
doSync = window.textsecure.messaging.syncView.bind(
window.textsecure.messaging
);
doSync = messaging.syncView.bind(messaging);
break;
case SyncTypeList.Read:
doSync = window.textsecure.messaging.syncReadMessages.bind(
window.textsecure.messaging
);
doSync = messaging.syncReadMessages.bind(messaging);
break;
case SyncTypeList.ViewOnceOpen:
doSync = window.textsecure.messaging.syncViewOnceOpen.bind(
window.textsecure.messaging
);
doSync = messaging.syncViewOnceOpen.bind(messaging);
break;
default: {
throw missingCaseError(type);

View file

@ -103,9 +103,14 @@ export class SingleProtoJobQueue extends JobQueue<SingleProtoJobData> {
syncMessage: isSyncMessage,
});
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
try {
await handleMessageSend(
window.textsecure.messaging.sendIndividualProto({
messaging.sendIndividualProto({
contentHint,
identifier,
options,

32
ts/model-types.d.ts vendored
View file

@ -28,6 +28,10 @@ import AccessRequiredEnum = Proto.AccessControl.AccessRequired;
import MemberRoleEnum = Proto.Member.Role;
import { SeenStatus } from './MessageSeenStatus';
import { GiftBadgeStates } from './components/conversation/Message';
import { LinkPreviewType } from './types/message/LinkPreviews';
import type { StickerType } from './types/Stickers';
import { MIMEType } from './types/MIME';
export type WhatIsThis = any;
@ -61,17 +65,15 @@ export type GroupMigrationType = {
droppedMemberIds: Array<string>;
invitedMembers: Array<GroupV2PendingMemberType>;
};
export type PreviewType = {
domain: string;
image: AttachmentType;
title: string;
url: string;
export type QuotedAttachment = {
contentType: MIMEType;
fileName?: string;
thumbnail?: AttachmentType;
};
export type PreviewMessageType = Array<PreviewType>;
export type QuotedMessageType = {
attachments: Array<typeof window.WhatIsThis>;
attachments: Array<WhatIsThis /* QuotedAttachment */>;
// `author` is an old attribute that holds the author's E164. We shouldn't use it for
// new messages, but old messages might have this attribute.
author?: string;
@ -91,16 +93,6 @@ type StoryReplyContextType = {
messageId: string;
};
export type StickerMessageType = {
packId: string;
stickerId: number;
packKey: string;
data?: AttachmentType;
path?: string;
width?: number;
height?: number;
};
export type RetryOptions = Readonly<{
type: 'session-reset';
uuid: string;
@ -182,8 +174,8 @@ export type MessageAttributesType = {
| 'verified-change';
body?: string;
attachments?: Array<AttachmentType>;
preview?: PreviewMessageType;
sticker?: StickerMessageType;
preview?: Array<LinkPreviewType>;
sticker?: StickerType;
sent_at: number;
unidentifiedDeliveries?: Array<string>;
contact?: Array<EmbeddedContactType>;

View file

@ -38,6 +38,7 @@ import type {
StickerType,
} from '../textsecure/SendMessage';
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
import MessageSender from '../textsecure/SendMessage';
import type { CallbackResultType } from '../textsecure/Types.d';
import type { ConversationType } from '../state/ducks/conversations';
import type {
@ -1249,7 +1250,9 @@ export class ConversationModel extends window.Backbone
}
async sendTypingMessage(isTyping: boolean): Promise<void> {
if (!window.textsecure.messaging) {
const { messaging } = window.textsecure;
if (!messaging) {
return;
}
@ -1298,8 +1301,7 @@ export class ConversationModel extends window.Backbone
`sendTypingMessage(${this.idForLogging()}): sending ${content.isTyping}`
);
const contentMessage =
window.textsecure.messaging.getTypingContentMessage(content);
const contentMessage = messaging.getTypingContentMessage(content);
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
@ -1309,7 +1311,7 @@ export class ConversationModel extends window.Backbone
};
if (isDirectConversation(this.attributes)) {
await handleMessageSend(
window.textsecure.messaging.sendMessageProtoAndWait({
messaging.sendMessageProtoAndWait({
timestamp,
recipients: groupMembers,
proto: contentMessage,
@ -2504,7 +2506,7 @@ export class ConversationModel extends window.Backbone
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getMessageRequestResponseSync({
MessageSender.getMessageRequestResponseSync({
threadE164: this.get('e164'),
threadUuid: this.get('uuid'),
groupId,
@ -2696,12 +2698,7 @@ export class ConversationModel extends window.Backbone
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getVerificationSync(
e164,
uuid.toString(),
state,
key
)
MessageSender.getVerificationSync(e164, uuid.toString(), state, key)
);
} catch (error) {
log.error(
@ -3987,15 +3984,11 @@ export class ConversationModel extends window.Backbone
'desktop.mandatoryProfileSharing'
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const destination = this.getSendTarget()!;
const recipients = this.getRecipients();
await this.maybeApplyUniversalTimer();
const expireTimer = this.get('expireTimer');
const recipientMaybeConversations = map(recipients, identifier =>
const recipientMaybeConversations = map(this.getRecipients(), identifier =>
window.ConversationController.get(identifier)
);
const recipientConversations = filter(
@ -4020,7 +4013,8 @@ export class ConversationModel extends window.Backbone
}
// Here we move attachments to disk
const messageWithSchema = await upgradeMessageSchema({
const attributes = await upgradeMessageSchema({
id: UUID.generate().toString(),
timestamp: now,
type: 'outgoing',
body,
@ -4033,7 +4027,6 @@ export class ConversationModel extends window.Backbone
received_at: window.Signal.Util.incrementMessageCounter(),
received_at_ms: now,
expireTimer,
recipients,
readStatus: ReadStatus.Read,
seenStatus: SeenStatus.NotApplicable,
sticker,
@ -4049,14 +4042,6 @@ export class ConversationModel extends window.Backbone
storyId,
});
if (isDirectConversation(this.attributes)) {
messageWithSchema.destination = destination;
}
const attributes: MessageAttributesType = {
...messageWithSchema,
id: UUID.generate().toString(),
};
const model = new window.Whisper.Message(attributes);
const message = window.MessageController.register(model.id, model);
message.cachedOutgoingContactData = contact;
@ -4588,6 +4573,11 @@ export class ConversationModel extends window.Backbone
// Deprecated: only applies to GroupV1
async leaveGroup(): Promise<void> {
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('leaveGroup: Cannot leave v1 group when offline!');
}
if (!isGroupV1(this.attributes)) {
throw new Error(
`leaveGroup: Group ${this.idForLogging()} is not GroupV1!`
@ -4628,11 +4618,7 @@ export class ConversationModel extends window.Backbone
const options = await getSendOptions(this.attributes);
message.send(
handleMessageSend(
window.textsecure.messaging.leaveGroup(
groupId,
groupIdentifiers,
options
),
messaging.leaveGroup(groupId, groupIdentifiers, options),
{ messageIds: [], sendType: 'legacyGroupChange' }
)
);
@ -4797,7 +4783,11 @@ export class ConversationModel extends window.Backbone
return;
}
const avatar = await window.textsecure.messaging.getAvatar(avatarPath);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('setProfileAvatar: Cannot fetch avatar when offline!');
}
const avatar = await messaging.getAvatar(avatarPath);
// decrypt
const decrypted = decryptProfile(avatar, decryptionKey);
@ -5017,17 +5007,17 @@ export class ConversationModel extends window.Backbone
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const number = this.get('e164')!;
try {
const parsedNumber = window.libphonenumber.parse(number);
const regionCode = getRegionCodeForNumber(parsedNumber);
const parsedNumber = window.libphonenumberInstance.parse(number);
const regionCode = getRegionCodeForNumber(number);
if (regionCode === window.storage.get('regionCode')) {
return window.libphonenumber.format(
return window.libphonenumberInstance.format(
parsedNumber,
window.libphonenumber.PhoneNumberFormat.NATIONAL
window.libphonenumberFormat.NATIONAL
);
}
return window.libphonenumber.format(
return window.libphonenumberInstance.format(
parsedNumber,
window.libphonenumber.PhoneNumberFormat.INTERNATIONAL
window.libphonenumberFormat.INTERNATIONAL
);
} catch (e) {
return number;

View file

@ -205,7 +205,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
if (_.isObject(attributes)) {
this.set(
TypedMessage.initializeSchemaVersion({
message: attributes,
message: attributes as MessageAttributesType,
logger: log,
})
);
@ -1638,6 +1638,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
return;
}
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('sendSyncMessage: messaging not available!');
}
this.syncPromise = this.syncPromise || Promise.resolve();
const next = async () => {
const dataMessage = this.get('dataMessage');
@ -1677,7 +1682,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
);
return handleMessageSend(
window.textsecure.messaging.sendSyncMessage({
messaging.sendSyncMessage({
encodedDataMessage: dataMessage,
timestamp: this.get('sent_at'),
destination: conv.get('e164'),
@ -2347,6 +2352,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
]);
const withQuoteReference = {
...message.attributes,
...initialMessage,
quote,
storyId: storyQuote?.id,
@ -2410,12 +2416,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
};
// GroupV1
if (!hasGroupV2Prop && dataMessage.group) {
if (!hasGroupV2Prop && initialMessage.group) {
const pendingGroupUpdate: GroupV1Update = {};
const memberConversations: Array<ConversationModel> =
await Promise.all(
dataMessage.group.membersE164.map((e164: string) =>
initialMessage.group.membersE164.map((e164: string) =>
window.ConversationController.getOrCreateAndWait(
e164,
'private'
@ -2426,20 +2432,20 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
attributes = {
...attributes,
type: 'group',
groupId: dataMessage.group.id,
groupId: initialMessage.group.id,
};
if (dataMessage.group.type === GROUP_TYPES.UPDATE) {
if (initialMessage.group.type === GROUP_TYPES.UPDATE) {
attributes = {
...attributes,
name: dataMessage.group.name,
name: initialMessage.group.name,
members: _.union(members, conversation.get('members')),
};
if (dataMessage.group.name !== conversation.get('name')) {
pendingGroupUpdate.name = dataMessage.group.name;
if (initialMessage.group.name !== conversation.get('name')) {
pendingGroupUpdate.name = initialMessage.group.name;
}
const avatarAttachment = dataMessage.group.avatar;
const avatarAttachment = initialMessage.group.avatar;
let downloadedAvatar;
let hash;
@ -2520,7 +2526,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
attributes.left = false;
conversation.set({ addedBy: getContactId(message.attributes) });
}
} else if (dataMessage.group.type === GROUP_TYPES.QUIT) {
} else if (initialMessage.group.type === GROUP_TYPES.QUIT) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const sender = window.ConversationController.get(senderId)!;
const inGroup = Boolean(
@ -2584,7 +2590,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
expirationTimerUpdate: {
source,
sourceUuid,
expireTimer: dataMessage.expireTimer,
expireTimer: initialMessage.expireTimer,
},
});
conversation.set({ expireTimer: dataMessage.expireTimer });
@ -2626,8 +2632,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
}
if (dataMessage.profileKey) {
const { profileKey } = dataMessage;
if (initialMessage.profileKey) {
const { profileKey } = initialMessage;
if (
source === window.textsecure.storage.user.getNumber() ||
sourceUuid ===
@ -2700,10 +2706,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
navigator.languages,
window.getLocale()
);
const response =
await window.textsecure.messaging.server.getBoostBadgesFromServer(
userLanguages
);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('handleDataMessage: messaging is not available');
}
const response = await messaging.server.getBoostBadgesFromServer(
userLanguages
);
const boostBadgesByLevel = parseBoostBadgeListFromServer(
response,
updatesUrl

View file

@ -49,6 +49,7 @@ import type {
RemoteRecord,
UnknownRecord,
} from '../types/StorageService.d';
import MessageSender from '../textsecure/SendMessage';
type IManifestRecordIdentifier = Proto.ManifestRecord.IIdentifier;
@ -604,9 +605,7 @@ async function uploadManifest(
backOff.reset();
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getFetchManifestSyncMessage()
);
await singleProtoJobQueue.add(MessageSender.getFetchManifestSyncMessage());
} catch (error) {
log.error(
`storageService.upload(${version}): Failed to queue sync message`,
@ -630,10 +629,6 @@ async function stopStorageServiceSync(reason: Error) {
await sleep(backOff.getAndIncrement());
log.info('storageService.stopStorageServiceSync: requesting new keys');
setTimeout(async () => {
if (!window.textsecure.messaging) {
throw new Error('storageService.stopStorageServiceSync: We are offline!');
}
if (window.ConversationController.areWePrimaryDevice()) {
log.warn(
'stopStorageServiceSync: We are primary device; not sending key sync request'
@ -641,9 +636,7 @@ async function stopStorageServiceSync(reason: Error) {
return;
}
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getRequestKeySyncMessage()
);
await singleProtoJobQueue.add(MessageSender.getRequestKeySyncMessage());
} catch (error) {
log.error(
'storageService.stopStorageServiceSync: Failed to queue sync message',
@ -974,6 +967,11 @@ async function processRemoteRecords(
if (!storageKeyBase64) {
throw new Error('No storage key');
}
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available');
}
const storageKey = Bytes.fromBase64(storageKeyBase64);
log.info(
@ -993,13 +991,12 @@ async function processRemoteRecords(
const readOperation = new Proto.ReadOperation();
readOperation.readKey = batch.map(Bytes.fromBase64);
const storageItemsBuffer =
await window.textsecure.messaging.getStorageRecords(
Proto.ReadOperation.encode(readOperation).finish(),
{
credentials,
}
);
const storageItemsBuffer = await messaging.getStorageRecords(
Proto.ReadOperation.encode(readOperation).finish(),
{
credentials,
}
);
return Proto.StorageItems.decode(storageItemsBuffer).items ?? [];
},
@ -1403,9 +1400,7 @@ async function upload(fromSync = false): Promise<void> {
}
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getRequestKeySyncMessage()
);
await singleProtoJobQueue.add(MessageSender.getRequestKeySyncMessage());
} catch (error) {
log.error(
'storageService.upload: Failed to queue sync message',

View file

@ -12,11 +12,17 @@ import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue';
import { strictAssert } from '../util/assert';
import { isWhitespace } from '../util/whitespaceStringUtil';
import type { AvatarUpdateType } from '../types/Avatar';
import MessageSender from '../textsecure/SendMessage';
export async function writeProfile(
conversation: ConversationType,
avatar: AvatarUpdateType
): Promise<void> {
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
// Before we write anything we request the user's profile so that we can
// have an up-to-date paymentAddress to be able to include it when we write
const model = window.ConversationController.get(conversation.id);
@ -44,9 +50,7 @@ export async function writeProfile(
conversation,
avatar
);
const avatarRequestHeaders = await window.textsecure.messaging.putProfile(
profileData
);
const avatarRequestHeaders = await messaging.putProfile(profileData);
// Upload the avatar if provided
// delete existing files on disk if avatar has been removed
@ -64,7 +68,7 @@ export async function writeProfile(
log.info('writeProfile: not updating avatar');
} else if (avatarRequestHeaders && encryptedAvatarData && newAvatar) {
log.info('writeProfile: uploading new avatar');
const avatarUrl = await window.textsecure.messaging.uploadAvatar(
const avatarUrl = await messaging.uploadAvatar(
avatarRequestHeaders,
encryptedAvatarData
);
@ -109,7 +113,7 @@ export async function writeProfile(
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getFetchLocalProfileSyncMessage()
MessageSender.getFetchLocalProfileSyncMessage()
);
} catch (error) {
log.error(

View file

@ -6,6 +6,7 @@ import dataInterface from '../sql/Client';
import { updateOurUsername } from '../util/updateOurUsername';
import * as Errors from '../types/errors';
import * as log from '../logging/log';
import MessageSender from '../textsecure/SendMessage';
export async function writeUsername({
username,
@ -14,6 +15,11 @@ export async function writeUsername({
username: string | undefined;
previousUsername: string | undefined;
}): Promise<void> {
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging interface is not available!');
}
const me = window.ConversationController.getOurConversationOrThrow();
await updateOurUsername();
@ -22,9 +28,9 @@ export async function writeUsername({
}
if (username) {
await window.textsecure.messaging.putUsername(username);
await messaging.putUsername(username);
} else {
await window.textsecure.messaging.deleteUsername();
await messaging.deleteUsername();
}
// Update backbone, update DB, then tell linked devices about profile update
@ -36,7 +42,7 @@ export async function writeUsername({
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getFetchLocalProfileSyncMessage()
MessageSender.getFetchLocalProfileSyncMessage()
);
} catch (error) {
log.error(

View file

@ -4,22 +4,13 @@
import * as log from '../logging/log';
import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue';
import * as Errors from '../types/errors';
import MessageSender from '../textsecure/SendMessage';
export async function sendStickerPackSync(
packId: string,
packKey: string,
installed: boolean
): Promise<void> {
const { textsecure } = window;
if (!textsecure.messaging) {
log.error(
'shim: Cannot call sendStickerPackSync, textsecure.messaging is falsey'
);
return;
}
if (window.ConversationController.areWePrimaryDevice()) {
log.warn(
'shims/sendStickerPackSync: We are primary device; not sending sync'
@ -29,7 +20,7 @@ export async function sendStickerPackSync(
try {
await singleProtoJobQueue.add(
textsecure.messaging.getStickerPackSync([
MessageSender.getStickerPackSync([
{
packId,
packKey,

501
ts/signal.ts Normal file
View file

@ -0,0 +1,501 @@
// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// The idea with this file is to make it webpackable for the style guide
import * as Backbone from './backbone';
import * as Crypto from './Crypto';
import * as Curve from './Curve';
import { start as conversationControllerStart } from './ConversationController';
import Data from './sql/Client';
import * as Groups from './groups';
import * as OS from './OS';
import * as RemoteConfig from './RemoteConfig';
import * as Util from './util';
// Components
import { AttachmentList } from './components/conversation/AttachmentList';
import { ChatColorPicker } from './components/ChatColorPicker';
import { ConfirmationDialog } from './components/ConfirmationDialog';
import { ContactModal } from './components/conversation/ContactModal';
import { Emojify } from './components/conversation/Emojify';
import { MessageDetail } from './components/conversation/MessageDetail';
import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
import { SystemTraySettingsCheckboxes } from './components/conversation/SystemTraySettingsCheckboxes';
// State
import { createChatColorPicker } from './state/roots/createChatColorPicker';
import { createConversationDetails } from './state/roots/createConversationDetails';
import { createApp } from './state/roots/createApp';
import { createForwardMessageModal } from './state/roots/createForwardMessageModal';
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
import { createGroupV1MigrationModal } from './state/roots/createGroupV1MigrationModal';
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
import { createLeftPane } from './state/roots/createLeftPane';
import { createMessageDetail } from './state/roots/createMessageDetail';
import { createConversationNotificationsSettings } from './state/roots/createConversationNotificationsSettings';
import { createGroupV2Permissions } from './state/roots/createGroupV2Permissions';
import { createPendingInvites } from './state/roots/createPendingInvites';
import { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
import { createStickerManager } from './state/roots/createStickerManager';
import { createStickerPreviewModal } from './state/roots/createStickerPreviewModal';
import { createShortcutGuideModal } from './state/roots/createShortcutGuideModal';
import { createStore } from './state/createStore';
import * as appDuck from './state/ducks/app';
import * as callingDuck from './state/ducks/calling';
import * as conversationsDuck from './state/ducks/conversations';
import * as emojisDuck from './state/ducks/emojis';
import * as expirationDuck from './state/ducks/expiration';
import * as itemsDuck from './state/ducks/items';
import * as linkPreviewsDuck from './state/ducks/linkPreviews';
import * as networkDuck from './state/ducks/network';
import * as searchDuck from './state/ducks/search';
import * as stickersDuck from './state/ducks/stickers';
import * as updatesDuck from './state/ducks/updates';
import * as userDuck from './state/ducks/user';
import * as conversationsSelectors from './state/selectors/conversations';
import * as searchSelectors from './state/selectors/search';
// Types
import * as TypesAttachment from './types/Attachment';
import * as VisualAttachment from './types/VisualAttachment';
import * as MessageType from './types/Message2';
import { UUID } from './types/UUID';
import { Address } from './types/Address';
import { QualifiedAddress } from './types/QualifiedAddress';
// Processes / Services
import { initializeGroupCredentialFetcher } from './services/groupCredentialFetcher';
import { initializeNetworkObserver } from './services/networkObserver';
import { initializeUpdateListener } from './services/updateListener';
import { calling } from './services/calling';
import {
enableStorageService,
eraseAllStorageServiceState,
runStorageServiceSyncJob,
storageServiceUploadJob,
} from './services/storage';
import type { LoggerType } from './types/Logging';
import type {
AttachmentType,
AttachmentWithHydratedData,
} from './types/Attachment';
import type { MessageAttributesType, QuotedMessageType } from './model-types.d';
import type { SignalCoreType } from './window.d';
import type { EmbeddedContactType } from './types/EmbeddedContact';
import type { ContactWithHydratedAvatar } from './textsecure/SendMessage';
import type { LinkPreviewType } from './types/message/LinkPreviews';
import type { StickerType, StickerWithHydratedData } from './types/Stickers';
type MigrationsModuleType = {
attachmentsPath: string;
copyIntoAttachmentsDirectory: (
path: string
) => Promise<{ path: string; size: number }>;
copyIntoTempDirectory: (
path: string
) => Promise<{ path: string; size: number }>;
deleteAttachmentData: (path: string) => Promise<void>;
deleteAvatar: (path: string) => Promise<void>;
deleteDraftFile: (path: string) => Promise<void>;
deleteExternalMessageFiles: (
attributes: MessageAttributesType
) => Promise<void>;
deleteSticker: (path: string) => Promise<void>;
deleteTempFile: (path: string) => Promise<void>;
doesAttachmentExist: (path: string) => Promise<boolean>;
getAbsoluteAttachmentPath: (path: string) => string;
getAbsoluteAvatarPath: (src: string) => string;
getAbsoluteBadgeImageFilePath: (path: string) => string;
getAbsoluteDraftPath: (path: string) => string;
getAbsoluteStickerPath: (path: string) => string;
getAbsoluteTempPath: (path: string) => string;
loadAttachmentData: (
attachment: AttachmentType
) => Promise<AttachmentWithHydratedData>;
loadContactData: (
contact: Array<EmbeddedContactType> | undefined
) => Promise<Array<ContactWithHydratedAvatar> | undefined>;
loadMessage: (
message: MessageAttributesType
) => Promise<MessageAttributesType>;
loadPreviewData: (
preview: Array<LinkPreviewType> | undefined
) => Promise<Array<LinkPreviewType>>;
loadQuoteData: (
quote: QuotedMessageType | null | undefined
) => Promise<QuotedMessageType | null>;
loadStickerData: (
sticker: StickerType | undefined
) => Promise<StickerWithHydratedData | undefined>;
openFileInFolder: (target: string) => Promise<void>;
readAttachmentData: (path: string) => Promise<Uint8Array>;
readDraftData: (path: string) => Promise<Uint8Array>;
readStickerData: (path: string) => Promise<Uint8Array>;
readTempData: (path: string) => Promise<Uint8Array>;
saveAttachmentToDisk: (options: {
data: Uint8Array;
name: string;
}) => Promise<null | { fullPath: string; name: string }>;
processNewAttachment: (attachment: AttachmentType) => Promise<AttachmentType>;
processNewSticker: (stickerData: Uint8Array) => Promise<{
path: string;
width: number;
height: number;
}>;
processNewEphemeralSticker: (stickerData: Uint8Array) => Promise<{
path: string;
width: number;
height: number;
}>;
upgradeMessageSchema: (
attributes: MessageAttributesType,
options?: { maxVersion?: number }
) => Promise<MessageAttributesType>;
writeMessageAttachments: (
message: MessageAttributesType
) => Promise<MessageAttributesType>;
writeNewAttachmentData: (data: Uint8Array) => Promise<string>;
writeNewDraftData: (data: Uint8Array) => Promise<string>;
writeNewAvatarData: (data: Uint8Array) => Promise<string>;
writeNewBadgeImageFileData: (data: Uint8Array) => Promise<string>;
};
export function initializeMigrations({
getRegionCode,
Attachments,
Type,
VisualType,
logger,
userDataPath,
}: {
getRegionCode: () => string | undefined;
Attachments: AttachmentsModuleType;
Type: typeof TypesAttachment;
VisualType: typeof VisualAttachment;
logger: LoggerType;
userDataPath: string;
}): MigrationsModuleType {
if (!Attachments) {
throw new Error('initializeMigrations: Missing provided attachments!');
}
const {
createAbsolutePathGetter,
createReader,
createWriterForExisting,
createWriterForNew,
createDoesExist,
getAvatarsPath,
getDraftPath,
getPath,
getStickersPath,
getBadgesPath,
getTempPath,
openFileInFolder,
saveAttachmentToDisk,
} = Attachments;
const {
getImageDimensions,
makeImageThumbnail,
makeObjectUrl,
makeVideoScreenshot,
revokeObjectUrl,
} = VisualType;
const attachmentsPath = getPath(userDataPath);
const readAttachmentData = createReader(attachmentsPath);
const loadAttachmentData = Type.loadData(readAttachmentData);
const loadContactData = MessageType.loadContactData(loadAttachmentData);
const loadPreviewData = MessageType.loadPreviewData(loadAttachmentData);
const loadQuoteData = MessageType.loadQuoteData(loadAttachmentData);
const loadStickerData = MessageType.loadStickerData(loadAttachmentData);
const getAbsoluteAttachmentPath = createAbsolutePathGetter(attachmentsPath);
const deleteOnDisk = Attachments.createDeleter(attachmentsPath);
const writeExistingAttachmentData = createWriterForExisting(attachmentsPath);
const writeNewAttachmentData = createWriterForNew(attachmentsPath);
const copyIntoAttachmentsDirectory =
Attachments.copyIntoAttachmentsDirectory(attachmentsPath);
const doesAttachmentExist = createDoesExist(attachmentsPath);
const stickersPath = getStickersPath(userDataPath);
const writeNewStickerData = createWriterForNew(stickersPath);
const getAbsoluteStickerPath = createAbsolutePathGetter(stickersPath);
const deleteSticker = Attachments.createDeleter(stickersPath);
const readStickerData = createReader(stickersPath);
const badgesPath = getBadgesPath(userDataPath);
const getAbsoluteBadgeImageFilePath = createAbsolutePathGetter(badgesPath);
const writeNewBadgeImageFileData = createWriterForNew(badgesPath, '.svg');
const tempPath = getTempPath(userDataPath);
const getAbsoluteTempPath = createAbsolutePathGetter(tempPath);
const writeNewTempData = createWriterForNew(tempPath);
const deleteTempFile = Attachments.createDeleter(tempPath);
const readTempData = createReader(tempPath);
const copyIntoTempDirectory =
Attachments.copyIntoAttachmentsDirectory(tempPath);
const draftPath = getDraftPath(userDataPath);
const getAbsoluteDraftPath = createAbsolutePathGetter(draftPath);
const writeNewDraftData = createWriterForNew(draftPath);
const deleteDraftFile = Attachments.createDeleter(draftPath);
const readDraftData = createReader(draftPath);
const avatarsPath = getAvatarsPath(userDataPath);
const getAbsoluteAvatarPath = createAbsolutePathGetter(avatarsPath);
const writeNewAvatarData = createWriterForNew(avatarsPath);
const deleteAvatar = Attachments.createDeleter(avatarsPath);
return {
attachmentsPath,
copyIntoAttachmentsDirectory,
copyIntoTempDirectory,
deleteAttachmentData: deleteOnDisk,
deleteAvatar,
deleteDraftFile,
deleteExternalMessageFiles: MessageType.deleteAllExternalFiles({
deleteAttachmentData: Type.deleteData(deleteOnDisk),
deleteOnDisk,
}),
deleteSticker,
deleteTempFile,
doesAttachmentExist,
getAbsoluteAttachmentPath,
getAbsoluteAvatarPath,
getAbsoluteBadgeImageFilePath,
getAbsoluteDraftPath,
getAbsoluteStickerPath,
getAbsoluteTempPath,
loadAttachmentData,
loadContactData,
loadMessage: MessageType.createAttachmentLoader(loadAttachmentData),
loadPreviewData,
loadQuoteData,
loadStickerData,
openFileInFolder,
readAttachmentData,
readDraftData,
readStickerData,
readTempData,
saveAttachmentToDisk,
processNewAttachment: (attachment: AttachmentType) =>
MessageType.processNewAttachment(attachment, {
writeNewAttachmentData,
getAbsoluteAttachmentPath,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
}),
processNewSticker: (stickerData: Uint8Array) =>
MessageType.processNewSticker(stickerData, {
writeNewStickerData,
getAbsoluteStickerPath,
getImageDimensions,
logger,
}),
processNewEphemeralSticker: (stickerData: Uint8Array) =>
MessageType.processNewSticker(stickerData, {
writeNewStickerData: writeNewTempData,
getAbsoluteStickerPath: getAbsoluteTempPath,
getImageDimensions,
logger,
}),
upgradeMessageSchema: (
message: MessageAttributesType,
options: { maxVersion?: number } = {}
) => {
const { maxVersion } = options;
return MessageType.upgradeSchema(message, {
writeNewAttachmentData,
getRegionCode,
getAbsoluteAttachmentPath,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
maxVersion,
getAbsoluteStickerPath,
writeNewStickerData,
});
},
writeMessageAttachments: MessageType.createAttachmentDataWriter({
writeExistingAttachmentData,
logger,
}),
writeNewAttachmentData: createWriterForNew(attachmentsPath),
writeNewAvatarData,
writeNewDraftData,
writeNewBadgeImageFileData,
};
}
type StringGetterType = (basePath: string) => string;
type AttachmentsModuleType = {
getAvatarsPath: StringGetterType;
getBadgesPath: StringGetterType;
getDraftPath: StringGetterType;
getPath: StringGetterType;
getStickersPath: StringGetterType;
getTempPath: StringGetterType;
getUpdateCachePath: StringGetterType;
createDeleter: (root: string) => (relativePath: string) => Promise<void>;
createReader: (root: string) => (relativePath: string) => Promise<Uint8Array>;
getRelativePath: (name: string) => string;
createName: (suffix?: string) => string;
copyIntoAttachmentsDirectory: (
root: string
) => (sourcePath: string) => Promise<{ path: string; size: number }>;
createWriterForNew: (
root: string,
suffix?: string
) => (bytes: Uint8Array) => Promise<string>;
createWriterForExisting: (
root: string
) => (options: { data?: Uint8Array; path?: string }) => Promise<string>;
createAbsolutePathGetter: (
rootPath: string
) => (relativePath: string) => string;
createDoesExist: (root: string) => (relativePath: string) => Promise<boolean>;
openFileInFolder: (target: string) => Promise<void>;
saveAttachmentToDisk: ({
data,
name,
}: {
data: Uint8Array;
name: string;
}) => Promise<null | { fullPath: string; name: string }>;
};
export const setup = (options: {
Attachments: AttachmentsModuleType;
getRegionCode: () => string | undefined;
logger: LoggerType;
userDataPath: string;
}): SignalCoreType => {
const { Attachments, getRegionCode, logger, userDataPath } = options;
const Migrations = initializeMigrations({
getRegionCode,
Attachments,
Type: TypesAttachment,
VisualType: VisualAttachment,
logger,
userDataPath,
});
const Components = {
AttachmentList,
ChatColorPicker,
ConfirmationDialog,
ContactModal,
Emojify,
MessageDetail,
Quote,
StagedLinkPreview,
DisappearingTimeDialog,
SystemTraySettingsCheckboxes,
};
const Roots = {
createApp,
createChatColorPicker,
createConversationDetails,
createForwardMessageModal,
createGroupLinkManagement,
createGroupV1MigrationModal,
createGroupV2JoinModal,
createGroupV2Permissions,
createLeftPane,
createMessageDetail,
createConversationNotificationsSettings,
createPendingInvites,
createSafetyNumberViewer,
createShortcutGuideModal,
createStickerManager,
createStickerPreviewModal,
};
const Ducks = {
app: appDuck,
calling: callingDuck,
conversations: conversationsDuck,
emojis: emojisDuck,
expiration: expirationDuck,
items: itemsDuck,
linkPreviews: linkPreviewsDuck,
network: networkDuck,
updates: updatesDuck,
user: userDuck,
search: searchDuck,
stickers: stickersDuck,
};
const Selectors = {
conversations: conversationsSelectors,
search: searchSelectors,
};
const Services = {
calling,
enableStorageService,
eraseAllStorageServiceState,
initializeGroupCredentialFetcher,
initializeNetworkObserver,
initializeUpdateListener,
runStorageServiceSyncJob,
storageServiceUploadJob,
};
const State = {
createStore,
Roots,
Ducks,
Selectors,
};
const Types = {
Message: MessageType,
// Mostly for debugging
UUID,
Address,
QualifiedAddress,
};
return {
Backbone,
Components,
Crypto,
Curve,
// Note: used in test/index.html, and not type-checked!
conversationControllerStart,
Data,
Groups,
Migrations,
OS,
RemoteConfig,
Services,
State,
Types,
Util,
};
};

View file

@ -199,6 +199,7 @@ export type UnprocessedType = {
attempts: number;
envelope?: string;
messageAgeSec?: number;
source?: string;
sourceUuid?: string;
sourceDevice?: number;

View file

@ -10,6 +10,7 @@ import {
} from '../textsecure/processDataMessage';
import type { ProcessedAttachment } from '../textsecure/Types.d';
import { SignalService as Proto } from '../protobuf';
import { IMAGE_GIF } from '../types/MIME';
const FLAGS = Proto.DataMessage.Flags;
@ -19,12 +20,16 @@ const UNPROCESSED_ATTACHMENT: Proto.IAttachmentPointer = {
cdnId: Long.fromNumber(123),
key: new Uint8Array([1, 2, 3]),
digest: new Uint8Array([4, 5, 6]),
contentType: IMAGE_GIF,
size: 34,
};
const PROCESSED_ATTACHMENT: ProcessedAttachment = {
cdnId: '123',
key: 'AQID',
digest: 'BAUG',
contentType: IMAGE_GIF,
size: 34,
};
const GROUP_ID = new Uint8Array([0x68, 0x65, 0x79]);
@ -118,7 +123,6 @@ describe('processDataMessage', () => {
id: GROUP_ID,
name: 'should be deleted',
membersE164: ['should be deleted'],
avatar: {},
type: Proto.GroupContext.Type.DELIVER,
},
});

View file

@ -28,6 +28,10 @@ describe('license comments', () => {
[firstLine, secondLine] = await readFirstLines(file, 2);
}
if (!firstLine || !secondLine) {
throw new Error(`file ${file}: was missing a first or second line!`);
}
const { groups = {} } =
firstLine.match(
/Copyright (?<startYearWithDash>\d{4}-)?(?<endYearString>\d{4}) Signal Messenger, LLC/

View file

@ -10,10 +10,7 @@ import * as Bytes from '../../Bytes';
import * as MIME from '../../types/MIME';
import type { EmbeddedContactType } from '../../types/EmbeddedContact';
import type {
MessageAttributesType,
StickerMessageType,
} from '../../model-types.d';
import type { MessageAttributesType } from '../../model-types.d';
import type { AttachmentType } from '../../types/Attachment';
import type { LoggerType } from '../../types/Logging';
@ -75,12 +72,11 @@ describe('Message', () => {
revokeObjectUrl: (_objectUrl: string) => undefined,
writeNewAttachmentData: async (_data: Uint8Array) =>
'fake-attachment-path',
writeNewStickerData: async (_sticker: StickerMessageType) =>
'fake-sticker-path',
writeNewStickerData: async (_data: Uint8Array) => 'fake-sticker-path',
...props,
};
}
const writeExistingAttachmentData = () => Promise.resolve();
const writeExistingAttachmentData = () => Promise.resolve('path');
describe('createAttachmentDataWriter', () => {
it('should ignore messages that didnt go through attachment migration', async () => {
@ -155,6 +151,7 @@ describe('Message', () => {
Bytes.toString(attachment.data || new Uint8Array()),
'Its easy if you try'
);
return 'path';
};
const actual = await Message.createAttachmentDataWriter({
@ -214,6 +211,7 @@ describe('Message', () => {
Bytes.toString(attachment.data || new Uint8Array()),
'Its easy if you try'
);
return 'path';
};
const actual = await Message.createAttachmentDataWriter({
@ -272,6 +270,7 @@ describe('Message', () => {
Bytes.toString(attachment.data || new Uint8Array()),
'Its easy if you try'
);
return 'path';
};
const actual = await Message.createAttachmentDataWriter({
@ -279,6 +278,7 @@ describe('Message', () => {
logger,
})(input);
assert.deepEqual(actual, expected);
return 'path';
});
});

51
ts/textsecure.d.ts vendored
View file

@ -1,51 +0,0 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { UnidentifiedSenderMessageContent } from '@signalapp/libsignal-client';
import MessageSender from './textsecure/SendMessage';
import SyncRequest from './textsecure/SyncRequest';
import EventTarget from './textsecure/EventTarget';
import SendMessage, { SendOptionsType } from './textsecure/SendMessage';
import { WebAPIType } from './textsecure/WebAPI';
import utils from './textsecure/Helpers';
import { CallingMessage as CallingMessageClass } from 'ringrtc';
import { WhatIsThis } from './window.d';
import { Storage } from './textsecure/Storage';
import {
StorageServiceCallOptionsType,
StorageServiceCredentials,
ProcessedAttachment,
} from './textsecure/Types.d';
export type UnprocessedType = {
attempts: number;
decrypted?: string;
envelope?: string;
id: string;
receivedAtCounter: number | null;
timestamp: number;
serverGuid?: string;
serverTimestamp?: number;
source?: string;
sourceDevice?: number;
sourceUuid?: string;
destinationUuid?: string;
messageAgeSec?: number;
version: number;
};
export { StorageServiceCallOptionsType, StorageServiceCredentials };
export type TextSecureType = {
storage: Storage;
server: WebAPIType;
messageSender: MessageSender;
messaging: SendMessage;
utils: typeof utils;
EventTarget: typeof EventTarget;
AccountManager: WhatIsThis;
MessageSender: typeof MessageSender;
SyncRequest: typeof SyncRequest;
};

View file

@ -55,7 +55,6 @@ import * as Errors from '../types/errors';
import { isEnabled } from '../RemoteConfig';
import { SignalService as Proto } from '../protobuf';
import type { UnprocessedType } from '../textsecure.d';
import { deriveGroupFields, MASTER_KEY_LENGTH } from '../groups';
import createTaskWithTimeout from './TaskWithTimeout';
@ -81,6 +80,7 @@ import type {
ProcessedSent,
ProcessedEnvelope,
IRequestHandler,
UnprocessedType,
} from './Types.d';
import {
EmptyEvent,
@ -114,6 +114,7 @@ import * as log from '../logging/log';
import * as durations from '../util/durations';
import { areArraysMatchingSets } from '../util/areArraysMatchingSets';
import { generateBlurHash } from '../util/generateBlurHash';
import { APPLICATION_OCTET_STREAM } from '../types/MIME';
const GROUPV1_ID_LENGTH = 16;
const GROUPV2_ID_LENGTH = 32;
@ -1800,8 +1801,14 @@ export default class MessageReceiver
}
if (msg.textAttachment) {
const { text } = msg.textAttachment;
if (!text) {
throw new Error('Text attachments must have text!');
}
attachments.push({
size: msg.textAttachment.text?.length,
size: text.length,
contentType: APPLICATION_OCTET_STREAM,
textAttachment: msg.textAttachment,
blurHash: generateBlurHash(
(msg.textAttachment.color ||

View file

@ -38,7 +38,11 @@ import type {
WebAPIType,
} from './WebAPI';
import createTaskWithTimeout from './TaskWithTimeout';
import type { CallbackResultType } from './Types.d';
import type {
CallbackResultType,
StorageServiceCallOptionsType,
StorageServiceCredentials,
} from './Types.d';
import type {
SerializedCertificateType,
SendLogCallbackType,
@ -47,10 +51,6 @@ import OutgoingMessage from './OutgoingMessage';
import type { CDSResponseType } from './CDSSocketManager';
import * as Bytes from '../Bytes';
import { getRandomBytes, getZeroes, encryptAttachment } from '../Crypto';
import type {
StorageServiceCallOptionsType,
StorageServiceCredentials,
} from '../textsecure.d';
import {
MessageError,
SignedPreKeyRotationError,
@ -73,6 +73,7 @@ import {
numberToEmailType,
numberToAddressType,
} from '../types/EmbeddedContact';
import type { StickerWithHydratedData } from '../types/Stickers';
export type SendMetadataType = {
[identifier: string]: {
@ -106,13 +107,7 @@ type GroupCallUpdateType = {
eraId: string;
};
export type StickerType = {
packId: string;
stickerId: number;
packKey: string;
data: Readonly<AttachmentType>;
emoji?: string;
export type StickerType = StickerWithHydratedData & {
attachmentPointer?: Proto.IAttachmentPointer;
};
@ -631,7 +626,7 @@ export default class MessageSender {
);
}
getRandomPadding(): Uint8Array {
static getRandomPadding(): Uint8Array {
// Generate a random int from 1 and 512
const buffer = getRandomBytes(2);
const paddingLength = (new Uint16Array(buffer)[0] & 0x1ff) + 1;
@ -996,7 +991,7 @@ export default class MessageSender {
};
}
createSyncMessage(): Proto.SyncMessage {
static createSyncMessage(): Proto.SyncMessage {
const syncMessage = new Proto.SyncMessage();
syncMessage.padding = this.getRandomPadding();
@ -1287,7 +1282,7 @@ export default class MessageSender {
];
}
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
syncMessage.sent = sentMessage;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
@ -1303,12 +1298,12 @@ export default class MessageSender {
});
}
getRequestBlockSyncMessage(): SingleProtoJobData {
static getRequestBlockSyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
@ -1326,12 +1321,12 @@ export default class MessageSender {
};
}
getRequestConfigurationSyncMessage(): SingleProtoJobData {
static getRequestConfigurationSyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
@ -1349,7 +1344,7 @@ export default class MessageSender {
};
}
getRequestGroupSyncMessage(): SingleProtoJobData {
static getRequestGroupSyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
@ -1372,7 +1367,7 @@ export default class MessageSender {
};
}
getRequestContactSyncMessage(): SingleProtoJobData {
static getRequestContactSyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
@ -1395,7 +1390,7 @@ export default class MessageSender {
};
}
getRequestPniIdentitySyncMessage(): SingleProtoJobData {
static getRequestPniIdentitySyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
@ -1418,7 +1413,7 @@ export default class MessageSender {
};
}
getFetchManifestSyncMessage(): SingleProtoJobData {
static getFetchManifestSyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const fetchLatest = new Proto.SyncMessage.FetchLatest();
@ -1442,7 +1437,7 @@ export default class MessageSender {
};
}
getFetchLocalProfileSyncMessage(): SingleProtoJobData {
static getFetchLocalProfileSyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const fetchLatest = new Proto.SyncMessage.FetchLatest();
@ -1466,7 +1461,7 @@ export default class MessageSender {
};
}
getRequestKeySyncMessage(): SingleProtoJobData {
static getRequestKeySyncMessage(): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
@ -1500,7 +1495,7 @@ export default class MessageSender {
): Promise<CallbackResultType> {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
syncMessage.read = [];
for (let i = 0; i < reads.length; i += 1) {
const proto = new Proto.SyncMessage.Read({
@ -1534,7 +1529,7 @@ export default class MessageSender {
): Promise<CallbackResultType> {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
syncMessage.viewed = views.map(
view =>
new Proto.SyncMessage.Viewed({
@ -1577,7 +1572,7 @@ export default class MessageSender {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
const viewOnceOpen = new Proto.SyncMessage.ViewOnceOpen();
if (senderE164 !== undefined) {
@ -1601,7 +1596,7 @@ export default class MessageSender {
});
}
getMessageRequestResponseSync(
static getMessageRequestResponseSync(
options: Readonly<{
threadE164?: string;
threadUuid?: string;
@ -1611,7 +1606,7 @@ export default class MessageSender {
): SingleProtoJobData {
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
const response = new Proto.SyncMessage.MessageRequestResponse();
if (options.threadE164 !== undefined) {
@ -1642,7 +1637,7 @@ export default class MessageSender {
};
}
getStickerPackSync(
static getStickerPackSync(
operations: ReadonlyArray<{
packId: string;
packKey: string;
@ -1663,7 +1658,7 @@ export default class MessageSender {
return operation;
});
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
syncMessage.stickerPackOperation = packOperations;
const contentMessage = new Proto.Content();
@ -1682,7 +1677,7 @@ export default class MessageSender {
};
}
getVerificationSync(
static getVerificationSync(
destinationE164: string | undefined,
destinationUuid: string | undefined,
state: number,
@ -1694,7 +1689,7 @@ export default class MessageSender {
throw new Error('syncVerification: Neither e164 nor UUID were provided');
}
const padding = this.getRandomPadding();
const padding = MessageSender.getRandomPadding();
const verified = new Proto.Verified();
verified.state = state;
@ -1707,7 +1702,7 @@ export default class MessageSender {
verified.identityKey = identityKey;
verified.nullMessage = padding;
const syncMessage = this.createSyncMessage();
const syncMessage = MessageSender.createSyncMessage();
syncMessage.verified = verified;
const contentMessage = new Proto.Content();
@ -1832,7 +1827,7 @@ export default class MessageSender {
});
}
getNullMessage({
static getNullMessage({
uuid,
e164,
padding,
@ -1848,7 +1843,7 @@ export default class MessageSender {
throw new Error('sendNullMessage: Got neither uuid nor e164!');
}
nullMessage.padding = padding || this.getRandomPadding();
nullMessage.padding = padding || MessageSender.getRandomPadding();
const contentMessage = new Proto.Content();
contentMessage.nullMessage = nullMessage;

View file

@ -29,19 +29,12 @@ class SyncRequestInner extends EventTarget {
timeoutMillis: number;
constructor(
private sender: MessageSender,
private receiver: MessageReceiver,
timeoutMillis?: number
) {
constructor(private receiver: MessageReceiver, timeoutMillis?: number) {
super();
if (
!(sender instanceof MessageSender) ||
!(receiver instanceof MessageReceiver)
) {
if (!(receiver instanceof MessageReceiver)) {
throw new Error(
'Tried to construct a SyncRequest without MessageSender and MessageReceiver'
'Tried to construct a SyncRequest without MessageReceiver'
);
}
@ -61,8 +54,6 @@ class SyncRequestInner extends EventTarget {
}
this.started = true;
const { sender } = this;
if (window.ConversationController.areWePrimaryDevice()) {
log.warn('SyncRequest.start: We are primary device; returning early');
return;
@ -73,10 +64,12 @@ class SyncRequestInner extends EventTarget {
);
try {
await Promise.all([
singleProtoJobQueue.add(sender.getRequestConfigurationSyncMessage()),
singleProtoJobQueue.add(sender.getRequestBlockSyncMessage()),
singleProtoJobQueue.add(sender.getRequestContactSyncMessage()),
singleProtoJobQueue.add(sender.getRequestGroupSyncMessage()),
singleProtoJobQueue.add(
MessageSender.getRequestConfigurationSyncMessage()
),
singleProtoJobQueue.add(MessageSender.getRequestBlockSyncMessage()),
singleProtoJobQueue.add(MessageSender.getRequestContactSyncMessage()),
singleProtoJobQueue.add(MessageSender.getRequestGroupSyncMessage()),
]);
} catch (error: unknown) {
log.error(
@ -135,12 +128,8 @@ export default class SyncRequest {
handler: EventHandler
) => void;
constructor(
sender: MessageSender,
receiver: MessageReceiver,
timeoutMillis?: number
) {
const inner = new SyncRequestInner(sender, receiver, timeoutMillis);
constructor(receiver: MessageReceiver, timeoutMillis?: number) {
const inner = new SyncRequestInner(receiver, timeoutMillis);
this.inner = inner;
this.addEventListener = inner.addEventListener.bind(inner);
this.removeEventListener = inner.removeEventListener.bind(inner);

View file

@ -6,6 +6,7 @@ import type { IncomingWebSocketRequest } from './WebsocketResources';
import type { UUID } from '../types/UUID';
import type { TextAttachmentType } from '../types/Attachment';
import { GiftBadgeStates } from '../components/conversation/Message';
import { MIMEType } from '../types/MIME';
export {
IdentityKeyType,
@ -97,9 +98,9 @@ export type ProcessedAttachment = {
cdnId?: string;
cdnKey?: string;
digest?: string;
contentType?: string;
contentType: MIMEType;
key?: string;
size?: number;
size: number;
fileName?: string;
flags?: number;
width?: number;

View file

@ -52,10 +52,6 @@ import { calculateAgreement, generateKeyPair } from '../Curve';
import * as linkPreviewFetch from '../linkPreviews/linkPreviewFetch';
import { isBadgeImageFileUrlValid } from '../badges/isBadgeImageFileUrlValid';
import type {
StorageServiceCallOptionsType,
StorageServiceCredentials,
} from '../textsecure.d';
import { SocketManager } from './SocketManager';
import type { CDSResponseType } from './CDSSocketManager';
import { CDSSocketManager } from './CDSSocketManager';
@ -64,7 +60,12 @@ import { SignalService as Proto } from '../protobuf';
import { HTTPError } from './Errors';
import type MessageSender from './SendMessage';
import type { WebAPICredentials, IRequestHandler } from './Types.d';
import type {
WebAPICredentials,
IRequestHandler,
StorageServiceCallOptionsType,
StorageServiceCredentials,
} from './Types.d';
import { handleStatusCode, translateError } from './Utils';
import * as log from '../logging/log';
import { maybeParseUrl } from '../util/url';
@ -601,16 +602,16 @@ type InitializeOptionsType = {
directoryUrl?: string;
directoryEnclaveId?: string;
directoryTrustAnchor?: string;
directoryV2Url: string;
directoryV2PublicKey: string;
directoryV2CodeHashes: ReadonlyArray<string>;
directoryV2Url?: string;
directoryV2PublicKey?: string;
directoryV2CodeHashes?: ReadonlyArray<string>;
cdnUrlObject: {
readonly '0': string;
readonly [propName: string]: string;
};
certificateAuthority: string;
contentProxyUrl: string;
proxyUrl: string;
proxyUrl: string | undefined;
version: string;
};
@ -1018,6 +1019,13 @@ export type ProxiedRequestOptionsType = {
end?: number;
};
export type TopLevelType = {
multiRecipient200ResponseSchema: typeof multiRecipient200ResponseSchema;
multiRecipient409ResponseSchema: typeof multiRecipient409ResponseSchema;
multiRecipient410ResponseSchema: typeof multiRecipient410ResponseSchema;
initialize: (options: InitializeOptionsType) => WebAPIConnectType;
};
// We first set up the data that won't change during this session of the app
export function initialize({
url,
@ -1138,8 +1146,15 @@ export function initialize({
socketManager.authenticate({ username, password });
}
const cdsUrl = directoryV2Url || directoryUrl;
if (!cdsUrl) {
throw new Error('No CDS url available!');
}
if (!directoryV2PublicKey || !directoryV2CodeHashes?.length) {
throw new Error('No CDS public key or code hashes available');
}
const cdsSocketManager = new CDSSocketManager({
url: directoryV2Url,
url: cdsUrl,
publicKey: directoryV2PublicKey,
codeHashes: directoryV2CodeHashes,
certificateAuthority,

View file

@ -12,7 +12,25 @@ import { Storage } from './Storage';
import * as WebAPI from './WebAPI';
import WebSocketResource from './WebsocketResources';
export const textsecure = {
export type TextSecureType = {
utils: typeof utils;
storage: Storage;
AccountManager: typeof AccountManager;
ContactBuffer: typeof ContactBuffer;
EventTarget: typeof EventTarget;
GroupBuffer: typeof GroupBuffer;
MessageReceiver: typeof MessageReceiver;
MessageSender: typeof MessageSender;
SyncRequest: typeof SyncRequest;
WebAPI: typeof WebAPI;
WebSocketResource: typeof WebSocketResource;
server?: WebAPI.WebAPIType;
messaging?: MessageSender;
};
export const textsecure: TextSecureType = {
utils,
storage: new Storage(),
@ -26,5 +44,3 @@ export const textsecure = {
WebAPI,
WebSocketResource,
};
export default textsecure;

View file

@ -26,6 +26,7 @@ import type {
} from './Types.d';
import { WarnOnlyError } from './Errors';
import { GiftBadgeStates } from '../components/conversation/Message';
import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
const FLAGS = Proto.DataMessage.Flags;
export const ATTACHMENT_MAX = 32;
@ -47,12 +48,21 @@ export function processAttachment(
const { cdnId } = attachment;
const hasCdnId = Long.isLong(cdnId) ? !cdnId.isZero() : Boolean(cdnId);
const { contentType, digest, key, size } = attachment;
if (!size) {
throw new Error('Missing size on incoming attachment!');
}
return {
...shallowDropNull(attachment),
cdnId: hasCdnId ? String(cdnId) : undefined,
key: attachment.key ? Bytes.toBase64(attachment.key) : undefined,
digest: attachment.digest ? Bytes.toBase64(attachment.digest) : undefined,
contentType: contentType
? stringToMIMEType(contentType)
: APPLICATION_OCTET_STREAM,
digest: digest ? Bytes.toBase64(digest) : undefined,
key: key ? Bytes.toBase64(key) : undefined,
size,
};
}

View file

@ -83,6 +83,10 @@ export type AttachmentType = {
key?: string;
};
export type AttachmentWithHydratedData = AttachmentType & {
data: Uint8Array;
};
export enum TextAttachmentStyleType {
DEFAULT = 0,
REGULAR = 1,
@ -379,19 +383,21 @@ export function hasData(attachment: AttachmentType): boolean {
export function loadData(
readAttachmentData: (path: string) => Promise<Uint8Array>
): (attachment?: AttachmentType) => Promise<AttachmentType> {
): (attachment: AttachmentType) => Promise<AttachmentWithHydratedData> {
if (!is.function_(readAttachmentData)) {
throw new TypeError("'readAttachmentData' must be a function");
}
return async (attachment?: AttachmentType): Promise<AttachmentType> => {
return async (
attachment: AttachmentType
): Promise<AttachmentWithHydratedData> => {
if (!isValid(attachment)) {
throw new TypeError("'attachment' is not valid");
}
const isAlreadyLoaded = Boolean(attachment.data);
if (isAlreadyLoaded) {
return attachment;
return attachment as AttachmentWithHydratedData;
}
if (!is.string(attachment.path)) {

View file

@ -4,7 +4,7 @@
import { isFunction, isObject, isString, omit } from 'lodash';
import * as Contact from './EmbeddedContact';
import type { AttachmentType } from './Attachment';
import type { AttachmentType, AttachmentWithHydratedData } from './Attachment';
import {
autoOrientJPEG,
captureDimensionsAndScreenshot,
@ -24,11 +24,10 @@ import type { EmbeddedContactType } from './EmbeddedContact';
import type {
MessageAttributesType,
PreviewMessageType,
PreviewType,
QuotedMessageType,
StickerMessageType,
} from '../model-types.d';
import type { LinkPreviewType } from './message/LinkPreviews';
import type { StickerType, StickerWithHydratedData } from './Stickers';
export { hasExpiration } from './Message';
@ -45,7 +44,7 @@ export type ContextType = {
width: number;
height: number;
}>;
getRegionCode: () => string;
getRegionCode: () => string | undefined;
logger: LoggerType;
makeImageThumbnail: (params: {
size: number;
@ -65,12 +64,12 @@ export type ContextType = {
maxVersion?: number;
revokeObjectUrl: (objectUrl: string) => void;
writeNewAttachmentData: (data: Uint8Array) => Promise<string>;
writeNewStickerData: (sticker: StickerMessageType) => Promise<string>;
writeNewStickerData: (data: Uint8Array) => Promise<string>;
};
type WriteExistingAttachmentDataType = (
attachment: Pick<AttachmentType, 'data' | 'path'>
) => Promise<void>;
) => Promise<string>;
export type ContextWithMessageType = ContextType & {
message: MessageAttributesType;
@ -343,7 +342,7 @@ export const _mapPreviewAttachments =
);
}
const upgradeWithContext = async (preview: PreviewType) => {
const upgradeWithContext = async (preview: LinkPreviewType) => {
const { image } = preview;
if (!image) {
return preview;
@ -544,7 +543,17 @@ export const processNewAttachment = async (
makeImageThumbnail,
makeVideoScreenshot,
logger,
}: ContextType
}: Pick<
ContextType,
| 'writeNewAttachmentData'
| 'getAbsoluteAttachmentPath'
| 'makeObjectUrl'
| 'revokeObjectUrl'
| 'getImageDimensions'
| 'makeImageThumbnail'
| 'makeVideoScreenshot'
| 'logger'
>
): Promise<AttachmentType> => {
if (!isFunction(writeNewAttachmentData)) {
throw new TypeError('context.writeNewAttachmentData is required');
@ -595,13 +604,19 @@ export const processNewAttachment = async (
};
export const processNewSticker = async (
stickerData: StickerMessageType,
stickerData: Uint8Array,
{
writeNewStickerData,
getAbsoluteStickerPath,
getImageDimensions,
logger,
}: ContextType
}: Pick<
ContextType,
| 'writeNewStickerData'
| 'getAbsoluteStickerPath'
| 'getImageDimensions'
| 'logger'
>
): Promise<{ path: string; width: number; height: number }> => {
if (!isFunction(writeNewStickerData)) {
throw new TypeError('context.writeNewStickerData is required');
@ -633,7 +648,7 @@ export const processNewSticker = async (
type LoadAttachmentType = (
attachment: AttachmentType
) => Promise<AttachmentType>;
) => Promise<AttachmentWithHydratedData>;
export const createAttachmentLoader = (
loadAttachmentData: LoadAttachmentType
@ -694,16 +709,16 @@ export const loadContactData = (
loadAttachmentData: LoadAttachmentType
): ((
contact: Array<EmbeddedContactType> | undefined
) => Promise<Array<EmbeddedContactType> | null>) => {
) => Promise<Array<EmbeddedContactType> | undefined>) => {
if (!isFunction(loadAttachmentData)) {
throw new TypeError('loadContactData: loadAttachmentData is required');
}
return async (
contact: Array<EmbeddedContactType> | undefined
): Promise<Array<EmbeddedContactType> | null> => {
): Promise<Array<EmbeddedContactType> | undefined> => {
if (!contact) {
return null;
return undefined;
}
return Promise.all(
@ -736,12 +751,14 @@ export const loadContactData = (
export const loadPreviewData = (
loadAttachmentData: LoadAttachmentType
): ((preview: PreviewMessageType) => Promise<PreviewMessageType>) => {
): ((
preview: Array<LinkPreviewType> | undefined
) => Promise<Array<LinkPreviewType>>) => {
if (!isFunction(loadAttachmentData)) {
throw new TypeError('loadPreviewData: loadAttachmentData is required');
}
return async (preview: PreviewMessageType) => {
return async (preview: Array<LinkPreviewType> | undefined) => {
if (!preview || !preview.length) {
return [];
}
@ -763,14 +780,16 @@ export const loadPreviewData = (
export const loadStickerData = (
loadAttachmentData: LoadAttachmentType
): ((sticker: StickerMessageType) => Promise<StickerMessageType | null>) => {
): ((
sticker: StickerType | undefined
) => Promise<StickerWithHydratedData | undefined>) => {
if (!isFunction(loadAttachmentData)) {
throw new TypeError('loadStickerData: loadAttachmentData is required');
}
return async (sticker: StickerMessageType) => {
return async (sticker: StickerType | undefined) => {
if (!sticker || !sticker.data) {
return null;
return undefined;
}
return {
@ -966,8 +985,8 @@ export const createAttachmentDataWriter = ({
};
const writePreviewImage = async (
item: PreviewType
): Promise<PreviewType> => {
item: LinkPreviewType
): Promise<LinkPreviewType> => {
const { image } = item;
if (!image) {
return omit(item, ['image']);

View file

@ -0,0 +1,66 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { z } from 'zod';
import { themeSettingSchema } from './StorageUIKeys';
import { environmentSchema } from '../environment';
const configRequiredStringSchema = z.string().nonempty();
export type ConfigRequiredStringType = z.infer<
typeof configRequiredStringSchema
>;
const configOptionalStringSchema = configRequiredStringSchema.or(z.undefined());
export type configOptionalStringType = z.infer<
typeof configOptionalStringSchema
>;
export const rendererConfigSchema = z.object({
appInstance: configOptionalStringSchema,
appStartInitialSpellcheckSetting: z.boolean(),
buildCreation: z.number(),
buildExpiration: z.number(),
cdnUrl0: configRequiredStringSchema,
cdnUrl2: configRequiredStringSchema,
certificateAuthority: configRequiredStringSchema,
contentProxyUrl: configRequiredStringSchema,
crashDumpsPath: configRequiredStringSchema,
directoryEnclaveId: configOptionalStringSchema,
directoryTrustAnchor: configOptionalStringSchema,
directoryUrl: configOptionalStringSchema,
directoryV2CodeHashes: z.array(z.string().nonempty()).or(z.undefined()),
directoryV2PublicKey: configOptionalStringSchema,
directoryV2Url: configOptionalStringSchema,
directoryVersion: z.number(),
enableCI: z.boolean(),
environment: environmentSchema,
homePath: configRequiredStringSchema,
hostname: configRequiredStringSchema,
locale: configRequiredStringSchema,
name: configRequiredStringSchema,
nodeVersion: configRequiredStringSchema,
proxyUrl: configOptionalStringSchema,
reducedMotionSetting: z.boolean(),
serverPublicParams: configRequiredStringSchema,
serverTrustRoot: configRequiredStringSchema,
serverUrl: configRequiredStringSchema,
sfuUrl: configRequiredStringSchema,
storageUrl: configRequiredStringSchema,
theme: themeSettingSchema,
updatesUrl: configRequiredStringSchema,
userDataPath: configRequiredStringSchema,
version: configRequiredStringSchema,
// Only used by main window
isMainWindowFullScreen: z.boolean(),
// Only for tests
argv: configOptionalStringSchema,
// Only for permission popup window
forCalling: z.boolean(),
forCamera: z.boolean(),
});
export type RendererConfigType = z.infer<typeof rendererConfigSchema>;

View file

@ -12,12 +12,12 @@ import { maybeParseUrl } from '../util/url';
import * as Bytes from '../Bytes';
import * as Errors from './errors';
import { deriveStickerPackKey, decryptAttachment } from '../Crypto';
import type { MIMEType } from './MIME';
import { IMAGE_WEBP } from './MIME';
import type { MIMEType } from './MIME';
import { sniffImageMimeType } from '../util/sniffImageMimeType';
import type { AttachmentType } from './Attachment';
import type { AttachmentType, AttachmentWithHydratedData } from './Attachment';
import type {
StickerType,
StickerType as StickerFromDBType,
StickerPackType,
StickerPackStatusType,
} from '../sql/Interface';
@ -26,6 +26,20 @@ import { SignalService as Proto } from '../protobuf';
import * as log from '../logging/log';
import type { StickersStateType } from '../state/ducks/stickers';
export type StickerType = {
packId: string;
stickerId: number;
packKey: string;
emoji?: string;
data?: AttachmentType;
path?: string;
width?: number;
height?: number;
};
export type StickerWithHydratedData = StickerType & {
data: AttachmentWithHydratedData;
};
export type RecentStickerType = Readonly<{
stickerId: number;
packId: string;
@ -300,11 +314,16 @@ async function downloadSticker(
packKey: string,
proto: Proto.StickerPack.ISticker,
{ ephemeral }: { ephemeral?: boolean } = {}
): Promise<Omit<StickerType, 'isCoverOnly'>> {
): Promise<Omit<StickerFromDBType, 'isCoverOnly'>> {
const { id, emoji } = proto;
strictAssert(id !== undefined && id !== null, "Sticker id can't be null");
const ciphertext = await window.textsecure.messaging.getSticker(packId, id);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
const ciphertext = await messaging.getSticker(packId, id);
const plaintext = decryptSticker(packKey, ciphertext);
const sticker = ephemeral
@ -401,9 +420,12 @@ export async function downloadEphemeralPack(
};
stickerPackAdded(placeholder);
const ciphertext = await window.textsecure.messaging.getStickerPackManifest(
packId
);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
const ciphertext = await messaging.getStickerPackManifest(packId);
const plaintext = decryptSticker(packKey, ciphertext);
const proto = Proto.StickerPack.decode(plaintext);
const firstStickerProto = proto.stickers ? proto.stickers[0] : null;
@ -599,9 +621,12 @@ async function doDownloadStickerPack(
};
stickerPackAdded(placeholder);
const ciphertext = await window.textsecure.messaging.getStickerPackManifest(
packId
);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
const ciphertext = await messaging.getStickerPackManifest(packId);
const plaintext = decryptSticker(packKey, ciphertext);
const proto = Proto.StickerPack.decode(plaintext);
const firstStickerProto = proto.stickers ? proto.stickers[0] : undefined;
@ -776,7 +801,7 @@ export function getStickerPackStatus(
export function getSticker(
packId: string,
stickerId: number
): StickerType | undefined {
): StickerFromDBType | undefined {
const pack = getStickerPack(packId);
if (!pack || !pack.stickers) {
@ -803,9 +828,7 @@ export async function copyStickerToAttachments(
const { path, size } =
await window.Signal.Migrations.copyIntoAttachmentsDirectory(absolutePath);
const { data } = await window.Signal.Migrations.loadAttachmentData({
path,
});
const data = await window.Signal.Migrations.readAttachmentData(path);
let contentType: MIMEType;
const sniffedMimeType = sniffImageMimeType(data);

12
ts/types/Storage.d.ts vendored
View file

@ -12,19 +12,15 @@ import type { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
import type { RetryItemType } from '../util/retryPlaceholders';
import type { ConfigMapType as RemoteConfigType } from '../RemoteConfig';
import type { SystemTraySetting } from './SystemTraySetting';
import type {
ExtendedStorageID,
RemoteRecord,
UnknownRecord,
} from './StorageService';
import type { ExtendedStorageID, UnknownRecord } from './StorageService';
import type { GroupCredentialType } from '../textsecure/WebAPI';
import type {
KeyPairType,
SessionResetsType,
StorageServiceCredentials,
} from '../textsecure/Types.d';
import { UUIDStringType } from './UUID';
import type { ThemeSettingType } from './StorageUIKeys';
import { RegisteredChallengeType } from '../challenge';
export type SerializedCertificateType = {
@ -34,8 +30,6 @@ export type SerializedCertificateType = {
export type ZoomFactorType = 0.75 | 1 | 1.25 | 1.5 | 2 | number;
export type ThemeSettingType = 'system' | 'light' | 'dark';
export type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
export type IdentityKeyMap = Record<

View file

@ -1,8 +1,13 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { z } from 'zod';
import type { StorageAccessType } from './Storage.d';
export const themeSettingSchema = z.enum(['system', 'light', 'dark']);
export type ThemeSettingType = z.infer<typeof themeSettingSchema>;
// Configuration keys that only affect UI
export const STORAGE_UI_KEYS: ReadonlyArray<keyof StorageAccessType> = [
'always-relay-calls',

View file

@ -23,12 +23,14 @@ export type RenderTextCallbackType = (options: {
key: number;
}) => JSX.Element | string;
export type ReplacementValuesType = {
[key: string]: string | number | undefined;
};
export type ReplacementValuesType =
| Array<string>
| {
[key: string]: string | number | undefined;
};
export type LocalizerType = {
(key: string, values?: Array<string> | ReplacementValuesType): string;
(key: string, values?: ReplacementValuesType): string;
getLocale(): string;
};

View file

@ -8,7 +8,7 @@ export type LinkPreviewType = {
description?: string;
domain: string;
url: string;
isStickerPack: boolean;
isStickerPack?: boolean;
image?: Readonly<AttachmentType>;
date?: number;
};

View file

@ -12,6 +12,11 @@ export async function downloadAttachment(
): Promise<DownloadedAttachmentType | null> {
let migratedAttachment: AttachmentType;
const { server } = window.textsecure;
if (!server) {
throw new Error('window.textsecure.server is not available!');
}
const { id: legacyId } = attachmentData;
if (legacyId === undefined) {
migratedAttachment = attachmentData;
@ -24,10 +29,7 @@ export async function downloadAttachment(
let downloaded;
try {
downloaded = await doDownloadAttachment(
window.textsecure.server,
migratedAttachment
);
downloaded = await doDownloadAttachment(server, migratedAttachment);
} catch (error) {
// Attachments on the server expire after 30 days, then start returning 404
if (error && error.code === 404) {

View file

@ -32,6 +32,7 @@ import type {
import { SignalService as Proto } from '../protobuf';
import * as log from '../logging/log';
import MessageSender from '../textsecure/SendMessage';
const RETRY_LIMIT = 5;
@ -125,6 +126,11 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
log.info(`onRetryRequest/${logId}: Resending message`);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error(`onRetryRequest/${logId}: messaging is not available!`);
}
const { contentHint, messageIds, proto, timestamp } = sentProto;
const { contentProto, groupId } = await maybeAddSenderKeyDistributionMessage({
@ -141,7 +147,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
'private'
);
const sendOptions = await getSendOptions(recipientConversation.attributes);
const promise = window.textsecure.messaging.sendMessageProtoAndWait({
const promise = messaging.sendMessageProtoAndWait({
timestamp,
recipients: [requesterUuid],
proto: new Proto.Content(contentProto),
@ -263,6 +269,13 @@ async function sendDistributionMessageOrNullMessage(
let sentDistributionMessage = false;
log.info(`sendDistributionMessageOrNullMessage/${logId}: Starting...`);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error(
`sendDistributionMessageOrNullMessage/${logId}: messaging is not available!`
);
}
const conversation = window.ConversationController.getOrCreate(
requesterUuid,
'private'
@ -286,7 +299,7 @@ async function sendDistributionMessageOrNullMessage(
try {
await handleMessageSend(
window.textsecure.messaging.sendSenderKeyDistributionMessage(
messaging.sendSenderKeyDistributionMessage(
{
contentHint: ContentHint.RESENDABLE,
distributionId,
@ -322,11 +335,11 @@ async function sendDistributionMessageOrNullMessage(
// Enqueue a null message using the newly-created session
try {
const nullMessage = window.textsecure.messaging.getNullMessage({
const nullMessage = MessageSender.getNullMessage({
uuid: requesterUuid,
});
await handleMessageSend(
window.textsecure.messaging.sendIndividualProto({
messaging.sendIndividualProto({
...nullMessage,
options: sendOptions,
proto: Proto.Content.decode(
@ -397,6 +410,13 @@ async function maybeAddSenderKeyDistributionMessage({
requestGroupId,
});
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error(
`maybeAddSenderKeyDistributionMessage/${logId}: messaging is not available!`
);
}
if (!conversation) {
log.warn(
`maybeAddSenderKeyDistributionMessage/${logId}: Unable to find conversation`
@ -421,7 +441,7 @@ async function maybeAddSenderKeyDistributionMessage({
const senderKeyInfo = conversation.get('senderKeyInfo');
if (senderKeyInfo && senderKeyInfo.distributionId) {
const protoWithDistributionMessage =
await window.textsecure.messaging.getSenderKeyDistributionMessage(
await messaging.getSenderKeyDistributionMessage(
senderKeyInfo.distributionId,
{ throwIfNotInDatabase: true, timestamp }
);
@ -463,6 +483,11 @@ async function requestResend(decryptionError: DecryptionErrorEventData) {
groupId: groupId ? `groupv2(${groupId})` : undefined,
});
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error(`requestResend/${logId}: messaging is not available!`);
}
// 1. Find the target conversation
const group = groupId
@ -495,7 +520,7 @@ async function requestResend(decryptionError: DecryptionErrorEventData) {
const plaintext = PlaintextContent.from(message);
const options = await getSendOptions(conversation.attributes);
const result = await handleMessageSend(
window.textsecure.messaging.sendRetryRequest({
messaging.sendRetryRequest({
plaintext,
options,
groupId,

View file

@ -10,12 +10,10 @@ const NINETY_ONE_DAYS = 91 * ONE_DAY_MS;
const THIRTY_ONE_DAYS = 31 * ONE_DAY_MS;
export function hasExpired(): boolean {
const { getExpiration } = window;
let buildExpiration = 0;
try {
buildExpiration = parseInt(getExpiration(), 10);
buildExpiration = window.getExpiration();
if (buildExpiration) {
log.info('Build expires: ', new Date(buildExpiration).toISOString());
}

View file

@ -8283,7 +8283,7 @@
{
"rule": "jQuery-$(",
"path": "sticker-creator/util/i18n.tsx",
"line": " const FIND_REPLACEMENTS = /\\$([^$]+)\\$/g;",
"line": " const FIND_REPLACEMENTS = /\\$([^$]+)\\$/g;",
"reasonCategory": "falseMatch",
"updated": "2020-07-21T18:34:59.251Z"
},

View file

@ -62,12 +62,15 @@ export async function lookupConversationWithoutUuid(
const { showUserNotFoundModal, setIsFetchingUUID } = options;
setIsFetchingUUID(identifier, true);
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
try {
let conversationId: string | undefined;
if (options.type === 'e164') {
const serverLookup = await window.textsecure.messaging.getUuidsForE164s([
options.e164,
]);
const serverLookup = await messaging.getUuidsForE164s([options.e164]);
if (serverLookup[options.e164]) {
conversationId = window.ConversationController.ensureContactIds({
@ -131,10 +134,13 @@ async function checkForUsername(
return undefined;
}
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
try {
const profile = await window.textsecure.messaging.getProfileForUsername(
username
);
const profile = await messaging.getProfileForUsername(username);
if (!profile.uuid) {
log.error("checkForUsername: Returned profile didn't include a uuid");

View file

@ -74,9 +74,7 @@ export type ElectronLocaleType =
| 'zh_CN'
| 'zh_TW';
export function mapToSupportLocale(
ourLocale: ElectronLocaleType
): SupportLocaleType {
export function mapToSupportLocale(ourLocale: string): SupportLocaleType {
if (ourLocale === 'ar') {
return ourLocale;
}

View file

@ -2,14 +2,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { partition } from 'lodash';
import type { AttachmentType } from '../types/Attachment';
import type { EmbeddedContactType } from '../types/EmbeddedContact';
import type {
MessageAttributesType,
PreviewMessageType,
QuotedMessageType,
StickerMessageType,
} from '../model-types.d';
import * as AttachmentDownloads from '../messageModifiers/AttachmentDownloads';
import * as log from '../logging/log';
import { isLongMessage } from '../types/MIME';
@ -21,13 +13,22 @@ import {
} from '../types/Stickers';
import dataInterface from '../sql/Client';
import type { AttachmentType } from '../types/Attachment';
import type { EmbeddedContactType } from '../types/EmbeddedContact';
import type {
MessageAttributesType,
QuotedMessageType,
} from '../model-types.d';
import type { StickerType } from '../types/Stickers';
import type { LinkPreviewType } from '../types/message/LinkPreviews';
type ReturnType = {
bodyAttachment?: AttachmentType;
attachments: Array<AttachmentType>;
preview: PreviewMessageType;
preview: Array<LinkPreviewType>;
contact: Array<EmbeddedContactType>;
quote?: QuotedMessageType;
sticker?: StickerMessageType;
sticker?: StickerType;
};
// Receive logic

View file

@ -45,6 +45,11 @@ export async function sendReceipts({
throw missingCaseError(type);
}
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
if (requiresUserSetting && !window.storage.get('read-receipt-setting')) {
log.info('requires user setting. Not sending these receipts');
return;
@ -119,7 +124,7 @@ export async function sendReceipts({
const messageIds = batch.map(receipt => receipt.messageId);
await handleMessageSend(
window.textsecure.messaging[methodName]({
messaging[methodName]({
senderE164: sender.get('e164'),
senderUuid: sender.get('uuid'),
timestamps,

View file

@ -3288,16 +3288,20 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
return this.getGroupPreview(url, abortSignal);
}
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
// This is already checked elsewhere, but we want to be extra-careful.
if (!LinkPreview.shouldPreviewHref(url)) {
return null;
}
const linkPreviewMetadata =
await window.textsecure.messaging.fetchLinkPreviewMetadata(
url,
abortSignal
);
const linkPreviewMetadata = await messaging.fetchLinkPreviewMetadata(
url,
abortSignal
);
if (!linkPreviewMetadata || abortSignal.aborted) {
return null;
}
@ -3307,11 +3311,10 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
if (imageHref && LinkPreview.shouldPreviewHref(imageHref)) {
let objectUrl: void | string;
try {
const fullSizeImage =
await window.textsecure.messaging.fetchLinkPreviewImage(
imageHref,
abortSignal
);
const fullSizeImage = await messaging.fetchLinkPreviewImage(
imageHref,
abortSignal
);
if (abortSignal.aborted) {
return null;
}

315
ts/window.d.ts vendored
View file

@ -3,19 +3,21 @@
// Captures the globals put in place by preload.js, background.js and others
import type { Cancelable } from 'lodash';
import { Store } from 'redux';
import * as Backbone from 'backbone';
import * as Underscore from 'underscore';
import moment from 'moment';
import PQueue from 'p-queue/dist';
import { Ref } from 'react';
import { assert } from 'chai';
import { imageToBlurHash } from './util/imageToBlurHash';
import * as Util from './util';
import {
ConversationModelCollectionType,
MessageModelCollectionType,
} from './model-types.d';
import { TextSecureType } from './textsecure.d';
import { textsecure } from './textsecure';
import { Storage } from './textsecure/Storage';
import {
ChallengeHandler,
@ -28,7 +30,7 @@ import * as Crypto from './Crypto';
import * as Curve from './Curve';
import * as RemoteConfig from './RemoteConfig';
import * as OS from './OS';
import { getEnvironment } from './environment';
import { Environment, getEnvironment } from './environment';
import { LocalizerType, ThemeType } from './types/Util';
import type { Receipt } from './types/Receipt';
import { ConversationController } from './ConversationController';
@ -65,9 +67,8 @@ import * as stickersDuck from './state/ducks/stickers';
import * as conversationsSelectors from './state/selectors/conversations';
import * as searchSelectors from './state/selectors/search';
import AccountManager from './textsecure/AccountManager';
import { ContactWithHydratedAvatar } from './textsecure/SendMessage';
import Data from './sql/Client';
import { PhoneNumberFormat } from 'google-libphonenumber';
import { PhoneNumber, PhoneNumberFormat } from 'google-libphonenumber';
import { MessageModel } from './models/messages';
import { ConversationModel } from './models/conversations';
import { BatcherType } from './util/batcher';
@ -79,8 +80,6 @@ import { MessageDetail } from './components/conversation/MessageDetail';
import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
import { DownloadedAttachmentType } from './types/Attachment';
import { ElectronLocaleType } from './util/mapToSupportLocale';
import { SignalProtocolStore } from './SignalProtocolStore';
import { StartupQueue } from './util/StartupQueue';
import { SocketStatus } from './types/SocketStatus';
@ -95,7 +94,9 @@ import { CI } from './CI';
import { IPCEventsType } from './util/createIPCEvents';
import { ConversationView } from './views/conversation_view';
import type { SignalContextType } from './windows/context';
import type { EmbeddedContactType } from './types/EmbeddedContact';
import type * as Message2 from './types/Message2';
import type { initializeMigrations } from './signal';
import { RendererConfigType } from './types/RendererConfig';
export { Long } from 'long';
@ -135,12 +136,105 @@ export declare class WebAudioRecorderClass {
worker: Worker;
}
export type SignalCoreType = {
Backbone: any;
Crypto: typeof Crypto;
Curve: typeof Curve;
Data: typeof Data;
Groups: typeof Groups;
RemoteConfig: typeof RemoteConfig;
Services: {
calling: CallingClass;
enableStorageService: () => void;
eraseAllStorageServiceState: (options?: {
keepUnknownFields?: boolean | undefined;
}) => Promise<void>;
initializeGroupCredentialFetcher: () => Promise<void>;
initializeNetworkObserver: (network: ReduxActions['network']) => void;
initializeUpdateListener: (updates: ReduxActions['updates']) => void;
retryPlaceholders?: Util.RetryPlaceholders;
lightSessionResetQueue?: PQueue;
runStorageServiceSyncJob: (() => void) & Cancelable;
storageServiceUploadJob: (() => void) & Cancelable;
};
Migrations: ReturnType<typeof initializeMigrations>;
Types: {
Message: typeof Message2;
UUID: typeof UUID;
Address: typeof Address;
QualifiedAddress: typeof QualifiedAddress;
};
Util: typeof Util;
Components: {
AttachmentList: typeof AttachmentList;
ChatColorPicker: typeof ChatColorPicker;
ConfirmationDialog: typeof ConfirmationDialog;
ContactModal: typeof ContactModal;
DisappearingTimeDialog: typeof DisappearingTimeDialog;
MessageDetail: typeof MessageDetail;
Quote: typeof Quote;
StagedLinkPreview: typeof StagedLinkPreview;
};
OS: typeof OS;
State: {
createStore: typeof createStore;
Roots: {
createApp: typeof createApp;
createChatColorPicker: typeof createChatColorPicker;
createConversationDetails: typeof createConversationDetails;
createForwardMessageModal: typeof createForwardMessageModal;
createGroupLinkManagement: typeof createGroupLinkManagement;
createGroupV1MigrationModal: typeof createGroupV1MigrationModal;
createGroupV2JoinModal: typeof createGroupV2JoinModal;
createGroupV2Permissions: typeof createGroupV2Permissions;
createLeftPane: typeof createLeftPane;
createMessageDetail: typeof createMessageDetail;
createConversationNotificationsSettings: typeof createConversationNotificationsSettings;
createPendingInvites: typeof createPendingInvites;
createSafetyNumberViewer: typeof createSafetyNumberViewer;
createShortcutGuideModal: typeof createShortcutGuideModal;
createStickerManager: typeof createStickerManager;
createStickerPreviewModal: typeof createStickerPreviewModal;
};
Ducks: {
app: typeof appDuck;
calling: typeof callingDuck;
conversations: typeof conversationsDuck;
emojis: typeof emojisDuck;
expiration: typeof expirationDuck;
items: typeof itemsDuck;
linkPreviews: typeof linkPreviewsDuck;
network: typeof networkDuck;
updates: typeof updatesDuck;
user: typeof userDuck;
search: typeof searchDuck;
stickers: typeof stickersDuck;
};
Selectors: {
conversations: typeof conversationsSelectors;
search: typeof searchSelectors;
};
};
conversationControllerStart: () => void;
challengeHandler?: ChallengeHandler;
};
declare global {
// We want to extend `window`'s properties, so we need an interface.
// eslint-disable-next-line no-restricted-syntax
interface Window {
// Used in Sticker Creator to create proper paths to emoji images
ROOT_PATH?: string;
// Used for sticker creator localization
localeMessages: { [key: string]: { message: string } };
// Note: used in background.html, and not type-checked
startApp: () => void;
preloadStartTime: number;
preloadEndTime: number;
preloadConnectTime: number;
removeSetupMenuItems: () => unknown;
showPermissionsPopup: (
forCalling: boolean,
@ -151,7 +245,6 @@ declare global {
_: typeof Underscore;
$: typeof jQuery;
moment: typeof moment;
imageToBlurHash: typeof imageToBlurHash;
loadImage: any;
isBehindProxy: () => boolean;
@ -174,6 +267,8 @@ declare global {
baseAttachmentsPath: string;
baseStickersPath: string;
baseTempPath: string;
baseDraftPath: string;
closeAbout: () => void;
crashReports: {
getCount: () => Promise<number>;
upload: () => Promise<void>;
@ -183,14 +278,15 @@ declare global {
enterKeyboardMode: () => void;
enterMouseMode: () => void;
getAccountManager: () => AccountManager;
getAppInstance: () => string | undefined;
getBuiltInImages: () => Promise<Array<string>>;
getConversations: () => ConversationModelCollectionType;
getBuildCreation: () => number;
getEnvironment: typeof getEnvironment;
getExpiration: () => string;
getExpiration: () => number;
getHostName: () => string;
getInteractionMode: () => 'mouse' | 'keyboard';
getLocale: () => ElectronLocaleType;
getLocale: () => string;
getMediaCameraPermissions: () => Promise<boolean>;
getMediaPermissions: () => Promise<boolean>;
getServerPublicParams: () => string;
@ -206,11 +302,12 @@ declare global {
isBeforeVersion: (version: string, anotherVersion: string) => boolean;
isFullScreen: () => boolean;
initialTheme?: ThemeType;
libphonenumber: {
parse: (number: string) => string;
getRegionCodeForNumber: (number: string) => string;
format: (number: string, format: PhoneNumberFormat) => string;
libphonenumberInstance: {
parse: (number: string) => PhoneNumber;
getRegionCodeForNumber: (number: PhoneNumber) => string | undefined;
format: (number: PhoneNumber, format: PhoneNumberFormat) => string;
};
libphonenumberFormat: typeof PhoneNumberFormat;
nodeSetImmediate: typeof setImmediate;
onFullScreenChange: (fullScreen: boolean) => void;
platform: string;
@ -233,7 +330,7 @@ declare global {
showKeyboardShortcuts: () => void;
storage: Storage;
systemTheme: WhatIsThis;
textsecure: TextSecureType;
textsecure: typeof textsecure;
titleBarDoubleClick: () => void;
unregisterForActive: (handler: () => void) => void;
updateTrayIcon: (count: number) => void;
@ -243,180 +340,7 @@ declare global {
Accessibility: {
reducedMotionSetting: boolean;
};
Signal: {
Backbone: any;
Crypto: typeof Crypto;
Curve: typeof Curve;
Data: typeof Data;
Groups: typeof Groups;
RemoteConfig: typeof RemoteConfig;
Services: {
calling: CallingClass;
enableStorageService: () => boolean;
eraseAllStorageServiceState: (options?: {
keepUnknownFields?: boolean;
}) => Promise<void>;
initializeGroupCredentialFetcher: () => void;
initializeNetworkObserver: (network: ReduxActions['network']) => void;
initializeUpdateListener: (updates: ReduxActions['updates']) => void;
retryPlaceholders?: Util.RetryPlaceholders;
lightSessionResetQueue?: PQueue;
runStorageServiceSyncJob: () => Promise<void>;
storageServiceUploadJob: () => void;
};
Migrations: {
readTempData: (path: string) => Promise<Uint8Array>;
deleteAttachmentData: (path: string) => Promise<void>;
doesAttachmentExist: (path: string) => Promise<boolean>;
writeNewAttachmentData: (data: Uint8Array) => Promise<string>;
deleteExternalMessageFiles: (attributes: unknown) => Promise<void>;
getAbsoluteAttachmentPath: (path: string) => string;
loadAttachmentData: <T extends { path?: string }>(
attachment: T
) => Promise<
T & {
data: Uint8Array;
size: number;
}
>;
loadQuoteData: (quote: unknown) => WhatIsThis;
loadContactData: (
contact?: Array<EmbeddedContactType>
) => Promise<Array<ContactWithHydratedAvatar> | undefined>;
loadPreviewData: (preview: unknown) => WhatIsThis;
loadStickerData: (sticker: unknown) => WhatIsThis;
readStickerData: (path: string) => Promise<Uint8Array>;
deleteSticker: (path: string) => Promise<void>;
getAbsoluteStickerPath: (path: string) => string;
processNewEphemeralSticker: (stickerData: Uint8Array) => {
path: string;
width: number;
height: number;
};
processNewSticker: (stickerData: Uint8Array) => {
path: string;
width: number;
height: number;
};
copyIntoAttachmentsDirectory: (
path: string
) => Promise<{ path: string; size: number }>;
upgradeMessageSchema: (attributes: unknown) => WhatIsThis;
processNewAttachment: (
attachment: DownloadedAttachmentType
) => Promise<DownloadedAttachmentType>;
copyIntoTempDirectory: (
path: string
) => Promise<{ path: string; size: number }>;
deleteDraftFile: (path: string) => Promise<void>;
deleteTempFile: (path: string) => Promise<void>;
getAbsoluteDraftPath: any;
getAbsoluteTempPath: any;
openFileInFolder: any;
readAttachmentData: (path: string) => Promise<Uint8Array>;
readDraftData: (path: string) => Promise<Uint8Array>;
saveAttachmentToDisk: (options: {
data: Uint8Array;
name: string;
}) => Promise<null | { fullPath: string; name: string }>;
writeNewDraftData: (data: Uint8Array) => Promise<string>;
deleteAvatar: (path: string) => Promise<void>;
getAbsoluteAvatarPath: (src: string) => string;
writeNewAvatarData: (data: Uint8Array) => Promise<string>;
getAbsoluteBadgeImageFilePath: (path: string) => string;
writeNewBadgeImageFileData: (data: Uint8Array) => Promise<string>;
};
Types: {
Message: {
CURRENT_SCHEMA_VERSION: number;
VERSION_NEEDED_FOR_DISPLAY: number;
GROUP: 'group';
PRIVATE: 'private';
initializeSchemaVersion: (version: {
message: unknown;
logger: unknown;
}) => unknown & {
schemaVersion: number;
};
hasExpiration: (json: string) => boolean;
};
Sticker: {
emoji: string;
packId: string;
packKey: string;
stickerId: number;
data: {
pending: boolean;
path: string;
};
width: number;
height: number;
path: string;
};
UUID: typeof UUID;
Address: typeof Address;
QualifiedAddress: typeof QualifiedAddress;
};
Util: typeof Util;
Components: {
AttachmentList: typeof AttachmentList;
ChatColorPicker: typeof ChatColorPicker;
ConfirmationDialog: typeof ConfirmationDialog;
ContactModal: typeof ContactModal;
DisappearingTimeDialog: typeof DisappearingTimeDialog;
MessageDetail: typeof MessageDetail;
Quote: typeof Quote;
StagedLinkPreview: typeof StagedLinkPreview;
};
OS: typeof OS;
State: {
createStore: typeof createStore;
Roots: {
createApp: typeof createApp;
createChatColorPicker: typeof createChatColorPicker;
createConversationDetails: typeof createConversationDetails;
createForwardMessageModal: typeof createForwardMessageModal;
createGroupLinkManagement: typeof createGroupLinkManagement;
createGroupV1MigrationModal: typeof createGroupV1MigrationModal;
createGroupV2JoinModal: typeof createGroupV2JoinModal;
createGroupV2Permissions: typeof createGroupV2Permissions;
createLeftPane: typeof createLeftPane;
createMessageDetail: typeof createMessageDetail;
createConversationNotificationsSettings: typeof createConversationNotificationsSettings;
createPendingInvites: typeof createPendingInvites;
createSafetyNumberViewer: typeof createSafetyNumberViewer;
createShortcutGuideModal: typeof createShortcutGuideModal;
createStickerManager: typeof createStickerManager;
createStickerPreviewModal: typeof createStickerPreviewModal;
};
Ducks: {
app: typeof appDuck;
calling: typeof callingDuck;
conversations: typeof conversationsDuck;
emojis: typeof emojisDuck;
expiration: typeof expirationDuck;
items: typeof itemsDuck;
linkPreviews: typeof linkPreviewsDuck;
network: typeof networkDuck;
updates: typeof updatesDuck;
user: typeof userDuck;
search: typeof searchDuck;
stickers: typeof stickersDuck;
};
Selectors: {
conversations: typeof conversationsSelectors;
search: typeof searchSelectors;
};
};
conversationControllerStart: WhatIsThis;
Emojis: {
getInitialState: () => WhatIsThis;
load: () => void;
};
challengeHandler: ChallengeHandler;
};
Signal: SignalCoreType;
ConversationController: ConversationController;
Events: IPCEventsType;
@ -445,6 +369,14 @@ declare global {
// Context Isolation
SignalContext: SignalContextType;
// Test only
assert: typeof assert;
// Used in test/index.html, and therefore not type-checked!
testUtilities: {
onComplete: (data: any) => void;
prepareTests: () => void;
};
}
// We want to extend `Error`, so we need an interface.
@ -453,6 +385,9 @@ declare global {
originalError?: Event;
reason?: any;
stackForLog?: string;
// Used in sticker creator to attach messages to errors
errorMessageI18nKey?: string;
}
// We want to extend `Element`'s properties, so we need an interface.

View file

@ -127,7 +127,7 @@ export const createWriterForNew = (
export const createWriterForExisting = (
root: string
): ((options: { data: Uint8Array; path: string }) => Promise<string>) => {
): ((options: { data?: Uint8Array; path?: string }) => Promise<string>) => {
if (!isString(root)) {
throw new TypeError("'root' must be a path");
}
@ -136,15 +136,15 @@ export const createWriterForExisting = (
data: bytes,
path: relativePath,
}: {
data: Uint8Array;
path: string;
data?: Uint8Array;
path?: string;
}): Promise<string> => {
if (!isString(relativePath)) {
throw new TypeError("'relativePath' must be a path");
}
if (!isTypedArray(bytes)) {
throw new TypeError("'arrayBuffer' must be an array buffer");
if (!bytes) {
throw new TypeError("'data' must be a Uint8Array");
}
const buffer = Buffer.from(bytes);

View file

@ -3,8 +3,7 @@
import { ipcRenderer } from 'electron';
import type { MenuItemConstructorOptions } from 'electron';
import url from 'url';
import type { ParsedUrlQuery } from 'querystring';
import type { MenuOptionsType, MenuActionType } from '../types/menu';
import type { IPCEventsValuesType } from '../util/createIPCEvents';
import type { LocalizerType } from '../types/Util';
@ -12,6 +11,8 @@ import type { LoggerType } from '../types/Logging';
import type { LocaleMessagesType } from '../types/I18N';
import type { NativeThemeType } from '../context/createNativeThemeListener';
import type { SettingType } from '../util/preload';
import type { RendererConfigType } from '../types/RendererConfig';
import { Bytes } from '../context/Bytes';
import { Crypto } from '../context/Crypto';
import { Timers } from '../context/Timers';
@ -29,7 +30,11 @@ import { waitForSettingsChange } from './waitForSettingsChange';
import { createNativeThemeListener } from '../context/createNativeThemeListener';
import { isWindows, isLinux, isMacOS } from '../OS';
const config = url.parse(window.location.toString(), true).query;
const params = new URLSearchParams(document.location.search);
const configParam = params.get('config');
strictAssert(typeof configParam === 'string', 'config is not a string');
const config = JSON.parse(configParam);
const { locale } = config;
strictAssert(locale, 'locale could not be parsed from config');
strictAssert(typeof locale === 'string', 'locale is not a string');
@ -62,7 +67,7 @@ export type SignalContextType = {
isLinux: typeof isLinux;
isMacOS: typeof isMacOS;
};
config: ParsedUrlQuery;
config: RendererConfigType;
getAppInstance: () => string | undefined;
getEnvironment: () => string;
getNodeVersion: () => string;
@ -94,7 +99,7 @@ export const SignalContext: SignalContextType = {
getAppInstance: (): string | undefined =>
config.appInstance ? String(config.appInstance) : undefined,
getEnvironment,
getNodeVersion: (): string => String(config.node_version),
getNodeVersion: (): string => String(config.nodeVersion),
getVersion: (): string => String(config.version),
getPath: (name: 'userData' | 'home'): string => {
return String(config[`${name}Path`]);

View file

@ -0,0 +1,376 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer as ipc } from 'electron';
import * as semver from 'semver';
import { mapValues, noop } from 'lodash';
import { parseIntWithFallback } from '../../util/parseIntWithFallback';
import { UUIDKind } from '../../types/UUID';
import { ThemeType } from '../../types/Util';
import { getEnvironment, Environment } from '../../environment';
import { SignalContext } from '../context';
import * as log from '../../logging/log';
import { strictAssert } from '../../util/assert';
// It is important to call this as early as possible
window.i18n = SignalContext.i18n;
// We are comfortable doing this because we verified the type on the other side!
const { config } = window.SignalContext;
// Flags for testing
window.GV2_ENABLE_SINGLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_STATE_PROCESSING = true;
window.GV2_ENABLE_PRE_JOIN_FETCH = true;
window.GV2_MIGRATION_DISABLE_ADD = false;
window.GV2_MIGRATION_DISABLE_INVITE = false;
window.RETRY_DELAY = false;
window.platform = process.platform;
window.getTitle = () => title;
window.getLocale = () => config.locale;
window.getEnvironment = getEnvironment;
window.getAppInstance = () => config.appInstance;
window.getVersion = () => config.version;
window.getBuildCreation = () => parseIntWithFallback(config.buildCreation, 0);
window.getExpiration = () => {
const sixtyDays = 60 * 86400 * 1000;
const remoteBuildExpiration = window.storage.get('remoteBuildExpiration');
const { buildExpiration } = config;
const localBuildExpiration = window.Events.getAutoDownloadUpdate()
? buildExpiration
: buildExpiration - sixtyDays;
if (remoteBuildExpiration) {
return remoteBuildExpiration < localBuildExpiration
? remoteBuildExpiration
: localBuildExpiration;
}
return localBuildExpiration;
};
window.Accessibility = {
reducedMotionSetting: Boolean(config.reducedMotionSetting),
};
window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.getServerPublicParams = () => config.serverPublicParams;
window.getSfuUrl = () => config.sfuUrl;
window.isBehindProxy = () => Boolean(config.proxyUrl);
let title = config.name;
if (getEnvironment() !== Environment.Production) {
title += ` - ${getEnvironment()}`;
}
if (config.appInstance) {
title += ` - ${config.appInstance}`;
}
if (config.theme === 'light') {
window.initialTheme = ThemeType.light;
} else if (config.theme === 'dark') {
window.initialTheme = ThemeType.dark;
}
window.getAutoLaunch = () => {
return ipc.invoke('get-auto-launch');
};
window.setAutoLaunch = value => {
return ipc.invoke('set-auto-launch', value);
};
window.isBeforeVersion = (toCheck, baseVersion) => {
try {
return semver.lt(toCheck, baseVersion);
} catch (error) {
log.error(
`isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
error && error.stack ? error.stack : error
);
return true;
}
};
window.isAfterVersion = (toCheck, baseVersion) => {
try {
return semver.gt(toCheck, baseVersion);
} catch (error) {
log.error(
`isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
error && error.stack ? error.stack : error
);
return true;
}
};
window.setBadgeCount = count => ipc.send('set-badge-count', count);
window.logAuthenticatedConnect = () => {
if (window.preloadConnectTime === 0) {
window.preloadConnectTime = Date.now();
}
};
window.logAppLoadedEvent = ({ processedCount }) =>
ipc.send('signal-app-loaded', {
preloadTime: window.preloadEndTime - window.preloadStartTime,
connectTime: window.preloadConnectTime - window.preloadEndTime,
processedCount,
});
// We never do these in our code, so we'll prevent it everywhere
window.open = () => null;
// Playwright uses `eval` for `.evaluate()` API
if (!config.enableCI && config.environment !== 'test') {
// eslint-disable-next-line no-eval, no-multi-assign
window.eval = global.eval = () => null;
}
window.drawAttention = () => {
log.info('draw attention');
ipc.send('draw-attention');
};
window.showWindow = () => {
log.info('show window');
ipc.send('show-window');
};
window.titleBarDoubleClick = () => {
ipc.send('title-bar-double-click');
};
window.setAutoHideMenuBar = autoHide =>
ipc.send('set-auto-hide-menu-bar', autoHide);
window.setMenuBarVisibility = visibility =>
ipc.send('set-menu-bar-visibility', visibility);
window.updateSystemTraySetting = (
systemTraySetting /* : Readonly<SystemTraySetting> */
) => {
ipc.send('update-system-tray-setting', systemTraySetting);
};
window.restart = () => {
log.info('restart');
ipc.send('restart');
};
window.shutdown = () => {
log.info('shutdown');
ipc.send('shutdown');
};
window.showDebugLog = () => {
log.info('showDebugLog');
ipc.send('show-debug-log');
};
window.closeAbout = () => ipc.send('close-about');
window.readyForUpdates = () => ipc.send('ready-for-updates');
window.updateTrayIcon = unreadCount =>
ipc.send('update-tray-icon', unreadCount);
ipc.on('additional-log-data-request', async event => {
const ourConversation = window.ConversationController.getOurConversation();
const ourCapabilities = ourConversation
? ourConversation.get('capabilities')
: undefined;
const remoteConfig = window.storage.get('remoteConfig') || {};
let statistics;
try {
statistics = await window.Signal.Data.getStatisticsForLogging();
} catch (error) {
statistics = {};
}
const ourUuid = window.textsecure.storage.user.getUuid();
const ourPni = window.textsecure.storage.user.getUuid(UUIDKind.PNI);
event.sender.send('additional-log-data-response', {
capabilities: ourCapabilities || {},
remoteConfig: mapValues(remoteConfig, ({ value, enabled }) => {
const enableString = enabled ? 'enabled' : 'disabled';
const valueString = value && value !== 'TRUE' ? ` ${value}` : '';
return `${enableString}${valueString}`;
}),
statistics,
user: {
deviceId: window.textsecure.storage.user.getDeviceId(),
e164: window.textsecure.storage.user.getNumber(),
uuid: ourUuid && ourUuid.toString(),
pni: ourPni && ourPni.toString(),
conversationId: ourConversation && ourConversation.id,
},
});
});
ipc.on('set-up-as-new-device', () => {
window.Whisper.events.trigger('setupAsNewDevice');
});
ipc.on('set-up-as-standalone', () => {
window.Whisper.events.trigger('setupAsStandalone');
});
ipc.on('challenge:response', (_event, response) => {
window.Whisper.events.trigger('challengeResponse', response);
});
ipc.on('power-channel:suspend', () => {
window.Whisper.events.trigger('powerMonitorSuspend');
});
ipc.on('power-channel:resume', () => {
window.Whisper.events.trigger('powerMonitorResume');
});
ipc.on('power-channel:lock-screen', () => {
window.Whisper.events.trigger('powerMonitorLockScreen');
});
ipc.on('window:set-window-stats', (_event, stats) => {
if (!window.Whisper.events) {
return;
}
window.Whisper.events.trigger('setWindowStats', stats);
});
ipc.on('window:set-menu-options', (_event, options) => {
if (!window.Whisper.events) {
return;
}
window.Whisper.events.trigger('setMenuOptions', options);
});
window.sendChallengeRequest = request => ipc.send('challenge:request', request);
{
let isFullScreen = Boolean(config.isMainWindowFullScreen);
window.isFullScreen = () => isFullScreen;
// This is later overwritten.
window.onFullScreenChange = noop;
ipc.on('full-screen-change', (_event, isFull) => {
isFullScreen = Boolean(isFull);
window.onFullScreenChange(isFullScreen);
});
}
// Settings-related events
window.showSettings = () => ipc.send('show-settings');
window.showPermissionsPopup = (forCalling, forCamera) =>
ipc.invoke('show-permissions-popup', forCalling, forCamera);
ipc.on('show-keyboard-shortcuts', () => {
window.Events.showKeyboardShortcuts();
});
ipc.on('add-dark-overlay', () => {
window.Events.addDarkOverlay();
});
ipc.on('remove-dark-overlay', () => {
window.Events.removeDarkOverlay();
});
window.getBuiltInImages = () =>
new Promise((resolve, reject) => {
ipc.once('get-success-built-in-images', (_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
});
ipc.send('get-built-in-images');
});
ipc.on('delete-all-data', async () => {
const { deleteAllData } = window.Events;
if (!deleteAllData) {
return;
}
try {
await deleteAllData();
} catch (error) {
log.error('delete-all-data: error', error && error.stack);
}
});
ipc.on('show-sticker-pack', (_event, info) => {
const { packId, packKey } = info;
const { showStickerPack } = window.Events;
if (showStickerPack) {
showStickerPack(packId, packKey);
}
});
ipc.on('show-group-via-link', (_event, info) => {
const { hash } = info;
const { showGroupViaLink } = window.Events;
if (showGroupViaLink) {
showGroupViaLink(hash);
}
});
ipc.on('show-conversation-via-signal.me', (_event, info) => {
const { hash } = info;
strictAssert(typeof hash === 'string', 'Got an invalid hash over IPC');
const { showConversationViaSignalDotMe } = window.Events;
if (showConversationViaSignalDotMe) {
showConversationViaSignalDotMe(hash);
}
});
ipc.on('unknown-sgnl-link', () => {
const { unknownSignalLink } = window.Events;
if (unknownSignalLink) {
unknownSignalLink();
}
});
ipc.on('install-sticker-pack', (_event, info) => {
const { packId, packKey } = info;
const { installStickerPack } = window.Events;
if (installStickerPack) {
installStickerPack(packId, packKey);
}
});
ipc.on('get-ready-for-shutdown', async () => {
const { shutdown } = window.Events || {};
if (!shutdown) {
log.error('preload shutdown handler: shutdown method not found');
ipc.send('now-ready-for-shutdown');
return;
}
try {
await shutdown();
ipc.send('now-ready-for-shutdown');
} catch (error) {
ipc.send(
'now-ready-for-shutdown',
error && error.stack ? error.stack : error
);
}
});
ipc.on('show-release-notes', () => {
const { showReleaseNotes } = window.Events;
if (showReleaseNotes) {
showReleaseNotes();
}
});
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');

View file

@ -0,0 +1,91 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer as ipc } from 'electron';
import Backbone from 'backbone';
import { PhoneNumberUtil, PhoneNumberFormat } from 'google-libphonenumber';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as moment from 'moment';
import 'moment/min/locales.min';
import PQueue from 'p-queue';
import { textsecure } from '../../textsecure';
import { imageToBlurHash } from '../../util/imageToBlurHash';
import { ActiveWindowService } from '../../services/ActiveWindowService';
import * as Attachments from '../attachments';
import { setup } from '../../signal';
import { addSensitivePath } from '../../util/privacy';
import * as log from '../../logging/log';
import { SignalContext } from '../context';
window.nodeSetImmediate = setImmediate;
window.Backbone = Backbone;
window.textsecure = textsecure;
const { config } = window.SignalContext;
window.WebAPI = window.textsecure.WebAPI.initialize({
url: config.serverUrl,
storageUrl: config.storageUrl,
updatesUrl: config.updatesUrl,
directoryVersion: config.directoryVersion,
directoryUrl: config.directoryUrl,
directoryEnclaveId: config.directoryEnclaveId,
directoryTrustAnchor: config.directoryTrustAnchor,
directoryV2Url: config.directoryV2Url,
directoryV2PublicKey: config.directoryV2PublicKey,
directoryV2CodeHashes: config.directoryV2CodeHashes,
cdnUrlObject: {
0: config.cdnUrl0,
2: config.cdnUrl2,
},
certificateAuthority: config.certificateAuthority,
contentProxyUrl: config.contentProxyUrl,
proxyUrl: config.proxyUrl,
version: config.version,
});
window.imageToBlurHash = imageToBlurHash;
window.libphonenumberInstance = PhoneNumberUtil.getInstance();
window.libphonenumberFormat = PhoneNumberFormat;
const activeWindowService = new ActiveWindowService();
activeWindowService.initialize(window.document, ipc);
window.isActive = activeWindowService.isActive.bind(activeWindowService);
window.registerForActive =
activeWindowService.registerForActive.bind(activeWindowService);
window.unregisterForActive =
activeWindowService.unregisterForActive.bind(activeWindowService);
window.React = React;
window.ReactDOM = ReactDOM;
window.PQueue = PQueue;
const { locale } = config;
moment.updateLocale(locale, {
relativeTime: {
s: window.i18n('timestamp_s'),
m: window.i18n('timestamp_m'),
h: window.i18n('timestamp_h'),
},
});
moment.locale(locale);
const userDataPath = SignalContext.getPath('userData');
window.baseAttachmentsPath = Attachments.getPath(userDataPath);
window.baseStickersPath = Attachments.getStickersPath(userDataPath);
window.baseTempPath = Attachments.getTempPath(userDataPath);
window.baseDraftPath = Attachments.getDraftPath(userDataPath);
addSensitivePath(window.baseAttachmentsPath);
if (config.crashDumpsPath) {
addSensitivePath(config.crashDumpsPath);
}
window.Signal = setup({
Attachments,
getRegionCode: () => window.storage.get('regionCode'),
logger: log,
userDataPath,
});

View file

@ -0,0 +1,13 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// These all need access to window.Signal:
import '../../models/messages';
import '../../models/conversations';
import '../../backbone/views/whisper_view';
import '../../views/conversation_view';
import '../../views/inbox_view';
import '../../SignalProtocolStore';
import '../../background';

View file

@ -0,0 +1,18 @@
// Copyright 2017-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable no-console */
/* eslint-disable global-require */
const { config } = window.SignalContext;
if (config.environment === 'test') {
console.log('Importing test infrastructure...');
require('./preload_test');
}
if (config.enableCI) {
console.log('Importing CI infrastructure...');
const { CI } = require('../../CI');
window.CI = new CI(window.getTitle());
}

View file

@ -0,0 +1,24 @@
// Copyright 2017-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable global-require */
import * as log from '../../logging/log';
window.preloadStartTime = Date.now();
try {
require('./start');
} catch (error) {
/* eslint-disable no-console */
if (console._log) {
console._log('preload error!', error.stack);
}
console.log('preload error!', error.stack);
/* eslint-enable no-console */
throw error;
}
window.preloadEndTime = Date.now();
log.info('preload complete');

View file

@ -0,0 +1,28 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-console */
import { ipcRenderer as ipc } from 'electron';
import { sync } from 'fast-glob';
// eslint-disable-next-line import/no-extraneous-dependencies
import { assert } from 'chai';
window.assert = assert;
// This is a hack to let us run TypeScript tests in the renderer process. See the
// code in `test/index.html`.
window.testUtilities = {
onComplete(info) {
return ipc.invoke('ci:test-electron:done', info);
},
prepareTests() {
console.log('Preparing tests...');
sync('../../test-{both,electron}/**/*_test.js', {
absolute: true,
cwd: __dirname,
}).forEach(require);
},
};

29
ts/windows/main/start.ts Normal file
View file

@ -0,0 +1,29 @@
// Copyright 2017-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as log from '../../logging/log';
import './phase1-ipc';
import '../preload';
import './phase2-dependencies';
import './phase3-post-signal';
import './phase4-test';
window.addEventListener('contextmenu', e => {
const node = e.target as Element | null;
const isEditable = Boolean(
node?.closest('textarea, input, [contenteditable="true"]')
);
const isLink = Boolean(node?.closest('a'));
const isImage = Boolean(node?.closest('.Lightbox img'));
const hasSelection = Boolean(window.getSelection()?.toString());
if (!isEditable && !hasSelection && !isLink && !isImage) {
e.preventDefault();
}
});
if (window.SignalContext.config.proxyUrl) {
log.info('Using provided proxy url');
}

View file

@ -25,8 +25,7 @@ contextBridge.exposeInMainWorld(
contextBridge.exposeInMainWorld('SignalContext', {
...SignalContext,
renderWindow: () => {
const forCalling = SignalContext.config.forCalling === 'true';
const forCamera = SignalContext.config.forCamera === 'true';
const { forCalling, forCamera } = SignalContext.config;
let message;
if (forCalling) {

View file

@ -253,7 +253,7 @@ const renderPreferences = async () => {
editCustomColor: ipcEditCustomColor,
getConversationsWithCustomColor: ipcGetConversationsWithCustomColor,
initialSpellCheckSetting:
SignalContext.config.appStartInitialSpellcheckSetting === 'true',
SignalContext.config.appStartInitialSpellcheckSetting,
makeSyncRequest: ipcMakeSyncRequest,
removeCustomColor: ipcRemoveCustomColor,
removeCustomColorOnConversations: ipcRemoveCustomColorOnConversations,

View file

@ -59,5 +59,5 @@
// "experimentalDecorators": true, // Enables experimental support for ES7 decorators.
// "emitDecoratorMetadata": true, // Enables experimental support for emitting type metadata for decorators.
},
"include": ["ts/**/*", "app/*", "package.json"]
"include": ["ts/**/*", "app/**/*", "sticker-creator/**/*", "package.json"]
}

View file

@ -4465,12 +4465,10 @@ atomic-sleep@^1.0.0:
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
attr-accept@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52"
integrity sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==
dependencies:
core-js "^2.5.0"
attr-accept@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
autoprefixer@^9.8.6:
version "9.8.8"
@ -5979,7 +5977,7 @@ core-js-pure@^3.8.1:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.22.8.tgz#f2157793b58719196ccf9673cc14f3683adc0957"
integrity sha512-bOxbZIy9S5n4OVH63XaLVXZ49QKicjowDx/UELyJ68vxfCRpYsbyh/WNZNfEfAk+ekA8vSjt+gCDpvh672bc3w==
core-js@2.6.9, core-js@^2.4.0, core-js@^2.5.0:
core-js@2.6.9, core-js@^2.4.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
@ -8027,12 +8025,12 @@ file-loader@^6.2.0:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
file-selector@^0.1.11:
version "0.1.12"
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0"
integrity sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==
file-selector@^0.1.12:
version "0.1.19"
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.19.tgz#8ecc9d069a6f544f2e4a096b64a8052e70ec8abf"
integrity sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==
dependencies:
tslib "^1.9.0"
tslib "^2.0.1"
file-system-cache@^1.0.5:
version "1.0.5"
@ -13337,13 +13335,13 @@ react-dom@17.0.2:
object-assign "^4.1.1"
scheduler "^0.20.2"
react-dropzone@10.1.7:
version "10.1.7"
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.1.7.tgz#faba6b460a41a27a2d642316602924fe0ec2139c"
integrity sha512-PT4DHJCQrY/2vVljupveqnlwzVRQGK7U6mhoO0ox6kSJV0EK6W1ZmZpRyHMA1S6/KUOzN+1pASUMSqV/4uAIXg==
react-dropzone@10.2.2:
version "10.2.2"
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.2.2.tgz#67b4db7459589a42c3b891a82eaf9ade7650b815"
integrity sha512-U5EKckXVt6IrEyhMMsgmHQiWTGLudhajPPG77KFSvgsMqNEHSyGpqWvOMc5+DhEah/vH4E1n+J5weBNLd5VtyA==
dependencies:
attr-accept "^1.1.3"
file-selector "^0.1.11"
attr-accept "^2.0.0"
file-selector "^0.1.12"
prop-types "^15.7.2"
react-element-to-jsx-string@^14.3.4: