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

@ -1,48 +0,0 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { release as osRelease } from 'os';
import semver from 'semver';
const createIsPlatform = (
platform: typeof process.platform
): ((minVersion?: string) => boolean) => {
return minVersion => {
if (process.platform !== platform) {
return false;
}
if (minVersion === undefined) {
return true;
}
return semver.gte(osRelease(), minVersion);
};
};
export const isMacOS = createIsPlatform('darwin');
export const isLinux = createIsPlatform('linux');
export const isWindows = createIsPlatform('win32');
// Windows 10 and above
export const hasCustomTitleBar = (): boolean =>
isWindows('10.0.0') || Boolean(process.env.CUSTOM_TITLEBAR);
export const getName = (): string => {
if (isMacOS()) {
return 'macOS';
}
if (isWindows()) {
return 'Windows';
}
return 'Linux';
};
export const getClassName = (): string => {
if (isMacOS()) {
return 'os-macos';
}
if (isWindows()) {
return 'os-windows';
}
return 'os-linux';
};

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { makeEnumParser } from '../util/enum';
import * as OS from '../OS';
import OS from '../util/os/osMain';
export enum AudioDeviceModule {
Default = 'Default',

View file

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

View file

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

View file

@ -1,8 +1,8 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { config } from './config';
import { localeMessages } from './localeMessages';
import { setupI18n } from '../util/setupI18n';
import { strictAssert } from '../util/assert';
@ -16,7 +16,6 @@ strictAssert(
'locale is not a string'
);
const localeMessages = ipcRenderer.sendSync('locale-data');
const i18n = setupI18n(resolvedTranslationsLocale, localeMessages);
export { i18n };

View file

@ -0,0 +1,6 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
export const localeMessages = ipcRenderer.sendSync('locale-data');

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { throttle } from 'lodash';
import { throttle } from '../util/throttle';
// Idle timer - you're active for ACTIVE_TIMEOUT after one of these events
const ACTIVE_TIMEOUT = 15 * 1000;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ import os from 'os';
import Sinon from 'sinon';
import { assert } from 'chai';
import { getOSFunctions } from '../../util/os/shared';
import * as Settings from '../../types/Settings';
describe('Settings', () => {
@ -21,8 +22,9 @@ describe('Settings', () => {
describe('getAudioNotificationSupport', () => {
it('returns native support on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
const OS = getOSFunctions(os.release());
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.Native
);
});
@ -30,8 +32,9 @@ describe('Settings', () => {
it('returns no support on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
const OS = getOSFunctions(os.release());
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.None
);
});
@ -39,16 +42,18 @@ describe('Settings', () => {
it('returns native support on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
const OS = getOSFunctions(os.release());
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.Native
);
});
it('returns custom support on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
const OS = getOSFunctions(os.release());
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.getAudioNotificationSupport(OS),
Settings.AudioNotificationSupport.Custom
);
});
@ -57,48 +62,56 @@ describe('Settings', () => {
describe('isAudioNotificationSupported', () => {
it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isTrue(Settings.isAudioNotificationSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isAudioNotificationSupported(OS));
});
it('returns false on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isFalse(Settings.isAudioNotificationSupported());
const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isAudioNotificationSupported(OS));
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isAudioNotificationSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isAudioNotificationSupported(OS));
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isAudioNotificationSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isAudioNotificationSupported(OS));
});
});
describe('isNotificationGroupingSupported', () => {
it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isTrue(Settings.isNotificationGroupingSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isNotificationGroupingSupported(OS));
});
it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isFalse(Settings.isNotificationGroupingSupported());
const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isNotificationGroupingSupported(OS));
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isNotificationGroupingSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isNotificationGroupingSupported(OS));
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isNotificationGroupingSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isNotificationGroupingSupported(OS));
});
});
@ -106,88 +119,103 @@ describe('Settings', () => {
it('returns true on Windows', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isAutoLaunchSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isAutoLaunchSupported(OS));
});
it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isTrue(Settings.isAutoLaunchSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isAutoLaunchSupported(OS));
});
it('returns false on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isFalse(Settings.isAutoLaunchSupported());
const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isAutoLaunchSupported(OS));
});
});
describe('isHideMenuBarSupported', () => {
it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isFalse(Settings.isHideMenuBarSupported());
const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isHideMenuBarSupported(OS));
});
it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isTrue(Settings.isHideMenuBarSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isHideMenuBarSupported(OS));
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isHideMenuBarSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isHideMenuBarSupported(OS));
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isHideMenuBarSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isHideMenuBarSupported(OS));
});
});
describe('isDrawAttentionSupported', () => {
it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isFalse(Settings.isDrawAttentionSupported());
const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isDrawAttentionSupported(OS));
});
it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isTrue(Settings.isDrawAttentionSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isDrawAttentionSupported(OS));
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isDrawAttentionSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isDrawAttentionSupported(OS));
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isDrawAttentionSupported());
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isDrawAttentionSupported(OS));
});
});
describe('isSystemTraySupported', () => {
it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isFalse(Settings.isSystemTraySupported('1.2.3'));
const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isSystemTraySupported(OS, '1.2.3'));
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isSystemTraySupported('1.2.3'));
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isSystemTraySupported(OS, '1.2.3'));
});
it('returns false on Linux production', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isFalse(Settings.isSystemTraySupported('1.2.3'));
const OS = getOSFunctions(os.release());
assert.isFalse(Settings.isSystemTraySupported(OS, '1.2.3'));
});
it('returns true on Linux beta', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isSystemTraySupported('1.2.3-beta.4'));
const OS = getOSFunctions(os.release());
assert.isTrue(Settings.isSystemTraySupported(OS, '1.2.3-beta.4'));
});
});
});

View file

@ -715,7 +715,7 @@ export default class MessageSender {
storyMessage.fileAttachment = fileAttachment;
} catch (error) {
if (error instanceof HTTPError) {
throw new MessageError(message, error);
throw new MessageError(storyMessage, error);
} else {
throw error;
}

View file

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

View file

@ -2,9 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import semver from 'semver';
import os from 'os';
import * as OS from '../OS';
import type { OSType } from '../util/os/shared';
import { isProduction } from '../util/version';
const MIN_WINDOWS_VERSION = '8.0.0';
@ -15,7 +14,9 @@ export enum AudioNotificationSupport {
Custom,
}
export function getAudioNotificationSupport(): AudioNotificationSupport {
export function getAudioNotificationSupport(
OS: OSType
): AudioNotificationSupport {
if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) {
return AudioNotificationSupport.Native;
}
@ -25,42 +26,48 @@ export function getAudioNotificationSupport(): AudioNotificationSupport {
return AudioNotificationSupport.None;
}
export const isAudioNotificationSupported = (): boolean =>
getAudioNotificationSupport() !== AudioNotificationSupport.None;
export const isAudioNotificationSupported = (OS: OSType): boolean =>
getAudioNotificationSupport(OS) !== AudioNotificationSupport.None;
// Using `Notification::tag` has a bug on Windows 7:
// https://github.com/electron/electron/issues/11189
export const isNotificationGroupingSupported = (): boolean =>
export const isNotificationGroupingSupported = (OS: OSType): boolean =>
!OS.isWindows() || OS.isWindows(MIN_WINDOWS_VERSION);
// Login item settings are only supported on macOS and Windows, according to [Electron's
// docs][0].
// [0]: https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
export const isAutoLaunchSupported = (): boolean =>
export const isAutoLaunchSupported = (OS: OSType): boolean =>
OS.isWindows() || OS.isMacOS();
// the "hide menu bar" option is specific to Windows and Linux
export const isHideMenuBarSupported = (): boolean => !OS.isMacOS();
export const isHideMenuBarSupported = (OS: OSType): boolean => !OS.isMacOS();
// the "draw attention on notification" option is specific to Windows and Linux
export const isDrawAttentionSupported = (): boolean => !OS.isMacOS();
export const isDrawAttentionSupported = (OS: OSType): boolean => !OS.isMacOS();
/**
* Returns `true` if you can minimize the app to the system tray. Users can override this
* option with a command line flag, but that is not officially supported.
*/
export const isSystemTraySupported = (appVersion: string): boolean =>
export const isSystemTraySupported = (
OS: OSType,
appVersion: string
): boolean =>
// We eventually want to support Linux in production.
OS.isWindows() || (OS.isLinux() && !isProduction(appVersion));
// On Windows minimize and start in system tray is default when app is selected
// to launch at login, because we can provide `['--start-in-tray']` args.
export const isMinimizeToAndStartInSystemTraySupported = (
OS: OSType,
appVersion: string
): boolean => !OS.isWindows() && isSystemTraySupported(appVersion);
): boolean => !OS.isWindows() && isSystemTraySupported(OS, appVersion);
export const isAutoDownloadUpdatesSupported = (): boolean =>
export const isAutoDownloadUpdatesSupported = (OS: OSType): boolean =>
OS.isWindows() || OS.isMacOS();
export const shouldHideExpiringMessageBody = (): boolean =>
OS.isWindows() || (OS.isMacOS() && semver.lt(os.release(), '21.1.0'));
export const shouldHideExpiringMessageBody = (
OS: OSType,
release: string
): boolean => OS.isWindows() || (OS.isMacOS() && semver.lt(release, '21.1.0'));

View file

@ -5,6 +5,8 @@ import type { IntlShape } from 'react-intl';
import type { UUIDStringType } from './UUID';
import type { LocaleDirection } from '../../app/locale';
import type { LocaleMessagesType } from './I18N';
export type StoryContextType = {
authorUuid?: UUIDStringType;
timestamp: number;
@ -24,6 +26,7 @@ export type LocalizerType = {
getIntl(): IntlShape;
isLegacyFormat(key: string): boolean;
getLocale(): string;
getLocaleMessages(): LocaleMessagesType;
getLocaleDirection(): LocaleDirection;
};

View file

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

View file

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

View file

@ -24,8 +24,7 @@ const excludedFilesRegexp = RegExp(
[
'^release/',
'^preload.bundle.js(LICENSE.txt|map)?',
'^about.browser.bundle.js(LICENSE.txt|map)?',
'^about.preload.bundle.js(LICENSE.txt|map)?',
'^bundles/',
'^storybook-static/',
// Non-distributed files

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

@ -0,0 +1,9 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import os from 'os';
import { getOSFunctions } from './shared';
const OS = getOSFunctions(os.release());
export default OS;

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

@ -0,0 +1,9 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { config } from '../../context/config';
import { getOSFunctions } from './shared';
const OS = getOSFunctions(config.osRelease);
export default OS;

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

@ -0,0 +1,68 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import semver from 'semver';
function createIsPlatform(
platform: typeof process.platform,
osRelease: string
): (minVersion?: string) => boolean {
return minVersion => {
if (process.platform !== platform) {
return false;
}
if (minVersion === undefined) {
return true;
}
return semver.gte(osRelease, minVersion);
};
}
export type OSType = {
getClassName: () => string;
getName: () => string;
hasCustomTitleBar: () => boolean;
isLinux: (minVersion?: string) => boolean;
isMacOS: (minVersion?: string) => boolean;
isWindows: (minVersion?: string) => boolean;
};
export function getOSFunctions(osRelease: string): OSType {
const isMacOS = createIsPlatform('darwin', osRelease);
const isLinux = createIsPlatform('linux', osRelease);
const isWindows = createIsPlatform('win32', osRelease);
// Windows 10 and above
const hasCustomTitleBar = (): boolean =>
isWindows('10.0.0') || Boolean(process.env.CUSTOM_TITLEBAR);
const getName = (): string => {
if (isMacOS()) {
return 'macOS';
}
if (isWindows()) {
return 'Windows';
}
return 'Linux';
};
const getClassName = (): string => {
if (isMacOS()) {
return 'os-macos';
}
if (isWindows()) {
return 'os-windows';
}
return 'os-linux';
};
return {
getClassName,
getName,
hasCustomTitleBar,
isLinux,
isMacOS,
isWindows,
};
}

View file

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

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

@ -0,0 +1,70 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// eslint-disable-next-line @typescript-eslint/ban-types
export function throttle(func: Function, wait: number): () => void {
let lastCallTime: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let lastArgs: Array<any> | undefined;
let timerId: NodeJS.Timeout | undefined;
function call() {
const args = lastArgs || [];
lastArgs = undefined;
func(...args);
}
function leading() {
timerId = setTimeout(timerExpired, wait);
call();
}
function remainingWait(time: number) {
const timeSinceLastCall = time - lastCallTime;
return wait - timeSinceLastCall;
}
function shouldInvoke(time: number) {
const timeSinceLastCall = time - lastCallTime;
return (
lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0
);
}
function timerExpired() {
const time = Date.now();
if (shouldInvoke(time)) {
return trailing();
}
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailing() {
timerId = undefined;
if (lastArgs) {
return call();
}
lastArgs = undefined;
}
return (...args) => {
const time = Date.now();
const isInvoking = shouldInvoke(time);
lastArgs = args;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leading();
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
};
}

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as semver from 'semver';
import moment from 'moment';
export const isProduction = (version: string): boolean => {
const parsed = semver.parse(version);
@ -34,7 +33,22 @@ export const generateAlphaVersion = (options: {
throw new Error(`generateAlphaVersion: Invalid version ${currentVersion}`);
}
const formattedDate = moment().utc().format('YYYYMMDD.HH');
const dateTimeParts = new Intl.DateTimeFormat('en', {
day: '2-digit',
hour: '2-digit',
hourCycle: 'h23',
month: '2-digit',
timeZone: 'GMT',
year: 'numeric',
}).formatToParts(new Date());
const dateTimeMap = new Map();
dateTimeParts.forEach(({ type, value }) => {
dateTimeMap.set(type, value);
});
const formattedDate = `${dateTimeMap.get('year')}${dateTimeMap.get(
'month'
)}${dateTimeMap.get('day')}.${dateTimeMap.get('hour')}`;
const formattedVersion = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
return `${formattedVersion}-alpha.${formattedDate}-${shortSha}`;

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as fs from 'fs';
import { isWindows } from '../OS';
import OS from './os/osMain';
const ZONE_IDENTIFIER_CONTENTS = Buffer.from('[ZoneTransfer]\r\nZoneId=3');
@ -27,7 +27,7 @@ const ZONE_IDENTIFIER_CONTENTS = Buffer.from('[ZoneTransfer]\r\nZoneId=3');
export async function writeWindowsZoneIdentifier(
filePath: string
): Promise<void> {
if (!isWindows()) {
if (!OS.isWindows()) {
throw new Error('writeWindowsZoneIdentifier should only run on Windows');
}

44
ts/window.d.ts vendored
View file

@ -3,7 +3,6 @@
// Captures the globals put in place by preload.js, background.js and others
import type { MenuItemConstructorOptions } from 'electron';
import type { Store } from 'redux';
import type * as Backbone from 'backbone';
import type PQueue from 'p-queue/dist';
@ -28,7 +27,7 @@ import type * as Groups from './groups';
import type * as Crypto from './Crypto';
import type * as Curve from './Curve';
import type * as RemoteConfig from './RemoteConfig';
import type * as OS from './OS';
import type { OSType } from './util/os/shared';
import type { getEnvironment } from './environment';
import type { LocalizerType, ThemeType } from './types/Util';
import type { Receipt } from './types/Receipt';
@ -56,6 +55,7 @@ import type { SignalContextType } from './windows/context';
import type * as Message2 from './types/Message2';
import type { initializeMigrations } from './signal';
import type { RetryPlaceholders } from './util/retryPlaceholders';
import type { PropsPreloadType as PreferencesPropsType } from './components/Preferences';
import type { LocaleDirection } from '../app/locale';
export { Long } from 'long';
@ -103,21 +103,46 @@ export type FeatureFlagType = {
GV2_MIGRATION_DISABLE_INVITE: boolean;
};
type AboutWindowType = {
type AboutWindowPropsType = {
arch: string;
environmentText: string;
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
hasCustomTitleBar: boolean;
i18n: LocalizerType;
version: string;
platform: string;
};
type DebugLogWindowPropsType = {
downloadLog: (text: string) => unknown;
fetchLogs: () => Promise<string>;
uploadLogs: (text: string) => Promise<string>;
};
type PermissionsWindowPropsType = {
forCamera: boolean;
forCalling: boolean;
onAccept: () => void;
onClose: () => void;
};
type ScreenShareWindowPropsType = {
onStopSharing: () => void;
presentedSourceName: string;
};
type SettingsOnRenderCallbackType = (props: PreferencesPropsType) => void;
type SettingsWindowPropsType = {
onRender: (callback: SettingsOnRenderCallbackType) => void;
};
export type SignalCoreType = {
AboutWindow?: AboutWindowType;
AboutWindowProps?: AboutWindowPropsType;
Crypto: typeof Crypto;
Curve: typeof Curve;
Data: typeof Data;
DebugLogWindowProps?: DebugLogWindowPropsType;
Groups: typeof Groups;
PermissionsWindowProps?: PermissionsWindowPropsType;
RemoteConfig: typeof RemoteConfig;
ScreenShareWindowProps?: ScreenShareWindowPropsType;
Services: {
calling: CallingClass;
initializeGroupCredentialFetcher: () => Promise<void>;
@ -127,6 +152,7 @@ export type SignalCoreType = {
lightSessionResetQueue?: PQueue;
storage: typeof StorageService;
};
SettingsWindowProps?: SettingsWindowPropsType;
Migrations: ReturnType<typeof initializeMigrations>;
Types: {
Message: typeof Message2;
@ -137,7 +163,7 @@ export type SignalCoreType = {
Components: {
ConfirmationDialog: typeof ConfirmationDialog;
};
OS: typeof OS;
OS: OSType;
State: {
createStore: typeof createStore;
Roots: {

View file

@ -5,20 +5,32 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { About } from '../../components/About';
import { i18n } from '../sandboxedInit';
import { strictAssert } from '../../util/assert';
const { AboutWindow } = window.Signal;
const { AboutWindowProps } = window.Signal;
strictAssert(AboutWindow, 'window values not provided');
strictAssert(AboutWindowProps, 'window values not provided');
let platform = '';
if (AboutWindowProps.platform === 'darwin') {
if (AboutWindowProps.arch === 'arm64') {
platform = ` (${window.i18n('icu:appleSilicon')})`;
} else {
platform = ' (Intel)';
}
}
const environmentText = `${AboutWindowProps.environmentText}${platform}`;
ReactDOM.render(
<About
closeAbout={() => AboutWindow.executeMenuRole('close')}
environment={AboutWindow.environmentText}
executeMenuRole={AboutWindow.executeMenuRole}
hasCustomTitleBar={AboutWindow.hasCustomTitleBar}
i18n={AboutWindow.i18n}
version={AboutWindow.version}
closeAbout={() => window.SignalContext.executeMenuRole('close')}
environment={environmentText}
executeMenuRole={window.SignalContext.executeMenuRole}
hasCustomTitleBar={window.SignalContext.OS.hasCustomTitleBar()}
i18n={i18n}
version={window.SignalContext.getVersion()}
/>,
document.getElementById('app')
);

View file

@ -1,22 +1,10 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MenuItemConstructorOptions } from 'electron';
import { contextBridge, ipcRenderer } from 'electron';
import { activeWindowService } from '../../context/activeWindowService';
import { contextBridge } from 'electron';
import { MinimalSignalContext } from '../minimalContext';
import { config } from '../../context/config';
import { createNativeThemeListener } from '../../context/createNativeThemeListener';
import { createSetting } from '../../util/preload';
import { environment } from '../../context/environment';
import { getClassName } from '../../OS';
import { i18n } from '../../context/i18n';
import { waitForSettingsChange } from '../../context/waitForSettingsChange';
async function executeMenuRole(
role: MenuItemConstructorOptions['role']
): Promise<void> {
await ipcRenderer.invoke('executeMenuRole', role);
}
const environments: Array<string> = [environment];
@ -24,40 +12,12 @@ if (config.appInstance) {
environments.push(String(config.appInstance));
}
let platform = '';
if (process.platform === 'darwin') {
if (process.arch === 'arm64') {
platform = ` (${i18n('icu:appleSilicon')})`;
} else {
platform = ' (Intel)';
}
}
const environmentText = `${environments.join(' - ')}${platform}`;
const hasCustomTitleBar = ipcRenderer.sendSync('getHasCustomTitleBar');
const Signal = {
AboutWindow: {
environmentText,
executeMenuRole,
hasCustomTitleBar,
i18n,
version: String(config.version),
AboutWindowProps: {
arch: process.arch,
environmentText: environments.join(' - '),
platform: process.platform,
},
};
contextBridge.exposeInMainWorld('Signal', Signal);
// TODO DESKTOP-5054
const SignalContext = {
activeWindowService,
OS: {
getClassName,
hasCustomTitleBar: () => hasCustomTitleBar,
},
Settings: {
themeSetting: createSetting('themeSetting', { setter: false }),
waitForChange: waitForSettingsChange,
},
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
};
contextBridge.exposeInMainWorld('SignalContext', SignalContext);
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);

View file

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

View file

@ -8,7 +8,6 @@ import type { MenuOptionsType, MenuActionType } from '../types/menu';
import type { IPCEventsValuesType } from '../util/createIPCEvents';
import type { LocalizerType } from '../types/Util';
import type { LoggerType } from '../types/Logging';
import type { LocaleMessagesType } from '../types/I18N';
import type { NativeThemeType } from '../context/createNativeThemeListener';
import type { SettingType } from '../util/preload';
import type { RendererConfigType } from '../types/RendererConfig';
@ -18,29 +17,10 @@ import { Crypto } from '../context/Crypto';
import { Timers } from '../context/Timers';
import type { ActiveWindowServiceType } from '../services/ActiveWindowService';
import { config } from '../context/config';
import { i18n } from '../context/i18n';
import { activeWindowService } from '../context/activeWindowService';
import {
getEnvironment,
parseEnvironment,
setEnvironment,
} from '../environment';
import { strictAssert } from '../util/assert';
import { createSetting } from '../util/preload';
import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
import { waitForSettingsChange } from '../context/waitForSettingsChange';
import { createNativeThemeListener } from '../context/createNativeThemeListener';
import {
isWindows,
isLinux,
isMacOS,
hasCustomTitleBar,
getClassName,
} from '../OS';
const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment));
import { MinimalSignalContext } from './minimalContext';
strictAssert(Boolean(window.SignalContext), 'context must be defined');
@ -51,89 +31,53 @@ export type MainWindowStatsType = Readonly<{
isFullScreen: boolean;
}>;
export type SignalContextType = {
bytes: Bytes;
crypto: Crypto;
timers: Timers;
nativeThemeListener: NativeThemeType;
setIsCallActive: (isCallActive: boolean) => unknown;
export type MinimalSignalContextType = {
activeWindowService: ActiveWindowServiceType;
config: RendererConfigType;
executeMenuAction: (action: MenuActionType) => Promise<void>;
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
getAppInstance: () => string | undefined;
getEnvironment: () => string;
getI18nLocale: LocalizerType['getLocale'];
getI18nLocaleMessages: LocalizerType['getLocaleMessages'];
getMainWindowStats: () => Promise<MainWindowStatsType>;
getMenuOptions: () => Promise<MenuOptionsType>;
getNodeVersion: () => string;
getPath: (name: 'userData' | 'home') => string;
getVersion: () => string;
nativeThemeListener: NativeThemeType;
Settings: {
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
waitForChange: () => Promise<void>;
};
OS: {
hasCustomTitleBar: () => boolean;
getClassName: () => string;
platform: string;
isWindows: typeof isWindows;
isLinux: typeof isLinux;
isMacOS: typeof isMacOS;
hasCustomTitleBar: typeof hasCustomTitleBar;
getClassName: typeof getClassName;
release: string;
};
config: RendererConfigType;
getAppInstance: () => string | undefined;
getEnvironment: () => string;
getNodeVersion: () => string;
getVersion: () => string;
getPath: (name: 'userData' | 'home') => string;
i18n: LocalizerType;
localeMessages: LocaleMessagesType;
log: LoggerType;
renderWindow?: () => void;
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
getMainWindowStats: () => Promise<MainWindowStatsType>;
getMenuOptions: () => Promise<MenuOptionsType>;
executeMenuAction: (action: MenuActionType) => Promise<void>;
};
export type SignalContextType = {
bytes: Bytes;
crypto: Crypto;
i18n: LocalizerType;
log: LoggerType;
renderWindow?: () => void;
setIsCallActive: (isCallActive: boolean) => unknown;
timers: Timers;
} & MinimalSignalContextType;
export const SignalContext: SignalContextType = {
activeWindowService,
Settings: {
themeSetting: createSetting('themeSetting', { setter: false }),
waitForChange: waitForSettingsChange,
},
OS: {
platform: process.platform,
isWindows,
isLinux,
isMacOS,
hasCustomTitleBar,
getClassName,
},
...MinimalSignalContext,
bytes: new Bytes(),
config,
crypto: new Crypto(),
getAppInstance: (): string | undefined =>
config.appInstance ? String(config.appInstance) : undefined,
getEnvironment,
getNodeVersion: (): string => String(config.nodeVersion),
getVersion: (): string => String(config.version),
getPath: (name: 'userData' | 'home'): string => {
return String(config[`${name}Path`]);
},
i18n,
localeMessages,
log: window.SignalContext.log,
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
setIsCallActive(isCallActive: boolean): void {
ipcRenderer.send('set-is-call-active', isCallActive);
},
timers: new Timers(),
async executeMenuRole(
role: MenuItemConstructorOptions['role']
): Promise<void> {
await ipcRenderer.invoke('executeMenuRole', role);
},
async getMainWindowStats(): Promise<MainWindowStatsType> {
return ipcRenderer.invoke('getMainWindowStats');
},
async getMenuOptions(): Promise<MenuOptionsType> {
return ipcRenderer.invoke('getMenuOptions');
},
async executeMenuAction(action: MenuActionType): Promise<void> {
return ipcRenderer.invoke('executeMenuAction', action);
},
};
window.SignalContext = SignalContext;

View file

@ -0,0 +1,25 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { render } from 'react-dom';
import { DebugLogWindow } from '../../components/DebugLogWindow';
import { i18n } from '../sandboxedInit';
import { strictAssert } from '../../util/assert';
const { DebugLogWindowProps } = window.Signal;
strictAssert(DebugLogWindowProps, 'window values not provided');
render(
<DebugLogWindow
hasCustomTitleBar={window.SignalContext.OS.hasCustomTitleBar()}
executeMenuRole={window.SignalContext.executeMenuRole}
closeWindow={() => window.SignalContext.executeMenuRole('close')}
downloadLog={DebugLogWindowProps.downloadLog}
i18n={i18n}
fetchLogs={DebugLogWindowProps.fetchLogs}
uploadLogs={DebugLogWindowProps.uploadLogs}
/>,
document.getElementById('app')
);

View file

@ -1,49 +1,32 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge, ipcRenderer } from 'electron';
import { MinimalSignalContext } from '../minimalContext';
import { SignalContext } from '../context';
import { DebugLogWindow } from '../../components/DebugLogWindow';
import * as debugLog from '../../logging/debuglogs';
import { upload } from '../../logging/uploadDebugLog';
import * as logger from '../../logging/log';
function downloadLog(logText: string) {
ipcRenderer.send('show-debug-log-save-dialog', logText);
}
contextBridge.exposeInMainWorld('SignalContext', {
...SignalContext,
renderWindow: () => {
const environmentText: Array<string> = [SignalContext.getEnvironment()];
async function fetchLogs() {
const data = await ipcRenderer.invoke('fetch-log');
return ipcRenderer.invoke(
'DebugLogs.getLogs',
data,
window.navigator.userAgent
);
}
const appInstance = SignalContext.getAppInstance();
if (appInstance) {
environmentText.push(appInstance);
}
function uploadLogs(logs: string) {
return ipcRenderer.invoke('DebugLogs.upload', logs);
}
ReactDOM.render(
React.createElement(DebugLogWindow, {
hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
executeMenuRole: SignalContext.executeMenuRole,
closeWindow: () => SignalContext.executeMenuRole('close'),
downloadLog: (logText: string) =>
ipcRenderer.send('show-debug-log-save-dialog', logText),
i18n: SignalContext.i18n,
fetchLogs() {
return debugLog.fetch(
SignalContext.getNodeVersion(),
SignalContext.getVersion()
);
},
uploadLogs(logs: string) {
return upload({
content: logs,
appVersion: SignalContext.getVersion(),
logger,
});
},
}),
document.getElementById('app')
);
const Signal = {
DebugLogWindowProps: {
downloadLog,
fetchLogs,
uploadLogs,
},
});
};
contextBridge.exposeInMainWorld('Signal', Signal);
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);

View file

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

View file

@ -1,7 +1,14 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { setupI18n } from '../../util/setupI18n';
window.i18n = setupI18n(
window.SignalContext.getI18nLocale(),
window.SignalContext.getI18nLocaleMessages()
);
const message = document.getElementById('message');
if (message) {
message.innerHTML = window.SignalContext.i18n('icu:optimizingApplication');
message.innerHTML = window.i18n('icu:optimizingApplication');
}

View file

@ -0,0 +1,56 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MenuItemConstructorOptions } from 'electron';
import { ipcRenderer } from 'electron';
import type { MenuOptionsType, MenuActionType } from '../types/menu';
import type { MainWindowStatsType, MinimalSignalContextType } from './context';
import { activeWindowService } from '../context/activeWindowService';
import { config } from '../context/config';
import { createNativeThemeListener } from '../context/createNativeThemeListener';
import { createSetting } from '../util/preload';
import { environment } from '../context/environment';
import { localeMessages } from '../context/localeMessages';
import { waitForSettingsChange } from '../context/waitForSettingsChange';
const hasCustomTitleBar = ipcRenderer.sendSync('OS.getHasCustomTitleBar');
export const MinimalSignalContext: MinimalSignalContextType = {
activeWindowService,
config,
async executeMenuAction(action: MenuActionType): Promise<void> {
return ipcRenderer.invoke('executeMenuAction', action);
},
async executeMenuRole(
role: MenuItemConstructorOptions['role']
): Promise<void> {
await ipcRenderer.invoke('executeMenuRole', role);
},
getAppInstance: (): string | undefined =>
config.appInstance ? String(config.appInstance) : undefined,
getEnvironment: () => environment,
getNodeVersion: (): string => String(config.nodeVersion),
getPath: (name: 'userData' | 'home'): string => {
return String(config[`${name}Path`]);
},
getVersion: (): string => String(config.version),
async getMainWindowStats(): Promise<MainWindowStatsType> {
return ipcRenderer.invoke('getMainWindowStats');
},
async getMenuOptions(): Promise<MenuOptionsType> {
return ipcRenderer.invoke('getMenuOptions');
},
getI18nLocale: () => config.resolvedTranslationsLocale,
getI18nLocaleMessages: () => localeMessages,
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
OS: {
getClassName: () => ipcRenderer.sendSync('OS.getClassName'),
hasCustomTitleBar: () => hasCustomTitleBar,
platform: process.platform,
release: config.osRelease,
},
Settings: {
themeSetting: createSetting('themeSetting', { setter: false }),
waitForChange: waitForSettingsChange,
},
};

View file

@ -0,0 +1,36 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { PermissionsPopup } from '../../components/PermissionsPopup';
import { i18n } from '../sandboxedInit';
import { strictAssert } from '../../util/assert';
const { PermissionsWindowProps } = window.Signal;
strictAssert(PermissionsWindowProps, 'window values not provided');
const { forCalling, forCamera } = PermissionsWindowProps;
let message;
if (forCalling) {
if (forCamera) {
message = i18n('icu:videoCallingPermissionNeeded');
} else {
message = i18n('icu:audioCallingPermissionNeeded');
}
} else {
message = i18n('icu:audioPermissionNeeded');
}
ReactDOM.render(
<PermissionsPopup
i18n={i18n}
message={message}
onAccept={PermissionsWindowProps.onAccept}
onClose={PermissionsWindowProps.onClose}
/>,
document.getElementById('app')
);

View file

@ -1,14 +1,10 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge } from 'electron';
import { SignalContext } from '../context';
import { MinimalSignalContext } from '../minimalContext';
import { createSetting } from '../../util/preload';
import { PermissionsPopup } from '../../components/PermissionsPopup';
import { drop } from '../../util/drop';
const mediaCameraPermissions = createSetting('mediaCameraPermissions', {
getter: false,
@ -17,48 +13,28 @@ const mediaPermissions = createSetting('mediaPermissions', {
getter: false,
});
contextBridge.exposeInMainWorld(
'nativeThemeListener',
window.SignalContext.nativeThemeListener
);
const params = new URLSearchParams(document.location.search);
const forCalling = params.get('forCalling') === 'true';
const forCamera = params.get('forCamera') === 'true';
contextBridge.exposeInMainWorld('SignalContext', {
...SignalContext,
renderWindow: () => {
const params = new URLSearchParams(document.location.search);
const forCalling = params.get('forCalling') === 'true';
const forCamera = params.get('forCamera') === 'true';
function onClose() {
drop(MinimalSignalContext.executeMenuRole('close'));
}
let message;
if (forCalling) {
if (forCamera) {
message = SignalContext.i18n('icu:videoCallingPermissionNeeded');
const Signal = {
PermissionsWindowProps: {
forCalling,
forCamera,
onAccept: () => {
if (!forCamera) {
drop(mediaPermissions.setValue(true));
} else {
message = SignalContext.i18n('icu:audioCallingPermissionNeeded');
drop(mediaCameraPermissions.setValue(true));
}
} else {
message = SignalContext.i18n('icu:audioPermissionNeeded');
}
function onClose() {
void SignalContext.executeMenuRole('close');
}
ReactDOM.render(
React.createElement(PermissionsPopup, {
i18n: SignalContext.i18n,
message,
onAccept: () => {
if (!forCamera) {
void mediaPermissions.setValue(true);
} else {
void mediaCameraPermissions.setValue(true);
}
onClose();
},
onClose,
}),
document.getElementById('app')
);
onClose();
},
onClose,
},
});
};
contextBridge.exposeInMainWorld('Signal', Signal);
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);

View file

@ -0,0 +1,15 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import './applyTheme';
import { setupI18n } from '../util/setupI18n';
document.body.classList.add(window.SignalContext.OS.getClassName());
if (window.SignalContext.OS.hasCustomTitleBar()) {
document.body.classList.add('os-has-custom-titlebar');
}
export const i18n = setupI18n(
window.SignalContext.getI18nLocale(),
window.SignalContext.getI18nLocaleMessages()
);

View file

@ -0,0 +1,24 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { CallingScreenSharingController } from '../../components/CallingScreenSharingController';
import { i18n } from '../sandboxedInit';
import { strictAssert } from '../../util/assert';
const { ScreenShareWindowProps } = window.Signal;
strictAssert(ScreenShareWindowProps, 'window values not provided');
ReactDOM.render(
<CallingScreenSharingController
i18n={i18n}
onCloseController={() => window.SignalContext.executeMenuRole('close')}
onStopSharing={ScreenShareWindowProps.onStopSharing}
presentedSourceName={ScreenShareWindowProps.presentedSourceName}
/>,
document.getElementById('app')
);

View file

@ -1,29 +1,18 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge, ipcRenderer } from 'electron';
import { MinimalSignalContext } from '../minimalContext';
import { SignalContext } from '../context';
import { CallingScreenSharingController } from '../../components/CallingScreenSharingController';
const params = new URLSearchParams(document.location.search);
contextBridge.exposeInMainWorld('SignalContext', SignalContext);
function renderScreenSharingController(presentedSourceName: string): void {
ReactDOM.render(
React.createElement(CallingScreenSharingController, {
platform: process.platform,
executeMenuRole: SignalContext.executeMenuRole,
i18n: SignalContext.i18n,
onCloseController: () => SignalContext.executeMenuRole('close'),
onStopSharing: () => ipcRenderer.send('stop-screen-share'),
presentedSourceName,
}),
document.getElementById('app')
);
}
ipcRenderer.once('render-screen-sharing-controller', (_, name: string) => {
renderScreenSharingController(name);
});
const Signal = {
ScreenShareWindowProps: {
onStopSharing: () => {
ipcRenderer.send('stop-screen-share');
},
presentedSourceName: params.get('sourceName'),
},
};
contextBridge.exposeInMainWorld('Signal', Signal);
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);

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

@ -0,0 +1,221 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import type { PropsPreloadType } from '../../components/Preferences';
import { i18n } from '../sandboxedInit';
import { Preferences } from '../../components/Preferences';
import { startInteractionMode } from '../../services/InteractionMode';
import { strictAssert } from '../../util/assert';
const { SettingsWindowProps } = window.Signal;
strictAssert(SettingsWindowProps, 'window values not provided');
startInteractionMode();
SettingsWindowProps.onRender(
({
addCustomColor,
availableCameras,
availableMicrophones,
availableSpeakers,
blockedCount,
closeSettings,
customColors,
defaultConversationColor,
deviceName,
doDeleteAllData,
doneRendering,
editCustomColor,
executeMenuRole,
getConversationsWithCustomColor,
hasAudioNotifications,
hasAutoDownloadUpdate,
hasAutoLaunch,
hasCallNotifications,
hasCallRingtoneNotification,
hasCountMutedConversations,
hasCustomTitleBar,
hasHideMenuBar,
hasIncomingCallNotifications,
hasLinkPreviews,
hasMediaCameraPermissions,
hasMediaPermissions,
hasMinimizeToAndStartInSystemTray,
hasMinimizeToSystemTray,
hasNotificationAttention,
hasNotifications,
hasReadReceipts,
hasRelayCalls,
hasSpellCheck,
hasStoriesDisabled,
hasTextFormatting,
hasTypingIndicators,
initialSpellCheckSetting,
isAudioNotificationsSupported,
isAutoDownloadUpdatesSupported,
isAutoLaunchSupported,
isFormattingFlagEnabled,
isHideMenuBarSupported,
isMinimizeToAndStartInSystemTraySupported,
isNotificationAttentionSupported,
isPhoneNumberSharingSupported,
isSyncSupported,
isSystemTraySupported,
lastSyncTime,
makeSyncRequest,
notificationContent,
onAudioNotificationsChange,
onAutoDownloadUpdateChange,
onAutoLaunchChange,
onCallNotificationsChange,
onCallRingtoneNotificationChange,
onCountMutedConversationsChange,
onHasStoriesDisabledChanged,
onHideMenuBarChange,
onIncomingCallNotificationsChange,
onLastSyncTimeChange,
onMediaCameraPermissionsChange,
onMediaPermissionsChange,
onMinimizeToAndStartInSystemTrayChange,
onMinimizeToSystemTrayChange,
onNotificationAttentionChange,
onNotificationContentChange,
onNotificationsChange,
onRelayCallsChange,
onSelectedCameraChange,
onSelectedMicrophoneChange,
onSelectedSpeakerChange,
onSentMediaQualityChange,
onSpellCheckChange,
onTextFormattingChange,
onThemeChange,
onUniversalExpireTimerChange,
onWhoCanFindMeChange,
onWhoCanSeeMeChange,
onZoomFactorChange,
removeCustomColor,
removeCustomColorOnConversations,
resetAllChatColors,
resetDefaultChatColor,
selectedCamera,
selectedMicrophone,
selectedSpeaker,
sentMediaQualitySetting,
setGlobalDefaultConversationColor,
shouldShowStoriesSettings,
themeSetting,
universalExpireTimer,
whoCanFindMe,
whoCanSeeMe,
zoomFactor,
}: PropsPreloadType) => {
ReactDOM.render(
<Preferences
addCustomColor={addCustomColor}
availableCameras={availableCameras}
availableMicrophones={availableMicrophones}
availableSpeakers={availableSpeakers}
blockedCount={blockedCount}
closeSettings={closeSettings}
customColors={customColors}
defaultConversationColor={defaultConversationColor}
deviceName={deviceName}
doDeleteAllData={doDeleteAllData}
doneRendering={doneRendering}
editCustomColor={editCustomColor}
executeMenuRole={executeMenuRole}
getConversationsWithCustomColor={getConversationsWithCustomColor}
hasAudioNotifications={hasAudioNotifications}
hasAutoDownloadUpdate={hasAutoDownloadUpdate}
hasAutoLaunch={hasAutoLaunch}
hasCallNotifications={hasCallNotifications}
hasCallRingtoneNotification={hasCallRingtoneNotification}
hasCountMutedConversations={hasCountMutedConversations}
hasCustomTitleBar={hasCustomTitleBar}
hasHideMenuBar={hasHideMenuBar}
hasIncomingCallNotifications={hasIncomingCallNotifications}
hasLinkPreviews={hasLinkPreviews}
hasMediaCameraPermissions={hasMediaCameraPermissions}
hasMediaPermissions={hasMediaPermissions}
hasMinimizeToAndStartInSystemTray={hasMinimizeToAndStartInSystemTray}
hasMinimizeToSystemTray={hasMinimizeToSystemTray}
hasNotificationAttention={hasNotificationAttention}
hasNotifications={hasNotifications}
hasReadReceipts={hasReadReceipts}
hasRelayCalls={hasRelayCalls}
hasSpellCheck={hasSpellCheck}
hasStoriesDisabled={hasStoriesDisabled}
hasTextFormatting={hasTextFormatting}
hasTypingIndicators={hasTypingIndicators}
i18n={i18n}
initialSpellCheckSetting={initialSpellCheckSetting}
isAudioNotificationsSupported={isAudioNotificationsSupported}
isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported}
isAutoLaunchSupported={isAutoLaunchSupported}
isFormattingFlagEnabled={isFormattingFlagEnabled}
isHideMenuBarSupported={isHideMenuBarSupported}
isMinimizeToAndStartInSystemTraySupported={
isMinimizeToAndStartInSystemTraySupported
}
isNotificationAttentionSupported={isNotificationAttentionSupported}
isPhoneNumberSharingSupported={isPhoneNumberSharingSupported}
isSyncSupported={isSyncSupported}
isSystemTraySupported={isSystemTraySupported}
lastSyncTime={lastSyncTime}
makeSyncRequest={makeSyncRequest}
notificationContent={notificationContent}
onAudioNotificationsChange={onAudioNotificationsChange}
onAutoDownloadUpdateChange={onAutoDownloadUpdateChange}
onAutoLaunchChange={onAutoLaunchChange}
onCallNotificationsChange={onCallNotificationsChange}
onCallRingtoneNotificationChange={onCallRingtoneNotificationChange}
onCountMutedConversationsChange={onCountMutedConversationsChange}
onHasStoriesDisabledChanged={onHasStoriesDisabledChanged}
onHideMenuBarChange={onHideMenuBarChange}
onIncomingCallNotificationsChange={onIncomingCallNotificationsChange}
onLastSyncTimeChange={onLastSyncTimeChange}
onMediaCameraPermissionsChange={onMediaCameraPermissionsChange}
onMediaPermissionsChange={onMediaPermissionsChange}
onMinimizeToAndStartInSystemTrayChange={
onMinimizeToAndStartInSystemTrayChange
}
onMinimizeToSystemTrayChange={onMinimizeToSystemTrayChange}
onNotificationAttentionChange={onNotificationAttentionChange}
onNotificationContentChange={onNotificationContentChange}
onNotificationsChange={onNotificationsChange}
onRelayCallsChange={onRelayCallsChange}
onSelectedCameraChange={onSelectedCameraChange}
onSelectedMicrophoneChange={onSelectedMicrophoneChange}
onSelectedSpeakerChange={onSelectedSpeakerChange}
onSentMediaQualityChange={onSentMediaQualityChange}
onSpellCheckChange={onSpellCheckChange}
onTextFormattingChange={onTextFormattingChange}
onThemeChange={onThemeChange}
onUniversalExpireTimerChange={onUniversalExpireTimerChange}
onWhoCanFindMeChange={onWhoCanFindMeChange}
onWhoCanSeeMeChange={onWhoCanSeeMeChange}
onZoomFactorChange={onZoomFactorChange}
removeCustomColorOnConversations={removeCustomColorOnConversations}
removeCustomColor={removeCustomColor}
resetAllChatColors={resetAllChatColors}
resetDefaultChatColor={resetDefaultChatColor}
selectedCamera={selectedCamera}
selectedMicrophone={selectedMicrophone}
selectedSpeaker={selectedSpeaker}
sentMediaQualitySetting={sentMediaQualitySetting}
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
shouldShowStoriesSettings={shouldShowStoriesSettings}
themeSetting={themeSetting}
universalExpireTimer={universalExpireTimer}
whoCanFindMe={whoCanFindMe}
whoCanSeeMe={whoCanSeeMe}
zoomFactor={zoomFactor}
/>,
document.getElementById('app')
);
}
);

View file

@ -1,13 +1,12 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge, ipcRenderer } from 'electron';
import { MinimalSignalContext } from '../minimalContext';
import { SignalContext } from '../context';
import type { PropsPreloadType } from '../../components/Preferences';
import OS from '../../util/os/osPreload';
import * as Settings from '../../types/Settings';
import { Preferences } from '../../components/Preferences';
import {
SystemTraySetting,
parseSystemTraySetting,
@ -16,7 +15,6 @@ import {
import { awaitObject } from '../../util/awaitObject';
import { DurationInSeconds } from '../../util/durations';
import { createSetting, createCallback } from '../../util/preload';
import { startInteractionMode } from '../../services/InteractionMode';
function doneRendering() {
ipcRenderer.send('settings-done-rendering');
@ -128,9 +126,18 @@ function getSystemTraySettingValues(systemTraySetting: SystemTraySetting): {
};
}
const renderPreferences = async () => {
startInteractionMode();
let renderInBrowser = (_props: PropsPreloadType): void => {
throw new Error('render is not defined');
};
function attachRenderCallback<Value>(f: (value: Value) => Promise<Value>) {
return async (value: Value) => {
await f(value);
void renderPreferences();
};
}
async function renderPreferences() {
const {
blockedCount,
deviceName,
@ -222,7 +229,7 @@ const renderPreferences = async () => {
const { hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray } =
getSystemTraySettingValues(systemTraySetting);
const onUniversalExpireTimerChange = reRender(
const onUniversalExpireTimerChange = attachRenderCallback(
settingUniversalExpireTimer.setValue
);
@ -270,13 +277,13 @@ const renderPreferences = async () => {
// Actions and other props
addCustomColor: ipcAddCustomColor,
closeSettings: () => SignalContext.executeMenuRole('close'),
closeSettings: () => MinimalSignalContext.executeMenuRole('close'),
doDeleteAllData: () => ipcRenderer.send('delete-all-data'),
doneRendering,
editCustomColor: ipcEditCustomColor,
getConversationsWithCustomColor: ipcGetConversationsWithCustomColor,
initialSpellCheckSetting:
SignalContext.config.appStartInitialSpellcheckSetting,
MinimalSignalContext.config.appStartInitialSpellcheckSetting,
makeSyncRequest: ipcMakeSyncRequest,
removeCustomColor: ipcRemoveCustomColor,
removeCustomColorOnConversations: ipcRemoveCustomColorOnConversations,
@ -286,93 +293,121 @@ const renderPreferences = async () => {
shouldShowStoriesSettings,
// Limited support features
isAudioNotificationsSupported: Settings.isAudioNotificationSupported(),
isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(),
isAutoLaunchSupported: Settings.isAutoLaunchSupported(),
isHideMenuBarSupported: Settings.isHideMenuBarSupported(),
isNotificationAttentionSupported: Settings.isDrawAttentionSupported(),
isAudioNotificationsSupported: Settings.isAudioNotificationSupported(OS),
isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(OS),
isAutoLaunchSupported: Settings.isAutoLaunchSupported(OS),
isHideMenuBarSupported: Settings.isHideMenuBarSupported(OS),
isNotificationAttentionSupported: Settings.isDrawAttentionSupported(OS),
isPhoneNumberSharingSupported,
isSyncSupported: !isSyncNotSupported,
isSystemTraySupported: Settings.isSystemTraySupported(
SignalContext.getVersion()
OS,
MinimalSignalContext.getVersion()
),
isMinimizeToAndStartInSystemTraySupported:
Settings.isMinimizeToAndStartInSystemTraySupported(
SignalContext.getVersion()
OS,
MinimalSignalContext.getVersion()
),
// Feature flags
isFormattingFlagEnabled,
// Change handlers
onAudioNotificationsChange: reRender(settingAudioNotification.setValue),
onAutoDownloadUpdateChange: reRender(settingAutoDownloadUpdate.setValue),
onAutoLaunchChange: reRender(settingAutoLaunch.setValue),
onCallNotificationsChange: reRender(settingCallSystemNotification.setValue),
onCallRingtoneNotificationChange: reRender(
onAudioNotificationsChange: attachRenderCallback(
settingAudioNotification.setValue
),
onAutoDownloadUpdateChange: attachRenderCallback(
settingAutoDownloadUpdate.setValue
),
onAutoLaunchChange: attachRenderCallback(settingAutoLaunch.setValue),
onCallNotificationsChange: attachRenderCallback(
settingCallSystemNotification.setValue
),
onCallRingtoneNotificationChange: attachRenderCallback(
settingCallRingtoneNotification.setValue
),
onCountMutedConversationsChange: reRender(
onCountMutedConversationsChange: attachRenderCallback(
settingCountMutedConversations.setValue
),
onHasStoriesDisabledChanged: reRender(async (value: boolean) => {
await settingHasStoriesDisabled.setValue(value);
if (!value) {
void ipcDeleteAllMyStories();
onHasStoriesDisabledChanged: attachRenderCallback(
async (value: boolean) => {
await settingHasStoriesDisabled.setValue(value);
if (!value) {
void ipcDeleteAllMyStories();
}
return value;
}
return value;
}),
onHideMenuBarChange: reRender(settingHideMenuBar.setValue),
onIncomingCallNotificationsChange: reRender(
),
onHideMenuBarChange: attachRenderCallback(settingHideMenuBar.setValue),
onIncomingCallNotificationsChange: attachRenderCallback(
settingIncomingCallNotification.setValue
),
onLastSyncTimeChange: reRender(settingLastSyncTime.setValue),
onMediaCameraPermissionsChange: reRender(
onLastSyncTimeChange: attachRenderCallback(settingLastSyncTime.setValue),
onMediaCameraPermissionsChange: attachRenderCallback(
settingMediaCameraPermissions.setValue
),
onMinimizeToAndStartInSystemTrayChange: reRender(async (value: boolean) => {
await settingSystemTraySetting.setValue(
value
? SystemTraySetting.MinimizeToAndStartInSystemTray
: SystemTraySetting.MinimizeToSystemTray
);
return value;
}),
onMinimizeToSystemTrayChange: reRender(async (value: boolean) => {
await settingSystemTraySetting.setValue(
value
? SystemTraySetting.MinimizeToSystemTray
: SystemTraySetting.DoNotUseSystemTray
);
return value;
}),
onMediaPermissionsChange: reRender(settingMediaPermissions.setValue),
onNotificationAttentionChange: reRender(
onMinimizeToAndStartInSystemTrayChange: attachRenderCallback(
async (value: boolean) => {
await settingSystemTraySetting.setValue(
value
? SystemTraySetting.MinimizeToAndStartInSystemTray
: SystemTraySetting.MinimizeToSystemTray
);
return value;
}
),
onMinimizeToSystemTrayChange: attachRenderCallback(
async (value: boolean) => {
await settingSystemTraySetting.setValue(
value
? SystemTraySetting.MinimizeToSystemTray
: SystemTraySetting.DoNotUseSystemTray
);
return value;
}
),
onMediaPermissionsChange: attachRenderCallback(
settingMediaPermissions.setValue
),
onNotificationAttentionChange: attachRenderCallback(
settingNotificationDrawAttention.setValue
),
onNotificationContentChange: reRender(settingNotificationSetting.setValue),
onNotificationsChange: reRender(async (value: boolean) => {
onNotificationContentChange: attachRenderCallback(
settingNotificationSetting.setValue
),
onNotificationsChange: attachRenderCallback(async (value: boolean) => {
await settingNotificationSetting.setValue(
value ? DEFAULT_NOTIFICATION_SETTING : 'off'
);
return value;
}),
onRelayCallsChange: reRender(settingRelayCalls.setValue),
onSelectedCameraChange: reRender(settingVideoInput.setValue),
onSelectedMicrophoneChange: reRender(settingAudioInput.setValue),
onSelectedSpeakerChange: reRender(settingAudioOutput.setValue),
onSentMediaQualityChange: reRender(settingSentMediaQuality.setValue),
onSpellCheckChange: reRender(settingSpellCheck.setValue),
onTextFormattingChange: reRender(settingTextFormatting.setValue),
onThemeChange: reRender(settingTheme.setValue),
onRelayCallsChange: attachRenderCallback(settingRelayCalls.setValue),
onSelectedCameraChange: attachRenderCallback(settingVideoInput.setValue),
onSelectedMicrophoneChange: attachRenderCallback(
settingAudioInput.setValue
),
onSelectedSpeakerChange: attachRenderCallback(settingAudioOutput.setValue),
onSentMediaQualityChange: attachRenderCallback(
settingSentMediaQuality.setValue
),
onSpellCheckChange: attachRenderCallback(settingSpellCheck.setValue),
onTextFormattingChange: attachRenderCallback(
settingTextFormatting.setValue
),
onThemeChange: attachRenderCallback(settingTheme.setValue),
onUniversalExpireTimerChange: (newValue: number): Promise<void> => {
return onUniversalExpireTimerChange(
DurationInSeconds.fromSeconds(newValue)
);
},
onWhoCanFindMeChange: reRender(settingPhoneNumberDiscoverability.setValue),
onWhoCanSeeMeChange: reRender(settingPhoneNumberSharing.setValue),
onWhoCanFindMeChange: attachRenderCallback(
settingPhoneNumberDiscoverability.setValue
),
onWhoCanSeeMeChange: attachRenderCallback(
settingPhoneNumberSharing.setValue
),
// Zoom factor change doesn't require immediate rerender since it will:
// 1. Update the zoom factor in the main window
@ -381,28 +416,22 @@ const renderPreferences = async () => {
// rerender.
onZoomFactorChange: settingZoomFactor.setValue,
i18n: SignalContext.i18n,
hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
executeMenuRole: SignalContext.executeMenuRole,
hasCustomTitleBar: MinimalSignalContext.OS.hasCustomTitleBar(),
executeMenuRole: MinimalSignalContext.executeMenuRole,
};
function reRender<Value>(f: (value: Value) => Promise<Value>) {
return async (value: Value) => {
await f(value);
void renderPreferences();
};
}
ReactDOM.render(
React.createElement(Preferences, props),
document.getElementById('app')
);
};
renderInBrowser(props);
}
ipcRenderer.on('preferences-changed', () => renderPreferences());
contextBridge.exposeInMainWorld('SignalContext', {
...SignalContext,
renderWindow: renderPreferences,
});
const Signal = {
SettingsWindowProps: {
onRender: (renderer: (_props: PropsPreloadType) => void) => {
renderInBrowser = renderer;
void renderPreferences();
},
},
};
contextBridge.exposeInMainWorld('Signal', Signal);
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);