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 1190 additions and 615 deletions

View file

@ -26,8 +26,7 @@ ts/**/*.js
.eslintrc.js .eslintrc.js
webpack.config.ts webpack.config.ts
preload.bundle.* preload.bundle.*
about.browser.bundle.* bundles/**
about.preload.bundle.*
# Sticker Creator has its own eslint config # Sticker Creator has its own eslint config
sticker-creator/** sticker-creator/**

3
.gitignore vendored
View file

@ -27,8 +27,7 @@ libtextsecure/components.js
stylesheets/*.css stylesheets/*.css
/storybook-static/ /storybook-static/
preload.bundle.* preload.bundle.*
about.browser.bundle.* bundles/
about.preload.bundle.*
ts/sql/mainWorker.bundle.js.LICENSE.txt ts/sql/mainWorker.bundle.js.LICENSE.txt
# React / TypeScript # React / TypeScript

View file

@ -41,8 +41,7 @@ js/WebAudioRecorderMp3.js
stylesheets/_intlTelInput.scss stylesheets/_intlTelInput.scss
preload.bundle.* preload.bundle.*
about.browser.bundle.* bundles/**
about.preload.bundle.*
# Sticker Creator has its own prettier config # Sticker Creator has its own prettier config
sticker-creator/** sticker-creator/**

View file

@ -391,6 +391,30 @@ Signal Desktop makes use of the following open source projects.
License: MIT 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 ## cirbuf
MIT License MIT License
@ -2398,6 +2422,10 @@ Signal Desktop makes use of the following open source projects.
License: MIT License: MIT
## uuid-browser
License: MIT
## websocket ## websocket
Apache License Apache License

View file

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

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as log from '../ts/logging/log'; import * as log from '../ts/logging/log';
import OS from '../ts/util/os/osMain';
import { import {
parseSystemTraySetting, parseSystemTraySetting,
SystemTraySetting, SystemTraySetting,
@ -54,7 +55,7 @@ export class SystemTraySettingCache {
log.info( log.info(
`getSystemTraySetting saw --use-tray-icon flag. Returning ${result}` `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'); const fastValue = this.ephemeralConfig.get('system-tray-setting');
if (fastValue !== undefined) { if (fastValue !== undefined) {
log.info('getSystemTraySetting got fast value', fastValue); 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 { isProduction } from '../ts/util/version';
import { upload as uploadDebugLog } from '../ts/logging/uploadDebugLog'; import { upload as uploadDebugLog } from '../ts/logging/uploadDebugLog';
import { SignalService as Proto } from '../ts/protobuf'; 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>> { async function getPendingDumps(): Promise<ReadonlyArray<string>> {
const crashDumpsPath = await realpath(app.getPath('crashDumps')); 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 { ThemeType } from '../ts/types/Util';
import * as Errors from '../ts/types/errors'; import * as Errors from '../ts/types/errors';
import { resolveCanonicalLocales } from '../ts/util/resolveCanonicalLocales'; 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 { explodePromise } from '../ts/util/explodePromise';
import './startup_config'; import './startup_config';
@ -94,7 +96,7 @@ import type { CreateTemplateOptionsType } from './menu';
import type { MenuActionType } from '../ts/types/menu'; import type { MenuActionType } from '../ts/types/menu';
import { createTemplate } from './menu'; import { createTemplate } from './menu';
import { installFileHandler, installWebHandler } from './protocol_filter'; 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 { isProduction } from '../ts/util/version';
import { import {
isSgnlHref, isSgnlHref,
@ -390,7 +392,11 @@ function getResolvedMessagesLocale(): LocaleType {
return resolvedTranslationsLocale; return resolvedTranslationsLocale;
} }
type PrepareUrlOptions = { forCalling?: boolean; forCamera?: boolean }; type PrepareUrlOptions = {
forCalling?: boolean;
forCamera?: boolean;
sourceName?: string;
};
async function prepareFileUrl( async function prepareFileUrl(
pathSegments: ReadonlyArray<string>, pathSegments: ReadonlyArray<string>,
@ -403,9 +409,9 @@ async function prepareFileUrl(
async function prepareUrl( async function prepareUrl(
url: URL, url: URL,
{ forCalling, forCamera }: PrepareUrlOptions = {} { forCalling, forCamera, sourceName }: PrepareUrlOptions = {}
): Promise<string> { ): Promise<string> {
return setUrlSearchParams(url, { forCalling, forCamera }).href; return setUrlSearchParams(url, { forCalling, forCamera, sourceName }).href;
} }
async function handleUrl(rawTarget: string) { async function handleUrl(rawTarget: string) {
@ -1155,9 +1161,9 @@ async function showScreenShareWindow(sourceName: string) {
...defaultWebPrefs, ...defaultWebPrefs,
nodeIntegration: false, nodeIntegration: false,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
sandbox: false, sandbox: true,
contextIsolation: 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, x: Math.floor(display.size.width / 2) - width / 2,
y: 24, y: 24,
@ -1173,17 +1179,13 @@ async function showScreenShareWindow(sourceName: string) {
screenShareWindow.once('ready-to-show', () => { screenShareWindow.once('ready-to-show', () => {
if (screenShareWindow) { if (screenShareWindow) {
screenShareWindow.showInactive(); screenShareWindow.show();
screenShareWindow.webContents.send(
'render-screen-sharing-controller',
sourceName
);
} }
}); });
await safeLoadURL( await safeLoadURL(
screenShareWindow, screenShareWindow,
await prepareFileUrl([__dirname, '../screenShare.html']) await prepareFileUrl([__dirname, '../screenShare.html'], { sourceName })
); );
} }
@ -1210,9 +1212,9 @@ async function showAbout() {
...defaultWebPrefs, ...defaultWebPrefs,
nodeIntegration: false, nodeIntegration: false,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
sandbox: false, sandbox: true,
contextIsolation: true, contextIsolation: true,
preload: join(__dirname, '../about.preload.bundle.js'), preload: join(__dirname, '../bundles/about/preload.js'),
nativeWindowOpen: true, nativeWindowOpen: true,
}, },
}; };
@ -1261,9 +1263,9 @@ async function showSettingsWindow() {
...defaultWebPrefs, ...defaultWebPrefs,
nodeIntegration: false, nodeIntegration: false,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
sandbox: false, sandbox: true,
contextIsolation: true, contextIsolation: true,
preload: join(__dirname, '../ts/windows/settings/preload.js'), preload: join(__dirname, '../bundles/settings/preload.js'),
nativeWindowOpen: true, nativeWindowOpen: true,
}, },
}; };
@ -1341,9 +1343,9 @@ async function showDebugLogWindow() {
...defaultWebPrefs, ...defaultWebPrefs,
nodeIntegration: false, nodeIntegration: false,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
sandbox: false, sandbox: true,
contextIsolation: true, contextIsolation: true,
preload: join(__dirname, '../ts/windows/debuglog/preload.js'), preload: join(__dirname, '../bundles/debuglog/preload.js'),
nativeWindowOpen: true, nativeWindowOpen: true,
}, },
parent: mainWindow, parent: mainWindow,
@ -1406,9 +1408,9 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
...defaultWebPrefs, ...defaultWebPrefs,
nodeIntegration: false, nodeIntegration: false,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
sandbox: false, sandbox: true,
contextIsolation: true, contextIsolation: true,
preload: join(__dirname, '../ts/windows/permissions/preload.js'), preload: join(__dirname, '../bundles/permissions/preload.js'),
nativeWindowOpen: true, nativeWindowOpen: true,
}, },
parent: mainWindow, parent: mainWindow,
@ -1676,7 +1678,7 @@ app.on('ready', async () => {
// would still show the window. // would still show the window.
// (User can change these settings later) // (User can change these settings later)
if ( if (
isSystemTraySupported(app.getVersion()) && isSystemTraySupported(OS, app.getVersion()) &&
(await systemTraySettingCache.get()) === SystemTraySetting.Uninitialized (await systemTraySettingCache.get()) === SystemTraySetting.Uninitialized
) { ) {
const newValue = SystemTraySetting.MinimizeToSystemTray; const newValue = SystemTraySetting.MinimizeToSystemTray;
@ -1799,9 +1801,9 @@ app.on('ready', async () => {
webPreferences: { webPreferences: {
...defaultWebPrefs, ...defaultWebPrefs,
nodeIntegration: false, nodeIntegration: false,
sandbox: false, sandbox: true,
contextIsolation: true, contextIsolation: true,
preload: join(__dirname, '../ts/windows/loading/preload.js'), preload: join(__dirname, '../bundles/loading/preload.js'),
}, },
icon: windowIcon, icon: windowIcon,
}); });
@ -2278,6 +2280,8 @@ ipc.on('get-config', async event => {
enableCI, enableCI,
nodeVersion: process.versions.node, nodeVersion: process.versions.node,
hostname: os.hostname(), hostname: os.hostname(),
osRelease: os.release(),
osVersion: os.version(),
appInstance: process.env.NODE_APP_INSTANCE || undefined, appInstance: process.env.NODE_APP_INSTANCE || undefined,
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy || undefined, proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy || undefined,
contentProxyUrl: config.get<string>('contentProxyUrl'), contentProxyUrl: config.get<string>('contentProxyUrl'),
@ -2320,11 +2324,39 @@ ipc.on('locale-data', event => {
event.returnValue = getResolvedMessagesLocale().messages; event.returnValue = getResolvedMessagesLocale().messages;
}); });
ipc.on('getHasCustomTitleBar', event => { // TODO DESKTOP-5241
ipc.on('OS.getHasCustomTitleBar', event => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
event.returnValue = OS.hasCustomTitleBar(); 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 => { ipc.on('user-config-key', event => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
event.returnValue = userConfig.get('key'); event.returnValue = userConfig.get('key');

View file

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

View file

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

View file

@ -11,6 +11,9 @@
"email": "support@signal.org" "email": "support@signal.org"
}, },
"browserslist": "last 1 chrome versions", "browserslist": "last 1 chrome versions",
"browser": {
"uuid": "uuid-browser"
},
"main": "app/main.js", "main": "app/main.js",
"scripts": { "scripts": {
"postinstall": "yarn build:acknowledgments && patch-package && yarn electron:install-app-deps", "postinstall": "yarn build:acknowledgments && patch-package && yarn electron:install-app-deps",
@ -56,7 +59,7 @@
"svgo": "svgo --multipass images/**/*.svg", "svgo": "svgo --multipass images/**/*.svg",
"transpile": "run-p check:types build:esbuild", "transpile": "run-p check:types build:esbuild",
"check:types": "tsc --noEmit", "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", "clean-transpile": "yarn run clean-transpile-once && yarn run clean-transpile-once",
"open-coverage": "open coverage/lcov-report/index.html", "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", "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", "blob-util": "2.0.2",
"blueimp-load-image": "5.14.0", "blueimp-load-image": "5.14.0",
"blurhash": "1.1.3", "blurhash": "1.1.3",
"buffer": "6.0.3",
"cirbuf": "1.0.1", "cirbuf": "1.0.1",
"classnames": "2.2.5", "classnames": "2.2.5",
"config": "1.28.1", "config": "1.28.1",
@ -169,6 +173,7 @@
"split2": "4.0.0", "split2": "4.0.0",
"type-fest": "3.5.0", "type-fest": "3.5.0",
"uuid": "3.3.2", "uuid": "3.3.2",
"uuid-browser": "3.1.0",
"websocket": "1.0.34", "websocket": "1.0.34",
"zod": "3.5.1" "zod": "3.5.1"
}, },
@ -424,6 +429,7 @@
"config/default.json", "config/default.json",
"config/${env.SIGNAL_ENV}.json", "config/${env.SIGNAL_ENV}.json",
"config/local-${env.SIGNAL_ENV}.json", "config/local-${env.SIGNAL_ENV}.json",
"bundles/**",
"background.html", "background.html",
"about.html", "about.html",
"screenShare.html", "screenShare.html",
@ -456,8 +462,6 @@
"app/*", "app/*",
"preload.bundle.js", "preload.bundle.js",
"preload_utils.js", "preload_utils.js",
"about.preload.bundle.js",
"about.browser.bundle.js",
"main.js", "main.js",
"images/**", "images/**",
"fonts/**", "fonts/**",

