diff --git a/.eslintignore b/.eslintignore index 2e54eb8290..25dff64df3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -26,8 +26,7 @@ ts/**/*.js .eslintrc.js webpack.config.ts preload.bundle.* -about.browser.bundle.* -about.preload.bundle.* +bundles/** # Sticker Creator has its own eslint config sticker-creator/** diff --git a/.gitignore b/.gitignore index ef4be0355a..ee1effb3fb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,7 @@ libtextsecure/components.js stylesheets/*.css /storybook-static/ preload.bundle.* -about.browser.bundle.* -about.preload.bundle.* +bundles/ ts/sql/mainWorker.bundle.js.LICENSE.txt # React / TypeScript diff --git a/.prettierignore b/.prettierignore index f241676df1..d8437df316 100644 --- a/.prettierignore +++ b/.prettierignore @@ -41,8 +41,7 @@ js/WebAudioRecorderMp3.js stylesheets/_intlTelInput.scss preload.bundle.* -about.browser.bundle.* -about.preload.bundle.* +bundles/** # Sticker Creator has its own prettier config sticker-creator/** diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 948df5c47b..3b6e304387 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -391,6 +391,30 @@ Signal Desktop makes use of the following open source projects. License: MIT +## buffer + + The MIT License (MIT) + + Copyright (c) Feross Aboukhadijeh, and other contributors. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ## cirbuf MIT License @@ -2398,6 +2422,10 @@ Signal Desktop makes use of the following open source projects. License: MIT +## uuid-browser + + License: MIT + ## websocket Apache License diff --git a/about.html b/about.html index e5e5e921d3..4d79964326 100644 --- a/about.html +++ b/about.html @@ -29,10 +29,6 @@
- - + diff --git a/app/SystemTraySettingCache.ts b/app/SystemTraySettingCache.ts index f1fca090c2..a6a8ec6f8f 100644 --- a/app/SystemTraySettingCache.ts +++ b/app/SystemTraySettingCache.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../ts/logging/log'; +import OS from '../ts/util/os/osMain'; import { parseSystemTraySetting, SystemTraySetting, @@ -54,7 +55,7 @@ export class SystemTraySettingCache { log.info( `getSystemTraySetting saw --use-tray-icon flag. Returning ${result}` ); - } else if (isSystemTraySupported(this.appVersion)) { + } else if (isSystemTraySupported(OS, this.appVersion)) { const fastValue = this.ephemeralConfig.get('system-tray-setting'); if (fastValue !== undefined) { log.info('getSystemTraySetting got fast value', fastValue); diff --git a/app/crashReports.ts b/app/crashReports.ts index bfb71ee0d8..8ea2d920d8 100644 --- a/app/crashReports.ts +++ b/app/crashReports.ts @@ -10,7 +10,7 @@ import * as Errors from '../ts/types/errors'; import { isProduction } from '../ts/util/version'; import { upload as uploadDebugLog } from '../ts/logging/uploadDebugLog'; import { SignalService as Proto } from '../ts/protobuf'; -import * as OS from '../ts/OS'; +import OS from '../ts/util/os/osMain'; async function getPendingDumps(): Promise> { const crashDumpsPath = await realpath(app.getPath('crashDumps')); diff --git a/app/main.ts b/app/main.ts index f909afc323..bd4423a2c6 100644 --- a/app/main.ts +++ b/app/main.ts @@ -48,6 +48,8 @@ import type { ThemeSettingType } from '../ts/types/StorageUIKeys'; import { ThemeType } from '../ts/types/Util'; import * as Errors from '../ts/types/errors'; import { resolveCanonicalLocales } from '../ts/util/resolveCanonicalLocales'; +import * as debugLog from '../ts/logging/debuglogs'; +import * as uploadDebugLog from '../ts/logging/uploadDebugLog'; import { explodePromise } from '../ts/util/explodePromise'; import './startup_config'; @@ -94,7 +96,7 @@ import type { CreateTemplateOptionsType } from './menu'; import type { MenuActionType } from '../ts/types/menu'; import { createTemplate } from './menu'; import { installFileHandler, installWebHandler } from './protocol_filter'; -import * as OS from '../ts/OS'; +import OS from '../ts/util/os/osMain'; import { isProduction } from '../ts/util/version'; import { isSgnlHref, @@ -390,7 +392,11 @@ function getResolvedMessagesLocale(): LocaleType { return resolvedTranslationsLocale; } -type PrepareUrlOptions = { forCalling?: boolean; forCamera?: boolean }; +type PrepareUrlOptions = { + forCalling?: boolean; + forCamera?: boolean; + sourceName?: string; +}; async function prepareFileUrl( pathSegments: ReadonlyArray, @@ -403,9 +409,9 @@ async function prepareFileUrl( async function prepareUrl( url: URL, - { forCalling, forCamera }: PrepareUrlOptions = {} + { forCalling, forCamera, sourceName }: PrepareUrlOptions = {} ): Promise { - return setUrlSearchParams(url, { forCalling, forCamera }).href; + return setUrlSearchParams(url, { forCalling, forCamera, sourceName }).href; } async function handleUrl(rawTarget: string) { @@ -1155,9 +1161,9 @@ async function showScreenShareWindow(sourceName: string) { ...defaultWebPrefs, nodeIntegration: false, nodeIntegrationInWorker: false, - sandbox: false, + sandbox: true, contextIsolation: true, - preload: join(__dirname, '../ts/windows/screenShare/preload.js'), + preload: join(__dirname, '../bundles/screenShare/preload.js'), }, x: Math.floor(display.size.width / 2) - width / 2, y: 24, @@ -1173,17 +1179,13 @@ async function showScreenShareWindow(sourceName: string) { screenShareWindow.once('ready-to-show', () => { if (screenShareWindow) { - screenShareWindow.showInactive(); - screenShareWindow.webContents.send( - 'render-screen-sharing-controller', - sourceName - ); + screenShareWindow.show(); } }); await safeLoadURL( screenShareWindow, - await prepareFileUrl([__dirname, '../screenShare.html']) + await prepareFileUrl([__dirname, '../screenShare.html'], { sourceName }) ); } @@ -1210,9 +1212,9 @@ async function showAbout() { ...defaultWebPrefs, nodeIntegration: false, nodeIntegrationInWorker: false, - sandbox: false, + sandbox: true, contextIsolation: true, - preload: join(__dirname, '../about.preload.bundle.js'), + preload: join(__dirname, '../bundles/about/preload.js'), nativeWindowOpen: true, }, }; @@ -1261,9 +1263,9 @@ async function showSettingsWindow() { ...defaultWebPrefs, nodeIntegration: false, nodeIntegrationInWorker: false, - sandbox: false, + sandbox: true, contextIsolation: true, - preload: join(__dirname, '../ts/windows/settings/preload.js'), + preload: join(__dirname, '../bundles/settings/preload.js'), nativeWindowOpen: true, }, }; @@ -1341,9 +1343,9 @@ async function showDebugLogWindow() { ...defaultWebPrefs, nodeIntegration: false, nodeIntegrationInWorker: false, - sandbox: false, + sandbox: true, contextIsolation: true, - preload: join(__dirname, '../ts/windows/debuglog/preload.js'), + preload: join(__dirname, '../bundles/debuglog/preload.js'), nativeWindowOpen: true, }, parent: mainWindow, @@ -1406,9 +1408,9 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) { ...defaultWebPrefs, nodeIntegration: false, nodeIntegrationInWorker: false, - sandbox: false, + sandbox: true, contextIsolation: true, - preload: join(__dirname, '../ts/windows/permissions/preload.js'), + preload: join(__dirname, '../bundles/permissions/preload.js'), nativeWindowOpen: true, }, parent: mainWindow, @@ -1676,7 +1678,7 @@ app.on('ready', async () => { // would still show the window. // (User can change these settings later) if ( - isSystemTraySupported(app.getVersion()) && + isSystemTraySupported(OS, app.getVersion()) && (await systemTraySettingCache.get()) === SystemTraySetting.Uninitialized ) { const newValue = SystemTraySetting.MinimizeToSystemTray; @@ -1799,9 +1801,9 @@ app.on('ready', async () => { webPreferences: { ...defaultWebPrefs, nodeIntegration: false, - sandbox: false, + sandbox: true, contextIsolation: true, - preload: join(__dirname, '../ts/windows/loading/preload.js'), + preload: join(__dirname, '../bundles/loading/preload.js'), }, icon: windowIcon, }); @@ -2278,6 +2280,8 @@ ipc.on('get-config', async event => { enableCI, nodeVersion: process.versions.node, hostname: os.hostname(), + osRelease: os.release(), + osVersion: os.version(), appInstance: process.env.NODE_APP_INSTANCE || undefined, proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy || undefined, contentProxyUrl: config.get('contentProxyUrl'), @@ -2320,11 +2324,39 @@ ipc.on('locale-data', event => { event.returnValue = getResolvedMessagesLocale().messages; }); -ipc.on('getHasCustomTitleBar', event => { +// TODO DESKTOP-5241 +ipc.on('OS.getHasCustomTitleBar', event => { // eslint-disable-next-line no-param-reassign event.returnValue = OS.hasCustomTitleBar(); }); +// TODO DESKTOP-5241 +ipc.on('OS.getClassName', event => { + // eslint-disable-next-line no-param-reassign + event.returnValue = OS.getClassName(); +}); + +ipc.handle( + 'DebugLogs.getLogs', + async (_event, data: unknown, userAgent: string) => { + return debugLog.getLog( + data, + process.versions.node, + app.getVersion(), + os.version(), + userAgent + ); + } +); + +ipc.handle('DebugLogs.upload', async (_event, content: string) => { + return uploadDebugLog.upload({ + content, + appVersion: app.getVersion(), + logger: getLogger(), + }); +}); + ipc.on('user-config-key', event => { // eslint-disable-next-line no-param-reassign event.returnValue = userConfig.get('key'); diff --git a/debug_log.html b/debug_log.html index 8ec8f075ca..49cf80870e 100644 --- a/debug_log.html +++ b/debug_log.html @@ -29,10 +29,6 @@
- - + diff --git a/loading.html b/loading.html index 0b27c6cbf3..9ffdf0dbbd 100644 --- a/loading.html +++ b/loading.html @@ -33,6 +33,6 @@
- + diff --git a/package.json b/package.json index 51fbf16d86..fc1c0c8e74 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "email": "support@signal.org" }, "browserslist": "last 1 chrome versions", + "browser": { + "uuid": "uuid-browser" + }, "main": "app/main.js", "scripts": { "postinstall": "yarn build:acknowledgments && patch-package && yarn electron:install-app-deps", @@ -56,7 +59,7 @@ "svgo": "svgo --multipass images/**/*.svg", "transpile": "run-p check:types build:esbuild", "check:types": "tsc --noEmit", - "clean-transpile-once": "rimraf sticker-creator/dist app/**/*.js app/*.js ts/**/*.js ts/*.js tsconfig.tsbuildinfo", + "clean-transpile-once": "rimraf sticker-creator/dist app/**/*.js app/*.js ts/**/*.js ts/*.js bundles tsconfig.tsbuildinfo", "clean-transpile": "yarn run clean-transpile-once && yarn run clean-transpile-once", "open-coverage": "open coverage/lcov-report/index.html", "ready": "npm-run-all --print-label clean-transpile generate --parallel lint lint-deps lint-intl test-node test-electron", @@ -94,6 +97,7 @@ "blob-util": "2.0.2", "blueimp-load-image": "5.14.0", "blurhash": "1.1.3", + "buffer": "6.0.3", "cirbuf": "1.0.1", "classnames": "2.2.5", "config": "1.28.1", @@ -169,6 +173,7 @@ "split2": "4.0.0", "type-fest": "3.5.0", "uuid": "3.3.2", + "uuid-browser": "3.1.0", "websocket": "1.0.34", "zod": "3.5.1" }, @@ -424,6 +429,7 @@ "config/default.json", "config/${env.SIGNAL_ENV}.json", "config/local-${env.SIGNAL_ENV}.json", + "bundles/**", "background.html", "about.html", "screenShare.html", @@ -456,8 +462,6 @@ "app/*", "preload.bundle.js", "preload_utils.js", - "about.preload.bundle.js", - "about.browser.bundle.js", "main.js", "images/**", "fonts/**", diff --git a/patches/@types+backbone+1.4.5.patch b/patches/@types+backbone+1.4.5.patch index 883b82deee..57bfcd8a8a 100644 --- a/patches/@types+backbone+1.4.5.patch +++ b/patches/@types+backbone+1.4.5.patch @@ -1,19 +1,28 @@ diff --git a/node_modules/@types/backbone/index.d.ts b/node_modules/@types/backbone/index.d.ts -index a172230..6f6de9f 100644 +index a172230..2c62e92 100644 --- a/node_modules/@types/backbone/index.d.ts +++ b/node_modules/@types/backbone/index.d.ts +@@ -81,7 +81,7 @@ declare namespace Backbone { + collection?: Backbone.Collection; + } + +- type CombinedModelConstructorOptions = Model> = ModelConstructorOptions & E; ++ type CombinedModelConstructorOptions = Model> = ModelConstructorOptions & E; + + interface ModelSetOptions extends Silenceable, Validable { + } @@ -218,14 +218,14 @@ declare namespace Backbone { * E - Extensions to the model constructor options. You can accept additional constructor options * by listing them in the E parameter. */ - class Model extends ModelBase implements Events { + class Model = any, S = Backbone.ModelSetOptions, E = {}> extends ModelBase implements Events { - + /** * Do not use, prefer TypeScript's extend functionality. **/ public static extend(properties: any, classProperties?: any): any; - + - attributes: any; + attributes: T; changed: any[]; diff --git a/permissions_popup.html b/permissions_popup.html index cf8042775a..831233bcac 100644 --- a/permissions_popup.html +++ b/permissions_popup.html @@ -24,10 +24,6 @@
- - + diff --git a/screenShare.html b/screenShare.html index 924fb8ddc2..606175d768 100644 --- a/screenShare.html +++ b/screenShare.html @@ -24,5 +24,6 @@
+ diff --git a/scripts/esbuild.js b/scripts/esbuild.js index f71bae78b8..4110a31d0b 100644 --- a/scripts/esbuild.js +++ b/scripts/esbuild.js @@ -6,6 +6,7 @@ const path = require('path'); const glob = require('glob'); const ROOT_DIR = path.join(__dirname, '..'); +const BUNDLES_DIR = 'bundles'; const watch = process.argv.some(argv => argv === '-w' || argv === '--watch'); const isProd = process.argv.some(argv => argv === '-prod' || argv === '--prod'); @@ -26,6 +27,7 @@ const bundleDefaults = { 'process.env.NODE_ENV': isProd ? '"production"' : '"development"', }, bundle: true, + minify: isProd, external: [ // Native libraries '@signalapp/libsignal-client', @@ -61,55 +63,94 @@ const bundleDefaults = { ], }; -async function main() { - // App, tests, and scripts - const app = await esbuild.context({ - ...nodeDefaults, - format: 'cjs', - mainFields: ['browser', 'main'], - entryPoints: glob - .sync('{app,ts}/**/*.{ts,tsx}', { - nodir: true, - root: ROOT_DIR, - }) - .filter(file => !file.endsWith('.d.ts')), - outdir: path.join(ROOT_DIR), - }); +const sandboxedPreloadDefaults = { + ...nodeDefaults, + define: { + 'process.env.NODE_ENV': isProd ? '"production"' : '"development"', + }, + external: ['electron'], + bundle: true, + minify: isProd, +}; - // Preload bundle - const bundle = await esbuild.context({ - ...bundleDefaults, - mainFields: ['browser', 'main'], - entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'main', 'preload.ts')], - outfile: path.join(ROOT_DIR, 'preload.bundle.js'), - }); +const sandboxedBrowserDefaults = { + ...sandboxedPreloadDefaults, + chunkNames: 'chunks/[name]-[hash]', + format: 'esm', + outdir: path.join(ROOT_DIR, BUNDLES_DIR), + platform: 'browser', + splitting: true, +}; + +async function build({ appConfig, preloadConfig }) { + const app = await esbuild.context(appConfig); + const preload = await esbuild.context(preloadConfig); if (watch) { - await Promise.all([app.watch(), bundle.watch()]); + await Promise.all([app.watch(), preload.watch()]); } else { - await Promise.all([app.rebuild(), bundle.rebuild()]); + await Promise.all([app.rebuild(), preload.rebuild()]); await app.dispose(); - await bundle.dispose(); + await preload.dispose(); } } -main().catch(error => { +async function main() { + await build({ + appConfig: { + ...nodeDefaults, + format: 'cjs', + mainFields: ['browser', 'main'], + entryPoints: glob + .sync('{app,ts}/**/*.{ts,tsx}', { + nodir: true, + root: ROOT_DIR, + }) + .filter(file => !file.endsWith('.d.ts')), + outdir: path.join(ROOT_DIR), + }, + preloadConfig: { + ...bundleDefaults, + mainFields: ['browser', 'main'], + entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'main', 'preload.ts')], + outfile: path.join(ROOT_DIR, 'preload.bundle.js'), + }, + }); +} + +async function sandboxedEnv() { + await build({ + appConfig: { + ...sandboxedBrowserDefaults, + mainFields: ['browser', 'main'], + entryPoints: [ + path.join(ROOT_DIR, 'ts', 'windows', 'about', 'app.tsx'), + path.join(ROOT_DIR, 'ts', 'windows', 'debuglog', 'app.tsx'), + path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'start.ts'), + path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'app.tsx'), + path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'app.tsx'), + path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'app.tsx'), + ], + }, + preloadConfig: { + ...sandboxedPreloadDefaults, + mainFields: ['main'], + entryPoints: [ + path.join(ROOT_DIR, 'ts', 'windows', 'about', 'preload.ts'), + path.join(ROOT_DIR, 'ts', 'windows', 'debuglog', 'preload.ts'), + path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'preload.ts'), + path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'preload.ts'), + path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'preload.ts'), + path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'preload.ts'), + ], + format: 'cjs', + outdir: 'bundles', + }, + }); +} + +Promise.all([main(), sandboxedEnv()]).catch(error => { console.error(error.stack); process.exit(1); }); - -// About bundle -esbuild.build({ - ...bundleDefaults, - mainFields: ['browser', 'main'], - entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'about', 'app.tsx')], - outfile: path.join(ROOT_DIR, 'about.browser.bundle.js'), -}); - -esbuild.build({ - ...bundleDefaults, - mainFields: ['browser', 'main'], - entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'about', 'preload.ts')], - outfile: path.join(ROOT_DIR, 'about.preload.bundle.js'), -}); diff --git a/settings.html b/settings.html index 464a434f73..56ebba28b0 100644 --- a/settings.html +++ b/settings.html @@ -29,10 +29,6 @@
- - + diff --git a/ts/OS.ts b/ts/OS.ts deleted file mode 100644 index 617f2979d6..0000000000 --- a/ts/OS.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import { release as osRelease } from 'os'; -import semver from 'semver'; - -const createIsPlatform = ( - platform: typeof process.platform -): ((minVersion?: string) => boolean) => { - return minVersion => { - if (process.platform !== platform) { - return false; - } - if (minVersion === undefined) { - return true; - } - - return semver.gte(osRelease(), minVersion); - }; -}; - -export const isMacOS = createIsPlatform('darwin'); -export const isLinux = createIsPlatform('linux'); -export const isWindows = createIsPlatform('win32'); - -// Windows 10 and above -export const hasCustomTitleBar = (): boolean => - isWindows('10.0.0') || Boolean(process.env.CUSTOM_TITLEBAR); - -export const getName = (): string => { - if (isMacOS()) { - return 'macOS'; - } - if (isWindows()) { - return 'Windows'; - } - return 'Linux'; -}; - -export const getClassName = (): string => { - if (isMacOS()) { - return 'os-macos'; - } - if (isWindows()) { - return 'os-windows'; - } - return 'os-linux'; -}; diff --git a/ts/calling/audioDeviceModule.ts b/ts/calling/audioDeviceModule.ts index ec977cf315..c409917291 100644 --- a/ts/calling/audioDeviceModule.ts +++ b/ts/calling/audioDeviceModule.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { makeEnumParser } from '../util/enum'; -import * as OS from '../OS'; +import OS from '../util/os/osMain'; export enum AudioDeviceModule { Default = 'Default', diff --git a/ts/components/DebugLogWindow.tsx b/ts/components/DebugLogWindow.tsx index c916213e0b..7a70c880ee 100644 --- a/ts/components/DebugLogWindow.tsx +++ b/ts/components/DebugLogWindow.tsx @@ -4,18 +4,18 @@ import type { MouseEvent } from 'react'; import React, { useEffect, useState } from 'react'; import copyText from 'copy-text-to-clipboard'; +import type { ExecuteMenuRoleType } from './TitleBarContainer'; +import type { LocalizerType } from '../types/Util'; +import * as Errors from '../types/errors'; import * as log from '../logging/log'; import { Button, ButtonVariant } from './Button'; -import type { LocalizerType } from '../types/Util'; import { Spinner } from './Spinner'; +import { TitleBarContainer } from './TitleBarContainer'; import { ToastDebugLogError } from './ToastDebugLogError'; import { ToastLinkCopied } from './ToastLinkCopied'; -import { TitleBarContainer } from './TitleBarContainer'; -import type { ExecuteMenuRoleType } from './TitleBarContainer'; import { ToastLoadingFullLogs } from './ToastLoadingFullLogs'; -import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser'; import { createSupportUrl } from '../util/createSupportUrl'; -import * as Errors from '../types/errors'; +import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser'; import { useEscapeHandling } from '../hooks/useEscapeHandling'; import { useTheme } from '../hooks/useTheme'; @@ -137,7 +137,7 @@ export function DebugLogWindow({ }; const supportURL = createSupportUrl({ - locale: i18n.getLocale(), + locale: window.SignalContext.getI18nLocale(), query: { debugLog: publicLogURL, }, diff --git a/ts/components/Preferences.tsx b/ts/components/Preferences.tsx index 5d49482d39..49be99d435 100644 --- a/ts/components/Preferences.tsx +++ b/ts/components/Preferences.tsx @@ -1,11 +1,11 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { AudioDevice } from '@signalapp/ringrtc'; import type { ReactNode } from 'react'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { noop } from 'lodash'; import classNames from 'classnames'; -import type { AudioDevice } from '@signalapp/ringrtc'; import uuid from 'uuid'; import type { MediaDeviceSettings } from '../types/Calling'; @@ -15,6 +15,19 @@ import type { ZoomFactorType, } from '../types/Storage.d'; import type { ThemeSettingType } from '../types/StorageUIKeys'; +import type { ConversationType } from '../state/ducks/conversations'; +import type { + ConversationColorType, + CustomColorType, + DefaultConversationColorType, +} from '../types/Colors'; +import type { + LocalizerType, + SentMediaQualityType, + ThemeType, +} from '../types/Util'; +import type { ExecuteMenuRoleType } from './TitleBarContainer'; + import { Button, ButtonVariant } from './Button'; import { ChatColorPicker } from './ChatColorPicker'; import { Checkbox } from './Checkbox'; @@ -23,24 +36,12 @@ import { Variant as CircleCheckboxVariant, } from './CircleCheckbox'; import { ConfirmationDialog } from './ConfirmationDialog'; -import type { ConversationType } from '../state/ducks/conversations'; -import type { - ConversationColorType, - CustomColorType, - DefaultConversationColorType, -} from '../types/Colors'; import { DisappearingTimeDialog } from './DisappearingTimeDialog'; -import type { - LocalizerType, - SentMediaQualityType, - ThemeType, -} from '../types/Util'; import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability'; import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode'; import { Select } from './Select'; import { Spinner } from './Spinner'; import { TitleBarContainer } from './TitleBarContainer'; -import type { ExecuteMenuRoleType } from './TitleBarContainer'; import { getCustomColorStyle } from '../util/getCustomColorStyle'; import { DEFAULT_DURATIONS_IN_SECONDS, @@ -179,6 +180,8 @@ type PropsFunctionType = { export type PropsType = PropsDataType & PropsFunctionType; +export type PropsPreloadType = Omit; + enum Page { // Accessible through left nav General = 'General', diff --git a/ts/context/i18n.ts b/ts/context/i18n.ts index edf36636c2..3cdf5d8610 100644 --- a/ts/context/i18n.ts +++ b/ts/context/i18n.ts @@ -1,8 +1,8 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { ipcRenderer } from 'electron'; import { config } from './config'; +import { localeMessages } from './localeMessages'; import { setupI18n } from '../util/setupI18n'; import { strictAssert } from '../util/assert'; @@ -16,7 +16,6 @@ strictAssert( 'locale is not a string' ); -const localeMessages = ipcRenderer.sendSync('locale-data'); const i18n = setupI18n(resolvedTranslationsLocale, localeMessages); export { i18n }; diff --git a/ts/context/localeMessages.ts b/ts/context/localeMessages.ts new file mode 100644 index 0000000000..994f6d0df5 --- /dev/null +++ b/ts/context/localeMessages.ts @@ -0,0 +1,6 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { ipcRenderer } from 'electron'; + +export const localeMessages = ipcRenderer.sendSync('locale-data'); diff --git a/ts/environment.ts b/ts/environment.ts index 7bf4b65469..64149e550d 100644 --- a/ts/environment.ts +++ b/ts/environment.ts @@ -1,8 +1,6 @@ // 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. @@ -13,8 +11,6 @@ export enum Environment { Test = 'test', } -export const environmentSchema = z.nativeEnum(Environment); - let environment: undefined | Environment; export function getEnvironment(): Environment { diff --git a/ts/logging/debuglogs.ts b/ts/logging/debuglogs.ts index 393ea38b44..cb53c14819 100644 --- a/ts/logging/debuglogs.ts +++ b/ts/logging/debuglogs.ts @@ -2,8 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { memoize, sortBy } from 'lodash'; -import os from 'os'; -import { ipcRenderer as ipc } from 'electron'; import { reallyJsonStringify } from '../util/reallyJsonStringify'; import type { FetchLogIpcData, LogEntryType } from './shared'; import { @@ -42,16 +40,18 @@ const getHeader = ( user, }: Omit, nodeVersion: string, - appVersion: string + appVersion: string, + osVersion: string, + userAgent: string ): string => [ headerSection('System info', { Time: Date.now(), - 'User agent': window.navigator.userAgent, + 'User agent': userAgent, 'Node version': nodeVersion, Environment: getEnvironment(), 'App version': appVersion, - 'OS version': os.version(), + 'OS version': osVersion, }), headerSection('User info', user), headerSection('Capabilities', capabilities), @@ -79,17 +79,18 @@ function formatLine(mightBeEntry: unknown): string { return `${getLevel(entry.level)} ${entry.time} ${entry.msg}`; } -export async function fetch( +export function getLog( + data: unknown, nodeVersion: string, - appVersion: string -): Promise { - const data: unknown = await ipc.invoke('fetch-log'); - + appVersion: string, + osVersion: string, + userAgent: string +): string { let header: string; let body: string; if (isFetchLogIpcData(data)) { const { logEntries } = data; - header = getHeader(data, nodeVersion, appVersion); + header = getHeader(data, nodeVersion, appVersion, osVersion, userAgent); body = logEntries.map(formatLine).join('\n'); } else { header = headerSectionTitle('Partial logs'); diff --git a/ts/logging/log.ts b/ts/logging/log.ts index 37d0955c18..69feba5f43 100644 --- a/ts/logging/log.ts +++ b/ts/logging/log.ts @@ -1,7 +1,7 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { noop } from 'lodash'; +import noop from 'lodash/noop'; import type { LogFunction } from '../types/Logging'; import { LogLevel } from '../types/Logging'; diff --git a/ts/services/ActiveWindowService.ts b/ts/services/ActiveWindowService.ts index 402502ff2e..777b49b4c8 100644 --- a/ts/services/ActiveWindowService.ts +++ b/ts/services/ActiveWindowService.ts @@ -1,7 +1,7 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { throttle } from 'lodash'; +import { throttle } from '../util/throttle'; // Idle timer - you're active for ACTIVE_TIMEOUT after one of these events const ACTIVE_TIMEOUT = 15 * 1000; diff --git a/ts/services/notifications.ts b/ts/services/notifications.ts index 437c078d6a..b498368d18 100644 --- a/ts/services/notifications.ts +++ b/ts/services/notifications.ts @@ -1,6 +1,7 @@ // Copyright 2015 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import os from 'os'; import { debounce } from 'lodash'; import EventEmitter from 'events'; import { Sound } from '../util/Sound'; @@ -9,7 +10,7 @@ import { getAudioNotificationSupport, shouldHideExpiringMessageBody, } from '../types/Settings'; -import * as OS from '../OS'; +import OS from '../util/os/osMain'; import * as log from '../logging/log'; import { makeEnumParser } from '../util/enum'; import { missingCaseError } from '../util/missingCaseError'; @@ -144,7 +145,7 @@ class NotificationService extends EventEmitter { this.lastNotification?.close(); - const audioNotificationSupport = getAudioNotificationSupport(); + const audioNotificationSupport = getAudioNotificationSupport(OS); const notification = new window.Notification(title, { body: OS.isLinux() ? filterNotificationText(message) : message, @@ -299,7 +300,10 @@ class NotificationService extends EventEmitter { notificationTitle = senderTitle; ({ notificationIconUrl } = notificationData); - if (isExpiringMessage && shouldHideExpiringMessageBody()) { + if ( + isExpiringMessage && + shouldHideExpiringMessageBody(OS, os.release()) + ) { notificationMessage = i18n('icu:newMessage'); } else if (userSetting === NotificationSetting.NameOnly) { if (reaction) { diff --git a/ts/signal.ts b/ts/signal.ts index 752a1378fc..a2ee89c977 100644 --- a/ts/signal.ts +++ b/ts/signal.ts @@ -8,7 +8,7 @@ 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 OS from './util/os/osMain'; import * as RemoteConfig from './RemoteConfig'; // Components diff --git a/ts/state/ducks/user.ts b/ts/state/ducks/user.ts index 410bf96e7c..093cc042c6 100644 --- a/ts/state/ducks/user.ts +++ b/ts/state/ducks/user.ts @@ -9,7 +9,7 @@ import type { LocalizerType } from '../../types/Util'; import type { MenuOptionsType } from '../../types/menu'; import type { NoopActionType } from './noop'; import type { UUIDStringType } from '../../types/UUID'; -import * as OS from '../../OS'; +import OS from '../../util/os/osMain'; import { ThemeType } from '../../types/Util'; // State @@ -116,6 +116,7 @@ export function getEmptyState(): UserStateType { getLocale: intlNotSetup, getIntl: intlNotSetup, isLegacyFormat: intlNotSetup, + getLocaleMessages: intlNotSetup, getLocaleDirection: intlNotSetup, }), interactionMode: 'mouse', diff --git a/ts/state/getInitialState.ts b/ts/state/getInitialState.ts index e65736c58d..626e255b34 100644 --- a/ts/state/getInitialState.ts +++ b/ts/state/getInitialState.ts @@ -32,7 +32,7 @@ import type { MainWindowStatsType } from '../windows/context'; import type { MenuOptionsType } from '../types/menu'; import type { StoryDataType } from './ducks/stories'; import type { StoryDistributionListDataType } from './ducks/storyDistributionLists'; -import * as OS from '../OS'; +import OS from '../util/os/osMain'; import { UUIDKind } from '../types/UUID'; import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis'; import { getInitialState as stickers } from '../types/Stickers'; @@ -132,7 +132,7 @@ export function getInitialState({ interactionMode: getInteractionMode(), isMainWindowFullScreen: mainWindowStats.isFullScreen, isMainWindowMaximized: mainWindowStats.isMaximized, - localeMessages: window.SignalContext.localeMessages, + localeMessages: window.i18n.getLocaleMessages(), menuOptions, osName, ourACI, diff --git a/ts/state/smart/App.tsx b/ts/state/smart/App.tsx index b59b65f283..f0f3a1df51 100644 --- a/ts/state/smart/App.tsx +++ b/ts/state/smart/App.tsx @@ -7,7 +7,7 @@ import type { MenuItemConstructorOptions } from 'electron'; import type { MenuActionType } from '../../types/menu'; import { App } from '../../components/App'; -import { getName as getOSName, getClassName as getOSClassName } from '../../OS'; +import OS from '../../util/os/osMain'; import { SmartCallManager } from './CallManager'; import { SmartGlobalModalContainer } from './GlobalModalContainer'; import { SmartLightbox } from './Lightbox'; @@ -47,8 +47,8 @@ const mapStateToProps = (state: StateType) => { isFullScreen: getIsMainWindowFullScreen(state), menuOptions: getMenuOptions(state), hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(), - OS: getOSName(), - osClassName: getOSClassName(), + OS: OS.getName(), + osClassName: OS.getClassName(), hideMenuBar: getHideMenuBar(state), renderCallManager: () => ( diff --git a/ts/state/smart/InstallScreen.tsx b/ts/state/smart/InstallScreen.tsx index 0ba05672ee..9991e00b00 100644 --- a/ts/state/smart/InstallScreen.tsx +++ b/ts/state/smart/InstallScreen.tsx @@ -27,7 +27,7 @@ import { HTTPError } from '../../textsecure/Errors'; import { isRecord } from '../../util/isRecord'; import * as Errors from '../../types/errors'; import { normalizeDeviceName } from '../../util/normalizeDeviceName'; -import { getName as getOSName } from '../../OS'; +import OS from '../../util/os/osMain'; type PropsType = ComponentProps; @@ -258,7 +258,7 @@ export function SmartInstallScreen(): ReactElement { updates, currentVersion: window.getVersion(), startUpdate, - OS: getOSName(), + OS: OS.getName(), }, }; break; diff --git a/ts/state/smart/UnsupportedOSDialog.tsx b/ts/state/smart/UnsupportedOSDialog.tsx index f33f5042c2..f45690fa8d 100644 --- a/ts/state/smart/UnsupportedOSDialog.tsx +++ b/ts/state/smart/UnsupportedOSDialog.tsx @@ -8,7 +8,7 @@ import { UnsupportedOSDialog } from '../../components/UnsupportedOSDialog'; import { getIntl } from '../selectors/user'; import { getExpirationTimestamp } from '../selectors/expiration'; import type { WidthBreakpoint } from '../../components/_util'; -import { getName as getOSName } from '../../OS'; +import OS from '../../util/os/osMain'; export type PropsType = Readonly<{ type: 'warning' | 'error'; @@ -18,14 +18,14 @@ export type PropsType = Readonly<{ export function SmartUnsupportedOSDialog(ownProps: PropsType): JSX.Element { const i18n = useSelector(getIntl); const expirationTimestamp = useSelector(getExpirationTimestamp); - const OS = getOSName(); + const osName = OS.getName(); return ( ); } diff --git a/ts/state/smart/UpdateDialog.tsx b/ts/state/smart/UpdateDialog.tsx index 55115d5b03..f97b52ce85 100644 --- a/ts/state/smart/UpdateDialog.tsx +++ b/ts/state/smart/UpdateDialog.tsx @@ -8,7 +8,7 @@ import type { StateType } from '../reducer'; import { getIntl } from '../selectors/user'; import { getExpirationTimestamp } from '../selectors/expiration'; import type { WidthBreakpoint } from '../../components/_util'; -import { getName as getOSName } from '../../OS'; +import OS from '../../util/os/osMain'; type PropsType = Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>; @@ -18,7 +18,7 @@ const mapStateToProps = (state: StateType, ownProps: PropsType) => { i18n: getIntl(state), currentVersion: window.getVersion(), expirationTimestamp: getExpirationTimestamp(state), - OS: getOSName(), + OS: OS.getName(), ...ownProps, }; }; diff --git a/ts/test-node/types/Settings_test.ts b/ts/test-node/types/Settings_test.ts index 79239c605d..b2045e59d8 100644 --- a/ts/test-node/types/Settings_test.ts +++ b/ts/test-node/types/Settings_test.ts @@ -5,6 +5,7 @@ import os from 'os'; import Sinon from 'sinon'; import { assert } from 'chai'; +import { getOSFunctions } from '../../util/os/shared'; import * as Settings from '../../types/Settings'; describe('Settings', () => { @@ -21,8 +22,9 @@ describe('Settings', () => { describe('getAudioNotificationSupport', () => { it('returns native support on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); + const OS = getOSFunctions(os.release()); assert.strictEqual( - Settings.getAudioNotificationSupport(), + Settings.getAudioNotificationSupport(OS), Settings.AudioNotificationSupport.Native ); }); @@ -30,8 +32,9 @@ describe('Settings', () => { it('returns no support on Windows 7', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('7.0.0'); + const OS = getOSFunctions(os.release()); assert.strictEqual( - Settings.getAudioNotificationSupport(), + Settings.getAudioNotificationSupport(OS), Settings.AudioNotificationSupport.None ); }); @@ -39,16 +42,18 @@ describe('Settings', () => { it('returns native support on Windows 8', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('8.0.0'); + const OS = getOSFunctions(os.release()); assert.strictEqual( - Settings.getAudioNotificationSupport(), + Settings.getAudioNotificationSupport(OS), Settings.AudioNotificationSupport.Native ); }); it('returns custom support on Linux', () => { sandbox.stub(process, 'platform').value('linux'); + const OS = getOSFunctions(os.release()); assert.strictEqual( - Settings.getAudioNotificationSupport(), + Settings.getAudioNotificationSupport(OS), Settings.AudioNotificationSupport.Custom ); }); @@ -57,48 +62,56 @@ describe('Settings', () => { describe('isAudioNotificationSupported', () => { it('returns true on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); - assert.isTrue(Settings.isAudioNotificationSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isAudioNotificationSupported(OS)); }); it('returns false on Windows 7', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('7.0.0'); - assert.isFalse(Settings.isAudioNotificationSupported()); + const OS = getOSFunctions(os.release()); + assert.isFalse(Settings.isAudioNotificationSupported(OS)); }); it('returns true on Windows 8', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('8.0.0'); - assert.isTrue(Settings.isAudioNotificationSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isAudioNotificationSupported(OS)); }); it('returns true on Linux', () => { sandbox.stub(process, 'platform').value('linux'); - assert.isTrue(Settings.isAudioNotificationSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isAudioNotificationSupported(OS)); }); }); describe('isNotificationGroupingSupported', () => { it('returns true on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); - assert.isTrue(Settings.isNotificationGroupingSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isNotificationGroupingSupported(OS)); }); it('returns true on Windows 7', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('7.0.0'); - assert.isFalse(Settings.isNotificationGroupingSupported()); + const OS = getOSFunctions(os.release()); + assert.isFalse(Settings.isNotificationGroupingSupported(OS)); }); it('returns true on Windows 8', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('8.0.0'); - assert.isTrue(Settings.isNotificationGroupingSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isNotificationGroupingSupported(OS)); }); it('returns true on Linux', () => { sandbox.stub(process, 'platform').value('linux'); - assert.isTrue(Settings.isNotificationGroupingSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isNotificationGroupingSupported(OS)); }); }); @@ -106,88 +119,103 @@ describe('Settings', () => { it('returns true on Windows', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('8.0.0'); - assert.isTrue(Settings.isAutoLaunchSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isAutoLaunchSupported(OS)); }); it('returns true on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); - assert.isTrue(Settings.isAutoLaunchSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isAutoLaunchSupported(OS)); }); it('returns false on Linux', () => { sandbox.stub(process, 'platform').value('linux'); - assert.isFalse(Settings.isAutoLaunchSupported()); + const OS = getOSFunctions(os.release()); + assert.isFalse(Settings.isAutoLaunchSupported(OS)); }); }); describe('isHideMenuBarSupported', () => { it('returns false on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); - assert.isFalse(Settings.isHideMenuBarSupported()); + const OS = getOSFunctions(os.release()); + assert.isFalse(Settings.isHideMenuBarSupported(OS)); }); it('returns true on Windows 7', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('7.0.0'); - assert.isTrue(Settings.isHideMenuBarSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isHideMenuBarSupported(OS)); }); it('returns true on Windows 8', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('8.0.0'); - assert.isTrue(Settings.isHideMenuBarSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isHideMenuBarSupported(OS)); }); it('returns true on Linux', () => { sandbox.stub(process, 'platform').value('linux'); - assert.isTrue(Settings.isHideMenuBarSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isHideMenuBarSupported(OS)); }); }); describe('isDrawAttentionSupported', () => { it('returns false on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); - assert.isFalse(Settings.isDrawAttentionSupported()); + const OS = getOSFunctions(os.release()); + assert.isFalse(Settings.isDrawAttentionSupported(OS)); }); it('returns true on Windows 7', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('7.0.0'); - assert.isTrue(Settings.isDrawAttentionSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isDrawAttentionSupported(OS)); }); it('returns true on Windows 8', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('8.0.0'); - assert.isTrue(Settings.isDrawAttentionSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isDrawAttentionSupported(OS)); }); it('returns true on Linux', () => { sandbox.stub(process, 'platform').value('linux'); - assert.isTrue(Settings.isDrawAttentionSupported()); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isDrawAttentionSupported(OS)); }); }); describe('isSystemTraySupported', () => { it('returns false on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); - assert.isFalse(Settings.isSystemTraySupported('1.2.3')); + const OS = getOSFunctions(os.release()); + assert.isFalse(Settings.isSystemTraySupported(OS, '1.2.3')); }); it('returns true on Windows 8', () => { sandbox.stub(process, 'platform').value('win32'); sandbox.stub(os, 'release').returns('8.0.0'); - assert.isTrue(Settings.isSystemTraySupported('1.2.3')); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isSystemTraySupported(OS, '1.2.3')); }); it('returns false on Linux production', () => { sandbox.stub(process, 'platform').value('linux'); - assert.isFalse(Settings.isSystemTraySupported('1.2.3')); + const OS = getOSFunctions(os.release()); + assert.isFalse(Settings.isSystemTraySupported(OS, '1.2.3')); }); it('returns true on Linux beta', () => { sandbox.stub(process, 'platform').value('linux'); - assert.isTrue(Settings.isSystemTraySupported('1.2.3-beta.4')); + const OS = getOSFunctions(os.release()); + assert.isTrue(Settings.isSystemTraySupported(OS, '1.2.3-beta.4')); }); }); }); diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index 01695b00ce..b22b5eca87 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -715,7 +715,7 @@ export default class MessageSender { storyMessage.fileAttachment = fileAttachment; } catch (error) { if (error instanceof HTTPError) { - throw new MessageError(message, error); + throw new MessageError(storyMessage, error); } else { throw error; } diff --git a/ts/types/RendererConfig.ts b/ts/types/RendererConfig.ts index 79af911170..f0acb7e465 100644 --- a/ts/types/RendererConfig.ts +++ b/ts/types/RendererConfig.ts @@ -3,8 +3,10 @@ import { z } from 'zod'; +import { Environment } from '../environment'; import { themeSettingSchema } from './StorageUIKeys'; -import { environmentSchema } from '../environment'; + +const environmentSchema = z.nativeEnum(Environment); const configRequiredStringSchema = z.string().nonempty(); export type ConfigRequiredStringType = z.infer< @@ -39,6 +41,8 @@ export const rendererConfigSchema = z.object({ environment: environmentSchema, homePath: configRequiredStringSchema, hostname: configRequiredStringSchema, + osRelease: configRequiredStringSchema, + osVersion: configRequiredStringSchema, resolvedTranslationsLocale: configRequiredStringSchema, resolvedTranslationsLocaleDirection: z.enum(['ltr', 'rtl']), preferredSystemLocales: z.array(configRequiredStringSchema), diff --git a/ts/types/Settings.ts b/ts/types/Settings.ts index dbad91bf3e..6efe059678 100644 --- a/ts/types/Settings.ts +++ b/ts/types/Settings.ts @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import semver from 'semver'; -import os from 'os'; -import * as OS from '../OS'; +import type { OSType } from '../util/os/shared'; import { isProduction } from '../util/version'; const MIN_WINDOWS_VERSION = '8.0.0'; @@ -15,7 +14,9 @@ export enum AudioNotificationSupport { Custom, } -export function getAudioNotificationSupport(): AudioNotificationSupport { +export function getAudioNotificationSupport( + OS: OSType +): AudioNotificationSupport { if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) { return AudioNotificationSupport.Native; } @@ -25,42 +26,48 @@ export function getAudioNotificationSupport(): AudioNotificationSupport { return AudioNotificationSupport.None; } -export const isAudioNotificationSupported = (): boolean => - getAudioNotificationSupport() !== AudioNotificationSupport.None; +export const isAudioNotificationSupported = (OS: OSType): boolean => + getAudioNotificationSupport(OS) !== AudioNotificationSupport.None; // Using `Notification::tag` has a bug on Windows 7: // https://github.com/electron/electron/issues/11189 -export const isNotificationGroupingSupported = (): boolean => +export const isNotificationGroupingSupported = (OS: OSType): boolean => !OS.isWindows() || OS.isWindows(MIN_WINDOWS_VERSION); // Login item settings are only supported on macOS and Windows, according to [Electron's // docs][0]. // [0]: https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows -export const isAutoLaunchSupported = (): boolean => +export const isAutoLaunchSupported = (OS: OSType): boolean => OS.isWindows() || OS.isMacOS(); // the "hide menu bar" option is specific to Windows and Linux -export const isHideMenuBarSupported = (): boolean => !OS.isMacOS(); +export const isHideMenuBarSupported = (OS: OSType): boolean => !OS.isMacOS(); // the "draw attention on notification" option is specific to Windows and Linux -export const isDrawAttentionSupported = (): boolean => !OS.isMacOS(); +export const isDrawAttentionSupported = (OS: OSType): boolean => !OS.isMacOS(); /** * Returns `true` if you can minimize the app to the system tray. Users can override this * option with a command line flag, but that is not officially supported. */ -export const isSystemTraySupported = (appVersion: string): boolean => +export const isSystemTraySupported = ( + OS: OSType, + appVersion: string +): boolean => // We eventually want to support Linux in production. OS.isWindows() || (OS.isLinux() && !isProduction(appVersion)); // On Windows minimize and start in system tray is default when app is selected // to launch at login, because we can provide `['--start-in-tray']` args. export const isMinimizeToAndStartInSystemTraySupported = ( + OS: OSType, appVersion: string -): boolean => !OS.isWindows() && isSystemTraySupported(appVersion); +): boolean => !OS.isWindows() && isSystemTraySupported(OS, appVersion); -export const isAutoDownloadUpdatesSupported = (): boolean => +export const isAutoDownloadUpdatesSupported = (OS: OSType): boolean => OS.isWindows() || OS.isMacOS(); -export const shouldHideExpiringMessageBody = (): boolean => - OS.isWindows() || (OS.isMacOS() && semver.lt(os.release(), '21.1.0')); +export const shouldHideExpiringMessageBody = ( + OS: OSType, + release: string +): boolean => OS.isWindows() || (OS.isMacOS() && semver.lt(release, '21.1.0')); diff --git a/ts/types/Util.ts b/ts/types/Util.ts index 8fdfa7d076..279e23b47c 100644 --- a/ts/types/Util.ts +++ b/ts/types/Util.ts @@ -5,6 +5,8 @@ import type { IntlShape } from 'react-intl'; import type { UUIDStringType } from './UUID'; import type { LocaleDirection } from '../../app/locale'; +import type { LocaleMessagesType } from './I18N'; + export type StoryContextType = { authorUuid?: UUIDStringType; timestamp: number; @@ -24,6 +26,7 @@ export type LocalizerType = { getIntl(): IntlShape; isLegacyFormat(key: string): boolean; getLocale(): string; + getLocaleMessages(): LocaleMessagesType; getLocaleDirection(): LocaleDirection; }; diff --git a/ts/types/errors.ts b/ts/types/errors.ts index b029fea773..b0e655787d 100644 --- a/ts/types/errors.ts +++ b/ts/types/errors.ts @@ -1,20 +1,18 @@ // Copyright 2018 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { get, has } from 'lodash'; - export function toLogFormat(error: unknown): string { let result = ''; if (error instanceof Error && error.stack) { result = error.stack; - } else if (has(error, 'message')) { - result = get(error, 'message'); + } else if (error && typeof error === 'object' && 'message' in error) { + result = String(error.message); } else { result = String(error); } - if (has(error, 'cause')) { - result += `\nCaused by: ${String(get(error, 'cause'))}`; + if (error && typeof error === 'object' && 'cause' in error) { + result += `\nCaused by: ${String(error.cause)}`; } return result; diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 265f0fa151..957f3a484e 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -2525,7 +2525,7 @@ { "rule": "DOM-innerHTML", "path": "ts/windows/loading/start.ts", - "line": " message.innerHTML = window.SignalContext.i18n('icu:optimizingApplication');", + "line": " message.innerHTML = window.i18n('icu:optimizingApplication');", "reasonCategory": "usageTrusted", "updated": "2021-09-17T21:02:59.414Z" }, diff --git a/ts/util/lint/linter.ts b/ts/util/lint/linter.ts index 2d4ae938fb..c8eb49c464 100644 --- a/ts/util/lint/linter.ts +++ b/ts/util/lint/linter.ts @@ -24,8 +24,7 @@ const excludedFilesRegexp = RegExp( [ '^release/', '^preload.bundle.js(LICENSE.txt|map)?', - '^about.browser.bundle.js(LICENSE.txt|map)?', - '^about.preload.bundle.js(LICENSE.txt|map)?', + '^bundles/', '^storybook-static/', // Non-distributed files diff --git a/ts/util/os/osMain.ts b/ts/util/os/osMain.ts new file mode 100644 index 0000000000..fd9e56884d --- /dev/null +++ b/ts/util/os/osMain.ts @@ -0,0 +1,9 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import os from 'os'; +import { getOSFunctions } from './shared'; + +const OS = getOSFunctions(os.release()); + +export default OS; diff --git a/ts/util/os/osPreload.ts b/ts/util/os/osPreload.ts new file mode 100644 index 0000000000..c57454e800 --- /dev/null +++ b/ts/util/os/osPreload.ts @@ -0,0 +1,9 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { config } from '../../context/config'; +import { getOSFunctions } from './shared'; + +const OS = getOSFunctions(config.osRelease); + +export default OS; diff --git a/ts/util/os/shared.ts b/ts/util/os/shared.ts new file mode 100644 index 0000000000..01fcd66f8d --- /dev/null +++ b/ts/util/os/shared.ts @@ -0,0 +1,68 @@ +// Copyright 2018 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import semver from 'semver'; + +function createIsPlatform( + platform: typeof process.platform, + osRelease: string +): (minVersion?: string) => boolean { + return minVersion => { + if (process.platform !== platform) { + return false; + } + if (minVersion === undefined) { + return true; + } + + return semver.gte(osRelease, minVersion); + }; +} + +export type OSType = { + getClassName: () => string; + getName: () => string; + hasCustomTitleBar: () => boolean; + isLinux: (minVersion?: string) => boolean; + isMacOS: (minVersion?: string) => boolean; + isWindows: (minVersion?: string) => boolean; +}; + +export function getOSFunctions(osRelease: string): OSType { + const isMacOS = createIsPlatform('darwin', osRelease); + const isLinux = createIsPlatform('linux', osRelease); + const isWindows = createIsPlatform('win32', osRelease); + + // Windows 10 and above + const hasCustomTitleBar = (): boolean => + isWindows('10.0.0') || Boolean(process.env.CUSTOM_TITLEBAR); + + const getName = (): string => { + if (isMacOS()) { + return 'macOS'; + } + if (isWindows()) { + return 'Windows'; + } + return 'Linux'; + }; + + const getClassName = (): string => { + if (isMacOS()) { + return 'os-macos'; + } + if (isWindows()) { + return 'os-windows'; + } + return 'os-linux'; + }; + + return { + getClassName, + getName, + hasCustomTitleBar, + isLinux, + isMacOS, + isWindows, + }; +} diff --git a/ts/util/setupI18n.ts b/ts/util/setupI18n.ts index a1e4b5619b..980fe29c6a 100644 --- a/ts/util/setupI18n.ts +++ b/ts/util/setupI18n.ts @@ -172,6 +172,7 @@ export function setupI18n( return legacyMessages[key] != null; }; getMessage.getLocale = () => locale; + getMessage.getLocaleMessages = () => messages; getMessage.getLocaleDirection = () => { return window.getResolvedMessagesLocaleDirection(); }; diff --git a/ts/util/throttle.ts b/ts/util/throttle.ts new file mode 100644 index 0000000000..900501cd2c --- /dev/null +++ b/ts/util/throttle.ts @@ -0,0 +1,70 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +// eslint-disable-next-line @typescript-eslint/ban-types +export function throttle(func: Function, wait: number): () => void { + let lastCallTime: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lastArgs: Array | undefined; + let timerId: NodeJS.Timeout | undefined; + + function call() { + const args = lastArgs || []; + lastArgs = undefined; + func(...args); + } + + function leading() { + timerId = setTimeout(timerExpired, wait); + call(); + } + + function remainingWait(time: number) { + const timeSinceLastCall = time - lastCallTime; + return wait - timeSinceLastCall; + } + + function shouldInvoke(time: number) { + const timeSinceLastCall = time - lastCallTime; + + return ( + lastCallTime === undefined || + timeSinceLastCall >= wait || + timeSinceLastCall < 0 + ); + } + + function timerExpired() { + const time = Date.now(); + if (shouldInvoke(time)) { + return trailing(); + } + timerId = setTimeout(timerExpired, remainingWait(time)); + } + + function trailing() { + timerId = undefined; + + if (lastArgs) { + return call(); + } + lastArgs = undefined; + } + + return (...args) => { + const time = Date.now(); + const isInvoking = shouldInvoke(time); + + lastArgs = args; + lastCallTime = time; + + if (isInvoking) { + if (timerId === undefined) { + return leading(); + } + } + if (timerId === undefined) { + timerId = setTimeout(timerExpired, wait); + } + }; +} diff --git a/ts/util/version.ts b/ts/util/version.ts index 323e24308d..74c3d40ab6 100644 --- a/ts/util/version.ts +++ b/ts/util/version.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as semver from 'semver'; -import moment from 'moment'; export const isProduction = (version: string): boolean => { const parsed = semver.parse(version); @@ -34,7 +33,22 @@ export const generateAlphaVersion = (options: { throw new Error(`generateAlphaVersion: Invalid version ${currentVersion}`); } - const formattedDate = moment().utc().format('YYYYMMDD.HH'); + const dateTimeParts = new Intl.DateTimeFormat('en', { + day: '2-digit', + hour: '2-digit', + hourCycle: 'h23', + month: '2-digit', + timeZone: 'GMT', + year: 'numeric', + }).formatToParts(new Date()); + const dateTimeMap = new Map(); + dateTimeParts.forEach(({ type, value }) => { + dateTimeMap.set(type, value); + }); + const formattedDate = `${dateTimeMap.get('year')}${dateTimeMap.get( + 'month' + )}${dateTimeMap.get('day')}.${dateTimeMap.get('hour')}`; + const formattedVersion = `${parsed.major}.${parsed.minor}.${parsed.patch}`; return `${formattedVersion}-alpha.${formattedDate}-${shortSha}`; diff --git a/ts/util/windowsZoneIdentifier.ts b/ts/util/windowsZoneIdentifier.ts index 1a0549fcba..a51db87095 100644 --- a/ts/util/windowsZoneIdentifier.ts +++ b/ts/util/windowsZoneIdentifier.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as fs from 'fs'; -import { isWindows } from '../OS'; +import OS from './os/osMain'; const ZONE_IDENTIFIER_CONTENTS = Buffer.from('[ZoneTransfer]\r\nZoneId=3'); @@ -27,7 +27,7 @@ const ZONE_IDENTIFIER_CONTENTS = Buffer.from('[ZoneTransfer]\r\nZoneId=3'); export async function writeWindowsZoneIdentifier( filePath: string ): Promise { - if (!isWindows()) { + if (!OS.isWindows()) { throw new Error('writeWindowsZoneIdentifier should only run on Windows'); } diff --git a/ts/window.d.ts b/ts/window.d.ts index 819b33db3b..241e266a8f 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -3,7 +3,6 @@ // Captures the globals put in place by preload.js, background.js and others -import type { MenuItemConstructorOptions } from 'electron'; import type { Store } from 'redux'; import type * as Backbone from 'backbone'; import type PQueue from 'p-queue/dist'; @@ -28,7 +27,7 @@ import type * as Groups from './groups'; import type * as Crypto from './Crypto'; import type * as Curve from './Curve'; import type * as RemoteConfig from './RemoteConfig'; -import type * as OS from './OS'; +import type { OSType } from './util/os/shared'; import type { getEnvironment } from './environment'; import type { LocalizerType, ThemeType } from './types/Util'; import type { Receipt } from './types/Receipt'; @@ -56,6 +55,7 @@ import type { SignalContextType } from './windows/context'; import type * as Message2 from './types/Message2'; import type { initializeMigrations } from './signal'; import type { RetryPlaceholders } from './util/retryPlaceholders'; +import type { PropsPreloadType as PreferencesPropsType } from './components/Preferences'; import type { LocaleDirection } from '../app/locale'; export { Long } from 'long'; @@ -103,21 +103,46 @@ export type FeatureFlagType = { GV2_MIGRATION_DISABLE_INVITE: boolean; }; -type AboutWindowType = { +type AboutWindowPropsType = { + arch: string; environmentText: string; - executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise; - hasCustomTitleBar: boolean; - i18n: LocalizerType; - version: string; + platform: string; +}; + +type DebugLogWindowPropsType = { + downloadLog: (text: string) => unknown; + fetchLogs: () => Promise; + uploadLogs: (text: string) => Promise; +}; + +type PermissionsWindowPropsType = { + forCamera: boolean; + forCalling: boolean; + onAccept: () => void; + onClose: () => void; +}; + +type ScreenShareWindowPropsType = { + onStopSharing: () => void; + presentedSourceName: string; +}; + +type SettingsOnRenderCallbackType = (props: PreferencesPropsType) => void; + +type SettingsWindowPropsType = { + onRender: (callback: SettingsOnRenderCallbackType) => void; }; export type SignalCoreType = { - AboutWindow?: AboutWindowType; + AboutWindowProps?: AboutWindowPropsType; Crypto: typeof Crypto; Curve: typeof Curve; Data: typeof Data; + DebugLogWindowProps?: DebugLogWindowPropsType; Groups: typeof Groups; + PermissionsWindowProps?: PermissionsWindowPropsType; RemoteConfig: typeof RemoteConfig; + ScreenShareWindowProps?: ScreenShareWindowPropsType; Services: { calling: CallingClass; initializeGroupCredentialFetcher: () => Promise; @@ -127,6 +152,7 @@ export type SignalCoreType = { lightSessionResetQueue?: PQueue; storage: typeof StorageService; }; + SettingsWindowProps?: SettingsWindowPropsType; Migrations: ReturnType; Types: { Message: typeof Message2; @@ -137,7 +163,7 @@ export type SignalCoreType = { Components: { ConfirmationDialog: typeof ConfirmationDialog; }; - OS: typeof OS; + OS: OSType; State: { createStore: typeof createStore; Roots: { diff --git a/ts/windows/about/app.tsx b/ts/windows/about/app.tsx index 5964e7d2c4..8b82157718 100644 --- a/ts/windows/about/app.tsx +++ b/ts/windows/about/app.tsx @@ -5,20 +5,32 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { About } from '../../components/About'; +import { i18n } from '../sandboxedInit'; import { strictAssert } from '../../util/assert'; -const { AboutWindow } = window.Signal; +const { AboutWindowProps } = window.Signal; -strictAssert(AboutWindow, 'window values not provided'); +strictAssert(AboutWindowProps, 'window values not provided'); + +let platform = ''; +if (AboutWindowProps.platform === 'darwin') { + if (AboutWindowProps.arch === 'arm64') { + platform = ` (${window.i18n('icu:appleSilicon')})`; + } else { + platform = ' (Intel)'; + } +} + +const environmentText = `${AboutWindowProps.environmentText}${platform}`; ReactDOM.render( AboutWindow.executeMenuRole('close')} - environment={AboutWindow.environmentText} - executeMenuRole={AboutWindow.executeMenuRole} - hasCustomTitleBar={AboutWindow.hasCustomTitleBar} - i18n={AboutWindow.i18n} - version={AboutWindow.version} + closeAbout={() => window.SignalContext.executeMenuRole('close')} + environment={environmentText} + executeMenuRole={window.SignalContext.executeMenuRole} + hasCustomTitleBar={window.SignalContext.OS.hasCustomTitleBar()} + i18n={i18n} + version={window.SignalContext.getVersion()} />, document.getElementById('app') ); diff --git a/ts/windows/about/preload.ts b/ts/windows/about/preload.ts index c9ee6d6263..5a9244ad6b 100644 --- a/ts/windows/about/preload.ts +++ b/ts/windows/about/preload.ts @@ -1,22 +1,10 @@ // Copyright 2018 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { MenuItemConstructorOptions } from 'electron'; -import { contextBridge, ipcRenderer } from 'electron'; -import { activeWindowService } from '../../context/activeWindowService'; +import { contextBridge } from 'electron'; +import { MinimalSignalContext } from '../minimalContext'; import { config } from '../../context/config'; -import { createNativeThemeListener } from '../../context/createNativeThemeListener'; -import { createSetting } from '../../util/preload'; import { environment } from '../../context/environment'; -import { getClassName } from '../../OS'; -import { i18n } from '../../context/i18n'; -import { waitForSettingsChange } from '../../context/waitForSettingsChange'; - -async function executeMenuRole( - role: MenuItemConstructorOptions['role'] -): Promise { - await ipcRenderer.invoke('executeMenuRole', role); -} const environments: Array = [environment]; @@ -24,40 +12,12 @@ if (config.appInstance) { environments.push(String(config.appInstance)); } -let platform = ''; -if (process.platform === 'darwin') { - if (process.arch === 'arm64') { - platform = ` (${i18n('icu:appleSilicon')})`; - } else { - platform = ' (Intel)'; - } -} - -const environmentText = `${environments.join(' - ')}${platform}`; -const hasCustomTitleBar = ipcRenderer.sendSync('getHasCustomTitleBar'); - const Signal = { - AboutWindow: { - environmentText, - executeMenuRole, - hasCustomTitleBar, - i18n, - version: String(config.version), + AboutWindowProps: { + arch: process.arch, + environmentText: environments.join(' - '), + platform: process.platform, }, }; contextBridge.exposeInMainWorld('Signal', Signal); - -// TODO DESKTOP-5054 -const SignalContext = { - activeWindowService, - OS: { - getClassName, - hasCustomTitleBar: () => hasCustomTitleBar, - }, - Settings: { - themeSetting: createSetting('themeSetting', { setter: false }), - waitForChange: waitForSettingsChange, - }, - nativeThemeListener: createNativeThemeListener(ipcRenderer, window), -}; -contextBridge.exposeInMainWorld('SignalContext', SignalContext); +contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext); diff --git a/ts/windows/attachments.ts b/ts/windows/attachments.ts index 16a21c9011..c5e7cb15d6 100644 --- a/ts/windows/attachments.ts +++ b/ts/windows/attachments.ts @@ -12,7 +12,7 @@ import * as Bytes from '../Bytes'; import { isPathInside } from '../util/isPathInside'; import { writeWindowsZoneIdentifier } from '../util/windowsZoneIdentifier'; -import { isWindows } from '../OS'; +import OS from '../util/os/osMain'; export * from '../../app/attachments'; @@ -229,7 +229,7 @@ async function writeWithAttributes( const attrValue = `${type};${timestamp};${appName};${guid}`; await xattr.set(target, 'com.apple.quarantine', attrValue); - } else if (isWindows()) { + } else if (OS.isWindows()) { // This operation may fail (see the function's comments), which is not a show-stopper. try { await writeWindowsZoneIdentifier(target); diff --git a/ts/windows/context.ts b/ts/windows/context.ts index 82587506f7..9e963764bc 100644 --- a/ts/windows/context.ts +++ b/ts/windows/context.ts @@ -8,7 +8,6 @@ import type { MenuOptionsType, MenuActionType } from '../types/menu'; import type { IPCEventsValuesType } from '../util/createIPCEvents'; import type { LocalizerType } from '../types/Util'; 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'; @@ -18,29 +17,10 @@ import { Crypto } from '../context/Crypto'; import { Timers } from '../context/Timers'; import type { ActiveWindowServiceType } from '../services/ActiveWindowService'; -import { config } from '../context/config'; import { i18n } from '../context/i18n'; -import { activeWindowService } from '../context/activeWindowService'; -import { - getEnvironment, - parseEnvironment, - setEnvironment, -} from '../environment'; import { strictAssert } from '../util/assert'; -import { createSetting } from '../util/preload'; import { initialize as initializeLogging } from '../logging/set_up_renderer_logging'; -import { waitForSettingsChange } from '../context/waitForSettingsChange'; -import { createNativeThemeListener } from '../context/createNativeThemeListener'; -import { - isWindows, - isLinux, - isMacOS, - hasCustomTitleBar, - getClassName, -} from '../OS'; - -const localeMessages = ipcRenderer.sendSync('locale-data'); -setEnvironment(parseEnvironment(config.environment)); +import { MinimalSignalContext } from './minimalContext'; strictAssert(Boolean(window.SignalContext), 'context must be defined'); @@ -51,89 +31,53 @@ export type MainWindowStatsType = Readonly<{ isFullScreen: boolean; }>; -export type SignalContextType = { - bytes: Bytes; - crypto: Crypto; - timers: Timers; - nativeThemeListener: NativeThemeType; - setIsCallActive: (isCallActive: boolean) => unknown; - +export type MinimalSignalContextType = { activeWindowService: ActiveWindowServiceType; + config: RendererConfigType; + executeMenuAction: (action: MenuActionType) => Promise; + executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise; + getAppInstance: () => string | undefined; + getEnvironment: () => string; + getI18nLocale: LocalizerType['getLocale']; + getI18nLocaleMessages: LocalizerType['getLocaleMessages']; + getMainWindowStats: () => Promise; + getMenuOptions: () => Promise; + getNodeVersion: () => string; + getPath: (name: 'userData' | 'home') => string; + getVersion: () => string; + nativeThemeListener: NativeThemeType; Settings: { themeSetting: SettingType; waitForChange: () => Promise; }; OS: { + hasCustomTitleBar: () => boolean; + getClassName: () => string; platform: string; - isWindows: typeof isWindows; - isLinux: typeof isLinux; - isMacOS: typeof isMacOS; - hasCustomTitleBar: typeof hasCustomTitleBar; - getClassName: typeof getClassName; + release: string; }; - config: RendererConfigType; - getAppInstance: () => string | undefined; - getEnvironment: () => string; - getNodeVersion: () => string; - getVersion: () => string; - getPath: (name: 'userData' | 'home') => string; - i18n: LocalizerType; - localeMessages: LocaleMessagesType; - log: LoggerType; - renderWindow?: () => void; - executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise; - getMainWindowStats: () => Promise; - getMenuOptions: () => Promise; - executeMenuAction: (action: MenuActionType) => Promise; }; +export type SignalContextType = { + bytes: Bytes; + crypto: Crypto; + i18n: LocalizerType; + log: LoggerType; + renderWindow?: () => void; + setIsCallActive: (isCallActive: boolean) => unknown; + timers: Timers; +} & MinimalSignalContextType; + export const SignalContext: SignalContextType = { - activeWindowService, - Settings: { - themeSetting: createSetting('themeSetting', { setter: false }), - waitForChange: waitForSettingsChange, - }, - OS: { - platform: process.platform, - isWindows, - isLinux, - isMacOS, - hasCustomTitleBar, - getClassName, - }, + ...MinimalSignalContext, bytes: new Bytes(), - config, crypto: new Crypto(), - getAppInstance: (): string | undefined => - config.appInstance ? String(config.appInstance) : undefined, - getEnvironment, - getNodeVersion: (): string => String(config.nodeVersion), - getVersion: (): string => String(config.version), - getPath: (name: 'userData' | 'home'): string => { - return String(config[`${name}Path`]); - }, i18n, - localeMessages, log: window.SignalContext.log, - nativeThemeListener: createNativeThemeListener(ipcRenderer, window), setIsCallActive(isCallActive: boolean): void { ipcRenderer.send('set-is-call-active', isCallActive); }, timers: new Timers(), - async executeMenuRole( - role: MenuItemConstructorOptions['role'] - ): Promise { - await ipcRenderer.invoke('executeMenuRole', role); - }, - async getMainWindowStats(): Promise { - return ipcRenderer.invoke('getMainWindowStats'); - }, - async getMenuOptions(): Promise { - return ipcRenderer.invoke('getMenuOptions'); - }, - async executeMenuAction(action: MenuActionType): Promise { - return ipcRenderer.invoke('executeMenuAction', action); - }, }; window.SignalContext = SignalContext; diff --git a/ts/windows/debuglog/app.tsx b/ts/windows/debuglog/app.tsx new file mode 100644 index 0000000000..82126b8d78 --- /dev/null +++ b/ts/windows/debuglog/app.tsx @@ -0,0 +1,25 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; +import { render } from 'react-dom'; +import { DebugLogWindow } from '../../components/DebugLogWindow'; +import { i18n } from '../sandboxedInit'; +import { strictAssert } from '../../util/assert'; + +const { DebugLogWindowProps } = window.Signal; + +strictAssert(DebugLogWindowProps, 'window values not provided'); + +render( + window.SignalContext.executeMenuRole('close')} + downloadLog={DebugLogWindowProps.downloadLog} + i18n={i18n} + fetchLogs={DebugLogWindowProps.fetchLogs} + uploadLogs={DebugLogWindowProps.uploadLogs} + />, + document.getElementById('app') +); diff --git a/ts/windows/debuglog/preload.ts b/ts/windows/debuglog/preload.ts index 6749479c4d..e81e0d61fd 100644 --- a/ts/windows/debuglog/preload.ts +++ b/ts/windows/debuglog/preload.ts @@ -1,49 +1,32 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; -import ReactDOM from 'react-dom'; import { contextBridge, ipcRenderer } from 'electron'; +import { MinimalSignalContext } from '../minimalContext'; -import { SignalContext } from '../context'; -import { DebugLogWindow } from '../../components/DebugLogWindow'; -import * as debugLog from '../../logging/debuglogs'; -import { upload } from '../../logging/uploadDebugLog'; -import * as logger from '../../logging/log'; +function downloadLog(logText: string) { + ipcRenderer.send('show-debug-log-save-dialog', logText); +} -contextBridge.exposeInMainWorld('SignalContext', { - ...SignalContext, - renderWindow: () => { - const environmentText: Array = [SignalContext.getEnvironment()]; +async function fetchLogs() { + const data = await ipcRenderer.invoke('fetch-log'); + return ipcRenderer.invoke( + 'DebugLogs.getLogs', + data, + window.navigator.userAgent + ); +} - const appInstance = SignalContext.getAppInstance(); - if (appInstance) { - environmentText.push(appInstance); - } +function uploadLogs(logs: string) { + return ipcRenderer.invoke('DebugLogs.upload', logs); +} - ReactDOM.render( - React.createElement(DebugLogWindow, { - hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(), - executeMenuRole: SignalContext.executeMenuRole, - closeWindow: () => SignalContext.executeMenuRole('close'), - downloadLog: (logText: string) => - ipcRenderer.send('show-debug-log-save-dialog', logText), - i18n: SignalContext.i18n, - fetchLogs() { - return debugLog.fetch( - SignalContext.getNodeVersion(), - SignalContext.getVersion() - ); - }, - uploadLogs(logs: string) { - return upload({ - content: logs, - appVersion: SignalContext.getVersion(), - logger, - }); - }, - }), - document.getElementById('app') - ); +const Signal = { + DebugLogWindowProps: { + downloadLog, + fetchLogs, + uploadLogs, }, -}); +}; +contextBridge.exposeInMainWorld('Signal', Signal); +contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext); diff --git a/ts/windows/loading/preload.ts b/ts/windows/loading/preload.ts index af191ac214..7c10632803 100644 --- a/ts/windows/loading/preload.ts +++ b/ts/windows/loading/preload.ts @@ -2,7 +2,10 @@ // SPDX-License-Identifier: AGPL-3.0-only import { contextBridge } from 'electron'; +import { config } from '../../context/config'; +import { localeMessages } from '../../context/localeMessages'; -import { SignalContext } from '../context'; - -contextBridge.exposeInMainWorld('SignalContext', SignalContext); +contextBridge.exposeInMainWorld('SignalContext', { + getI18nLocale: () => config.resolvedTranslationsLocale, + getI18nLocaleMessages: () => localeMessages, +}); diff --git a/ts/windows/loading/start.ts b/ts/windows/loading/start.ts index 14a681d658..87803a7b71 100644 --- a/ts/windows/loading/start.ts +++ b/ts/windows/loading/start.ts @@ -1,7 +1,14 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { setupI18n } from '../../util/setupI18n'; + +window.i18n = setupI18n( + window.SignalContext.getI18nLocale(), + window.SignalContext.getI18nLocaleMessages() +); + const message = document.getElementById('message'); if (message) { - message.innerHTML = window.SignalContext.i18n('icu:optimizingApplication'); + message.innerHTML = window.i18n('icu:optimizingApplication'); } diff --git a/ts/windows/minimalContext.ts b/ts/windows/minimalContext.ts new file mode 100644 index 0000000000..ea0dd00fa3 --- /dev/null +++ b/ts/windows/minimalContext.ts @@ -0,0 +1,56 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { MenuItemConstructorOptions } from 'electron'; +import { ipcRenderer } from 'electron'; + +import type { MenuOptionsType, MenuActionType } from '../types/menu'; +import type { MainWindowStatsType, MinimalSignalContextType } from './context'; +import { activeWindowService } from '../context/activeWindowService'; +import { config } from '../context/config'; +import { createNativeThemeListener } from '../context/createNativeThemeListener'; +import { createSetting } from '../util/preload'; +import { environment } from '../context/environment'; +import { localeMessages } from '../context/localeMessages'; +import { waitForSettingsChange } from '../context/waitForSettingsChange'; + +const hasCustomTitleBar = ipcRenderer.sendSync('OS.getHasCustomTitleBar'); +export const MinimalSignalContext: MinimalSignalContextType = { + activeWindowService, + config, + async executeMenuAction(action: MenuActionType): Promise { + return ipcRenderer.invoke('executeMenuAction', action); + }, + async executeMenuRole( + role: MenuItemConstructorOptions['role'] + ): Promise { + await ipcRenderer.invoke('executeMenuRole', role); + }, + getAppInstance: (): string | undefined => + config.appInstance ? String(config.appInstance) : undefined, + getEnvironment: () => environment, + getNodeVersion: (): string => String(config.nodeVersion), + getPath: (name: 'userData' | 'home'): string => { + return String(config[`${name}Path`]); + }, + getVersion: (): string => String(config.version), + async getMainWindowStats(): Promise { + return ipcRenderer.invoke('getMainWindowStats'); + }, + async getMenuOptions(): Promise { + return ipcRenderer.invoke('getMenuOptions'); + }, + getI18nLocale: () => config.resolvedTranslationsLocale, + getI18nLocaleMessages: () => localeMessages, + nativeThemeListener: createNativeThemeListener(ipcRenderer, window), + OS: { + getClassName: () => ipcRenderer.sendSync('OS.getClassName'), + hasCustomTitleBar: () => hasCustomTitleBar, + platform: process.platform, + release: config.osRelease, + }, + Settings: { + themeSetting: createSetting('themeSetting', { setter: false }), + waitForChange: waitForSettingsChange, + }, +}; diff --git a/ts/windows/permissions/app.tsx b/ts/windows/permissions/app.tsx new file mode 100644 index 0000000000..d5a2d235d4 --- /dev/null +++ b/ts/windows/permissions/app.tsx @@ -0,0 +1,36 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { PermissionsPopup } from '../../components/PermissionsPopup'; +import { i18n } from '../sandboxedInit'; +import { strictAssert } from '../../util/assert'; + +const { PermissionsWindowProps } = window.Signal; + +strictAssert(PermissionsWindowProps, 'window values not provided'); + +const { forCalling, forCamera } = PermissionsWindowProps; + +let message; +if (forCalling) { + if (forCamera) { + message = i18n('icu:videoCallingPermissionNeeded'); + } else { + message = i18n('icu:audioCallingPermissionNeeded'); + } +} else { + message = i18n('icu:audioPermissionNeeded'); +} + +ReactDOM.render( + , + document.getElementById('app') +); diff --git a/ts/windows/permissions/preload.ts b/ts/windows/permissions/preload.ts index 6912d83613..b258f3f0bf 100644 --- a/ts/windows/permissions/preload.ts +++ b/ts/windows/permissions/preload.ts @@ -1,14 +1,10 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; -import ReactDOM from 'react-dom'; import { contextBridge } from 'electron'; - -import { SignalContext } from '../context'; - +import { MinimalSignalContext } from '../minimalContext'; import { createSetting } from '../../util/preload'; -import { PermissionsPopup } from '../../components/PermissionsPopup'; +import { drop } from '../../util/drop'; const mediaCameraPermissions = createSetting('mediaCameraPermissions', { getter: false, @@ -17,48 +13,28 @@ const mediaPermissions = createSetting('mediaPermissions', { getter: false, }); -contextBridge.exposeInMainWorld( - 'nativeThemeListener', - window.SignalContext.nativeThemeListener -); +const params = new URLSearchParams(document.location.search); +const forCalling = params.get('forCalling') === 'true'; +const forCamera = params.get('forCamera') === 'true'; -contextBridge.exposeInMainWorld('SignalContext', { - ...SignalContext, - renderWindow: () => { - const params = new URLSearchParams(document.location.search); - const forCalling = params.get('forCalling') === 'true'; - const forCamera = params.get('forCamera') === 'true'; +function onClose() { + drop(MinimalSignalContext.executeMenuRole('close')); +} - let message; - if (forCalling) { - if (forCamera) { - message = SignalContext.i18n('icu:videoCallingPermissionNeeded'); +const Signal = { + PermissionsWindowProps: { + forCalling, + forCamera, + onAccept: () => { + if (!forCamera) { + drop(mediaPermissions.setValue(true)); } else { - message = SignalContext.i18n('icu:audioCallingPermissionNeeded'); + drop(mediaCameraPermissions.setValue(true)); } - } else { - message = SignalContext.i18n('icu:audioPermissionNeeded'); - } - - function onClose() { - void SignalContext.executeMenuRole('close'); - } - - ReactDOM.render( - React.createElement(PermissionsPopup, { - i18n: SignalContext.i18n, - message, - onAccept: () => { - if (!forCamera) { - void mediaPermissions.setValue(true); - } else { - void mediaCameraPermissions.setValue(true); - } - onClose(); - }, - onClose, - }), - document.getElementById('app') - ); + onClose(); + }, + onClose, }, -}); +}; +contextBridge.exposeInMainWorld('Signal', Signal); +contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext); diff --git a/ts/windows/sandboxedInit.ts b/ts/windows/sandboxedInit.ts new file mode 100644 index 0000000000..a93ad90cdf --- /dev/null +++ b/ts/windows/sandboxedInit.ts @@ -0,0 +1,15 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import './applyTheme'; +import { setupI18n } from '../util/setupI18n'; + +document.body.classList.add(window.SignalContext.OS.getClassName()); +if (window.SignalContext.OS.hasCustomTitleBar()) { + document.body.classList.add('os-has-custom-titlebar'); +} + +export const i18n = setupI18n( + window.SignalContext.getI18nLocale(), + window.SignalContext.getI18nLocaleMessages() +); diff --git a/ts/windows/screenShare/app.tsx b/ts/windows/screenShare/app.tsx new file mode 100644 index 0000000000..0dc140119a --- /dev/null +++ b/ts/windows/screenShare/app.tsx @@ -0,0 +1,24 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { CallingScreenSharingController } from '../../components/CallingScreenSharingController'; +import { i18n } from '../sandboxedInit'; +import { strictAssert } from '../../util/assert'; + +const { ScreenShareWindowProps } = window.Signal; + +strictAssert(ScreenShareWindowProps, 'window values not provided'); + +ReactDOM.render( + window.SignalContext.executeMenuRole('close')} + onStopSharing={ScreenShareWindowProps.onStopSharing} + presentedSourceName={ScreenShareWindowProps.presentedSourceName} + />, + + document.getElementById('app') +); diff --git a/ts/windows/screenShare/preload.ts b/ts/windows/screenShare/preload.ts index 2b8e22d8a9..bc2f92ed72 100644 --- a/ts/windows/screenShare/preload.ts +++ b/ts/windows/screenShare/preload.ts @@ -1,29 +1,18 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; -import ReactDOM from 'react-dom'; import { contextBridge, ipcRenderer } from 'electron'; +import { MinimalSignalContext } from '../minimalContext'; -import { SignalContext } from '../context'; -import { CallingScreenSharingController } from '../../components/CallingScreenSharingController'; +const params = new URLSearchParams(document.location.search); -contextBridge.exposeInMainWorld('SignalContext', SignalContext); - -function renderScreenSharingController(presentedSourceName: string): void { - ReactDOM.render( - React.createElement(CallingScreenSharingController, { - platform: process.platform, - executeMenuRole: SignalContext.executeMenuRole, - i18n: SignalContext.i18n, - onCloseController: () => SignalContext.executeMenuRole('close'), - onStopSharing: () => ipcRenderer.send('stop-screen-share'), - presentedSourceName, - }), - document.getElementById('app') - ); -} - -ipcRenderer.once('render-screen-sharing-controller', (_, name: string) => { - renderScreenSharingController(name); -}); +const Signal = { + ScreenShareWindowProps: { + onStopSharing: () => { + ipcRenderer.send('stop-screen-share'); + }, + presentedSourceName: params.get('sourceName'), + }, +}; +contextBridge.exposeInMainWorld('Signal', Signal); +contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext); diff --git a/ts/windows/settings/app.tsx b/ts/windows/settings/app.tsx new file mode 100644 index 0000000000..dcffb3c551 --- /dev/null +++ b/ts/windows/settings/app.tsx @@ -0,0 +1,221 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import type { PropsPreloadType } from '../../components/Preferences'; +import { i18n } from '../sandboxedInit'; +import { Preferences } from '../../components/Preferences'; +import { startInteractionMode } from '../../services/InteractionMode'; +import { strictAssert } from '../../util/assert'; + +const { SettingsWindowProps } = window.Signal; + +strictAssert(SettingsWindowProps, 'window values not provided'); + +startInteractionMode(); + +SettingsWindowProps.onRender( + ({ + addCustomColor, + availableCameras, + availableMicrophones, + availableSpeakers, + blockedCount, + closeSettings, + customColors, + defaultConversationColor, + deviceName, + doDeleteAllData, + doneRendering, + editCustomColor, + executeMenuRole, + getConversationsWithCustomColor, + hasAudioNotifications, + hasAutoDownloadUpdate, + hasAutoLaunch, + hasCallNotifications, + hasCallRingtoneNotification, + hasCountMutedConversations, + hasCustomTitleBar, + hasHideMenuBar, + hasIncomingCallNotifications, + hasLinkPreviews, + hasMediaCameraPermissions, + hasMediaPermissions, + hasMinimizeToAndStartInSystemTray, + hasMinimizeToSystemTray, + hasNotificationAttention, + hasNotifications, + hasReadReceipts, + hasRelayCalls, + hasSpellCheck, + hasStoriesDisabled, + hasTextFormatting, + hasTypingIndicators, + initialSpellCheckSetting, + isAudioNotificationsSupported, + isAutoDownloadUpdatesSupported, + isAutoLaunchSupported, + isFormattingFlagEnabled, + isHideMenuBarSupported, + isMinimizeToAndStartInSystemTraySupported, + isNotificationAttentionSupported, + isPhoneNumberSharingSupported, + isSyncSupported, + isSystemTraySupported, + lastSyncTime, + makeSyncRequest, + notificationContent, + onAudioNotificationsChange, + onAutoDownloadUpdateChange, + onAutoLaunchChange, + onCallNotificationsChange, + onCallRingtoneNotificationChange, + onCountMutedConversationsChange, + onHasStoriesDisabledChanged, + onHideMenuBarChange, + onIncomingCallNotificationsChange, + onLastSyncTimeChange, + onMediaCameraPermissionsChange, + onMediaPermissionsChange, + onMinimizeToAndStartInSystemTrayChange, + onMinimizeToSystemTrayChange, + onNotificationAttentionChange, + onNotificationContentChange, + onNotificationsChange, + onRelayCallsChange, + onSelectedCameraChange, + onSelectedMicrophoneChange, + onSelectedSpeakerChange, + onSentMediaQualityChange, + onSpellCheckChange, + onTextFormattingChange, + onThemeChange, + onUniversalExpireTimerChange, + onWhoCanFindMeChange, + onWhoCanSeeMeChange, + onZoomFactorChange, + removeCustomColor, + removeCustomColorOnConversations, + resetAllChatColors, + resetDefaultChatColor, + selectedCamera, + selectedMicrophone, + selectedSpeaker, + sentMediaQualitySetting, + setGlobalDefaultConversationColor, + shouldShowStoriesSettings, + themeSetting, + universalExpireTimer, + whoCanFindMe, + whoCanSeeMe, + zoomFactor, + }: PropsPreloadType) => { + ReactDOM.render( + , + document.getElementById('app') + ); + } +); diff --git a/ts/windows/settings/preload.ts b/ts/windows/settings/preload.ts index b5ea315d3c..fca73240bc 100644 --- a/ts/windows/settings/preload.ts +++ b/ts/windows/settings/preload.ts @@ -1,13 +1,12 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; -import ReactDOM from 'react-dom'; import { contextBridge, ipcRenderer } from 'electron'; +import { MinimalSignalContext } from '../minimalContext'; -import { SignalContext } from '../context'; +import type { PropsPreloadType } from '../../components/Preferences'; +import OS from '../../util/os/osPreload'; import * as Settings from '../../types/Settings'; -import { Preferences } from '../../components/Preferences'; import { SystemTraySetting, parseSystemTraySetting, @@ -16,7 +15,6 @@ import { import { awaitObject } from '../../util/awaitObject'; import { DurationInSeconds } from '../../util/durations'; import { createSetting, createCallback } from '../../util/preload'; -import { startInteractionMode } from '../../services/InteractionMode'; function doneRendering() { ipcRenderer.send('settings-done-rendering'); @@ -128,9 +126,18 @@ function getSystemTraySettingValues(systemTraySetting: SystemTraySetting): { }; } -const renderPreferences = async () => { - startInteractionMode(); +let renderInBrowser = (_props: PropsPreloadType): void => { + throw new Error('render is not defined'); +}; +function attachRenderCallback(f: (value: Value) => Promise) { + return async (value: Value) => { + await f(value); + void renderPreferences(); + }; +} + +async function renderPreferences() { const { blockedCount, deviceName, @@ -222,7 +229,7 @@ const renderPreferences = async () => { const { hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray } = getSystemTraySettingValues(systemTraySetting); - const onUniversalExpireTimerChange = reRender( + const onUniversalExpireTimerChange = attachRenderCallback( settingUniversalExpireTimer.setValue ); @@ -270,13 +277,13 @@ const renderPreferences = async () => { // Actions and other props addCustomColor: ipcAddCustomColor, - closeSettings: () => SignalContext.executeMenuRole('close'), + closeSettings: () => MinimalSignalContext.executeMenuRole('close'), doDeleteAllData: () => ipcRenderer.send('delete-all-data'), doneRendering, editCustomColor: ipcEditCustomColor, getConversationsWithCustomColor: ipcGetConversationsWithCustomColor, initialSpellCheckSetting: - SignalContext.config.appStartInitialSpellcheckSetting, + MinimalSignalContext.config.appStartInitialSpellcheckSetting, makeSyncRequest: ipcMakeSyncRequest, removeCustomColor: ipcRemoveCustomColor, removeCustomColorOnConversations: ipcRemoveCustomColorOnConversations, @@ -286,93 +293,121 @@ const renderPreferences = async () => { shouldShowStoriesSettings, // Limited support features - isAudioNotificationsSupported: Settings.isAudioNotificationSupported(), - isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(), - isAutoLaunchSupported: Settings.isAutoLaunchSupported(), - isHideMenuBarSupported: Settings.isHideMenuBarSupported(), - isNotificationAttentionSupported: Settings.isDrawAttentionSupported(), + isAudioNotificationsSupported: Settings.isAudioNotificationSupported(OS), + isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(OS), + isAutoLaunchSupported: Settings.isAutoLaunchSupported(OS), + isHideMenuBarSupported: Settings.isHideMenuBarSupported(OS), + isNotificationAttentionSupported: Settings.isDrawAttentionSupported(OS), isPhoneNumberSharingSupported, isSyncSupported: !isSyncNotSupported, isSystemTraySupported: Settings.isSystemTraySupported( - SignalContext.getVersion() + OS, + MinimalSignalContext.getVersion() ), isMinimizeToAndStartInSystemTraySupported: Settings.isMinimizeToAndStartInSystemTraySupported( - SignalContext.getVersion() + OS, + MinimalSignalContext.getVersion() ), // Feature flags isFormattingFlagEnabled, // Change handlers - onAudioNotificationsChange: reRender(settingAudioNotification.setValue), - onAutoDownloadUpdateChange: reRender(settingAutoDownloadUpdate.setValue), - onAutoLaunchChange: reRender(settingAutoLaunch.setValue), - onCallNotificationsChange: reRender(settingCallSystemNotification.setValue), - onCallRingtoneNotificationChange: reRender( + onAudioNotificationsChange: attachRenderCallback( + settingAudioNotification.setValue + ), + onAutoDownloadUpdateChange: attachRenderCallback( + settingAutoDownloadUpdate.setValue + ), + onAutoLaunchChange: attachRenderCallback(settingAutoLaunch.setValue), + onCallNotificationsChange: attachRenderCallback( + settingCallSystemNotification.setValue + ), + onCallRingtoneNotificationChange: attachRenderCallback( settingCallRingtoneNotification.setValue ), - onCountMutedConversationsChange: reRender( + onCountMutedConversationsChange: attachRenderCallback( settingCountMutedConversations.setValue ), - onHasStoriesDisabledChanged: reRender(async (value: boolean) => { - await settingHasStoriesDisabled.setValue(value); - if (!value) { - void ipcDeleteAllMyStories(); + onHasStoriesDisabledChanged: attachRenderCallback( + async (value: boolean) => { + await settingHasStoriesDisabled.setValue(value); + if (!value) { + void ipcDeleteAllMyStories(); + } + return value; } - return value; - }), - onHideMenuBarChange: reRender(settingHideMenuBar.setValue), - onIncomingCallNotificationsChange: reRender( + ), + onHideMenuBarChange: attachRenderCallback(settingHideMenuBar.setValue), + onIncomingCallNotificationsChange: attachRenderCallback( settingIncomingCallNotification.setValue ), - onLastSyncTimeChange: reRender(settingLastSyncTime.setValue), - onMediaCameraPermissionsChange: reRender( + onLastSyncTimeChange: attachRenderCallback(settingLastSyncTime.setValue), + onMediaCameraPermissionsChange: attachRenderCallback( settingMediaCameraPermissions.setValue ), - onMinimizeToAndStartInSystemTrayChange: reRender(async (value: boolean) => { - await settingSystemTraySetting.setValue( - value - ? SystemTraySetting.MinimizeToAndStartInSystemTray - : SystemTraySetting.MinimizeToSystemTray - ); - return value; - }), - onMinimizeToSystemTrayChange: reRender(async (value: boolean) => { - await settingSystemTraySetting.setValue( - value - ? SystemTraySetting.MinimizeToSystemTray - : SystemTraySetting.DoNotUseSystemTray - ); - return value; - }), - onMediaPermissionsChange: reRender(settingMediaPermissions.setValue), - onNotificationAttentionChange: reRender( + onMinimizeToAndStartInSystemTrayChange: attachRenderCallback( + async (value: boolean) => { + await settingSystemTraySetting.setValue( + value + ? SystemTraySetting.MinimizeToAndStartInSystemTray + : SystemTraySetting.MinimizeToSystemTray + ); + return value; + } + ), + onMinimizeToSystemTrayChange: attachRenderCallback( + async (value: boolean) => { + await settingSystemTraySetting.setValue( + value + ? SystemTraySetting.MinimizeToSystemTray + : SystemTraySetting.DoNotUseSystemTray + ); + return value; + } + ), + onMediaPermissionsChange: attachRenderCallback( + settingMediaPermissions.setValue + ), + onNotificationAttentionChange: attachRenderCallback( settingNotificationDrawAttention.setValue ), - onNotificationContentChange: reRender(settingNotificationSetting.setValue), - onNotificationsChange: reRender(async (value: boolean) => { + onNotificationContentChange: attachRenderCallback( + settingNotificationSetting.setValue + ), + onNotificationsChange: attachRenderCallback(async (value: boolean) => { await settingNotificationSetting.setValue( value ? DEFAULT_NOTIFICATION_SETTING : 'off' ); return value; }), - onRelayCallsChange: reRender(settingRelayCalls.setValue), - onSelectedCameraChange: reRender(settingVideoInput.setValue), - onSelectedMicrophoneChange: reRender(settingAudioInput.setValue), - onSelectedSpeakerChange: reRender(settingAudioOutput.setValue), - onSentMediaQualityChange: reRender(settingSentMediaQuality.setValue), - onSpellCheckChange: reRender(settingSpellCheck.setValue), - onTextFormattingChange: reRender(settingTextFormatting.setValue), - onThemeChange: reRender(settingTheme.setValue), + onRelayCallsChange: attachRenderCallback(settingRelayCalls.setValue), + onSelectedCameraChange: attachRenderCallback(settingVideoInput.setValue), + onSelectedMicrophoneChange: attachRenderCallback( + settingAudioInput.setValue + ), + onSelectedSpeakerChange: attachRenderCallback(settingAudioOutput.setValue), + onSentMediaQualityChange: attachRenderCallback( + settingSentMediaQuality.setValue + ), + onSpellCheckChange: attachRenderCallback(settingSpellCheck.setValue), + onTextFormattingChange: attachRenderCallback( + settingTextFormatting.setValue + ), + onThemeChange: attachRenderCallback(settingTheme.setValue), onUniversalExpireTimerChange: (newValue: number): Promise => { return onUniversalExpireTimerChange( DurationInSeconds.fromSeconds(newValue) ); }, - onWhoCanFindMeChange: reRender(settingPhoneNumberDiscoverability.setValue), - onWhoCanSeeMeChange: reRender(settingPhoneNumberSharing.setValue), + onWhoCanFindMeChange: attachRenderCallback( + settingPhoneNumberDiscoverability.setValue + ), + onWhoCanSeeMeChange: attachRenderCallback( + settingPhoneNumberSharing.setValue + ), // Zoom factor change doesn't require immediate rerender since it will: // 1. Update the zoom factor in the main window @@ -381,28 +416,22 @@ const renderPreferences = async () => { // rerender. onZoomFactorChange: settingZoomFactor.setValue, - i18n: SignalContext.i18n, - - hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(), - executeMenuRole: SignalContext.executeMenuRole, + hasCustomTitleBar: MinimalSignalContext.OS.hasCustomTitleBar(), + executeMenuRole: MinimalSignalContext.executeMenuRole, }; - function reRender(f: (value: Value) => Promise) { - return async (value: Value) => { - await f(value); - void renderPreferences(); - }; - } - - ReactDOM.render( - React.createElement(Preferences, props), - document.getElementById('app') - ); -}; + renderInBrowser(props); +} ipcRenderer.on('preferences-changed', () => renderPreferences()); -contextBridge.exposeInMainWorld('SignalContext', { - ...SignalContext, - renderWindow: renderPreferences, -}); +const Signal = { + SettingsWindowProps: { + onRender: (renderer: (_props: PropsPreloadType) => void) => { + renderInBrowser = renderer; + void renderPreferences(); + }, + }, +}; +contextBridge.exposeInMainWorld('Signal', Signal); +contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext); diff --git a/yarn.lock b/yarn.lock index 841bf2cfa3..7ac3a67943 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6182,6 +6182,14 @@ buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" +buffer@6.0.3, buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^4.3.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" @@ -6198,14 +6206,6 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - bufferutil@^4.0.1: version "4.0.7" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" @@ -18385,7 +18385,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid-browser@^3.1.0: +uuid-browser@3.1.0, uuid-browser@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid-browser/-/uuid-browser-3.1.0.tgz#0f05a40aef74f9e5951e20efbf44b11871e56410" integrity sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA=