Enables sandbox for all windows except main

This commit is contained in:
Josh Perez 2023-04-20 17:23:19 -04:00 committed by GitHub
parent abb839c24b
commit e211837bcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1178 additions and 612 deletions

View File

@ -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/**

3
.gitignore vendored
View File

@ -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

View File

@ -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/**

View File

@ -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

View File

@ -29,10 +29,6 @@
</head>
<body>
<div id="app"></div>
<script type="application/javascript" src="ts/windows/init.js"></script>
<script
type="application/javascript"
src="about.browser.bundle.js"
></script>
<script type="module" src="bundles/about/app.js"></script>
</body>
</html>

View File

@ -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);

View File

@ -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<ReadonlyArray<string>> {
const crashDumpsPath = await realpath(app.getPath('crashDumps'));

View File

@ -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<string>,
@ -403,9 +409,9 @@ async function prepareFileUrl(
async function prepareUrl(
url: URL,
{ forCalling, forCamera }: PrepareUrlOptions = {}
{ forCalling, forCamera, sourceName }: PrepareUrlOptions = {}
): Promise<string> {
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<string>('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');

View File

@ -29,10 +29,6 @@
</head>
<body>
<div id="app"></div>
<script
type="application/javascript"
src="ts/windows/applyTheme.js"
></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
<script type="module" src="bundles/debuglog/app.js"></script>
</body>
</html>

View File

@ -33,6 +33,6 @@
</div>
<div id="message"></div>
</div>
<script type="text/javascript" src="ts/windows/loading/start.js"></script>
<script type="module" src="bundles/loading/start.js"></script>
</body>
</html>

View File

@ -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/**",

Binary file not shown.

View File

@ -24,10 +24,6 @@
</head>
<body>
<div id="app"></div>
<script
type="application/javascript"
src="ts/windows/applyTheme.js"
></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
<script type="module" src="bundles/permissions/app.js"></script>
</body>
</html>

View File

@ -24,5 +24,6 @@
</head>
<body>
<div id="app"></div>
<script type="module" src="bundles/screenShare/app.js"></script>
</body>
</html>

View File

@ -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'),
});

View File

@ -29,10 +29,6 @@
</head>
<body>
<div id="app"></div>
<script
type="application/javascript"
src="ts/windows/applyTheme.js"
></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
<script type="module" src="bundles/settings/app.js"></script>
</body>
</html>

View File

@ -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';
};

View File

@ -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',

View File

@ -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,
},

View File

@ -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<PropsType, 'i18n'>;
enum Page {
// Accessible through left nav
General = 'General',

View File

@ -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 };

View File

@ -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');

View File

@ -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 {

View File

@ -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<FetchLogIpcData, 'logEntries'>,
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<string> {
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');

View File

@ -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';

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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',

View File

@ -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,

View File

@ -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: () => (
<ModalContainer className="module-calling__modal-container">

View File

@ -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<typeof InstallScreen>;
@ -258,7 +258,7 @@ export function SmartInstallScreen(): ReactElement {
updates,
currentVersion: window.getVersion(),
startUpdate,
OS: getOSName(),
OS: OS.getName(),
},
};
break;

View File

@ -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 (
<UnsupportedOSDialog
{...ownProps}
i18n={i18n}
expirationTimestamp={expirationTimestamp}
OS={OS}
OS={osName}
/>
);
}

View File

@ -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,
};
};

View File

@ -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'));
});
});
});

View File

@ -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;
}

View File

@ -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),

View File

@ -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'));

View File

@ -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;
};

View File

@ -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;

View File

@ -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"
},

View File

@ -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

9
ts/util/os/osMain.ts Normal file
View File

@ -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;

9
ts/util/os/osPreload.ts Normal file
View File

@ -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;

68
ts/util/os/shared.ts Normal file
View File

@ -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,
};
}

View File

@ -172,6 +172,7 @@ export function setupI18n(
return legacyMessages[key] != null;
};
getMessage.getLocale = () => locale;
getMessage.getLocaleMessages = () => messages;
getMessage.getLocaleDirection = () => {
return window.getResolvedMessagesLocaleDirection();
};

70
ts/util/throttle.ts Normal file
View File

@ -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<any> | 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);
}
};
}

View File

@ -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}`;

View File

@ -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<void> {
if (!isWindows()) {
if (!OS.isWindows()) {
throw new Error('writeWindowsZoneIdentifier should only run on Windows');
}

44
ts/window.d.ts vendored
View File

@ -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<void>;
hasCustomTitleBar: boolean;
i18n: LocalizerType;
version: string;
platform: string;
};
type DebugLogWindowPropsType = {
downloadLog: (text: string) => unknown;
fetchLogs: () => Promise<string>;
uploadLogs: (text: string) => Promise<string>;
};
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<void>;
@ -127,6 +152,7 @@ export type SignalCoreType = {
lightSessionResetQueue?: PQueue;
storage: typeof StorageService;
};
SettingsWindowProps?: SettingsWindowPropsType;
Migrations: ReturnType<typeof initializeMigrations>;
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: {

View File

@ -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(
<About
closeAbout={() => 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')
);

View File

@ -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<void> {
await ipcRenderer.invoke('executeMenuRole', role);
}
const environments: Array<string> = [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);

View File

@ -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);

View File

@ -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<void>;
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
getAppInstance: () => string | undefined;
getEnvironment: () => string;
getI18nLocale: LocalizerType['getLocale'];
getI18nLocaleMessages: LocalizerType['getLocaleMessages'];
getMainWindowStats: () => Promise<MainWindowStatsType>;
getMenuOptions: () => Promise<MenuOptionsType>;
getNodeVersion: () => string;
getPath: (name: 'userData' | 'home') => string;
getVersion: () => string;
nativeThemeListener: NativeThemeType;
Settings: {
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
waitForChange: () => Promise<void>;
};
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<void>;
getMainWindowStats: () => Promise<MainWindowStatsType>;
getMenuOptions: () => Promise<MenuOptionsType>;
executeMenuAction: (action: MenuActionType) => Promise<void>;
};
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<void> {
await ipcRenderer.invoke('executeMenuRole', role);
},
async getMainWindowStats(): Promise<MainWindowStatsType> {
return ipcRenderer.invoke('getMainWindowStats');
},
async getMenuOptions(): Promise<MenuOptionsType> {
return ipcRenderer.invoke('getMenuOptions');
},
async executeMenuAction(action: MenuActionType): Promise<void> {
return ipcRenderer.invoke('executeMenuAction', action);
},
};
window.SignalContext = SignalContext;

View File

@ -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(
<DebugLogWindow
hasCustomTitleBar={window.SignalContext.OS.hasCustomTitleBar()}
executeMenuRole={window.SignalContext.executeMenuRole}
closeWindow={() => window.SignalContext.executeMenuRole('close')}
downloadLog={DebugLogWindowProps.downloadLog}
i18n={i18n}
fetchLogs={DebugLogWindowProps.fetchLogs}
uploadLogs={DebugLogWindowProps.uploadLogs}
/>,
document.getElementById('app')
);

View File

@ -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<string> = [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);

View File

@ -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,
});

View File

@ -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');
}

View File

@ -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<void> {
return ipcRenderer.invoke('executeMenuAction', action);
},
async executeMenuRole(
role: MenuItemConstructorOptions['role']
): Promise<void> {
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<MainWindowStatsType> {
return ipcRenderer.invoke('getMainWindowStats');
},
async getMenuOptions(): Promise<MenuOptionsType> {
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,
},
};

View File

@ -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(
<PermissionsPopup
i18n={i18n}
message={message}
onAccept={PermissionsWindowProps.onAccept}
onClose={PermissionsWindowProps.onClose}
/>,
document.getElementById('app')
);

View File

@ -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);

View File

@ -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()
);

View File

@ -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(
<CallingScreenSharingController
i18n={i18n}
onCloseController={() => window.SignalContext.executeMenuRole('close')}
onStopSharing={ScreenShareWindowProps.onStopSharing}
presentedSourceName={ScreenShareWindowProps.presentedSourceName}
/>,
document.getElementById('app')
);

View File

@ -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);

221
ts/windows/settings/app.tsx Normal file
View File

@ -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(
<Preferences
addCustomColor={addCustomColor}
availableCameras={availableCameras}
availableMicrophones={availableMicrophones}
availableSpeakers={availableSpeakers}
blockedCount={blockedCount}
closeSettings={closeSettings}
customColors={customColors}
defaultConversationColor={defaultConversationColor}
deviceName={deviceName}
doDeleteAllData={doDeleteAllData}
doneRendering={doneRendering}
editCustomColor={editCustomColor}
executeMenuRole={executeMenuRole}
getConversationsWithCustomColor={getConversationsWithCustomColor}
hasAudioNotifications={hasAudioNotifications}
hasAutoDownloadUpdate={hasAutoDownloadUpdate}
hasAutoLaunch={hasAutoLaunch}
hasCallNotifications={hasCallNotifications}
hasCallRingtoneNotification={hasCallRingtoneNotification}
hasCountMutedConversations={hasCountMutedConversations}
hasCustomTitleBar={hasCustomTitleBar}
hasHideMenuBar={hasHideMenuBar}
hasIncomingCallNotifications={hasIncomingCallNotifications}
hasLinkPreviews={hasLinkPreviews}
hasMediaCameraPermissions={hasMediaCameraPermissions}
hasMediaPermissions={hasMediaPermissions}
hasMinimizeToAndStartInSystemTray={hasMinimizeToAndStartInSystemTray}
hasMinimizeToSystemTray={hasMinimizeToSystemTray}
hasNotificationAttention={hasNotificationAttention}
hasNotifications={hasNotifications}
hasReadReceipts={hasReadReceipts}
hasRelayCalls={hasRelayCalls}
hasSpellCheck={hasSpellCheck}
hasStoriesDisabled={hasStoriesDisabled}
hasTextFormatting={hasTextFormatting}
hasTypingIndicators={hasTypingIndicators}
i18n={i18n}
initialSpellCheckSetting={initialSpellCheckSetting}
isAudioNotificationsSupported={isAudioNotificationsSupported}
isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported}
isAutoLaunchSupported={isAutoLaunchSupported}
isFormattingFlagEnabled={isFormattingFlagEnabled}
isHideMenuBarSupported={isHideMenuBarSupported}
isMinimizeToAndStartInSystemTraySupported={
isMinimizeToAndStartInSystemTraySupported
}
isNotificationAttentionSupported={isNotificationAttentionSupported}
isPhoneNumberSharingSupported={isPhoneNumberSharingSupported}
isSyncSupported={isSyncSupported}
isSystemTraySupported={isSystemTraySupported}
lastSyncTime={lastSyncTime}
makeSyncRequest={makeSyncRequest}
notificationContent={notificationContent}
onAudioNotificationsChange={onAudioNotificationsChange}
onAutoDownloadUpdateChange={onAutoDownloadUpdateChange}
onAutoLaunchChange={onAutoLaunchChange}
onCallNotificationsChange={onCallNotificationsChange}
onCallRingtoneNotificationChange={onCallRingtoneNotificationChange}
onCountMutedConversationsChange={onCountMutedConversationsChange}
onHasStoriesDisabledChanged={onHasStoriesDisabledChanged}
onHideMenuBarChange={onHideMenuBarChange}
onIncomingCallNotificationsChange={onIncomingCallNotificationsChange}
onLastSyncTimeChange={onLastSyncTimeChange}
onMediaCameraPermissionsChange={onMediaCameraPermissionsChange}
onMediaPermissionsChange={onMediaPermissionsChange}
onMinimizeToAndStartInSystemTrayChange={
onMinimizeToAndStartInSystemTrayChange
}
onMinimizeToSystemTrayChange={onMinimizeToSystemTrayChange}
onNotificationAttentionChange={onNotificationAttentionChange}
onNotificationContentChange={onNotificationContentChange}
onNotificationsChange={onNotificationsChange}
onRelayCallsChange={onRelayCallsChange}
onSelectedCameraChange={onSelectedCameraChange}
onSelectedMicrophoneChange={onSelectedMicrophoneChange}
onSelectedSpeakerChange={onSelectedSpeakerChange}
onSentMediaQualityChange={onSentMediaQualityChange}
onSpellCheckChange={onSpellCheckChange}
onTextFormattingChange={onTextFormattingChange}
onThemeChange={onThemeChange}
onUniversalExpireTimerChange={onUniversalExpireTimerChange}
onWhoCanFindMeChange={onWhoCanFindMeChange}
onWhoCanSeeMeChange={onWhoCanSeeMeChange}
onZoomFactorChange={onZoomFactorChange}
removeCustomColorOnConversations={removeCustomColorOnConversations}
removeCustomColor={removeCustomColor}
resetAllChatColors={resetAllChatColors}
resetDefaultChatColor={resetDefaultChatColor}
selectedCamera={selectedCamera}
selectedMicrophone={selectedMicrophone}
selectedSpeaker={selectedSpeaker}
sentMediaQualitySetting={sentMediaQualitySetting}
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
shouldShowStoriesSettings={shouldShowStoriesSettings}
themeSetting={themeSetting}
universalExpireTimer={universalExpireTimer}
whoCanFindMe={whoCanFindMe}
whoCanSeeMe={whoCanSeeMe}
zoomFactor={zoomFactor}
/>,
document.getElementById('app')
);
}
);

View File

@ -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<Value>(f: (value: Value) => Promise<Value>) {
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<void> => {
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<Value>(f: (value: Value) => Promise<Value>) {
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);

View File

@ -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=