View file

@ -1,19 +1,28 @@
diff --git a/node_modules/@types/backbone/index.d.ts b/node_modules/@types/backbone/index.d.ts diff --git a/node_modules/@types/backbone/index.d.ts b/node_modules/@types/backbone/index.d.ts
index a172230..6f6de9f 100644 index a172230..2c62e92 100644
--- a/node_modules/@types/backbone/index.d.ts --- a/node_modules/@types/backbone/index.d.ts
+++ b/node_modules/@types/backbone/index.d.ts +++ b/node_modules/@types/backbone/index.d.ts
@@ -81,7 +81,7 @@ declare namespace Backbone {
collection?: Backbone.Collection<TModel>;
}
- type CombinedModelConstructorOptions<E, M extends Model<any, any, E> = Model> = ModelConstructorOptions<M> & E;
+ type CombinedModelConstructorOptions<E, M extends Model<any, any, E> = Model<any, any, E>> = ModelConstructorOptions<M> & E;
interface ModelSetOptions extends Silenceable, Validable {
}
@@ -218,14 +218,14 @@ declare namespace Backbone { @@ -218,14 +218,14 @@ declare namespace Backbone {
* E - Extensions to the model constructor options. You can accept additional constructor options * E - Extensions to the model constructor options. You can accept additional constructor options
* by listing them in the E parameter. * by listing them in the E parameter.
*/ */
- class Model<T = any, S = Backbone.ModelSetOptions, E = {}> extends ModelBase implements Events { - class Model<T = any, S = Backbone.ModelSetOptions, E = {}> extends ModelBase implements Events {
+ class Model<T extends Record<string, any> = any, S = Backbone.ModelSetOptions, E = {}> extends ModelBase implements Events { + class Model<T extends Record<string, any> = any, S = Backbone.ModelSetOptions, E = {}> extends ModelBase implements Events {
/** /**
* Do not use, prefer TypeScript's extend functionality. * Do not use, prefer TypeScript's extend functionality.
**/ **/
public static extend(properties: any, classProperties?: any): any; public static extend(properties: any, classProperties?: any): any;
- attributes: any; - attributes: any;
+ attributes: T; + attributes: T;
changed: any[]; changed: any[];

View file

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

View file

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

View file

@ -6,6 +6,7 @@ const path = require('path');
const glob = require('glob'); const glob = require('glob');
const ROOT_DIR = path.join(__dirname, '..'); const ROOT_DIR = path.join(__dirname, '..');
const BUNDLES_DIR = 'bundles';
const watch = process.argv.some(argv => argv === '-w' || argv === '--watch'); const watch = process.argv.some(argv => argv === '-w' || argv === '--watch');
const isProd = process.argv.some(argv => argv === '-prod' || argv === '--prod'); const isProd = process.argv.some(argv => argv === '-prod' || argv === '--prod');
@ -26,6 +27,7 @@ const bundleDefaults = {
'process.env.NODE_ENV': isProd ? '"production"' : '"development"', 'process.env.NODE_ENV': isProd ? '"production"' : '"development"',
}, },
bundle: true, bundle: true,
minify: isProd,
external: [ external: [
// Native libraries // Native libraries
'@signalapp/libsignal-client', '@signalapp/libsignal-client',
@ -61,55 +63,94 @@ const bundleDefaults = {
], ],
}; };
async function main() { const sandboxedPreloadDefaults = {
// App, tests, and scripts ...nodeDefaults,
const app = await esbuild.context({ define: {
...nodeDefaults, 'process.env.NODE_ENV': isProd ? '"production"' : '"development"',
format: 'cjs', },
mainFields: ['browser', 'main'], external: ['electron'],
entryPoints: glob bundle: true,
.sync('{app,ts}/**/*.{ts,tsx}', { minify: isProd,
nodir: true, };
root: ROOT_DIR,
})
.filter(file => !file.endsWith('.d.ts')),
outdir: path.join(ROOT_DIR),
});
// Preload bundle const sandboxedBrowserDefaults = {
const bundle = await esbuild.context({ ...sandboxedPreloadDefaults,
...bundleDefaults, chunkNames: 'chunks/[name]-[hash]',
mainFields: ['browser', 'main'], format: 'esm',
entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'main', 'preload.ts')], outdir: path.join(ROOT_DIR, BUNDLES_DIR),
outfile: path.join(ROOT_DIR, 'preload.bundle.js'), platform: 'browser',
}); splitting: true,
};
async function build({ appConfig, preloadConfig }) {
const app = await esbuild.context(appConfig);
const preload = await esbuild.context(preloadConfig);
if (watch) { if (watch) {
await Promise.all([app.watch(), bundle.watch()]); await Promise.all([app.watch(), preload.watch()]);
} else { } else {
await Promise.all([app.rebuild(), bundle.rebuild()]); await Promise.all([app.rebuild(), preload.rebuild()]);
await app.dispose(); 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); console.error(error.stack);
process.exit(1); 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> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script <script type="module" src="bundles/settings/app.js"></script>
type="application/javascript"
src="ts/windows/applyTheme.js"
></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
</body> </body>
</html> </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 // SPDX-License-Identifier: AGPL-3.0-only
import { makeEnumParser } from '../util/enum'; import { makeEnumParser } from '../util/enum';
import * as OS from '../OS'; import OS from '../util/os/osMain';
export enum AudioDeviceModule { export enum AudioDeviceModule {
Default = 'Default', Default = 'Default',

View file

@ -4,18 +4,18 @@
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import copyText from 'copy-text-to-clipboard'; 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 * as log from '../logging/log';
import { Button, ButtonVariant } from './Button'; import { Button, ButtonVariant } from './Button';
import type { LocalizerType } from '../types/Util';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
import { TitleBarContainer } from './TitleBarContainer';
import { ToastDebugLogError } from './ToastDebugLogError'; import { ToastDebugLogError } from './ToastDebugLogError';
import { ToastLinkCopied } from './ToastLinkCopied'; import { ToastLinkCopied } from './ToastLinkCopied';
import { TitleBarContainer } from './TitleBarContainer';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import { ToastLoadingFullLogs } from './ToastLoadingFullLogs'; import { ToastLoadingFullLogs } from './ToastLoadingFullLogs';
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
import { createSupportUrl } from '../util/createSupportUrl'; import { createSupportUrl } from '../util/createSupportUrl';
import * as Errors from '../types/errors'; import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
import { useEscapeHandling } from '../hooks/useEscapeHandling'; import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useTheme } from '../hooks/useTheme'; import { useTheme } from '../hooks/useTheme';
@ -137,7 +137,7 @@ export function DebugLogWindow({
}; };
const supportURL = createSupportUrl({ const supportURL = createSupportUrl({
locale: i18n.getLocale(), locale: window.SignalContext.getI18nLocale(),
query: { query: {
debugLog: publicLogURL, debugLog: publicLogURL,
}, },

View file

@ -1,11 +1,11 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { AudioDevice } from '@signalapp/ringrtc';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import React, { useEffect, useState, useCallback, useMemo } from 'react'; import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { noop } from 'lodash'; import { noop } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import type { AudioDevice } from '@signalapp/ringrtc';
import uuid from 'uuid'; import uuid from 'uuid';
import type { MediaDeviceSettings } from '../types/Calling'; import type { MediaDeviceSettings } from '../types/Calling';
@ -15,6 +15,19 @@ import type {
ZoomFactorType, ZoomFactorType,
} from '../types/Storage.d'; } from '../types/Storage.d';
import type { ThemeSettingType } from '../types/StorageUIKeys'; 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 { Button, ButtonVariant } from './Button';
import { ChatColorPicker } from './ChatColorPicker'; import { ChatColorPicker } from './ChatColorPicker';
import { Checkbox } from './Checkbox'; import { Checkbox } from './Checkbox';
@ -23,24 +36,12 @@ import {
Variant as CircleCheckboxVariant, Variant as CircleCheckboxVariant,
} from './CircleCheckbox'; } from './CircleCheckbox';
import { ConfirmationDialog } from './ConfirmationDialog'; import { ConfirmationDialog } from './ConfirmationDialog';
import type { ConversationType } from '../state/ducks/conversations';
import type {
ConversationColorType,
CustomColorType,
DefaultConversationColorType,
} from '../types/Colors';
import { DisappearingTimeDialog } from './DisappearingTimeDialog'; import { DisappearingTimeDialog } from './DisappearingTimeDialog';
import type {
LocalizerType,
SentMediaQualityType,
ThemeType,
} from '../types/Util';
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability'; import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode'; import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
import { Select } from './Select'; import { Select } from './Select';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
import { TitleBarContainer } from './TitleBarContainer'; import { TitleBarContainer } from './TitleBarContainer';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import { getCustomColorStyle } from '../util/getCustomColorStyle'; import { getCustomColorStyle } from '../util/getCustomColorStyle';
import { import {
DEFAULT_DURATIONS_IN_SECONDS, DEFAULT_DURATIONS_IN_SECONDS,
@ -179,6 +180,8 @@ type PropsFunctionType = {
export type PropsType = PropsDataType & PropsFunctionType; export type PropsType = PropsDataType & PropsFunctionType;
export type PropsPreloadType = Omit<PropsType, 'i18n'>;
enum Page { enum Page {
// Accessible through left nav // Accessible through left nav
General = 'General', General = 'General',

View file

@ -1,8 +1,8 @@
// Copyright 2023 Signal Messenger, LLC // Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { config } from './config'; import { config } from './config';
import { localeMessages } from './localeMessages';
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
@ -16,7 +16,6 @@ strictAssert(
'locale is not a string' 'locale is not a string'
); );
const localeMessages = ipcRenderer.sendSync('locale-data');
const i18n = setupI18n(resolvedTranslationsLocale, localeMessages); const i18n = setupI18n(resolvedTranslationsLocale, localeMessages);
export { i18n }; 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 // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { z } from 'zod';
import { makeEnumParser } from './util/enum'; import { makeEnumParser } from './util/enum';
// Many places rely on this enum being a string. // Many places rely on this enum being a string.
@ -13,8 +11,6 @@ export enum Environment {
Test = 'test', Test = 'test',
} }
export const environmentSchema = z.nativeEnum(Environment);
let environment: undefined | Environment; let environment: undefined | Environment;
export function getEnvironment(): Environment { export function getEnvironment(): Environment {

View file

@ -2,8 +2,6 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { memoize, sortBy } from 'lodash'; import { memoize, sortBy } from 'lodash';
import os from 'os';
import { ipcRenderer as ipc } from 'electron';
import { reallyJsonStringify } from '../util/reallyJsonStringify'; import { reallyJsonStringify } from '../util/reallyJsonStringify';
import type { FetchLogIpcData, LogEntryType } from './shared'; import type { FetchLogIpcData, LogEntryType } from './shared';
import { import {
@ -42,16 +40,18 @@ const getHeader = (
user, user,
}: Omit<FetchLogIpcData, 'logEntries'>, }: Omit<FetchLogIpcData, 'logEntries'>,
nodeVersion: string, nodeVersion: string,
appVersion: string appVersion: string,
osVersion: string,
userAgent: string
): string => ): string =>
[ [
headerSection('System info', { headerSection('System info', {
Time: Date.now(), Time: Date.now(),
'User agent': window.navigator.userAgent, 'User agent': userAgent,
'Node version': nodeVersion, 'Node version': nodeVersion,
Environment: getEnvironment(), Environment: getEnvironment(),
'App version': appVersion, 'App version': appVersion,
'OS version': os.version(), 'OS version': osVersion,
}), }),
headerSection('User info', user), headerSection('User info', user),
headerSection('Capabilities', capabilities), headerSection('Capabilities', capabilities),
@ -79,17 +79,18 @@ function formatLine(mightBeEntry: unknown): string {
return `${getLevel(entry.level)} ${entry.time} ${entry.msg}`; return `${getLevel(entry.level)} ${entry.time} ${entry.msg}`;
} }
export async function fetch( export function getLog(
data: unknown,
nodeVersion: string, nodeVersion: string,
appVersion: string appVersion: string,
): Promise<string> { osVersion: string,
const data: unknown = await ipc.invoke('fetch-log'); userAgent: string
): string {
let header: string; let header: string;
let body: string; let body: string;
if (isFetchLogIpcData(data)) { if (isFetchLogIpcData(data)) {
const { logEntries } = data; const { logEntries } = data;
header = getHeader(data, nodeVersion, appVersion); header = getHeader(data, nodeVersion, appVersion, osVersion, userAgent);
body = logEntries.map(formatLine).join('\n'); body = logEntries.map(formatLine).join('\n');
} else { } else {
header = headerSectionTitle('Partial logs'); header = headerSectionTitle('Partial logs');

View file

@ -1,7 +1,7 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { noop } from 'lodash'; import noop from 'lodash/noop';
import type { LogFunction } from '../types/Logging'; import type { LogFunction } from '../types/Logging';
import { LogLevel } from '../types/Logging'; import { LogLevel } from '../types/Logging';

View file

@ -1,7 +1,7 @@
// Copyright 2020 Signal Messenger, LLC // Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // 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 // Idle timer - you're active for ACTIVE_TIMEOUT after one of these events
const ACTIVE_TIMEOUT = 15 * 1000; const ACTIVE_TIMEOUT = 15 * 1000;

View file

@ -1,6 +1,7 @@
// Copyright 2015 Signal Messenger, LLC // Copyright 2015 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import os from 'os';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import EventEmitter from 'events'; import EventEmitter from 'events';
import { Sound } from '../util/Sound'; import { Sound } from '../util/Sound';
@ -9,7 +10,7 @@ import {
getAudioNotificationSupport, getAudioNotificationSupport,
shouldHideExpiringMessageBody, shouldHideExpiringMessageBody,
} from '../types/Settings'; } from '../types/Settings';
import * as OS from '../OS'; import OS from '../util/os/osMain';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { makeEnumParser } from '../util/enum'; import { makeEnumParser } from '../util/enum';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
@ -144,7 +145,7 @@ class NotificationService extends EventEmitter {
this.lastNotification?.close(); this.lastNotification?.close();
const audioNotificationSupport = getAudioNotificationSupport(); const audioNotificationSupport = getAudioNotificationSupport(OS);
const notification = new window.Notification(title, { const notification = new window.Notification(title, {
body: OS.isLinux() ? filterNotificationText(message) : message, body: OS.isLinux() ? filterNotificationText(message) : message,
@ -299,7 +300,10 @@ class NotificationService extends EventEmitter {
notificationTitle = senderTitle; notificationTitle = senderTitle;
({ notificationIconUrl } = notificationData); ({ notificationIconUrl } = notificationData);
if (isExpiringMessage && shouldHideExpiringMessageBody()) { if (
isExpiringMessage &&
shouldHideExpiringMessageBody(OS, os.release())
) {
notificationMessage = i18n('icu:newMessage'); notificationMessage = i18n('icu:newMessage');
} else if (userSetting === NotificationSetting.NameOnly) { } else if (userSetting === NotificationSetting.NameOnly) {
if (reaction) { if (reaction) {

View file

@ -8,7 +8,7 @@ import * as Curve from './Curve';
import { start as conversationControllerStart } from './ConversationController'; import { start as conversationControllerStart } from './ConversationController';
import Data from './sql/Client'; import Data from './sql/Client';
import * as Groups from './groups'; import * as Groups from './groups';
import * as OS from './OS'; import OS from './util/os/osMain';
import * as RemoteConfig from './RemoteConfig'; import * as RemoteConfig from './RemoteConfig';
// Components // Components

View file

@ -9,7 +9,7 @@ import type { LocalizerType } from '../../types/Util';
import type { MenuOptionsType } from '../../types/menu'; import type { MenuOptionsType } from '../../types/menu';
import type { NoopActionType } from './noop'; import type { NoopActionType } from './noop';
import type { UUIDStringType } from '../../types/UUID'; import type { UUIDStringType } from '../../types/UUID';
import * as OS from '../../OS'; import OS from '../../util/os/osMain';
import { ThemeType } from '../../types/Util'; import { ThemeType } from '../../types/Util';
// State // State
@ -116,6 +116,7 @@ export function getEmptyState(): UserStateType {
getLocale: intlNotSetup, getLocale: intlNotSetup,
getIntl: intlNotSetup, getIntl: intlNotSetup,
isLegacyFormat: intlNotSetup, isLegacyFormat: intlNotSetup,
getLocaleMessages: intlNotSetup,
getLocaleDirection: intlNotSetup, getLocaleDirection: intlNotSetup,
}), }),
interactionMode: 'mouse', interactionMode: 'mouse',

View file

@ -32,7 +32,7 @@ import type { MainWindowStatsType } from '../windows/context';
import type { MenuOptionsType } from '../types/menu'; import type { MenuOptionsType } from '../types/menu';
import type { StoryDataType } from './ducks/stories'; import type { StoryDataType } from './ducks/stories';
import type { StoryDistributionListDataType } from './ducks/storyDistributionLists'; import type { StoryDistributionListDataType } from './ducks/storyDistributionLists';
import * as OS from '../OS'; import OS from '../util/os/osMain';
import { UUIDKind } from '../types/UUID'; import { UUIDKind } from '../types/UUID';
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis'; import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
import { getInitialState as stickers } from '../types/Stickers'; import { getInitialState as stickers } from '../types/Stickers';
@ -132,7 +132,7 @@ export function getInitialState({
interactionMode: getInteractionMode(), interactionMode: getInteractionMode(),
isMainWindowFullScreen: mainWindowStats.isFullScreen, isMainWindowFullScreen: mainWindowStats.isFullScreen,
isMainWindowMaximized: mainWindowStats.isMaximized, isMainWindowMaximized: mainWindowStats.isMaximized,
localeMessages: window.SignalContext.localeMessages, localeMessages: window.i18n.getLocaleMessages(),
menuOptions, menuOptions,
osName, osName,
ourACI, ourACI,

View file

@ -7,7 +7,7 @@ import type { MenuItemConstructorOptions } from 'electron';
import type { MenuActionType } from '../../types/menu'; import type { MenuActionType } from '../../types/menu';
import { App } from '../../components/App'; 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 { SmartCallManager } from './CallManager';
import { SmartGlobalModalContainer } from './GlobalModalContainer'; import { SmartGlobalModalContainer } from './GlobalModalContainer';
import { SmartLightbox } from './Lightbox'; import { SmartLightbox } from './Lightbox';
@ -47,8 +47,8 @@ const mapStateToProps = (state: StateType) => {
isFullScreen: getIsMainWindowFullScreen(state), isFullScreen: getIsMainWindowFullScreen(state),
menuOptions: getMenuOptions(state), menuOptions: getMenuOptions(state),
hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(), hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(),
OS: getOSName(), OS: OS.getName(),
osClassName: getOSClassName(), osClassName: OS.getClassName(),
hideMenuBar: getHideMenuBar(state), hideMenuBar: getHideMenuBar(state),
renderCallManager: () => ( renderCallManager: () => (
<ModalContainer className="module-calling__modal-container"> <ModalContainer className="module-calling__modal-container">

View file

@ -27,7 +27,7 @@ import { HTTPError } from '../../textsecure/Errors';
import { isRecord } from '../../util/isRecord'; import { isRecord } from '../../util/isRecord';
import * as Errors from '../../types/errors'; import * as Errors from '../../types/errors';
import { normalizeDeviceName } from '../../util/normalizeDeviceName'; import { normalizeDeviceName } from '../../util/normalizeDeviceName';
import { getName as getOSName } from '../../OS'; import OS from '../../util/os/osMain';
type PropsType = ComponentProps<typeof InstallScreen>; type PropsType = ComponentProps<typeof InstallScreen>;
@ -258,7 +258,7 @@ export function SmartInstallScreen(): ReactElement {
updates, updates,
currentVersion: window.getVersion(), currentVersion: window.getVersion(),
startUpdate, startUpdate,
OS: getOSName(), OS: OS.getName(),
}, },
}; };
break; break;

View file

@ -8,7 +8,7 @@ import { UnsupportedOSDialog } from '../../components/UnsupportedOSDialog';
import { getIntl } from '../selectors/user'; import { getIntl } from '../selectors/user';
import { getExpirationTimestamp } from '../selectors/expiration'; import { getExpirationTimestamp } from '../selectors/expiration';
import type { WidthBreakpoint } from '../../components/_util'; import type { WidthBreakpoint } from '../../components/_util';
import { getName as getOSName } from '../../OS'; import OS from '../../util/os/osMain';
export type PropsType = Readonly<{ export type PropsType = Readonly<{
type: 'warning' | 'error'; type: 'warning' | 'error';
@ -18,14 +18,14 @@ export type PropsType = Readonly<{
export function SmartUnsupportedOSDialog(ownProps: PropsType): JSX.Element { export function SmartUnsupportedOSDialog(ownProps: PropsType): JSX.Element {
const i18n = useSelector(getIntl); const i18n = useSelector(getIntl);
const expirationTimestamp = useSelector(getExpirationTimestamp); const expirationTimestamp = useSelector(getExpirationTimestamp);
const OS = getOSName(); const osName = OS.getName();
return ( return (
<UnsupportedOSDialog <UnsupportedOSDialog
{...ownProps} {...ownProps}
i18n={i18n} i18n={i18n}
expirationTimestamp={expirationTimestamp} expirationTimestamp={expirationTimestamp}
OS={OS} OS={osName}
/> />
); );
} }

View file

@ -8,7 +8,7 @@ import type { StateType } from '../reducer';
import { getIntl } from '../selectors/user'; import { getIntl } from '../selectors/user';
import { getExpirationTimestamp } from '../selectors/expiration'; import { getExpirationTimestamp } from '../selectors/expiration';
import type { WidthBreakpoint } from '../../components/_util'; import type { WidthBreakpoint } from '../../components/_util';
import { getName as getOSName } from '../../OS'; import OS from '../../util/os/osMain';
type PropsType = Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>; type PropsType = Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>;
@ -18,7 +18,7 @@ const mapStateToProps = (state: StateType, ownProps: PropsType) => {
i18n: getIntl(state), i18n: getIntl(state),
currentVersion: window.getVersion(), currentVersion: window.getVersion(),
expirationTimestamp: getExpirationTimestamp(state), expirationTimestamp: getExpirationTimestamp(state),
OS: getOSName(), OS: OS.getName(),
...ownProps, ...ownProps,
}; };
}; };

View file

@ -5,6 +5,7 @@ import os from 'os';
import Sinon from 'sinon'; import Sinon from 'sinon';
import { assert } from 'chai'; import { assert } from 'chai';
import { getOSFunctions } from '../../util/os/shared';
import * as Settings from '../../types/Settings'; import * as Settings from '../../types/Settings';
describe('Settings', () => { describe('Settings', () => {
@ -21,8 +22,9 @@ describe('Settings', () => {
describe('getAudioNotificationSupport', () => { describe('getAudioNotificationSupport', () => {
it('returns native support on macOS', () => { it('returns native support on macOS', () => {
sandbox.stub(process, 'platform').value('darwin'); sandbox.stub(process, 'platform').value('darwin');
const OS = getOSFunctions(os.release());
assert.strictEqual( assert.strictEqual(
Settings.getAudioNotificationSupport(), Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.Native Settings.AudioNotificationSupport.Native
); );
}); });
@ -30,8 +32,9 @@ describe('Settings', () => {
it('returns no support on Windows 7', () => { it('returns no support on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0'); sandbox.stub(os, 'release').returns('7.0.0');
const OS = getOSFunctions(os.release());
assert.strictEqual( assert.strictEqual(
Settings.getAudioNotificationSupport(), Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.None Settings.AudioNotificationSupport.None
); );
}); });
@ -39,16 +42,18 @@ describe('Settings', () => {
it('returns native support on Windows 8', () => { it('returns native support on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0'); sandbox.stub(os, 'release').returns('8.0.0');
const OS = getOSFunctions(os.release());
assert.strictEqual( assert.strictEqual(
Settings.getAudioNotificationSupport(), Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.Native Settings.AudioNotificationSupport.Native
); );
}); });
it('returns custom support on Linux', () => { it('returns custom support on Linux', () => {
sandbox.stub(process, 'platform').value('linux'); sandbox.stub(process, 'platform').value('linux');
const OS = getOSFunctions(os.release());
assert.strictEqual( assert.strictEqual(
Settings.getAudioNotificationSupport(), Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.Custom Settings.AudioNotificationSupport.Custom
); );
}); });
@ -57,48 +62,56 @@ describe('Settings', () => {
describe('isAudioNotificationSupported', () => { describe('isAudioNotificationSupported', () => {
it('returns true on macOS', () => { it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin'); 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', () => { it('returns false on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0'); 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', () => { it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0'); 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', () => { it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux'); sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isAudioNotificationSupported()); const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isAudioNotificationSupported(OS));
}); });
}); });
describe('isNotificationGroupingSupported', () => { describe('isNotificationGroupingSupported', () => {
it('returns true on macOS', () => { it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin'); 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', () => { it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0'); 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', () => { it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0'); 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', () => { it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('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', () => { it('returns true on Windows', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0'); 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', () => { it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin'); 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', () => { it('returns false on Linux', () => {
sandbox.stub(process, 'platform').value('linux'); sandbox.stub(process, 'platform').value('linux');
assert.isFalse(Settings.isAutoLaunchSupported()); const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isAutoLaunchSupported(OS));
}); });
}); });
describe('isHideMenuBarSupported', () => { describe('isHideMenuBarSupported', () => {
it('returns false on macOS', () => { it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin'); 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', () => { it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0'); 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', () => { it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0'); 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', () => { it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux'); sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isHideMenuBarSupported()); const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isHideMenuBarSupported(OS));
}); });
}); });
describe('isDrawAttentionSupported', () => { describe('isDrawAttentionSupported', () => {
it('returns false on macOS', () => { it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin'); 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', () => { it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0'); 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', () => { it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0'); 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', () => { it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux'); sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isDrawAttentionSupported()); const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isDrawAttentionSupported(OS));
}); });
}); });
describe('isSystemTraySupported', () => { describe('isSystemTraySupported', () => {
it('returns false on macOS', () => { it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin'); 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', () => { it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0'); 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', () => { it('returns false on Linux production', () => {
sandbox.stub(process, 'platform').value('linux'); 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', () => { it('returns true on Linux beta', () => {
sandbox.stub(process, 'platform').value('linux'); 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; storyMessage.fileAttachment = fileAttachment;
} catch (error) { } catch (error) {
if (error instanceof HTTPError) { if (error instanceof HTTPError) {
throw new MessageError(message, error); throw new MessageError(storyMessage, error);
} else { } else {
throw error; throw error;
} }

View file

@ -3,8 +3,10 @@
import { z } from 'zod'; import { z } from 'zod';
import { Environment } from '../environment';
import { themeSettingSchema } from './StorageUIKeys'; import { themeSettingSchema } from './StorageUIKeys';
import { environmentSchema } from '../environment';
const environmentSchema = z.nativeEnum(Environment);
const configRequiredStringSchema = z.string().nonempty(); const configRequiredStringSchema = z.string().nonempty();
export type ConfigRequiredStringType = z.infer< export type ConfigRequiredStringType = z.infer<
@ -39,6 +41,8 @@ export const rendererConfigSchema = z.object({
environment: environmentSchema, environment: environmentSchema,
homePath: configRequiredStringSchema, homePath: configRequiredStringSchema,
hostname: configRequiredStringSchema, hostname: configRequiredStringSchema,
osRelease: configRequiredStringSchema,
osVersion: configRequiredStringSchema,
resolvedTranslationsLocale: configRequiredStringSchema, resolvedTranslationsLocale: configRequiredStringSchema,
resolvedTranslationsLocaleDirection: z.enum(['ltr', 'rtl']), resolvedTranslationsLocaleDirection: z.enum(['ltr', 'rtl']),
preferredSystemLocales: z.array(configRequiredStringSchema), preferredSystemLocales: z.array(configRequiredStringSchema),

View file

@ -2,9 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import semver from 'semver'; 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'; import { isProduction } from '../util/version';
const MIN_WINDOWS_VERSION = '8.0.0'; const MIN_WINDOWS_VERSION = '8.0.0';
@ -15,7 +14,9 @@ export enum AudioNotificationSupport {
Custom, Custom,
} }
export function getAudioNotificationSupport(): AudioNotificationSupport { export function getAudioNotificationSupport(
OS: OSType
): AudioNotificationSupport {
if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) { if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) {
return AudioNotificationSupport.Native; return AudioNotificationSupport.Native;
} }
@ -25,42 +26,48 @@ export function getAudioNotificationSupport(): AudioNotificationSupport {
return AudioNotificationSupport.None; return AudioNotificationSupport.None;
} }
export const isAudioNotificationSupported = (): boolean => export const isAudioNotificationSupported = (OS: OSType): boolean =>
getAudioNotificationSupport() !== AudioNotificationSupport.None; getAudioNotificationSupport(OS) !== AudioNotificationSupport.None;
// Using `Notification::tag` has a bug on Windows 7: // Using `Notification::tag` has a bug on Windows 7:
// https://github.com/electron/electron/issues/11189 // https://github.com/electron/electron/issues/11189
export const isNotificationGroupingSupported = (): boolean => export const isNotificationGroupingSupported = (OS: OSType): boolean =>
!OS.isWindows() || OS.isWindows(MIN_WINDOWS_VERSION); !OS.isWindows() || OS.isWindows(MIN_WINDOWS_VERSION);
// Login item settings are only supported on macOS and Windows, according to [Electron's // Login item settings are only supported on macOS and Windows, according to [Electron's
// docs][0]. // docs][0].
// [0]: https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows // [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(); OS.isWindows() || OS.isMacOS();
// the "hide menu bar" option is specific to Windows and Linux // 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 // 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 * 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. * 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. // We eventually want to support Linux in production.
OS.isWindows() || (OS.isLinux() && !isProduction(appVersion)); OS.isWindows() || (OS.isLinux() && !isProduction(appVersion));
// On Windows minimize and start in system tray is default when app is selected // 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. // to launch at login, because we can provide `['--start-in-tray']` args.
export const isMinimizeToAndStartInSystemTraySupported = ( export const isMinimizeToAndStartInSystemTraySupported = (
OS: OSType,
appVersion: string 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(); OS.isWindows() || OS.isMacOS();
export const shouldHideExpiringMessageBody = (): boolean => export const shouldHideExpiringMessageBody = (
OS.isWindows() || (OS.isMacOS() && semver.lt(os.release(), '21.1.0')); 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 { UUIDStringType } from './UUID';
import type { LocaleDirection } from '../../app/locale'; import type { LocaleDirection } from '../../app/locale';
import type { LocaleMessagesType } from './I18N';
export type StoryContextType = { export type StoryContextType = {
authorUuid?: UUIDStringType; authorUuid?: UUIDStringType;
timestamp: number; timestamp: number;
@ -24,6 +26,7 @@ export type LocalizerType = {
getIntl(): IntlShape; getIntl(): IntlShape;
isLegacyFormat(key: string): boolean; isLegacyFormat(key: string): boolean;
getLocale(): string; getLocale(): string;
getLocaleMessages(): LocaleMessagesType;
getLocaleDirection(): LocaleDirection; getLocaleDirection(): LocaleDirection;
}; };

View file

@ -1,20 +1,18 @@
// Copyright 2018 Signal Messenger, LLC // Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { get, has } from 'lodash';
export function toLogFormat(error: unknown): string { export function toLogFormat(error: unknown): string {
let result = ''; let result = '';
if (error instanceof Error && error.stack) { if (error instanceof Error && error.stack) {
result = error.stack; result = error.stack;
} else if (has(error, 'message')) { } else if (error && typeof error === 'object' && 'message' in error) {
result = get(error, 'message'); result = String(error.message);
} else { } else {
result = String(error); result = String(error);
} }
if (has(error, 'cause')) { if (error && typeof error === 'object' && 'cause' in error) {
result += `\nCaused by: ${String(get(error, 'cause'))}`; result += `\nCaused by: ${String(error.cause)}`;
} }
return result; return result;

View file

@ -2525,7 +2525,7 @@
{ {
"rule": "DOM-innerHTML", "rule": "DOM-innerHTML",
"path": "ts/windows/loading/start.ts", "path": "ts/windows/loading/start.ts",
"line": " message.innerHTML = window.SignalContext.i18n('icu:optimizingApplication');", "line": " message.innerHTML = window.i18n('icu:optimizingApplication');",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-17T21:02:59.414Z" "updated": "2021-09-17T21:02:59.414Z"
}, },

View file

@ -24,8 +24,7 @@ const excludedFilesRegexp = RegExp(
[ [
'^release/', '^release/',
'^preload.bundle.js(LICENSE.txt|map)?', '^preload.bundle.js(LICENSE.txt|map)?',
'^about.browser.bundle.js(LICENSE.txt|map)?', '^bundles/',
'^about.preload.bundle.js(LICENSE.txt|map)?',
'^storybook-static/', '^storybook-static/',
// Non-distributed files // 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; return legacyMessages[key] != null;
}; };
getMessage.getLocale = () => locale; getMessage.getLocale = () => locale;
getMessage.getLocaleMessages = () => messages;
getMessage.getLocaleDirection = () => { getMessage.getLocaleDirection = () => {
return window.getResolvedMessagesLocaleDirection(); 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 // SPDX-License-Identifier: AGPL-3.0-only
import * as semver from 'semver'; import * as semver from 'semver';
import moment from 'moment';
export const isProduction = (version: string): boolean => { export const isProduction = (version: string): boolean => {
const parsed = semver.parse(version); const parsed = semver.parse(version);
@ -34,7 +33,22 @@ export const generateAlphaVersion = (options: {
throw new Error(`generateAlphaVersion: Invalid version ${currentVersion}`); 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}`; const formattedVersion = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
return `${formattedVersion}-alpha.${formattedDate}-${shortSha}`; return `${formattedVersion}-alpha.${formattedDate}-${shortSha}`;

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as fs from 'fs'; import * as fs from 'fs';
import { isWindows } from '../OS'; import OS from './os/osMain';
const ZONE_IDENTIFIER_CONTENTS = Buffer.from('[ZoneTransfer]\r\nZoneId=3'); 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( export async function writeWindowsZoneIdentifier(
filePath: string filePath: string
): Promise<void> { ): Promise<void> {
if (!isWindows()) { if (!OS.isWindows()) {
throw new Error('writeWindowsZoneIdentifier should only run on Windows'); 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 // 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 { Store } from 'redux';
import type * as Backbone from 'backbone'; import type * as Backbone from 'backbone';
import type PQueue from 'p-queue/dist'; 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 Crypto from './Crypto';
import type * as Curve from './Curve'; import type * as Curve from './Curve';
import type * as RemoteConfig from './RemoteConfig'; 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 { getEnvironment } from './environment';
import type { LocalizerType, ThemeType } from './types/Util'; import type { LocalizerType, ThemeType } from './types/Util';
import type { Receipt } from './types/Receipt'; 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 * as Message2 from './types/Message2';
import type { initializeMigrations } from './signal'; import type { initializeMigrations } from './signal';
import type { RetryPlaceholders } from './util/retryPlaceholders'; import type { RetryPlaceholders } from './util/retryPlaceholders';
import type { PropsPreloadType as PreferencesPropsType } from './components/Preferences';
import type { LocaleDirection } from '../app/locale'; import type { LocaleDirection } from '../app/locale';
export { Long } from 'long'; export { Long } from 'long';
@ -103,21 +103,46 @@ export type FeatureFlagType = {
GV2_MIGRATION_DISABLE_INVITE: boolean; GV2_MIGRATION_DISABLE_INVITE: boolean;
}; };
type AboutWindowType = { type AboutWindowPropsType = {
arch: string;
environmentText: string; environmentText: string;
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>; platform: string;
hasCustomTitleBar: boolean; };
i18n: LocalizerType;
version: 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 = { export type SignalCoreType = {
AboutWindow?: AboutWindowType; AboutWindowProps?: AboutWindowPropsType;
Crypto: typeof Crypto; Crypto: typeof Crypto;
Curve: typeof Curve; Curve: typeof Curve;
Data: typeof Data; Data: typeof Data;
DebugLogWindowProps?: DebugLogWindowPropsType;
Groups: typeof Groups; Groups: typeof Groups;
PermissionsWindowProps?: PermissionsWindowPropsType;
RemoteConfig: typeof RemoteConfig; RemoteConfig: typeof RemoteConfig;
ScreenShareWindowProps?: ScreenShareWindowPropsType;
Services: { Services: {
calling: CallingClass; calling: CallingClass;
initializeGroupCredentialFetcher: () => Promise<void>; initializeGroupCredentialFetcher: () => Promise<void>;
@ -127,6 +152,7 @@ export type SignalCoreType = {
lightSessionResetQueue?: PQueue; lightSessionResetQueue?: PQueue;
storage: typeof StorageService; storage: typeof StorageService;
}; };
SettingsWindowProps?: SettingsWindowPropsType;
Migrations: ReturnType<typeof initializeMigrations>; Migrations: ReturnType<typeof initializeMigrations>;
Types: { Types: {
Message: typeof Message2; Message: typeof Message2;
@ -137,7 +163,7 @@ export type SignalCoreType = {
Components: { Components: {
ConfirmationDialog: typeof ConfirmationDialog; ConfirmationDialog: typeof ConfirmationDialog;
}; };
OS: typeof OS; OS: OSType;
State: { State: {
createStore: typeof createStore; createStore: typeof createStore;
Roots: { Roots: {

View file

@ -5,20 +5,32 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { About } from '../../components/About'; import { About } from '../../components/About';
import { i18n } from '../sandboxedInit';
import { strictAssert } from '../../util/assert'; 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( ReactDOM.render(
<About <About
closeAbout={() => AboutWindow.executeMenuRole('close')} closeAbout={() => window.SignalContext.executeMenuRole('close')}
environment={AboutWindow.environmentText} environment={environmentText}
executeMenuRole={AboutWindow.executeMenuRole} executeMenuRole={window.SignalContext.executeMenuRole}
hasCustomTitleBar={AboutWindow.hasCustomTitleBar} hasCustomTitleBar={window.SignalContext.OS.hasCustomTitleBar()}
i18n={AboutWindow.i18n} i18n={i18n}
version={AboutWindow.version} version={window.SignalContext.getVersion()}
/>, />,
document.getElementById('app') document.getElementById('app')
); );

View file

@ -1,22 +1,10 @@
// Copyright 2018 Signal Messenger, LLC // Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { MenuItemConstructorOptions } from 'electron'; import { contextBridge } from 'electron';
import { contextBridge, ipcRenderer } from 'electron'; import { MinimalSignalContext } from '../minimalContext';
import { activeWindowService } from '../../context/activeWindowService';
import { config } from '../../context/config'; import { config } from '../../context/config';
import { createNativeThemeListener } from '../../context/createNativeThemeListener';
import { createSetting } from '../../util/preload';
import { environment } from '../../context/environment'; 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]; const environments: Array<string> = [environment];
@ -24,40 +12,12 @@ if (config.appInstance) {
environments.push(String(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 = { const Signal = {
AboutWindow: { AboutWindowProps: {
environmentText, arch: process.arch,
executeMenuRole, environmentText: environments.join(' - '),
hasCustomTitleBar, platform: process.platform,
i18n,
version: String(config.version),
}, },
}; };
contextBridge.exposeInMainWorld('Signal', Signal); contextBridge.exposeInMainWorld('Signal', Signal);
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);
// 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);

View file

@ -12,7 +12,7 @@ import * as Bytes from '../Bytes';
import { isPathInside } from '../util/isPathInside'; import { isPathInside } from '../util/isPathInside';
import { writeWindowsZoneIdentifier } from '../util/windowsZoneIdentifier'; import { writeWindowsZoneIdentifier } from '../util/windowsZoneIdentifier';
import { isWindows } from '../OS'; import OS from '../util/os/osMain';
export * from '../../app/attachments'; export * from '../../app/attachments';
@ -229,7 +229,7 @@ async function writeWithAttributes(
const attrValue = `${type};${timestamp};${appName};${guid}`; const attrValue = `${type};${timestamp};${appName};${guid}`;
await xattr.set(target, 'com.apple.quarantine', attrValue); 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. // This operation may fail (see the function's comments), which is not a show-stopper.
try { try {
await writeWindowsZoneIdentifier(target); await writeWindowsZoneIdentifier(target);

View file

@ -8,7 +8,6 @@ import type { MenuOptionsType, MenuActionType } from '../types/menu';
import type { IPCEventsValuesType } from '../util/createIPCEvents'; import type { IPCEventsValuesType } from '../util/createIPCEvents';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import type { LoggerType } from '../types/Logging'; import type { LoggerType } from '../types/Logging';
import type { LocaleMessagesType } from '../types/I18N';
import type { NativeThemeType } from '../context/createNativeThemeListener'; import type { NativeThemeType } from '../context/createNativeThemeListener';
import type { SettingType } from '../util/preload'; import type { SettingType } from '../util/preload';
import type { RendererConfigType } from '../types/RendererConfig'; import type { RendererConfigType } from '../types/RendererConfig';
@ -18,29 +17,10 @@ import { Crypto } from '../context/Crypto';
import { Timers } from '../context/Timers'; import { Timers } from '../context/Timers';
import type { ActiveWindowServiceType } from '../services/ActiveWindowService'; import type { ActiveWindowServiceType } from '../services/ActiveWindowService';
import { config } from '../context/config';
import { i18n } from '../context/i18n'; import { i18n } from '../context/i18n';
import { activeWindowService } from '../context/activeWindowService';
import {
getEnvironment,
parseEnvironment,
setEnvironment,
} from '../environment';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { createSetting } from '../util/preload';
import { initialize as initializeLogging } from '../logging/set_up_renderer_logging'; import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
import { waitForSettingsChange } from '../context/waitForSettingsChange'; import { MinimalSignalContext } from './minimalContext';
import { createNativeThemeListener } from '../context/createNativeThemeListener';
import {
isWindows,
isLinux,
isMacOS,
hasCustomTitleBar,
getClassName,
} from '../OS';
const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment));
strictAssert(Boolean(window.SignalContext), 'context must be defined'); strictAssert(Boolean(window.SignalContext), 'context must be defined');
@ -51,89 +31,53 @@ export type MainWindowStatsType = Readonly<{
isFullScreen: boolean; isFullScreen: boolean;
}>; }>;
export type SignalContextType = { export type MinimalSignalContextType = {
bytes: Bytes;
crypto: Crypto;
timers: Timers;
nativeThemeListener: NativeThemeType;
setIsCallActive: (isCallActive: boolean) => unknown;
activeWindowService: ActiveWindowServiceType; 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: { Settings: {
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>; themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
waitForChange: () => Promise<void>; waitForChange: () => Promise<void>;
}; };
OS: { OS: {
hasCustomTitleBar: () => boolean;
getClassName: () => string;
platform: string; platform: string;
isWindows: typeof isWindows; release: string;
isLinux: typeof isLinux;
isMacOS: typeof isMacOS;
hasCustomTitleBar: typeof hasCustomTitleBar;
getClassName: typeof getClassName;
}; };
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 = { export const SignalContext: SignalContextType = {
activeWindowService, ...MinimalSignalContext,
Settings: {
themeSetting: createSetting('themeSetting', { setter: false }),
waitForChange: waitForSettingsChange,
},
OS: {
platform: process.platform,
isWindows,
isLinux,
isMacOS,
hasCustomTitleBar,
getClassName,
},
bytes: new Bytes(), bytes: new Bytes(),
config,
crypto: new Crypto(), 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, i18n,
localeMessages,
log: window.SignalContext.log, log: window.SignalContext.log,
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
setIsCallActive(isCallActive: boolean): void { setIsCallActive(isCallActive: boolean): void {
ipcRenderer.send('set-is-call-active', isCallActive); ipcRenderer.send('set-is-call-active', isCallActive);
}, },
timers: new Timers(), 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; 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 // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge, ipcRenderer } from 'electron'; import { contextBridge, ipcRenderer } from 'electron';
import { MinimalSignalContext } from '../minimalContext';
import { SignalContext } from '../context'; function downloadLog(logText: string) {
import { DebugLogWindow } from '../../components/DebugLogWindow'; ipcRenderer.send('show-debug-log-save-dialog', logText);
import * as debugLog from '../../logging/debuglogs'; }
import { upload } from '../../logging/uploadDebugLog';
import * as logger from '../../logging/log';
contextBridge.exposeInMainWorld('SignalContext', { async function fetchLogs() {
...SignalContext, const data = await ipcRenderer.invoke('fetch-log');
renderWindow: () => { return ipcRenderer.invoke(
const environmentText: Array<string> = [SignalContext.getEnvironment()]; 'DebugLogs.getLogs',
data,
window.navigator.userAgent
);
}
const appInstance = SignalContext.getAppInstance(); function uploadLogs(logs: string) {
if (appInstance) { return ipcRenderer.invoke('DebugLogs.upload', logs);
environmentText.push(appInstance); }
}
ReactDOM.render( const Signal = {
React.createElement(DebugLogWindow, { DebugLogWindowProps: {
hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(), downloadLog,
executeMenuRole: SignalContext.executeMenuRole, fetchLogs,
closeWindow: () => SignalContext.executeMenuRole('close'), uploadLogs,
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')
);
}, },
}); };
contextBridge.exposeInMainWorld('Signal', Signal);
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);

View file

@ -2,7 +2,10 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { contextBridge } from 'electron'; import { contextBridge } from 'electron';
import { config } from '../../context/config';
import { localeMessages } from '../../context/localeMessages';
import { SignalContext } from '../context'; contextBridge.exposeInMainWorld('SignalContext', {
getI18nLocale: () => config.resolvedTranslationsLocale,
contextBridge.exposeInMainWorld('SignalContext', SignalContext); getI18nLocaleMessages: () => localeMessages,
});

View file

@ -1,7 +1,14 @@
// Copyright 2020 Signal Messenger, LLC // Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // 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'); const message = document.getElementById('message');
if (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 // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge } from 'electron'; import { contextBridge } from 'electron';
import { MinimalSignalContext } from '../minimalContext';
import { SignalContext } from '../context';
import { createSetting } from '../../util/preload'; import { createSetting } from '../../util/preload';
import { PermissionsPopup } from '../../components/PermissionsPopup'; import { drop } from '../../util/drop';
const mediaCameraPermissions = createSetting('mediaCameraPermissions', { const mediaCameraPermissions = createSetting('mediaCameraPermissions', {
getter: false, getter: false,
@ -17,48 +13,28 @@ const mediaPermissions = createSetting('mediaPermissions', {
getter: false, getter: false,
}); });
contextBridge.exposeInMainWorld( const params = new URLSearchParams(document.location.search);
'nativeThemeListener', const forCalling = params.get('forCalling') === 'true';
window.SignalContext.nativeThemeListener const forCamera = params.get('forCamera') === 'true';
);
contextBridge.exposeInMainWorld('SignalContext', { function onClose() {
...SignalContext, drop(MinimalSignalContext.executeMenuRole('close'));
renderWindow: () => { }
const params = new URLSearchParams(document.location.search);
const forCalling = params.get('forCalling') === 'true';
const forCamera = params.get('forCamera') === 'true';
let message; const Signal = {
if (forCalling) { PermissionsWindowProps: {
if (forCamera) { forCalling,
message = SignalContext.i18n('icu:videoCallingPermissionNeeded'); forCamera,
onAccept: () => {
if (!forCamera) {
drop(mediaPermissions.setValue(true));
} else { } else {
message = SignalContext.i18n('icu:audioCallingPermissionNeeded'); drop(mediaCameraPermissions.setValue(true));
} }
} else { onClose();
message = SignalContext.i18n('icu:audioPermissionNeeded'); },
} onClose,
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')
);
}, },
}); };
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 // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge, ipcRenderer } from 'electron'; import { contextBridge, ipcRenderer } from 'electron';
import { MinimalSignalContext } from '../minimalContext';
import { SignalContext } from '../context'; const params = new URLSearchParams(document.location.search);
import { CallingScreenSharingController } from '../../components/CallingScreenSharingController';
contextBridge.exposeInMainWorld('SignalContext', SignalContext); const Signal = {
ScreenShareWindowProps: {
function renderScreenSharingController(presentedSourceName: string): void { onStopSharing: () => {
ReactDOM.render( ipcRenderer.send('stop-screen-share');
React.createElement(CallingScreenSharingController, { },
platform: process.platform, presentedSourceName: params.get('sourceName'),
executeMenuRole: SignalContext.executeMenuRole, },
i18n: SignalContext.i18n, };
onCloseController: () => SignalContext.executeMenuRole('close'), contextBridge.exposeInMainWorld('Signal', Signal);
onStopSharing: () => ipcRenderer.send('stop-screen-share'), contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);
presentedSourceName,
}),
document.getElementById('app')
);
}
ipcRenderer.once('render-screen-sharing-controller', (_, name: string) => {
renderScreenSharingController(name);
});

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 // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge, ipcRenderer } from 'electron'; 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 * as Settings from '../../types/Settings';
import { Preferences } from '../../components/Preferences';
import { import {
SystemTraySetting, SystemTraySetting,
parseSystemTraySetting, parseSystemTraySetting,
@ -16,7 +15,6 @@ import {
import { awaitObject } from '../../util/awaitObject'; import { awaitObject } from '../../util/awaitObject';
import { DurationInSeconds } from '../../util/durations'; import { DurationInSeconds } from '../../util/durations';
import { createSetting, createCallback } from '../../util/preload'; import { createSetting, createCallback } from '../../util/preload';
import { startInteractionMode } from '../../services/InteractionMode';
function doneRendering() { function doneRendering() {
ipcRenderer.send('settings-done-rendering'); ipcRenderer.send('settings-done-rendering');
@ -128,9 +126,18 @@ function getSystemTraySettingValues(systemTraySetting: SystemTraySetting): {
}; };
} }
const renderPreferences = async () => { let renderInBrowser = (_props: PropsPreloadType): void => {
startInteractionMode(); 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 { const {
blockedCount, blockedCount,
deviceName, deviceName,
@ -222,7 +229,7 @@ const renderPreferences = async () => {
const { hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray } = const { hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray } =
getSystemTraySettingValues(systemTraySetting); getSystemTraySettingValues(systemTraySetting);
const onUniversalExpireTimerChange = reRender( const onUniversalExpireTimerChange = attachRenderCallback(
settingUniversalExpireTimer.setValue settingUniversalExpireTimer.setValue
); );
@ -270,13 +277,13 @@ const renderPreferences = async () => {
// Actions and other props // Actions and other props
addCustomColor: ipcAddCustomColor, addCustomColor: ipcAddCustomColor,
closeSettings: () => SignalContext.executeMenuRole('close'), closeSettings: () => MinimalSignalContext.executeMenuRole('close'),
doDeleteAllData: () => ipcRenderer.send('delete-all-data'), doDeleteAllData: () => ipcRenderer.send('delete-all-data'),
doneRendering, doneRendering,
editCustomColor: ipcEditCustomColor, editCustomColor: ipcEditCustomColor,
getConversationsWithCustomColor: ipcGetConversationsWithCustomColor, getConversationsWithCustomColor: ipcGetConversationsWithCustomColor,
initialSpellCheckSetting: initialSpellCheckSetting:
SignalContext.config.appStartInitialSpellcheckSetting, MinimalSignalContext.config.appStartInitialSpellcheckSetting,
makeSyncRequest: ipcMakeSyncRequest, makeSyncRequest: ipcMakeSyncRequest,
removeCustomColor: ipcRemoveCustomColor, removeCustomColor: ipcRemoveCustomColor,
removeCustomColorOnConversations: ipcRemoveCustomColorOnConversations, removeCustomColorOnConversations: ipcRemoveCustomColorOnConversations,
@ -286,93 +293,121 @@ const renderPreferences = async () => {
shouldShowStoriesSettings, shouldShowStoriesSettings,
// Limited support features // Limited support features
isAudioNotificationsSupported: Settings.isAudioNotificationSupported(), isAudioNotificationsSupported: Settings.isAudioNotificationSupported(OS),
isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(), isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(OS),
isAutoLaunchSupported: Settings.isAutoLaunchSupported(), isAutoLaunchSupported: Settings.isAutoLaunchSupported(OS),
isHideMenuBarSupported: Settings.isHideMenuBarSupported(), isHideMenuBarSupported: Settings.isHideMenuBarSupported(OS),
isNotificationAttentionSupported: Settings.isDrawAttentionSupported(), isNotificationAttentionSupported: Settings.isDrawAttentionSupported(OS),
isPhoneNumberSharingSupported, isPhoneNumberSharingSupported,
isSyncSupported: !isSyncNotSupported, isSyncSupported: !isSyncNotSupported,
isSystemTraySupported: Settings.isSystemTraySupported( isSystemTraySupported: Settings.isSystemTraySupported(
SignalContext.getVersion() OS,
MinimalSignalContext.getVersion()
), ),
isMinimizeToAndStartInSystemTraySupported: isMinimizeToAndStartInSystemTraySupported:
Settings.isMinimizeToAndStartInSystemTraySupported( Settings.isMinimizeToAndStartInSystemTraySupported(
SignalContext.getVersion() OS,
MinimalSignalContext.getVersion()
), ),
// Feature flags // Feature flags
isFormattingFlagEnabled, isFormattingFlagEnabled,
// Change handlers // Change handlers
onAudioNotificationsChange: reRender(settingAudioNotification.setValue), onAudioNotificationsChange: attachRenderCallback(
onAutoDownloadUpdateChange: reRender(settingAutoDownloadUpdate.setValue), settingAudioNotification.setValue
onAutoLaunchChange: reRender(settingAutoLaunch.setValue), ),
onCallNotificationsChange: reRender(settingCallSystemNotification.setValue), onAutoDownloadUpdateChange: attachRenderCallback(
onCallRingtoneNotificationChange: reRender( settingAutoDownloadUpdate.setValue
),
onAutoLaunchChange: attachRenderCallback(settingAutoLaunch.setValue),
onCallNotificationsChange: attachRenderCallback(
settingCallSystemNotification.setValue
),
onCallRingtoneNotificationChange: attachRenderCallback(
settingCallRingtoneNotification.setValue settingCallRingtoneNotification.setValue
), ),
onCountMutedConversationsChange: reRender( onCountMutedConversationsChange: attachRenderCallback(
settingCountMutedConversations.setValue settingCountMutedConversations.setValue
), ),
onHasStoriesDisabledChanged: reRender(async (value: boolean) => { onHasStoriesDisabledChanged: attachRenderCallback(
await settingHasStoriesDisabled.setValue(value); async (value: boolean) => {
if (!value) { await settingHasStoriesDisabled.setValue(value);
void ipcDeleteAllMyStories(); if (!value) {
void ipcDeleteAllMyStories();
}
return value;
} }
return value; ),
}), onHideMenuBarChange: attachRenderCallback(settingHideMenuBar.setValue),
onHideMenuBarChange: reRender(settingHideMenuBar.setValue), onIncomingCallNotificationsChange: attachRenderCallback(
onIncomingCallNotificationsChange: reRender(
settingIncomingCallNotification.setValue settingIncomingCallNotification.setValue
), ),
onLastSyncTimeChange: reRender(settingLastSyncTime.setValue), onLastSyncTimeChange: attachRenderCallback(settingLastSyncTime.setValue),
onMediaCameraPermissionsChange: reRender( onMediaCameraPermissionsChange: attachRenderCallback(
settingMediaCameraPermissions.setValue settingMediaCameraPermissions.setValue
), ),
onMinimizeToAndStartInSystemTrayChange: reRender(async (value: boolean) => { onMinimizeToAndStartInSystemTrayChange: attachRenderCallback(
await settingSystemTraySetting.setValue( async (value: boolean) => {
value await settingSystemTraySetting.setValue(
? SystemTraySetting.MinimizeToAndStartInSystemTray value
: SystemTraySetting.MinimizeToSystemTray ? SystemTraySetting.MinimizeToAndStartInSystemTray
); : SystemTraySetting.MinimizeToSystemTray
return value; );
}), return value;
onMinimizeToSystemTrayChange: reRender(async (value: boolean) => { }
await settingSystemTraySetting.setValue( ),
value onMinimizeToSystemTrayChange: attachRenderCallback(
? SystemTraySetting.MinimizeToSystemTray async (value: boolean) => {
: SystemTraySetting.DoNotUseSystemTray await settingSystemTraySetting.setValue(
); value
return value; ? SystemTraySetting.MinimizeToSystemTray
}), : SystemTraySetting.DoNotUseSystemTray
onMediaPermissionsChange: reRender(settingMediaPermissions.setValue), );
onNotificationAttentionChange: reRender( return value;
}
),
onMediaPermissionsChange: attachRenderCallback(
settingMediaPermissions.setValue
),
onNotificationAttentionChange: attachRenderCallback(
settingNotificationDrawAttention.setValue settingNotificationDrawAttention.setValue
), ),
onNotificationContentChange: reRender(settingNotificationSetting.setValue), onNotificationContentChange: attachRenderCallback(
onNotificationsChange: reRender(async (value: boolean) => { settingNotificationSetting.setValue
),
onNotificationsChange: attachRenderCallback(async (value: boolean) => {
await settingNotificationSetting.setValue( await settingNotificationSetting.setValue(
value ? DEFAULT_NOTIFICATION_SETTING : 'off' value ? DEFAULT_NOTIFICATION_SETTING : 'off'
); );
return value; return value;
}), }),
onRelayCallsChange: reRender(settingRelayCalls.setValue), onRelayCallsChange: attachRenderCallback(settingRelayCalls.setValue),
onSelectedCameraChange: reRender(settingVideoInput.setValue), onSelectedCameraChange: attachRenderCallback(settingVideoInput.setValue),
onSelectedMicrophoneChange: reRender(settingAudioInput.setValue), onSelectedMicrophoneChange: attachRenderCallback(
onSelectedSpeakerChange: reRender(settingAudioOutput.setValue), settingAudioInput.setValue
onSentMediaQualityChange: reRender(settingSentMediaQuality.setValue), ),
onSpellCheckChange: reRender(settingSpellCheck.setValue), onSelectedSpeakerChange: attachRenderCallback(settingAudioOutput.setValue),
onTextFormattingChange: reRender(settingTextFormatting.setValue), onSentMediaQualityChange: attachRenderCallback(
onThemeChange: reRender(settingTheme.setValue), settingSentMediaQuality.setValue
),
onSpellCheckChange: attachRenderCallback(settingSpellCheck.setValue),
onTextFormattingChange: attachRenderCallback(
settingTextFormatting.setValue
),
onThemeChange: attachRenderCallback(settingTheme.setValue),
onUniversalExpireTimerChange: (newValue: number): Promise<void> => { onUniversalExpireTimerChange: (newValue: number): Promise<void> => {
return onUniversalExpireTimerChange( return onUniversalExpireTimerChange(
DurationInSeconds.fromSeconds(newValue) DurationInSeconds.fromSeconds(newValue)
); );
}, },
onWhoCanFindMeChange: reRender(settingPhoneNumberDiscoverability.setValue), onWhoCanFindMeChange: attachRenderCallback(
onWhoCanSeeMeChange: reRender(settingPhoneNumberSharing.setValue), settingPhoneNumberDiscoverability.setValue
),
onWhoCanSeeMeChange: attachRenderCallback(
settingPhoneNumberSharing.setValue
),
// Zoom factor change doesn't require immediate rerender since it will: // Zoom factor change doesn't require immediate rerender since it will:
// 1. Update the zoom factor in the main window // 1. Update the zoom factor in the main window
@ -381,28 +416,22 @@ const renderPreferences = async () => {
// rerender. // rerender.
onZoomFactorChange: settingZoomFactor.setValue, onZoomFactorChange: settingZoomFactor.setValue,
i18n: SignalContext.i18n, hasCustomTitleBar: MinimalSignalContext.OS.hasCustomTitleBar(),
executeMenuRole: MinimalSignalContext.executeMenuRole,
hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
executeMenuRole: SignalContext.executeMenuRole,
}; };
function reRender<Value>(f: (value: Value) => Promise<Value>) { renderInBrowser(props);
return async (value: Value) => { }
await f(value);
void renderPreferences();
};
}
ReactDOM.render(
React.createElement(Preferences, props),
document.getElementById('app')
);
};
ipcRenderer.on('preferences-changed', () => renderPreferences()); ipcRenderer.on('preferences-changed', () => renderPreferences());
contextBridge.exposeInMainWorld('SignalContext', { const Signal = {
...SignalContext, SettingsWindowProps: {
renderWindow: renderPreferences, 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" version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" 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: buffer@^4.3.0:
version "4.9.1" version "4.9.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" 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" base64-js "^1.3.1"
ieee754 "^1.1.13" 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: bufferutil@^4.0.1:
version "4.0.7" version "4.0.7"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" 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" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== 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" version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid-browser/-/uuid-browser-3.1.0.tgz#0f05a40aef74f9e5951e20efbf44b11871e56410" resolved "https://registry.yarnpkg.com/uuid-browser/-/uuid-browser-3.1.0.tgz#0f05a40aef74f9e5951e20efbf44b11871e56410"
integrity sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA= integrity sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA=