Faster preferences window

This commit is contained in:
Josh Perez 2021-08-18 16:08:14 -04:00 committed by GitHub
parent ac55b8d643
commit 91af0dad78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 3567 additions and 2093 deletions

View File

@ -1246,7 +1246,7 @@
"message": "Link New Device",
"description": "The menu option shown in Signal iOS to add a new linked device"
},
"deviceName": {
"Preferences--device-name": {
"message": "Device name",
"description": "The label in settings panel shown for the user-provided name for this desktop instance"
},
@ -1278,7 +1278,7 @@
"installTryAgain": {
"message": "Try again"
},
"theme": {
"Preferences--theme": {
"message": "Theme",
"description": "Header for theme settings"
},
@ -1614,7 +1614,7 @@
"message": "Relay all calls through the Signal server to avoid revealing your IP address to your contact. Enabling will reduce call quality.",
"description": "Details describing the always relay calls setting"
},
"permissions": {
"Preferences--permissions": {
"message": "Permissions",
"description": "Header for permissions section of settings"
},
@ -1631,7 +1631,7 @@
"description": "Header for general options on the settings screen"
},
"spellCheckDescription": {
"message": "Enable spell check",
"message": "Spell check text entered in message composition box",
"description": "Description of the spell check setting"
},
"spellCheckWillBeEnabled": {
@ -1655,7 +1655,7 @@
"description": "Description for the automatic launch setting"
},
"clearDataHeader": {
"message": "Clear Data",
"message": "Clear application data",
"description": "Header in the settings dialog for the section dealing with data deletion"
},
"clearDataExplanation": {
@ -1803,7 +1803,7 @@
"description": "Error message displayed when sending to an unregistered user."
},
"sync": {
"message": "Contacts",
"message": "Import contacts",
"description": "Label for contact and group sync settings"
},
"syncExplanation": {
@ -1963,7 +1963,7 @@
}
},
"audioNotificationDescription": {
"message": "Play audio notification",
"message": "Play notification sounds",
"description": "Description for audio notification setting"
},
"callRingtoneNotificationDescription": {
@ -5009,7 +5009,7 @@
"description": "Displayed while checking if the contact is SMS-only"
},
"countMutedConversationsDescription": {
"message": "Count muted conversations in badge count",
"message": "Include muted conversations in badge count",
"description": "Description for counting muted conversations in badge setting"
},
"ContactModal--message": {
@ -5965,5 +5965,131 @@
"LeftPaneSetGroupMetadataHelper__avatar-modal-title": {
"message": "Group Avatar",
"description": "Title for the avatar picker in the group creation flow"
},
"Preferences__button--general": {
"message": "General",
"description": "Button to switch the settings view"
},
"Preferences__button--appearance": {
"message": "Appearance",
"description": "Button to switch the settings view"
},
"Preferences__button--chats": {
"message": "Chats",
"description": "Button to switch the settings view"
},
"Preferences__button--calls": {
"message": "Calls",
"description": "Button to switch the settings view"
},
"Preferences__button--notifications": {
"message": "Notifications",
"description": "Button to switch the settings view"
},
"Preferences__button--privacy": {
"message": "Privacy",
"description": "Button to switch the settings view"
},
"Preferences--lastSynced": {
"message": "Last import at $date$ $time$",
"description": "Label for date and time of last sync operation",
"placeholders": {
"date": {
"content": "$1",
"example": "6/9/2020"
},
"time": {
"content": "$2",
"example": "7:37:25 PM"
}
}
},
"Preferences--system": {
"message": "System",
"description": "Title for system type settings"
},
"Preferences--zoom": {
"message": "Zoom level",
"description": "Label for changing the zoom level"
},
"Preferences__link-previews--title": {
"message": "Generate link previews",
"description": "Title for the generate link previews setting"
},
"Preferences__link-previews--description": {
"message": "To change this setting, open the Signal app on your mobile device and navigate to Settings > Chats",
"description": "Description for the generate link previews setting"
},
"Preferences--advanced": {
"message": "Advanced",
"description": "Title for advanced settings"
},
"Preferences--notification-content": {
"message": "Notification content",
"description": "Label for the notification content setting select box"
},
"Preferences--blocked": {
"message": "Blocked",
"description": "Label for blocked contacts setting"
},
"Preferences--blocked-count-singular": {
"message": "$num$ contact",
"description": "Number of contacts blocked singular",
"placeholders": {
"num": {
"content": "$1",
"example": "1"
}
}
},
"Preferences--blocked-count-plural": {
"message": "$num$ contacts",
"description": "Number of contacts blocked plural",
"placeholders": {
"num": {
"content": "$1",
"example": "20"
}
}
},
"Preferences__who-can--title": {
"message": "Who can...",
"description": "Title for the 'who can do X' setting"
},
"Preferences__privacy--description": {
"message": "To change these settings, open the Signal app on your mobile device and navigate to Settings > Privacy",
"description": "Description for the 'who can do X' setting"
},
"Preferences__who-can--everybody": {
"message": "Everybody",
"description": "Option for who can see my X select"
},
"Preferences__who-can--contacts": {
"message": "My Contacts",
"description": "Option for who can see my X select"
},
"Preferences__who-can--nobody": {
"message": "Nobody",
"description": "Option for who can see my X select"
},
"Preferences--messaging": {
"message": "Messaging",
"description": "Title for the messaging settings"
},
"Preferences--see-me": {
"message": "See my phone number",
"description": "Label for the see my phone number setting"
},
"Preferences--find-me": {
"message": "Find me by my phone number",
"description": "Label for the find me by my phone number setting"
},
"Preferences--read-receipts": {
"message": "Read receipts",
"description": "Label for the read receipts setting"
},
"Preferences--typing-indicators": {
"message": "Typing indicators",
"description": "Label for the typing indicators setting"
}
}

View File

@ -10,7 +10,7 @@ import { start } from './base_config';
const userDataPath = app.getPath('userData');
const targetPath = join(userDataPath, 'ephemeral.json');
const ephemeralConfig = start('ephemeral', targetPath, {
export const ephemeralConfig = start('ephemeral', targetPath, {
allowMalformedOnStartup: true,
});

View File

@ -24,7 +24,7 @@ const PERMISSIONS: Record<string, boolean> = {
};
function _createPermissionHandler(
userConfig: ConfigType
userConfig: Pick<ConfigType, 'get'>
): Parameters<typeof ElectronSession.prototype.setPermissionRequestHandler>[0] {
return (_webContents, permission, callback, details): void => {
// We default 'media' permission to false, but the user can override that for
@ -75,7 +75,7 @@ export function installPermissionsHandler({
userConfig,
}: {
session: typeof ElectronSession;
userConfig: ConfigType;
userConfig: Pick<ConfigType, 'get'>;
}): void {
// Setting the permission request handler to null first forces any permissions to be
// requested again. Without this, revoked permissions might still be available if

View File

@ -24,7 +24,7 @@ console.log(`userData: ${app.getPath('userData')}`);
const userDataPath = app.getPath('userData');
const targetPath = join(userDataPath, 'config.json');
const userConfig = start('user', targetPath);
export const userConfig = start('user', targetPath);
export const get = userConfig.get.bind(userConfig);
export const remove = userConfig.remove.bind(userConfig);

View File

@ -6,6 +6,10 @@
const { ipcRenderer } = require('electron');
const url = require('url');
const copyText = require('copy-text-to-clipboard');
// It is important to call this as early as possible
require('./ts/windows/context');
const i18n = require('./js/modules/i18n');
const {
getEnvironment,
@ -13,10 +17,6 @@ const {
parseEnvironment,
} = require('./ts/environment');
const { Context: SignalContext } = require('./ts/context');
window.SignalContext = new SignalContext(ipcRenderer);
const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
const localeMessages = ipcRenderer.sendSync('locale-data');

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m12 7.5a4.5 4.5 0 1 1 -4.5 4.5 4.505 4.505 0 0 1 4.5-4.5m0-1.5a6 6 0 1 0 6 6 6 6 0 0 0 -6-6zm-7.5 5.25h-3.5v1.5h3.5zm18.5 0h-3.5v1.5h3.5zm-10.25 8.25h-1.5v3.5h1.5zm0-18.5h-1.5v3.5h1.5zm-5.523 5.167-2.475-2.476-1.061 1.061 2.475 2.475zm13.082 13.081-2.475-2.475-1.061 1.061 2.475 2.475zm-13.082-1.414-1.061-1.061-2.475 2.475 1.061 1.061zm13.082-13.082-1.061-1.061-2.475 2.476 1.061 1.06z"/></svg>

After

Width:  |  Height:  |  Size: 487 B

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18 12a6 6 0 1 1 -6-6 6 6 0 0 1 6 6zm-13.5-.75h-3.5v1.5h3.5zm18.5 0h-3.5v1.5h3.5zm-10.25 8.25h-1.5v3.5h1.5zm0-18.5h-1.5v3.5h1.5zm-5.523 5.167-2.475-2.476-1.061 1.061 2.475 2.475zm13.082 13.081-2.475-2.475-1.061 1.061 2.475 2.475zm-13.082-1.414-1.061-1.061-2.475 2.475 1.061 1.061zm13.082-13.082-1.061-1.061-2.475 2.476 1.061 1.06z"/></svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14.45 20.5a2.5 2.5 0 0 1 -4.9 0zm-2.45-17a4.521 4.521 0 0 0 -4.4 3.544l-1.42 6.832a5.537 5.537 0 0 1 -2.674 3.624h16.982a5.511 5.511 0 0 1 -2.664-3.6l-1.431-6.867a4.517 4.517 0 0 0 -4.393-3.533m0-1.5a6 6 0 0 1 5.862 4.727l1.427 6.843a4.033 4.033 0 0 0 1.975 2.646 1.5 1.5 0 0 1 -.764 2.784h-17a1.5 1.5 0 0 1 -.732-2.8 4.036 4.036 0 0 0 1.943-2.63l1.427-6.843a6 6 0 0 1 5.862-4.727z"/></svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14.45 20.5a2.5 2.5 0 0 1 -4.9 0zm6.814-4.284a4.033 4.033 0 0 1 -1.975-2.646l-1.427-6.843a6 6 0 0 0 -11.724 0l-1.427 6.843a4.036 4.036 0 0 1 -1.943 2.63 1.5 1.5 0 0 0 .732 2.8h17a1.5 1.5 0 0 0 .764-2.784z"/></svg>

After

Width:  |  Height:  |  Size: 305 B

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m17 9v-3a5 5 0 0 0 -10 0v3a3 3 0 0 0 -3 3v7a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3v-7a3 3 0 0 0 -3-3zm-8.5-3a3.5 3.5 0 0 1 7 0v3h-7zm4.25 9.792v2.208h-1.5v-2.208a1.5 1.5 0 1 1 1.5 0z"/></svg>

After

Width:  |  Height:  |  Size: 275 B

View File

@ -12,7 +12,7 @@ $(document).on('keydown', e => {
const $body = $(document.body);
async function applyTheme() {
const theme = await window.getThemeSetting();
const theme = await window.Settings.themeSetting.getValue();
$body.removeClass('light-theme');
$body.removeClass('dark-theme');
$body.addClass(`${theme === 'system' ? window.systemTheme : theme}-theme`);
@ -41,9 +41,9 @@ window.showConfirmationDialog({
okText: i18n('allowAccess'),
resolve: () => {
if (!window.forCamera) {
window.setMediaPermissions(true);
window.Settings.mediaPermissions.setValue(true);
} else {
window.setMediaCameraPermissions(true);
window.Settings.mediaCameraPermissions.setValue(true);
}
window.closePermissionsPopup();
},

View File

@ -1,71 +0,0 @@
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global $, Whisper */
$(document).on('keydown', e => {
if (e.keyCode === 27) {
window.closeSettings();
}
});
const $body = $(document.body);
async function applyTheme() {
const theme = await window.getThemeSetting();
$body.removeClass('light-theme');
$body.removeClass('dark-theme');
$body.addClass(`${theme === 'system' ? window.systemTheme : theme}-theme`);
}
applyTheme();
window.SignalContext.nativeThemeListener.subscribe(() => {
applyTheme();
});
const getInitialData = async () => ({
deviceName: await window.getDeviceName(),
themeSetting: await window.getThemeSetting(),
hideMenuBar: await window.getHideMenuBar(),
systemTray: await window.getSystemTraySetting(),
notificationSetting: await window.getNotificationSetting(),
audioNotification: await window.getAudioNotification(),
notificationDrawAttention: await window.getNotificationDrawAttention(),
countMutedConversations: await window.getCountMutedConversations(),
spellCheck: await window.getSpellCheck(),
autoLaunch: await window.getAutoLaunch(),
incomingCallNotification: await window.getIncomingCallNotification(),
callRingtoneNotification: await window.getCallRingtoneNotification(),
callSystemNotification: await window.getCallSystemNotification(),
alwaysRelayCalls: await window.getAlwaysRelayCalls(),
mediaPermissions: await window.getMediaPermissions(),
mediaCameraPermissions: await window.getMediaCameraPermissions(),
isPrimary: await window.isPrimary(),
lastSyncTime: await window.getLastSyncTime(),
universalExpireTimer: await window.getUniversalExpireTimer(),
});
window.initialRequest = getInitialData();
// eslint-disable-next-line more/no-then
window.initialRequest.then(
data => {
window.initialData = data;
window.view = new Whisper.SettingsView();
window.view.$el.appendTo($body);
},
error => {
window.log.error(
'settings.initialRequest error:',
error && error.stack ? error.stack : error
);
window.closeSettings();
}
);

View File

@ -1,476 +0,0 @@
// Copyright 2016-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global i18n: false */
/* global Whisper: false */
/* global $: false */
/* eslint-disable no-new */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
const { Settings } = window.Signal.Types;
const {
DEFAULT_DURATIONS_IN_SECONDS,
DEFAULT_DURATIONS_SET,
format: formatExpirationTimer,
} = window.Signal.Util.expirationTimer;
const CheckboxView = Whisper.View.extend({
initialize(options) {
this.name = options.name;
this.setFn = options.setFn;
this.value = options.value;
this.populate();
},
events: {
change: 'change',
},
change(e) {
const value = e.target.checked;
this.setFn(value);
window.log.info(this.name, 'changed to', value);
},
populate() {
this.$('input').prop('checked', !!this.value);
},
});
const MediaPermissionsSettingView = Whisper.View.extend({
initialize(options) {
this.value = options.value;
this.setFn = options.setFn;
this.populate();
},
events: {
change: 'change',
},
change(e) {
this.value = e.target.checked;
this.setFn(this.value);
window.log.info('media-permissions changed to', this.value);
},
populate() {
this.$('input').prop('checked', Boolean(this.value));
},
});
const MediaCameraPermissionsSettingView = Whisper.View.extend({
initialize(options) {
this.value = options.value;
this.setFn = options.setFn;
this.populate();
},
events: {
change: 'change',
},
change(e) {
this.value = e.target.checked;
this.setFn(this.value);
window.log.info('media-camera-permissions changed to', this.value);
},
populate() {
this.$('input').prop('checked', Boolean(this.value));
},
});
const DisappearingMessagesView = Whisper.View.extend({
template: () => $('#disappearingMessagesSettings').html(),
initialize(options) {
this.timeDialog = null;
this.value = options.value || 0;
this.render();
},
render_attributes() {
const isCustomValue = this.isCustomValue();
return {
title: i18n('disappearingMessages'),
timerValues: DEFAULT_DURATIONS_IN_SECONDS.map(seconds => {
const text = formatExpirationTimer(i18n, seconds, {
capitalizeOff: true,
});
return {
selected: seconds === this.value ? 'selected' : undefined,
value: seconds,
text,
};
}),
customSelected: isCustomValue ? 'selected' : undefined,
customText: i18n(
isCustomValue
? 'selectedCustomDisappearingTimeOption'
: 'customDisappearingTimeOption'
),
customInfo: isCustomValue
? {
text: formatExpirationTimer(i18n, this.value),
}
: undefined,
timerLabel: i18n('settings__DisappearingMessages__timer__label'),
footer: i18n('settings__DisappearingMessages__footer'),
};
},
events: {
change: 'change',
},
change(e) {
const value = parseInt(e.target.value, 10);
if (value === -1) {
this.showDialog();
return;
}
this.updateValue(value);
window.log.info('disappearing-messages-timer changed to', this.value);
},
isCustomValue() {
return this.value && !DEFAULT_DURATIONS_SET.has(this.value);
},
showDialog() {
this.closeDialog();
this.timeDialog = new window.Whisper.ReactWrapperView({
className: 'disappearing-time-dialog-wrapper',
Component: window.Signal.Components.DisappearingTimeDialog,
props: {
i18n,
initialValue: this.value,
onSubmit: newValue => {
this.updateValue(newValue);
this.closeDialog();
window.log.info(
'disappearing-messages-timer changed to custom value',
this.value
);
},
onClose: () => {
this.closeDialog();
},
},
});
},
closeDialog() {
if (this.timeDialog) {
this.timeDialog.remove();
}
this.timeDialog = null;
},
updateValue(newValue) {
this.value = newValue;
window.setUniversalExpireTimer(newValue);
this.render();
},
});
const RadioButtonGroupView = Whisper.View.extend({
initialize(options) {
this.name = options.name;
this.setFn = options.setFn;
this.value = options.value;
this.populate();
},
events: {
change: 'change',
},
change(e) {
const value = this.$(e.target).val();
this.setFn(value);
window.log.info(this.name, 'changed to', value);
},
populate() {
this.$(`#${this.name}-${this.value}`).attr('checked', 'checked');
},
});
Whisper.SettingsView = Whisper.View.extend({
className: 'settings modal expand',
template: () => $('#settings').html(),
initialize() {
this.render();
new RadioButtonGroupView({
el: this.$('.notification-settings'),
name: 'notification-setting',
value: window.initialData.notificationSetting,
setFn: window.setNotificationSetting,
});
new RadioButtonGroupView({
el: this.$('.theme-settings'),
name: 'theme-setting',
value: window.initialData.themeSetting,
setFn: theme => {
$(document.body)
.removeClass('dark-theme')
.removeClass('light-theme')
.addClass(
`${theme === 'system' ? window.systemTheme : theme}-theme`
);
window.setThemeSetting(theme);
},
});
if (Settings.isDrawAttentionSupported()) {
new CheckboxView({
el: this.$('.draw-attention-setting'),
name: 'draw-attention-setting',
value: window.initialData.notificationDrawAttention,
setFn: window.setNotificationDrawAttention,
});
}
if (Settings.isAudioNotificationSupported()) {
new CheckboxView({
el: this.$('.audio-notification-setting'),
name: 'audio-notification-setting',
value: window.initialData.audioNotification,
setFn: window.setAudioNotification,
});
}
new CheckboxView({
el: this.$('.badge-count-muted-conversations-setting'),
name: 'badge-count-muted-conversations-setting',
value: window.initialData.countMutedConversations,
setFn: window.setCountMutedConversations,
});
new CheckboxView({
el: this.$('.spell-check-setting'),
name: 'spell-check-setting',
value: window.initialData.spellCheck,
setFn: val => {
const $msg = this.$('.spell-check-setting-message');
if (val !== window.appStartInitialSpellcheckSetting) {
$msg.show();
$msg.attr('aria-hidden', false);
} else {
$msg.hide();
$msg.attr('aria-hidden', true);
}
window.setSpellCheck(val);
},
});
if (Settings.isAutoLaunchSupported()) {
new CheckboxView({
el: this.$('.auto-launch-setting'),
name: 'auto-launch-setting',
value: window.initialData.autoLaunch,
setFn: window.setAutoLaunch,
});
}
if (Settings.isHideMenuBarSupported()) {
new CheckboxView({
el: this.$('.menu-bar-setting'),
name: 'menu-bar-setting',
value: window.initialData.hideMenuBar,
setFn: window.setHideMenuBar,
});
}
new window.Whisper.ReactWrapperView({
el: this.$('.system-tray-setting-container'),
Component: window.Signal.Components.SystemTraySettingsCheckboxes,
props: {
i18n,
initialValue: window.initialData.systemTray,
isSystemTraySupported: Settings.isSystemTraySupported(
window.getVersion()
),
onChange: window.setSystemTraySetting,
},
});
new CheckboxView({
el: this.$('.always-relay-calls-setting'),
name: 'always-relay-calls-setting',
value: window.initialData.alwaysRelayCalls,
setFn: window.setAlwaysRelayCalls,
});
new CheckboxView({
el: this.$('.call-ringtone-notification-setting'),
name: 'call-ringtone-notification-setting',
value: window.initialData.callRingtoneNotification,
setFn: window.setCallRingtoneNotification,
});
new CheckboxView({
el: this.$('.call-system-notification-setting'),
name: 'call-system-notification-setting',
value: window.initialData.callSystemNotification,
setFn: window.setCallSystemNotification,
});
new CheckboxView({
el: this.$('.incoming-call-notification-setting'),
name: 'incoming-call-notification-setting',
value: window.initialData.incomingCallNotification,
setFn: window.setIncomingCallNotification,
});
new MediaPermissionsSettingView({
el: this.$('.media-permissions'),
value: window.initialData.mediaPermissions,
setFn: window.setMediaPermissions,
});
new MediaCameraPermissionsSettingView({
el: this.$('.media-camera-permissions'),
value: window.initialData.mediaCameraPermissions,
setFn: window.setMediaCameraPermissions,
});
const disappearingMessagesView = new DisappearingMessagesView({
value: window.initialData.universalExpireTimer,
name: 'disappearing-messages-setting',
});
this.$('.disappearing-messages-setting').append(
disappearingMessagesView.el
);
if (!window.initialData.isPrimary) {
const syncView = new SyncView().render();
this.$('.sync-setting').append(syncView.el);
}
},
events: {
'click .close': 'onClose',
'click .clear-data': 'onClearData',
},
render_attributes() {
const appStartSpellCheck = window.appStartInitialSpellcheckSetting;
const spellCheckDirty =
window.initialData.spellCheck !== appStartSpellCheck;
return {
deviceNameLabel: i18n('deviceName'),
deviceName: window.initialData.deviceName,
theme: i18n('theme'),
notifications: i18n('notifications'),
notificationSettingsDialog: i18n('notificationSettingsDialog'),
settings: i18n('Keyboard--preferences'),
disableNotifications: i18n('disableNotifications'),
nameAndMessage: i18n('nameAndMessage'),
noNameOrMessage: i18n('noNameOrMessage'),
nameOnly: i18n('nameOnly'),
notificationDrawAttention: i18n('notificationDrawAttention'),
audioNotificationDescription: i18n('audioNotificationDescription'),
isAudioNotificationSupported: Settings.isAudioNotificationSupported(),
isHideMenuBarSupported: Settings.isHideMenuBarSupported(),
isDrawAttentionSupported: Settings.isDrawAttentionSupported(),
isAutoLaunchSupported: Settings.isAutoLaunchSupported(),
hasSystemTheme: true,
themeLight: i18n('themeLight'),
themeDark: i18n('themeDark'),
themeSystem: i18n('themeSystem'),
hideMenuBar: i18n('hideMenuBar'),
clearDataHeader: i18n('clearDataHeader'),
clearDataButton: i18n('clearDataButton'),
clearDataExplanation: i18n('clearDataExplanation'),
calling: i18n('calling'),
countMutedConversationsDescription: i18n(
'countMutedConversationsDescription'
),
alwaysRelayCallsDescription: i18n('alwaysRelayCallsDescription'),
alwaysRelayCallsDetail: i18n('alwaysRelayCallsDetail'),
callRingtoneNotificationDescription: i18n(
'callRingtoneNotificationDescription'
),
callSystemNotificationDescription: i18n(
'callSystemNotificationDescription'
),
incomingCallNotificationDescription: i18n(
'incomingCallNotificationDescription'
),
permissions: i18n('permissions'),
mediaPermissionsDescription: i18n('mediaPermissionsDescription'),
mediaCameraPermissionsDescription: i18n(
'mediaCameraPermissionsDescription'
),
generalHeader: i18n('general'),
spellCheckDescription: i18n('spellCheckDescription'),
spellCheckHidden: spellCheckDirty ? 'false' : 'true',
spellCheckDisplay: spellCheckDirty ? 'inherit' : 'none',
spellCheckDirtyText: appStartSpellCheck
? i18n('spellCheckWillBeDisabled')
: i18n('spellCheckWillBeEnabled'),
autoLaunchDescription: i18n('autoLaunchDescription'),
};
},
onClose() {
window.closeSettings();
},
onClearData() {
window.deleteAllData();
window.closeSettings();
},
});
const SyncView = Whisper.View.extend({
template: () => $('#syncSettings').html(),
className: 'syncSettings',
events: {
'click .sync': 'sync',
},
initialize() {
this.lastSyncTime = window.initialData.lastSyncTime;
},
enable() {
this.$('.sync').text(i18n('syncNow'));
this.$('.sync').removeAttr('disabled');
},
disable() {
this.$('.sync').attr('disabled', 'disabled');
this.$('.sync').text(i18n('syncing'));
},
onsuccess() {
window.setLastSyncTime(Date.now());
this.lastSyncTime = Date.now();
window.log.info('sync successful');
this.enable();
this.render();
},
ontimeout() {
window.log.error('sync timed out');
this.$('.synced_at').hide();
this.$('.sync_failed').show();
this.enable();
},
async sync() {
this.$('.sync_failed').hide();
if (window.initialData.isPrimary) {
window.log.warn('Tried to sync from device 1');
return;
}
this.disable();
try {
await window.makeSyncRequest();
this.onsuccess();
} catch (error) {
window.log.error(
'settings sync timeout error:',
error && error.stack ? error.stack : error
);
this.ontimeout();
}
},
render_attributes() {
const attrs = {
sync: i18n('sync'),
syncNow: i18n('syncNow'),
syncExplanation: i18n('syncExplanation'),
syncFailed: i18n('syncFailed'),
};
let date = this.lastSyncTime;
if (date) {
date = new Date(date);
attrs.lastSynced = i18n('lastSynced');
attrs.syncDate = date.toLocaleDateString();
attrs.syncTime = date.toLocaleTimeString();
}
return attrs;
},
});
})();

167
main.js
View File

@ -37,7 +37,6 @@ const {
ipcMain: ipc,
Menu,
protocol: electronProtocol,
session,
shell,
systemPreferences,
} = electron;
@ -102,7 +101,6 @@ const {
installFileHandler,
installWebHandler,
} = require('./app/protocol_filter');
const { installPermissionsHandler } = require('./app/permissions');
const OS = require('./ts/OS');
const { isProduction } = require('./ts/util/version');
const {
@ -124,6 +122,7 @@ const { Environment, isTestEnvironment } = require('./ts/environment');
const { ChallengeMainHandler } = require('./ts/main/challengeMain');
const { NativeThemeNotifier } = require('./ts/main/NativeThemeNotifier');
const { PowerChannel } = require('./ts/main/powerChannel');
const { SettingsChannel } = require('./ts/main/settingsChannel');
const { maybeParseUrl, setUrlSearchParams } = require('./ts/util/url');
const { getHeicConverter } = require('./ts/workers/heicConverterMain');
@ -235,6 +234,7 @@ const loadLocale = require('./app/locale').load;
// Both of these will be set after app fires the 'ready' event
let logger;
let locale;
let settingsChannel;
function prepareFileUrl(
pathSegments /* : ReadonlyArray<string> */,
@ -425,6 +425,8 @@ async function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow(windowOptions);
settingsChannel.setMainWindow(mainWindow);
mainWindowCreated = true;
setupSpellChecker(mainWindow, locale.messages);
if (!startInTray && windowConfig && windowConfig.maximized) {
@ -567,6 +569,7 @@ async function createWindow() {
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = undefined;
settingsChannel.setMainWindow(mainWindow);
if (systemTrayService) {
systemTrayService.setMainWindow(mainWindow);
}
@ -875,40 +878,26 @@ function showSettingsWindow() {
settingsWindow.show();
return;
}
if (!mainWindow) {
return;
}
addDarkOverlay();
const size = mainWindow.getSize();
// center settings window over main window
const settingwidth = Math.min(500, size[0]);
const settingheight = Math.max(size[1] - 100, MIN_HEIGHT);
const mainPos = mainWindow.getPosition();
const mainSize = mainWindow.getSize();
const options = {
x: Math.round(mainPos[0] + mainSize[0] / 2 - settingwidth / 2),
y: Math.round(mainPos[1] + mainSize[1] / 2 - settingheight / 2),
width: settingwidth,
height: settingheight,
frame: false,
width: 700,
height: 700,
frame: true,
resizable: false,
title: locale.messages.signalDesktopPreferences.message,
autoHideMenuBar: true,
backgroundColor: '#3a76f0',
show: false,
modal: true,
modal: false,
webPreferences: {
...defaultWebPrefs,
nodeIntegration: false,
nodeIntegrationInWorker: false,
contextIsolation: false,
enableRemoteModule: true,
preload: path.join(__dirname, 'settings_preload.js'),
preload: path.join(__dirname, 'ts', 'windows', 'settings', 'preload.js'),
nativeWindowOpen: true,
},
parent: mainWindow,
};
settingsWindow = new BrowserWindow(options);
@ -924,6 +913,11 @@ function showSettingsWindow() {
settingsWindow.once('ready-to-show', () => {
settingsWindow.show();
settingsWindow.webContents.send('render');
if (config.get('openDevTools')) {
settingsWindow.webContents.openDevTools();
}
});
}
@ -1015,7 +1009,7 @@ async function showDebugLogWindow() {
return;
}
const theme = await pify(getDataFromMainWindow)('theme-setting');
const theme = await settingsChannel.getSettingFromMainWindow('themeSetting');
const size = mainWindow.getSize();
const options = {
width: Math.max(size[0] - 100, MIN_WIDTH),
@ -1068,7 +1062,9 @@ function showPermissionsPopupWindow(forCalling, forCamera) {
reject(new Error('No main window'));
}
const theme = await pify(getDataFromMainWindow)('theme-setting');
const theme = await settingsChannel.getSettingFromMainWindow(
'themeSetting'
);
const size = mainWindow.getSize();
const options = {
width: Math.min(400, size[0]),
@ -1154,6 +1150,9 @@ let ready = false;
app.on('ready', async () => {
const startTime = Date.now();
settingsChannel = new SettingsChannel();
settingsChannel.install();
// We use this event only a single time to log the startup time of the app
// from when it's first ready until the loading screen disappears.
ipc.once('signal-app-loaded', (event, info) => {
@ -1201,8 +1200,6 @@ app.on('ready', async () => {
protocol: electronProtocol,
});
installPermissionsHandler({ session, userConfig });
logger = await logging.initialize(getMainWindow);
logger.info('app ready');
logger.info(`starting version ${packageJson.version}`);
@ -1668,79 +1665,6 @@ ipc.on('close-settings', () => {
}
});
installSettingsGetter('device-name');
installSettingsGetter('theme-setting');
installSettingsSetter('theme-setting');
installSettingsGetter('hide-menu-bar');
installSettingsSetter('hide-menu-bar');
installSettingsGetter('system-tray-setting');
installSettingsSetter('system-tray-setting');
installSettingsGetter('notification-setting');
installSettingsSetter('notification-setting');
installSettingsGetter('notification-draw-attention');
installSettingsSetter('notification-draw-attention');
installSettingsGetter('audio-notification');
installSettingsSetter('audio-notification');
installSettingsGetter('badge-count-muted-conversations');
installSettingsSetter('badge-count-muted-conversations');
installSettingsGetter('spell-check');
installSettingsSetter('spell-check', true);
installSettingsGetter('auto-launch');
installSettingsSetter('auto-launch');
installSettingsGetter('always-relay-calls');
installSettingsSetter('always-relay-calls');
installSettingsGetter('call-ringtone-notification');
installSettingsSetter('call-ringtone-notification');
installSettingsGetter('call-system-notification');
installSettingsSetter('call-system-notification');
installSettingsGetter('incoming-call-notification');
installSettingsSetter('incoming-call-notification');
// These ones are different because its single source of truth is userConfig,
// not IndexedDB
ipc.on('get-media-permissions', event => {
event.sender.send(
'get-success-media-permissions',
null,
userConfig.get('mediaPermissions') || false
);
});
ipc.on('get-media-camera-permissions', event => {
event.sender.send(
'get-success-media-camera-permissions',
null,
userConfig.get('mediaCameraPermissions') || false
);
});
ipc.on('set-media-permissions', (event, value) => {
userConfig.set('mediaPermissions', value);
// We reinstall permissions handler to ensure that a revoked permission takes effect
installPermissionsHandler({ session, userConfig });
event.sender.send('set-success-media-permissions', null);
});
ipc.on('set-media-camera-permissions', (event, value) => {
userConfig.set('mediaCameraPermissions', value);
// We reinstall permissions handler to ensure that a revoked permission takes effect
installPermissionsHandler({ session, userConfig });
event.sender.send('set-success-media-camera-permissions', null);
});
installSettingsGetter('is-primary');
installSettingsGetter('sync-request');
installSettingsGetter('sync-time');
installSettingsSetter('sync-time');
installSettingsGetter('universal-expire-timer');
installSettingsSetter('universal-expire-timer');
ipc.on('delete-all-data', () => {
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.send('delete-all-data');
@ -1776,47 +1700,12 @@ ipc.on('get-user-data-path', event => {
event.returnValue = app.getPath('userData');
});
function getDataFromMainWindow(name, callback) {
ipc.once(`get-success-${name}`, (_event, error, value) =>
callback(error, value)
);
mainWindow.webContents.send(`get-${name}`);
}
function installSettingsGetter(name) {
ipc.on(`get-${name}`, event => {
if (mainWindow && mainWindow.webContents) {
getDataFromMainWindow(name, (error, value) => {
const contents = event.sender;
if (contents.isDestroyed()) {
return;
}
contents.send(`get-success-${name}`, error, value);
});
}
});
}
function installSettingsSetter(name, isEphemeral = false) {
ipc.on(`set-${name}`, (event, value) => {
if (isEphemeral) {
ephemeralConfig.set('spell-check', value);
}
if (mainWindow && mainWindow.webContents) {
ipc.once(`set-success-${name}`, (_event, error) => {
const contents = event.sender;
if (contents.isDestroyed()) {
return;
}
contents.send(`set-success-${name}`, error);
});
mainWindow.webContents.send(`set-${name}`, value);
}
});
}
// Refresh the settings window whenever preferences change
ipc.on('preferences-changed', () => {
if (settingsWindow && settingsWindow.webContents) {
settingsWindow.webContents.send('render');
}
});
function getIncomingHref(argv) {
return argv.find(arg => isSgnlHref(arg, logger));

View File

@ -416,7 +416,6 @@
"preload_utils.js",
"about_preload.js",
"screenShare_preload.js",
"settings_preload.js",
"permissions_popup_preload.js",
"debug_log_preload.js",
"main.js",

View File

@ -8,23 +8,24 @@ window.ReactDOM = require('react-dom');
const { ipcRenderer } = require('electron');
const url = require('url');
// It is important to call this as early as possible
require('./ts/windows/context');
const i18n = require('./js/modules/i18n');
const { ConfirmationDialog } = require('./ts/components/ConfirmationDialog');
const { makeGetter, makeSetter } = require('./preload_utils');
const {
getEnvironment,
setEnvironment,
parseEnvironment,
} = require('./ts/environment');
const { Context: SignalContext } = require('./ts/context');
const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment));
window.SignalContext = new SignalContext(ipcRenderer);
const { createSetting } = require('./ts/util/preload');
window.getEnvironment = getEnvironment;
window.getVersion = () => config.version;
@ -43,8 +44,14 @@ require('./ts/logging/set_up_renderer_logging').initialize();
window.closePermissionsPopup = () =>
ipcRenderer.send('close-permissions-popup');
window.setMediaPermissions = makeSetter('media-permissions');
window.setMediaCameraPermissions = makeSetter('media-camera-permissions');
window.getThemeSetting = makeGetter('theme-setting');
window.setThemeSetting = makeSetter('theme-setting');
window.Backbone = require('backbone');
window.Settings = {
mediaCameraPermissions: createSetting('mediaCameraPermissions', {
getter: false,
}),
mediaPermissions: createSetting('mediaPermissions', {
getter: false,
}),
themeSetting: createSetting('themeSetting', { setter: false }),
};

View File

@ -12,7 +12,10 @@ try {
const electron = require('electron');
const semver = require('semver');
const _ = require('lodash');
const { installGetter, installSetter } = require('./preload_utils');
// It is important to call this as early as possible
require('./ts/windows/context');
const {
getEnvironment,
setEnvironment,
@ -24,10 +27,6 @@ try {
const { remote } = electron;
const { app } = remote;
const { Context: SignalContext } = require('./ts/context');
window.SignalContext = new SignalContext(ipc);
window.sqlInitializer = require('./ts/sql/initialize');
const config = require('url').parse(window.location.toString(), true).query;
@ -254,146 +253,7 @@ try {
window.Events.removeDarkOverlay();
});
installGetter('device-name', 'getDeviceName');
installGetter('theme-setting', 'getThemeSetting');
installSetter('theme-setting', 'setThemeSetting');
installGetter('hide-menu-bar', 'getHideMenuBar');
installSetter('hide-menu-bar', 'setHideMenuBar');
installGetter('system-tray-setting', 'getSystemTraySetting');
installSetter('system-tray-setting', 'setSystemTraySetting');
installGetter('notification-setting', 'getNotificationSetting');
installSetter('notification-setting', 'setNotificationSetting');
installGetter('notification-draw-attention', 'getNotificationDrawAttention');
installSetter('notification-draw-attention', 'setNotificationDrawAttention');
installGetter('audio-notification', 'getAudioNotification');
installSetter('audio-notification', 'setAudioNotification');
installGetter(
'badge-count-muted-conversations',
'getCountMutedConversations'
);
installSetter(
'badge-count-muted-conversations',
'setCountMutedConversations'
);
window.getCountMutedConversations = () =>
new Promise((resolve, reject) => {
ipc.once(
'get-success-badge-count-muted-conversations',
(_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
}
);
ipc.send('get-badge-count-muted-conversations');
});
installGetter('spell-check', 'getSpellCheck');
installSetter('spell-check', 'setSpellCheck');
installGetter('auto-launch', 'getAutoLaunch');
installSetter('auto-launch', 'setAutoLaunch');
installGetter('always-relay-calls', 'getAlwaysRelayCalls');
installSetter('always-relay-calls', 'setAlwaysRelayCalls');
installGetter('call-ringtone-notification', 'getCallRingtoneNotification');
installSetter('call-ringtone-notification', 'setCallRingtoneNotification');
window.getCallRingtoneNotification = () =>
new Promise((resolve, reject) => {
ipc.once(
'get-success-call-ringtone-notification',
(_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
}
);
ipc.send('get-call-ringtone-notification');
});
installGetter('call-system-notification', 'getCallSystemNotification');
installSetter('call-system-notification', 'setCallSystemNotification');
window.getCallSystemNotification = () =>
new Promise((resolve, reject) => {
ipc.once(
'get-success-call-system-notification',
(_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
}
);
ipc.send('get-call-system-notification');
});
installGetter('incoming-call-notification', 'getIncomingCallNotification');
installSetter('incoming-call-notification', 'setIncomingCallNotification');
window.getIncomingCallNotification = () =>
new Promise((resolve, reject) => {
ipc.once(
'get-success-incoming-call-notification',
(_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
}
);
ipc.send('get-incoming-call-notification');
});
window.getAlwaysRelayCalls = () =>
new Promise((resolve, reject) => {
ipc.once('get-success-always-relay-calls', (_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
});
ipc.send('get-always-relay-calls');
});
window.getMediaPermissions = () =>
new Promise((resolve, reject) => {
ipc.once('get-success-media-permissions', (_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
});
ipc.send('get-media-permissions');
});
window.getMediaCameraPermissions = () =>
new Promise((resolve, reject) => {
ipc.once(
'get-success-media-camera-permissions',
(_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
}
);
ipc.send('get-media-camera-permissions');
});
require('./ts/windows/preload');
window.getBuiltInImages = () =>
new Promise((resolve, reject) => {
@ -407,13 +267,6 @@ try {
ipc.send('get-built-in-images');
});
installGetter('is-primary', 'isPrimary');
installGetter('sync-request', 'getSyncRequest');
installGetter('sync-time', 'getLastSyncTime');
installSetter('sync-time', 'setLastSyncTime');
installGetter('universal-expire-timer', 'getUniversalExpireTimer');
installSetter('universal-expire-timer', 'setUniversalExpireTimer');
ipc.on('delete-all-data', async () => {
const { deleteAllData } = window.Events;
if (!deleteAllData) {

View File

@ -1,77 +0,0 @@
// Copyright 2019-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global window */
const { ipcRenderer: ipc } = require('electron');
exports.installGetter = function installGetter(name, functionName) {
ipc.on(`get-${name}`, async () => {
const getFn = window.Events[functionName];
if (!getFn) {
ipc.send(
`get-success-${name}`,
`installGetter: ${functionName} not found for event ${name}`
);
return;
}
try {
ipc.send(`get-success-${name}`, null, await getFn());
} catch (error) {
ipc.send(
`get-success-${name}`,
error && error.stack ? error.stack : error
);
}
});
};
exports.installSetter = function installSetter(name, functionName) {
ipc.on(`set-${name}`, async (_event, value) => {
const setFn = window.Events[functionName];
if (!setFn) {
ipc.send(
`set-success-${name}`,
`installSetter: ${functionName} not found for event ${name}`
);
return;
}
try {
await setFn(value);
ipc.send(`set-success-${name}`);
} catch (error) {
ipc.send(
`set-success-${name}`,
error && error.stack ? error.stack : error
);
}
});
};
exports.makeGetter = function makeGetter(name) {
return () =>
new Promise((resolve, reject) => {
ipc.once(`get-success-${name}`, (event, error, value) => {
if (error) {
return reject(error);
}
return resolve(value);
});
ipc.send(`get-${name}`);
});
};
exports.makeSetter = function makeSetter(name) {
return value =>
new Promise((resolve, reject) => {
ipc.once(`set-success-${name}`, (event, error) => {
if (error) {
return reject(error);
}
return resolve();
});
ipc.send(`set-${name}`, value);
});
};

View File

@ -8,6 +8,9 @@ const ReactDOM = require('react-dom');
const url = require('url');
const { ipcRenderer } = require('electron');
// It is important to call this as early as possible
require('./ts/windows/context');
const i18n = require('./js/modules/i18n');
const {
getEnvironment,
@ -18,10 +21,6 @@ const {
CallingScreenSharingController,
} = require('./ts/components/CallingScreenSharingController');
const { Context: SignalContext } = require('./ts/context');
window.SignalContext = new SignalContext(ipcRenderer);
const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
const localeMessages = ipcRenderer.sendSync('locale-data');

View File

@ -1,4 +1,4 @@
<!-- Copyright 2018-2021 Signal Messenger, LLC -->
<!-- Copyright 2021 Signal Messenger, LLC -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
<html>
@ -6,11 +6,7 @@
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
child-src 'self';
connect-src 'self' https: wss:;
font-src 'self';
form-action 'self';
frame-src 'none';
img-src 'self' blob: data:;
media-src 'self' blob:;
object-src 'none';
@ -23,201 +19,12 @@
type="text/css"
/>
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<style></style>
</head>
<body></body>
<script type="text/x-tmpl-mustache" id="syncSettings">
<hr>
<h3>{{ sync }}</h3>
<div>
<button class='grey sync'>{{ syncNow }}</button>
<p>
{{ syncExplanation }}
<div class='synced_at'>
{{ lastSynced }} {{ syncDate }} {{ syncTime }}
</div>
<div class='sync_failed'>{{ syncFailed }}</div>
<div class='clearfix'></div>
</p>
</div>
</script>
<script type="text/x-tmpl-mustache" id="disappearingMessagesSettings">
<h3>{{ title }}</h3>
<div class="disappearing-messages-setting__timer {{#customInfo}}disappearing-messages-setting__timer--with-info{{/customInfo}}">
<label
class="disappearing-messages-setting__timer__label"
for='disappearing-messages-timer'
>
{{ timerLabel }}
</label>
<div class="disappearing-messages-setting__timer__right">
<div class="module-select">
<select
name='disappearing-messages-timer'
id='disappearing-messages-timer'
>
{{#timerValues}}
<option value="{{value}}" {{selected}}>
{{ text }}
</option>
{{/timerValues}}
<option value="-1" {{customSelected}}>
{{customText}}
</option>
</select>
</div>
{{#customInfo}}
<div class="disappearing-messages-setting__timer__right__info">
{{text}}
</div>
{{/customInfo}}
</div>
</div>
<div class='disappearing-messages-setting__footer'>
{{ footer }}
</div>
</script>
<script type="text/x-tmpl-mustache" id="settings">
<div class='content'>
<a class='x close' alt='close settings' href='#'></a>
<h2>{{ settings }}</h2>
<div class='device-name-settings'>
<b>{{ deviceNameLabel }}:</b> {{ deviceName }}
</div>
<hr>
<div class='theme-settings'>
<h3>{{ theme }}</h3>
{{#hasSystemTheme}}
<div>
<input type='radio' name='theme' id='theme-setting-system' value='system'>
<label for='theme-setting-system'>{{ themeSystem }}</label>
</div>
{{/hasSystemTheme}}
<div>
<input type='radio' name='theme' id='theme-setting-light' value='light'>
<label for='theme-setting-light'>{{ themeLight }}</label>
</div>
<div>
<input type='radio' name='theme' id='theme-setting-dark' value='dark'>
<label for='theme-setting-dark'>{{ themeDark }}</label>
</div>
</div>
<br />
{{ #isHideMenuBarSupported }}
<div class='menu-bar-setting'>
<input type='checkbox' name='hide-menu-bar' id='hide-menu-bar'/>
<label for='hide-menu-bar'>{{ hideMenuBar }}</label>
</div>
{{ /isHideMenuBarSupported }}
<hr>
<div class='notification-settings'>
<h3>{{ notifications }}</h3>
<p>{{ notificationSettingsDialog }}</p>
<div>
<input type='radio' name='notifications' id='notification-setting-message' value='message'>
<label for='notification-setting-message'>{{ nameAndMessage }} </label>
</div>
<div>
<input type='radio' name='notifications' id='notification-setting-name' value='name'/>
<label for='notification-setting-name'>{{ nameOnly }} </label>
</div>
<div>
<input type='radio' name='notifications' id='notification-setting-count' value='count'/>
<label for='notification-setting-count'>{{ noNameOrMessage }} </label>
</div>
<div>
<input type='radio' name='notifications' id='notification-setting-off' value='off'/>
<label for='notification-setting-off'>{{ disableNotifications }} </label>
</div>
</div>
{{ #isDrawAttentionSupported }}
<br />
<div class='draw-attention-setting'>
<input type='checkbox' name='notification-draw-attention' id='notification-draw-attention'/>
<label for='notification-draw-attention'>{{ notificationDrawAttention }}</label>
</div>
{{ /isDrawAttentionSupported }}
<br />
{{ #isAudioNotificationSupported }}
<div class='audio-notification-setting'>
<input type='checkbox' name='audio-notification' id='audio-notification'/>
<label for='audio-notification'>{{ audioNotificationDescription }}</label>
</div>
{{ /isAudioNotificationSupported }}
<div class='badge-count-muted-conversations-setting'>
<input type='checkbox' name='badge-count-muted-conversations' id='badge-count-muted-conversations'/>
<label for='badge-count-muted-conversations'>{{ countMutedConversationsDescription }}</label>
</div>
<hr>
<h3>{{ generalHeader }}</h3>
<div class='spell-check-setting'>
<input type='checkbox' name='spell-check-setting' id='spell-check-setting' />
<label for='spell-check-setting'>{{ spellCheckDescription }}</label>
<p class='spell-check-setting-message' style='display: {{ spellCheckDisplay }};' aria-hidden='{{ spellCheckHidden }}'>
{{ spellCheckDirtyText }}
</p>
</div>
<div class="system-tray-setting-container"></div>
{{ #isAutoLaunchSupported }}
<div class='auto-launch-setting'>
<input type='checkbox' name='auto-launch-setting' id='auto-launch-setting' />
<label for='auto-launch-setting'>{{ autoLaunchDescription }}</label>
</div>
{{ /isAutoLaunchSupported }}
<hr>
<div class='calling-setting'>
<h3>{{ calling }}</h3>
<div class='always-relay-calls-setting'>
<input type='checkbox' name='always-relay-calls' id='always-relay-calls' />
<label for='always-relay-calls'>{{ alwaysRelayCallsDescription }}</label>
<p>
<div class='detail'>
{{ alwaysRelayCallsDetail }}
</div>
</p>
</div>
<div class='call-ringtone-notification-setting'>
<input type='checkbox' name='call-ringtone-notification' id='call-ringtone-notification'/>
<label for='call-ringtone-notification'>{{ callRingtoneNotificationDescription }}</label>
</div>
<div class='call-system-notification-setting'>
<input type='checkbox' name='call-system-notification' id='call-system-notification'/>
<label for='call-system-notification'>{{ callSystemNotificationDescription }}</label>
</div>
<div class='incoming-call-notification-setting'>
<input type='checkbox' name='incoming-call-notification' id='incoming-call-notification'/>
<label for='incoming-call-notification'>{{ incomingCallNotificationDescription }}</label>
</div>
</div>
<hr>
<div class='permissions-setting'>
<h3>{{ permissions }}</h3>
<div class='media-permissions'>
<input type='checkbox' name='media-permissions' id='media-permissions' />
<label for='media-permissions'>{{ mediaPermissionsDescription }}</label>
</div>
<div class='media-camera-permissions'>
<input type='checkbox' name='media-camera-permissions' id='media-camera-permissions' />
<label for='media-camera-permissions'>{{ mediaCameraPermissionsDescription }}</label>
</div>
</div>
<div class='sync-setting'></div>
<hr>
<div class='disappearing-messages-setting'>
</div>
<hr>
<div class='clear-data-settings'>
<h3>{{ clearDataHeader }}</h3>
<div>
<button class='grey destructive clear-data'>{{ clearDataButton }}</button>
<p>{{ clearDataExplanation }}</p>
</div>
</div>
</div>
</script>
<script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="ts/backboneJquery.js"></script>
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>
<script type="text/javascript" src="js/views/settings_view.js"></script>
<script type="text/javascript" src="js/settings_start.js"></script>
<body>
<div id="app"></div>
<script
type="application/javascript"
src="ts/windows/settings/init.js"
></script>
</body>
</html>

View File

@ -1,131 +0,0 @@
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global window */
const { ipcRenderer } = require('electron');
const url = require('url');
const i18n = require('./js/modules/i18n');
const {
getEnvironment,
setEnvironment,
parseEnvironment,
} = require('./ts/environment');
const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment));
const { Context: SignalContext } = require('./ts/context');
window.SignalContext = new SignalContext(ipcRenderer);
window.platform = process.platform;
window.theme = config.theme;
window.i18n = i18n.setup(locale, localeMessages);
window.appStartInitialSpellcheckSetting =
config.appStartInitialSpellcheckSetting === 'true';
window.getEnvironment = getEnvironment;
window.getVersion = () => config.version;
window.getAppInstance = () => config.appInstance;
// So far we're only using this for Signal.Types
const Signal = require('./js/modules/signal');
window.Signal = Signal.setup({
Attachments: null,
userDataPath: null,
getRegionCode: () => null,
});
window.closeSettings = () => ipcRenderer.send('close-settings');
window.getDeviceName = makeGetter('device-name');
window.getThemeSetting = makeGetter('theme-setting');
window.setThemeSetting = makeSetter('theme-setting');
window.getHideMenuBar = makeGetter('hide-menu-bar');
window.setHideMenuBar = makeSetter('hide-menu-bar');
window.getSystemTraySetting = makeGetter('system-tray-setting');
window.setSystemTraySetting = makeSetter('system-tray-setting');
window.getSpellCheck = makeGetter('spell-check');
window.setSpellCheck = makeSetter('spell-check');
window.getAutoLaunch = makeGetter('auto-launch');
window.setAutoLaunch = makeSetter('auto-launch');
window.getAlwaysRelayCalls = makeGetter('always-relay-calls');
window.setAlwaysRelayCalls = makeSetter('always-relay-calls');
window.getNotificationSetting = makeGetter('notification-setting');
window.setNotificationSetting = makeSetter('notification-setting');
window.getNotificationDrawAttention = makeGetter('notification-draw-attention');
window.setNotificationDrawAttention = makeSetter('notification-draw-attention');
window.getAudioNotification = makeGetter('audio-notification');
window.setAudioNotification = makeSetter('audio-notification');
window.getCallRingtoneNotification = makeGetter('call-ringtone-notification');
window.setCallRingtoneNotification = makeSetter('call-ringtone-notification');
window.getCallSystemNotification = makeGetter('call-system-notification');
window.setCallSystemNotification = makeSetter('call-system-notification');
window.getIncomingCallNotification = makeGetter('incoming-call-notification');
window.setIncomingCallNotification = makeSetter('incoming-call-notification');
window.getCountMutedConversations = makeGetter(
'badge-count-muted-conversations'
);
window.setCountMutedConversations = makeSetter(
'badge-count-muted-conversations'
);
window.getMediaPermissions = makeGetter('media-permissions');
window.setMediaPermissions = makeSetter('media-permissions');
window.getMediaCameraPermissions = makeGetter('media-camera-permissions');
window.setMediaCameraPermissions = makeSetter('media-camera-permissions');
window.isPrimary = makeGetter('is-primary');
window.makeSyncRequest = makeGetter('sync-request');
window.getLastSyncTime = makeGetter('sync-time');
window.setLastSyncTime = makeSetter('sync-time');
window.getUniversalExpireTimer = makeGetter('universal-expire-timer');
window.setUniversalExpireTimer = makeSetter('universal-expire-timer');
window.deleteAllData = () => ipcRenderer.send('delete-all-data');
function makeGetter(name) {
return () =>
new Promise((resolve, reject) => {
ipcRenderer.once(`get-success-${name}`, (event, error, value) => {
if (error) {
return reject(error);
}
return resolve(value);
});
ipcRenderer.send(`get-${name}`);
});
}
function makeSetter(name) {
return value =>
new Promise((resolve, reject) => {
ipcRenderer.once(`set-success-${name}`, (event, error) => {
if (error) {
return reject(error);
}
return resolve();
});
ipcRenderer.send(`set-${name}`, value);
});
}
window.Backbone = require('backbone');
window.React = require('react');
window.ReactDOM = require('react-dom');
require('./ts/backbone/views/whisper_view');
require('./ts/backbone/views/toast_view');
require('./ts/logging/set_up_renderer_logging').initialize();

View File

@ -10,6 +10,10 @@ const config = require('url').parse(window.location.toString(), true).query;
const { noop, uniqBy } = require('lodash');
const pMap = require('p-map');
const client = require('@signalapp/signal-client');
// It is important to call this as early as possible
require('../ts/windows/context');
const { deriveStickerPackKey } = require('../ts/Crypto');
const { SignalService: Proto } = require('../ts/protobuf');
const {
@ -17,20 +21,16 @@ const {
setEnvironment,
parseEnvironment,
} = require('../ts/environment');
const { makeGetter } = require('../preload_utils');
const { createSetting } = require('../ts/util/preload');
const { dialog } = remote;
const { Context: SignalContext } = require('../ts/context');
const STICKER_SIZE = 512;
const MIN_STICKER_DIMENSION = 10;
const MAX_STICKER_DIMENSION = STICKER_SIZE;
const MAX_WEBP_STICKER_BYTE_LENGTH = 100 * 1024;
const MAX_ANIMATED_STICKER_BYTE_LENGTH = 300 * 1024;
window.SignalContext = new SignalContext(ipc);
setEnvironment(parseEnvironment(config.environment));
window.sqlInitializer = require('../ts/sql/initialize');
@ -274,10 +274,10 @@ async function encrypt(data, key, iv) {
return ciphertext;
}
const getThemeSetting = makeGetter('theme-setting');
const getThemeSetting = createSetting('theme-setting');
async function resolveTheme() {
const theme = (await getThemeSetting()) || 'system';
const theme = (await getThemeSetting.getValue()) || 'system';
if (process.platform === 'darwin' && theme === 'system') {
const { theme: nativeTheme } = window.SignalContext.nativeThemeListener;
return nativeTheme.shouldUseDarkColors ? 'dark' : 'light';

View File

@ -8755,17 +8755,6 @@ button.module-image__border-overlay:focus {
);
}
}
.module-avatar-popup__item__icon-colors {
@include light-theme {
@include color-svg(
'../images/icons/v2/color-outline-24.svg',
$color-gray-75
);
}
@include dark-theme {
@include color-svg('../images/icons/v2/color-solid-24.svg', $color-gray-15);
}
}
.module-avatar-popup__item__icon-archive {
@include light-theme {
@include color-svg(

View File

@ -0,0 +1,30 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
.Checkbox {
&__container {
align-items: center;
display: flex;
input {
height: 18px;
width: 18px;
}
}
&__checkbox {
height: 18px;
margin-right: 20px;
width: 18px;
}
&__description {
@include font-subtitle;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
}
}

View File

@ -0,0 +1,241 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
@mixin preferences-icon($light_svg, $dark_svg) {
&:before {
@include light-theme {
@include color-svg($light_svg, $color-gray-75);
}
@include dark-theme {
@include color-svg($dark_svg, $color-gray-15);
}
}
}
.Preferences {
display: flex;
overflow: hidden;
@include light-theme {
background: $color-white;
}
@include dark-theme {
background: $color-gray-95;
}
&__page-selector {
padding-top: 76px;
min-width: 240px;
@include light-theme {
background: $color-gray-02;
}
@include dark-theme {
background: $color-gray-80;
}
}
&__padding {
padding: 0 24px;
}
&__button {
@include button-reset;
@include font-body-1;
align-items: center;
display: flex;
height: 48px;
width: 100%;
padding: 14px 0;
&--selected {
@include light-theme {
background: $color-gray-15;
}
@include dark-theme {
background: $color-gray-65;
}
}
&:before {
content: '';
display: block;
height: 22px;
margin-left: 18px;
margin-right: 14px;
width: 22px;
}
&--general {
@include preferences-icon(
'../images/icons/v2/settings-outline-16.svg',
'../images/icons/v2/settings-outline-16.svg'
);
}
&--appearance {
@include preferences-icon(
'../images/icons/v2/appearance-outline-24.svg',
'../images/icons/v2/appearance-solid-24.svg'
);
}
&--chats {
@include preferences-icon(
'../images/icons/v2/message-outline-24.svg',
'../images/icons/v2/message-solid-24.svg'
);
}
&--calls {
@include preferences-icon(
'../images/icons/v2/video-outline-24.svg',
'../images/icons/v2/video-solid-24.svg'
);
}
&--notifications {
@include preferences-icon(
'../images/icons/v2/bell-outline-24.svg',
'../images/icons/v2/bell-solid-24.svg'
);
}
&--privacy {
@include preferences-icon(
'../images/icons/v2/lock-outline-24.svg',
'../images/icons/v2/lock-solid-24.svg'
);
&:before {
-webkit-mask-size: 75%;
}
}
}
&__settings-pane {
height: 100vh;
overflow: scroll;
width: 100%;
}
&__title {
@include font-body-1-bold;
align-items: center;
display: flex;
height: 76px;
padding: 42px 0 14px 0;
text-align: center;
&--header {
flex-grow: 1;
text-align: center;
}
}
&__settings-row {
padding-bottom: 12px;
h3 {
@include font-body-1-bold;
margin: 0;
margin-bottom: 8px;
}
}
&__settings-row:not(:last-child) {
border-bottom: 1px solid $color-gray-15;
@include light-theme {
border-color: $color-gray-15;
}
@include dark-theme {
border-color: $color-gray-65;
}
margin-bottom: 24px;
}
&__control {
align-items: center;
display: flex;
justify-content: space-between;
min-height: 48px;
padding: 4px 24px;
&--key {
flex-grow: 1;
padding-right: 20px;
}
&--value {
color: $color-gray-45;
}
&--clickable {
@include button-reset;
padding: 4px 24px;
width: 100%;
&:hover {
@include light-theme {
background: $color-gray-02;
}
@include dark-theme {
background: $color-gray-80;
}
}
}
}
&__checkbox {
padding: 10px 24px;
}
&__description {
@include font-subtitle;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
&--error {
color: $color-accent-red !important;
}
}
&__select {
width: 100%;
}
&__select-title {
display: block;
margin-bottom: 8px;
}
&__right-button {
display: flex;
justify-content: flex-end;
min-width: 120px;
}
&__back-icon {
@include button-reset;
display: inline-block;
height: 24px;
margin-left: 12px;
min-width: 24px;
vertical-align: text-bottom;
width: 24px;
@include light-theme {
@include color-svg(
'../images/icons/v2/chevron-left-24.svg',
$color-gray-90
);
}
@include dark-theme {
@include color-svg(
'../images/icons/v2/chevron-left-24.svg',
$color-gray-02
);
}
}
}

View File

@ -43,6 +43,7 @@
@import './components/CallingScreenSharingController.scss';
@import './components/CallingSelectPresentingSourcesModal.scss';
@import './components/ChatColorPicker.scss';
@import './components/Checkbox.scss';
@import './components/CompositionArea.scss';
@import './components/ContactName.scss';
@import './components/ContactPill.scss';
@ -65,6 +66,7 @@
@import './components/MessageAudio.scss';
@import './components/MessageDetail.scss';
@import './components/Modal.scss';
@import './components/Preferences.scss';
@import './components/ProfileEditor.scss';
@import './components/SafetyNumberChangeDialog.scss';
@import './components/SafetyNumberViewer.scss';

View File

@ -1,6 +1,7 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { webFrame } from 'electron';
import { isNumber, noop } from 'lodash';
import { bindActionCreators } from 'redux';
import { render, unstable_batchedUpdates as batchedUpdates } from 'react-dom';
@ -64,7 +65,6 @@ import {
EnvelopeEvent,
} from './textsecure/messageReceiverEvents';
import type { WebAPIType } from './textsecure/WebAPI';
import * as universalExpireTimer from './util/universalExpireTimer';
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
import { getSendOptions } from './util/getSendOptions';
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
@ -88,13 +88,11 @@ import {
SendStatus,
} from './messages/MessageSendState';
import * as AttachmentDownloads from './messageModifiers/AttachmentDownloads';
import {
SystemTraySetting,
parseSystemTraySetting,
} from './types/SystemTraySetting';
import * as Stickers from './types/Stickers';
import { SignalService as Proto } from './protobuf';
import { onRetryRequest, onDecryptionError } from './util/handleRetry';
import { themeChanged } from './shims/themeChanged';
import { createIPCEvents } from './util/createIPCEvents';
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
@ -582,7 +580,11 @@ export async function startApp(): Promise<void> {
window.log.info('Storage fetch');
window.storage.fetch();
function mapOldThemeToNew(theme: Readonly<unknown>) {
function mapOldThemeToNew(
theme: Readonly<
'system' | 'light' | 'dark' | 'android' | 'ios' | 'android-dark'
>
): 'system' | 'light' | 'dark' {
switch (theme) {
case 'dark':
case 'light':
@ -611,129 +613,7 @@ export async function startApp(): Promise<void> {
cleanupSessionResets();
// These make key operations available to IPC handlers created in preload.js
window.Events = {
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
getThemeSetting: (): 'light' | 'dark' | 'system' =>
window.storage.get(
'theme-setting',
window.platform === 'darwin' ? 'system' : 'light'
),
setThemeSetting: (value: 'light' | 'dark' | 'system') => {
window.storage.put('theme-setting', value);
onChangeTheme();
},
getHideMenuBar: () => window.storage.get('hide-menu-bar'),
setHideMenuBar: (value: boolean) => {
window.storage.put('hide-menu-bar', value);
window.setAutoHideMenuBar(value);
window.setMenuBarVisibility(!value);
},
getSystemTraySetting: (): SystemTraySetting =>
parseSystemTraySetting(window.storage.get('system-tray-setting')),
setSystemTraySetting: (value: Readonly<SystemTraySetting>) => {
window.storage.put('system-tray-setting', value);
window.updateSystemTraySetting(value);
},
getNotificationSetting: () =>
window.storage.get('notification-setting', 'message'),
setNotificationSetting: (value: 'message' | 'name' | 'count' | 'off') =>
window.storage.put('notification-setting', value),
getNotificationDrawAttention: () =>
window.storage.get('notification-draw-attention', true),
setNotificationDrawAttention: (value: boolean) =>
window.storage.put('notification-draw-attention', value),
getAudioNotification: () => window.storage.get('audio-notification'),
setAudioNotification: (value: boolean) =>
window.storage.put('audio-notification', value),
getCountMutedConversations: () =>
window.storage.get('badge-count-muted-conversations', false),
setCountMutedConversations: (value: boolean) => {
window.storage.put('badge-count-muted-conversations', value);
window.Whisper.events.trigger('updateUnreadCount');
},
getCallRingtoneNotification: () =>
window.storage.get('call-ringtone-notification', true),
setCallRingtoneNotification: (value: boolean) =>
window.storage.put('call-ringtone-notification', value),
getCallSystemNotification: () =>
window.storage.get('call-system-notification', true),
setCallSystemNotification: (value: boolean) =>
window.storage.put('call-system-notification', value),
getIncomingCallNotification: () =>
window.storage.get('incoming-call-notification', true),
setIncomingCallNotification: (value: boolean) =>
window.storage.put('incoming-call-notification', value),
getSpellCheck: () => window.storage.get('spell-check', true),
setSpellCheck: (value: boolean) => {
window.storage.put('spell-check', value);
},
getAlwaysRelayCalls: () => window.storage.get('always-relay-calls'),
setAlwaysRelayCalls: (value: boolean) =>
window.storage.put('always-relay-calls', value),
getAutoLaunch: () => window.getAutoLaunch(),
setAutoLaunch: (value: boolean) => window.setAutoLaunch(value),
isPrimary: () => window.textsecure.storage.user.getDeviceId() === 1,
getSyncRequest: () =>
new Promise<void>((resolve, reject) => {
const FIVE_MINUTES = 5 * 60 * 60 * 1000;
const syncRequest = window.getSyncRequest(FIVE_MINUTES);
syncRequest.addEventListener('success', () => resolve());
syncRequest.addEventListener('timeout', () =>
reject(new Error('timeout'))
);
}),
getLastSyncTime: () => window.storage.get('synced_at'),
setLastSyncTime: (value: number) =>
window.storage.put('synced_at', value),
getUniversalExpireTimer: (): number | undefined => {
return universalExpireTimer.get();
},
setUniversalExpireTimer: async (
newValue: number | undefined
): Promise<void> => {
await universalExpireTimer.set(newValue);
// Update account in Storage Service
const conversationId = window.ConversationController.getOurConversationIdOrThrow();
const account = window.ConversationController.get(conversationId);
assert(account, "Account wasn't found");
account.captureChange('universalExpireTimer');
// Add a notification to the currently open conversation
const state = window.reduxStore.getState();
const selectedId = state.conversations.selectedConversationId;
if (selectedId) {
const conversation = window.ConversationController.get(selectedId);
assert(conversation, "Conversation wasn't found");
await conversation.updateLastMessage();
}
},
addDarkOverlay: () => {
if ($('.dark-overlay').length) {
return;
}
$(document.body).prepend('<div class="dark-overlay"></div>');
$('.dark-overlay').on('click', () => $('.dark-overlay').remove());
},
removeDarkOverlay: () => $('.dark-overlay').remove(),
showKeyboardShortcuts: () => window.showKeyboardShortcuts(),
deleteAllData: async () => {
await window.sqlInitializer.goBackToMainProcess();
const clearDataView = new window.Whisper.ClearDataView().render();
$('body').append(clearDataView.el);
},
window.Events = createIPCEvents({
shutdown: async () => {
window.log.info('background/shutdown');
// Stop background processing
@ -763,112 +643,9 @@ export async function startApp(): Promise<void> {
// Shut down the data interface cleanly
await window.Signal.Data.shutdown();
},
});
showStickerPack: (packId: string, key: string) => {
// We can get these events even if the user has never linked this instance.
if (!window.Signal.Util.Registration.everDone()) {
window.log.warn('showStickerPack: Not registered, returning early');
return;
}
if (window.isShowingModal) {
window.log.warn(
'showStickerPack: Already showing modal, returning early'
);
return;
}
try {
window.isShowingModal = true;
// Kick off the download
Stickers.downloadEphemeralPack(packId, key);
const props = {
packId,
onClose: async () => {
window.isShowingModal = false;
stickerPreviewModalView.remove();
await Stickers.removeEphemeralPack(packId);
},
};
const stickerPreviewModalView = new window.Whisper.ReactWrapperView({
className: 'sticker-preview-modal-wrapper',
JSX: window.Signal.State.Roots.createStickerPreviewModal(
window.reduxStore,
props
),
});
} catch (error) {
window.isShowingModal = false;
window.log.error(
'showStickerPack: Ran into an error!',
error && error.stack ? error.stack : error
);
const errorView = new window.Whisper.ReactWrapperView({
className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal,
props: {
onClose: () => {
errorView.remove();
},
},
});
}
},
showGroupViaLink: async (hash: string) => {
// We can get these events even if the user has never linked this instance.
if (!window.Signal.Util.Registration.everDone()) {
window.log.warn('showGroupViaLink: Not registered, returning early');
return;
}
if (window.isShowingModal) {
window.log.warn(
'showGroupViaLink: Already showing modal, returning early'
);
return;
}
try {
await window.Signal.Groups.joinViaLink(hash);
} catch (error) {
window.log.error(
'showGroupViaLink: Ran into an error!',
error && error.stack ? error.stack : error
);
const errorView = new window.Whisper.ReactWrapperView({
className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal,
props: {
title: window.i18n('GroupV2--join--general-join-failure--title'),
description: window.i18n('GroupV2--join--general-join-failure'),
onClose: () => {
errorView.remove();
},
},
});
}
window.isShowingModal = false;
},
unknownSignalLink: () => {
window.log.warn('unknownSignalLink: Showing error dialog');
const errorView = new window.Whisper.ReactWrapperView({
className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal,
props: {
description: window.i18n('unknown-sgnl-link'),
onClose: () => {
errorView.remove();
},
},
});
},
installStickerPack: async (packId: string, key: string) => {
Stickers.downloadStickerPack(packId, key, {
finalStatus: 'installed',
});
},
};
webFrame.setZoomFactor(window.Events.getZoomFactor());
// How long since we were last running?
const lastHeartbeat = window.storage.get('lastHeartbeat', 0);
@ -2373,7 +2150,7 @@ export async function startApp(): Promise<void> {
'theme-setting',
await window.Events.getThemeSetting()
);
onChangeTheme();
themeChanged();
}
const syncRequest = window.getSyncRequest();
window.Whisper.events.trigger('contactsync:begin');
@ -2457,18 +2234,7 @@ export async function startApp(): Promise<void> {
}
}
function onChangeTheme() {
if (window.reduxActions && window.reduxActions.user) {
const theme = window.Events.getThemeSetting();
window.reduxActions.user.userChanged({
theme: theme === 'system' ? window.systemTheme : theme,
});
}
}
window.SignalContext.nativeThemeListener.subscribe(() => {
onChangeTheme();
});
window.SignalContext.nativeThemeListener.subscribe(themeChanged);
const FIVE_MINUTES = 5 * 60 * 1000;

View File

@ -41,7 +41,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
name: text('name', overrideProps.name || ''),
noteToSelf: boolean('noteToSelf', overrideProps.noteToSelf || false),
onEditProfile: action('onEditProfile'),
onSetChatColor: action('onSetChatColor'),
onViewArchive: action('onViewArchive'),
onViewPreferences: action('onViewPreferences'),
phoneNumber: text('phoneNumber', overrideProps.phoneNumber || ''),

View File

@ -13,7 +13,6 @@ export type Props = {
readonly i18n: LocalizerType;
onEditProfile: () => unknown;
onSetChatColor: () => unknown;
onViewPreferences: () => unknown;
onViewArchive: () => unknown;
@ -30,7 +29,6 @@ export const AvatarPopup = (props: Props): JSX.Element => {
phoneNumber,
title,
onEditProfile,
onSetChatColor,
onViewPreferences,
onViewArchive,
style,
@ -79,21 +77,6 @@ export const AvatarPopup = (props: Props): JSX.Element => {
{i18n('mainMenuSettings')}
</div>
</button>
<button
type="button"
className="module-avatar-popup__item"
onClick={onSetChatColor}
>
<div
className={classNames(
'module-avatar-popup__item__icon',
'module-avatar-popup__item__icon-colors'
)}
/>
<div className="module-avatar-popup__item__text">
{i18n('avatarMenuChatColors')}
</div>
</button>
<button
type="button"
className="module-avatar-popup__item"

View File

@ -26,7 +26,7 @@ const createProps = (): PropsType => ({
addCustomColor: action('addCustomColor'),
colorSelected: action('colorSelected'),
editCustomColor: action('editCustomColor'),
getConversationsWithCustomColor: (_: string) => [],
getConversationsWithCustomColor: (_: string) => Promise.resolve([]),
i18n,
removeCustomColor: action('removeCustomColor'),
removeCustomColorOnConversations: action('removeCustomColorOnConversations'),

View File

@ -26,7 +26,9 @@ type CustomColorDataType = {
export type PropsDataType = {
conversationId?: string;
customColors?: Record<string, CustomColorType>;
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
getConversationsWithCustomColor: (
colorId: string
) => Promise<Array<ConversationType>>;
i18n: LocalizerType;
isGlobal?: boolean;
selectedColor?: ConversationColorType;
@ -34,10 +36,7 @@ export type PropsDataType = {
};
type PropsActionType = {
addCustomColor: (
color: CustomColorType,
nextAction: (uuid: string) => unknown
) => unknown;
addCustomColor: (color: CustomColorType, conversationId?: string) => unknown;
colorSelected: (payload: {
conversationId: string;
conversationColor?: ConversationColorType;
@ -100,20 +99,10 @@ export const ChatColorPicker = ({
}
};
const onColorAdded = (value: CustomColorType) => {
return (id: string) => {
onSelectColor('custom', {
id,
value,
});
};
};
const renderCustomColorEditorWrapper = () => (
<CustomColorEditorWrapper
customColorToEdit={customColorToEdit}
i18n={i18n}
isGlobal={isGlobal}
onClose={() => setCustomColorToEdit(undefined)}
onSave={(color: CustomColorType) => {
if (customColorToEdit?.id) {
@ -123,16 +112,12 @@ export const ChatColorPicker = ({
value: color,
});
} else {
addCustomColor(color, onColorAdded(color));
addCustomColor(color, conversationId);
}
}}
/>
);
if (isGlobal && customColorToEdit) {
return renderCustomColorEditorWrapper();
}
return (
<div className="ChatColorPicker__container">
{customColorToEdit ? renderCustomColorEditorWrapper() : null}
@ -228,7 +213,7 @@ export const ChatColorPicker = ({
removeCustomColorOnConversations(colorId);
}}
onDupe={() => {
addCustomColor(colorValues, onColorAdded(colorValues));
addCustomColor(colorValues, conversationId);
}}
onEdit={() => {
setCustomColorToEdit({ id: colorId, value: colorValues });
@ -279,7 +264,9 @@ export const ChatColorPicker = ({
type CustomColorBubblePropsType = {
color: CustomColorType;
colorId: string;
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
getConversationsWithCustomColor: (
colorId: string
) => Promise<Array<ConversationType>>;
i18n: LocalizerType;
isSelected: boolean;
onDelete: () => unknown;
@ -398,11 +385,13 @@ const CustomColorBubble = ({
attributes={{
className: 'ChatColorPicker__context--delete',
}}
onClick={(event: MouseEvent) => {
onClick={async (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
const conversations = getConversationsWithCustomColor(colorId);
const conversations = await getConversationsWithCustomColor(
colorId
);
if (!conversations.length) {
onDelete();
} else {
@ -420,7 +409,6 @@ const CustomColorBubble = ({
type CustomColorEditorWrapperPropsType = {
customColorToEdit?: CustomColorDataType;
i18n: LocalizerType;
isGlobal: boolean;
onClose: () => unknown;
onSave: (color: CustomColorType) => unknown;
};
@ -428,7 +416,6 @@ type CustomColorEditorWrapperPropsType = {
const CustomColorEditorWrapper = ({
customColorToEdit,
i18n,
isGlobal,
onClose,
onSave,
}: CustomColorEditorWrapperPropsType): JSX.Element => {
@ -441,20 +428,16 @@ const CustomColorEditorWrapper = ({
/>
);
if (!isGlobal) {
return (
<Modal
hasXButton
i18n={i18n}
moduleClassName="ChatColorPicker__modal"
noMouseClose
onClose={onClose}
title={i18n('CustomColorEditor__title')}
>
{editor}
</Modal>
);
}
return editor;
return (
<Modal
hasXButton
i18n={i18n}
moduleClassName="ChatColorPicker__modal"
noMouseClose
onClose={onClose}
title={i18n('CustomColorEditor__title')}
>
{editor}
</Modal>
);
};

View File

@ -0,0 +1,27 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import { Checkbox, PropsType } from './Checkbox';
const createProps = (): PropsType => ({
checked: false,
label: 'Check Me!',
name: 'check-me',
onChange: action('onChange'),
});
const story = storiesOf('Components/Checkbox', module);
story.add('Normal', () => <Checkbox {...createProps()} />);
story.add('Checked', () => <Checkbox {...createProps()} checked />);
story.add('Description', () => (
<Checkbox {...createProps()} description="This is a checkbox" />
));
story.add('Disabled', () => <Checkbox {...createProps()} disabled />);

View File

@ -0,0 +1,47 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { getClassNamesFor } from '../util/getClassNamesFor';
export type PropsType = {
checked?: boolean;
description?: string;
disabled?: boolean;
label: string;
moduleClassName?: string;
name: string;
onChange: (value: boolean) => unknown;
};
export const Checkbox = ({
checked,
description,
disabled,
label,
moduleClassName,
name,
onChange,
}: PropsType): JSX.Element => {
const getClassName = getClassNamesFor('Checkbox', moduleClassName);
return (
<div className={getClassName('')}>
<div className={getClassName('__container')}>
<div className={getClassName('__checkbox')}>
<input
checked={Boolean(checked)}
disabled={disabled}
name={name}
onChange={ev => onChange(ev.target.checked)}
type="checkbox"
/>
</div>
<div>
<label htmlFor={name}>{label}</label>
<div className={getClassName('__description')}>{description}</div>
</div>
</div>
</div>
);
};

View File

@ -1,50 +1,17 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { Modal } from './Modal';
import { LocalizerType } from '../types/Util';
type PropsType = {
i18n: LocalizerType;
// ChatColorPicker
isChatColorEditorVisible: boolean;
renderChatColorPicker: () => JSX.Element;
toggleChatColorEditor: () => unknown;
// ProfileEditor
isProfileEditorVisible: boolean;
renderProfileEditor: () => JSX.Element;
};
export const GlobalModalContainer = ({
i18n,
// ChatColorPicker
isChatColorEditorVisible,
renderChatColorPicker,
toggleChatColorEditor,
// ProfileEditor
isProfileEditorVisible,
renderProfileEditor,
}: PropsType): JSX.Element | null => {
if (isChatColorEditorVisible) {
return (
<Modal
hasXButton
i18n={i18n}
moduleClassName="ChatColorPicker__modal"
noMouseClose
onClose={toggleChatColorEditor}
title={i18n('ChatColorPicker__global-chat-color')}
>
{renderChatColorPicker()}
</Modal>
);
}
if (isProfileEditorVisible) {
return renderProfileEditor();
}

View File

@ -58,7 +58,6 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
showArchivedConversations: action('showArchivedConversations'),
startComposing: action('startComposing'),
toggleChatColorEditor: action('toggleChatColorEditor'),
toggleProfileEditor: action('toggleProfileEditor'),
});

View File

@ -64,7 +64,6 @@ export type PropsType = {
showArchivedConversations: () => void;
startComposing: () => void;
toggleChatColorEditor: () => void;
toggleProfileEditor: () => void;
};
@ -353,7 +352,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
searchConversationName,
searchTerm,
showArchivedConversations,
toggleChatColorEditor,
toggleProfileEditor,
} = this.props;
const { showingAvatarPopup, popperRoot } = this.state;
@ -416,10 +414,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
toggleProfileEditor();
this.hideAvatarPopup();
}}
onSetChatColor={() => {
toggleChatColorEditor();
this.hideAvatarPopup();
}}
onViewPreferences={() => {
showSettings();
this.hideAvatarPopup();

View File

@ -0,0 +1,171 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import { Preferences, PropsType } from './Preferences';
import { setup as setupI18n } from '../../js/modules/i18n';
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
import { ThemeType } from '../types/Util';
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
const i18n = setupI18n('en', enMessages);
const availableMicrophones = [
{
name: 'DefAuLt (Headphones)',
index: 0,
uniqueId: 'Default',
i18nKey: 'default_communication_device',
},
];
const availableSpeakers = [
{
name: 'Default',
index: 0,
uniqueId: 'Default',
i18nKey: 'default_communication_device',
},
{
name: "Natalie's Airpods (Bluetooth)",
index: 1,
uniqueId: 'aa',
},
{
name: 'UE Boom (Bluetooth)',
index: 2,
uniqueId: 'bb',
},
];
const createProps = (): PropsType => ({
availableCameras: [
{
deviceId:
'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c',
groupId:
'63ee218d2446869e40adfc958ff98263e51f74382b0143328ee4826f20a76f47',
kind: 'videoinput' as MediaDeviceKind,
label: 'FaceTime HD Camera (Built-in) (9fba:bced)',
},
{
deviceId:
'e2db196a31d50ff9b135299dc0beea67f65b1a25a06d8a4ce76976751bb7a08d',
groupId:
'218ba7f00d7b1239cca15b9116769e5e7d30cc01104ebf84d667643661e0ecf9',
kind: 'videoinput' as MediaDeviceKind,
label: 'Logitech Webcam (4e72:9058)',
},
],
availableMicrophones,
availableSpeakers,
blockedCount: 0,
customColors: {},
defaultConversationColor: DEFAULT_CONVERSATION_COLOR,
deviceName: 'Work Windows ME',
hasAudioNotifications: true,
hasAutoLaunch: true,
hasCallNotifications: true,
hasCallRingtoneNotification: false,
hasCountMutedConversations: false,
hasHideMenuBar: false,
hasIncomingCallNotifications: true,
hasLinkPreviews: true,
hasMediaCameraPermissions: true,
hasMediaPermissions: true,
hasMinimizeToAndStartInSystemTray: true,
hasMinimizeToSystemTray: true,
hasNotificationAttention: false,
hasNotifications: true,
hasReadReceipts: true,
hasRelayCalls: false,
hasSpellCheck: true,
hasTypingIndicators: true,
lastSyncTime: Date.now(),
notificationContent: 'name',
selectedCamera:
'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c',
selectedMicrophone: availableMicrophones[0],
selectedSpeaker: availableSpeakers[1],
theme: ThemeType.light,
themeSetting: 'system',
universalExpireTimer: 3600,
whoCanFindMe: PhoneNumberDiscoverability.Discoverable,
whoCanSeeMe: PhoneNumberSharingMode.Everybody,
zoomFactor: 1,
addCustomColor: action('addCustomColor'),
editCustomColor: action('editCustomColor'),
doDeleteAllData: action('doDeleteAllData'),
getConversationsWithCustomColor: () => Promise.resolve([]),
initialSpellCheckSetting: true,
makeSyncRequest: () => {
action('makeSyncRequest');
return Promise.resolve();
},
removeCustomColor: action('removeCustomColor'),
removeCustomColorOnConversations: action('removeCustomColorOnConversations'),
resetAllChatColors: action('resetAllChatColors'),
resetDefaultChatColor: action('resetDefaultChatColor'),
setGlobalDefaultConversationColor: action(
'setGlobalDefaultConversationColor'
),
isAudioNotificationsSupported: true,
isAutoLaunchSupported: true,
isHideMenuBarSupported: true,
isNotificationAttentionSupported: true,
isSyncSupported: true,
isSystemTraySupported: true,
onAudioNotificationsChange: action('onAudioNotificationsChange'),
onAutoLaunchChange: action('onAutoLaunchChange'),
onCallNotificationsChange: action('onCallNotificationsChange'),
onCallRingtoneNotificationChange: action('onCallRingtoneNotificationChange'),
onCountMutedConversationsChange: action('onCountMutedConversationsChange'),
onHideMenuBarChange: action('onHideMenuBarChange'),
onIncomingCallNotificationsChange: action(
'onIncomingCallNotificationsChange'
),
onLastSyncTimeChange: action('onLastSyncTimeChange'),
onMediaCameraPermissionsChange: action('onMediaCameraPermissionsChange'),
onMediaPermissionsChange: action('onMediaPermissionsChange'),
onMinimizeToAndStartInSystemTrayChange: action(
'onMinimizeToAndStartInSystemTrayChange'
),
onMinimizeToSystemTrayChange: action('onMinimizeToSystemTrayChange'),
onNotificationAttentionChange: action('onNotificationAttentionChange'),
onNotificationContentChange: action('onNotificationContentChange'),
onNotificationsChange: action('onNotificationsChange'),
onRelayCallsChange: action('onRelayCallsChange'),
onSelectedCameraChange: action('onSelectedCameraChange'),
onSelectedMicrophoneChange: action('onSelectedMicrophoneChange'),
onSelectedSpeakerChange: action('onSelectedSpeakerChange'),
onSpellCheckChange: action('onSpellCheckChange'),
onThemeChange: action('onThemeChange'),
onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'),
onZoomFactorChange: action('onZoomFactorChange'),
i18n,
});
const story = storiesOf('Components/Preferences', module);
story.add('Preferences', () => <Preferences {...createProps()} />);
story.add('Blocked 1', () => (
<Preferences {...createProps()} blockedCount={1} />
));
story.add('Blocked Many', () => (
<Preferences {...createProps()} blockedCount={55} />
));
story.add('Custom universalExpireTimer', () => (
<Preferences {...createProps()} universalExpireTimer={9000} />
));

File diff suppressed because it is too large Load Diff

View File

@ -11,34 +11,48 @@ export type Option = Readonly<{
}>;
export type PropsType = Readonly<{
disabled?: boolean;
moduleClassName?: string;
name?: string;
options: ReadonlyArray<Option>;
onChange(value: string): void;
value: string | number;
value?: string | number;
}>;
export function Select(props: PropsType): JSX.Element {
const { moduleClassName, value, options, onChange } = props;
export function Select({
disabled,
moduleClassName,
name,
onChange,
options,
value,
}: PropsType): JSX.Element {
const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
onChange(event.target.value);
};
return (
<div className={classNames(['module-select', moduleClassName])}>
<select value={value} onChange={onSelectChange}>
{options.map(({ disabled, text, value: optionValue }) => {
return (
<option
disabled={disabled}
value={optionValue}
key={optionValue}
aria-label={text}
>
{text}
</option>
);
})}
<select
disabled={disabled}
name={name}
value={value}
onChange={onSelectChange}
>
{options.map(
({ disabled: optionDisabled, text, value: optionValue }) => {
return (
<option
disabled={optionDisabled}
value={optionValue}
key={optionValue}
aria-label={text}
>
{text}
</option>
);
}
)}
</select>
</div>
);

View File

@ -29,6 +29,7 @@ import {
} from './shared';
import * as log from './log';
import { reallyJsonStringify } from '../util/reallyJsonStringify';
import { Environment, getEnvironment } from '../environment';
// To make it easier to visually scan logs, we make all levels the same length
const levelFromName = pino().levels.values;
@ -81,7 +82,7 @@ const getHeader = ({
Time: Date.now(),
'User agent': window.navigator.userAgent,
'Node version': window.getNodeVersion(),
Environment: window.getEnvironment(),
Environment: getEnvironment(),
'App version': window.getVersion(),
}),
headerSection('User info', user),
@ -179,11 +180,8 @@ const publish = uploadDebugLogs;
// A modern logging interface for the browser
const env = window.getEnvironment();
const IS_PRODUCTION = env === 'production';
function logAtLevel(level: LogLevel, ...args: ReadonlyArray<unknown>): void {
if (!IS_PRODUCTION) {
if (getEnvironment() !== Environment.Production) {
const prefix = getLogLevelString(level)
.toUpperCase()
.padEnd(levelMaxLength, ' ');

218
ts/main/settingsChannel.ts Normal file
View File

@ -0,0 +1,218 @@
// Copyright 2017-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcMain as ipc, BrowserWindow, session } from 'electron';
import { userConfig } from '../../app/user_config';
import { ephemeralConfig } from '../../app/ephemeral_config';
import { installPermissionsHandler } from '../../app/permissions';
import { strictAssert } from '../util/assert';
import {
IPCEventsValuesType,
IPCEventsCallbacksType,
} from '../util/createIPCEvents';
export class SettingsChannel {
private mainWindow?: BrowserWindow;
public setMainWindow(mainWindow: BrowserWindow): void {
this.mainWindow = mainWindow;
}
public install(): void {
this.installSetting('deviceName', { setter: false });
// ChatColorPicker redux hookups
this.installCallback('getCustomColors');
this.installCallback('getConversationsWithCustomColor');
this.installCallback('resetAllChatColors');
this.installCallback('resetDefaultChatColor');
this.installCallback('addCustomColor');
this.installCallback('editCustomColor');
this.installCallback('removeCustomColor');
this.installCallback('removeCustomColorOnConversations');
this.installCallback('setGlobalDefaultConversationColor');
this.installCallback('getDefaultConversationColor');
// Various callbacks
this.installCallback('getAvailableIODevices');
this.installCallback('isPrimary');
this.installCallback('syncRequest');
// Getters only. These are set by the primary device
this.installSetting('blockedCount', { setter: false });
this.installSetting('linkPreviewSetting', { setter: false });
this.installSetting('phoneNumberDiscoverabilitySetting', { setter: false });
this.installSetting('phoneNumberSharingSetting', { setter: false });
this.installSetting('readReceiptSetting', { setter: false });
this.installSetting('typingIndicatorSetting', { setter: false });
this.installSetting('themeSetting');
this.installSetting('hideMenuBar');
this.installSetting('systemTraySetting');
this.installSetting('notificationSetting');
this.installSetting('notificationDrawAttention');
this.installSetting('audioNotification');
this.installSetting('countMutedConversations');
this.installSetting('spellCheck', {
isEphemeral: true,
});
this.installSetting('autoLaunch');
this.installSetting('alwaysRelayCalls');
this.installSetting('callRingtoneNotification');
this.installSetting('callSystemNotification');
this.installSetting('incomingCallNotification');
// Media settings
this.installSetting('preferredAudioInputDevice');
this.installSetting('preferredAudioOutputDevice');
this.installSetting('preferredVideoInputDevice');
this.installSetting('lastSyncTime');
this.installSetting('universalExpireTimer');
this.installSetting('zoomFactor');
installPermissionsHandler({ session, userConfig });
// These ones are different because its single source of truth is userConfig,
// not IndexedDB
ipc.on('settings:get:mediaPermissions', event => {
event.sender.send(
'settings:get-success:mediaPermissions',
null,
userConfig.get('mediaPermissions') || false
);
});
ipc.on('settings:get:mediaCameraPermissions', event => {
event.sender.send(
'settings:get-success:mediaCameraPermissions',
null,
userConfig.get('mediaCameraPermissions') || false
);
});
ipc.on('settings:set:mediaPermissions', (event, value) => {
userConfig.set('mediaPermissions', value);
// We reinstall permissions handler to ensure that a revoked permission takes effect
installPermissionsHandler({ session, userConfig });
event.sender.send('settings:set-success:mediaPermissions', null, value);
});
ipc.on('settings:set:mediaCameraPermissions', (event, value) => {
userConfig.set('mediaCameraPermissions', value);
// We reinstall permissions handler to ensure that a revoked permission takes effect
installPermissionsHandler({ session, userConfig });
event.sender.send(
'settings:set-success:mediaCameraPermissions',
null,
value
);
});
}
public getSettingFromMainWindow<Name extends keyof IPCEventsValuesType>(
name: Name
): Promise<IPCEventsValuesType[Name]> {
const { mainWindow } = this;
return new Promise((resolve, reject) => {
ipc.once(`settings:get-success:${name}`, (_event, error, value) => {
if (error) {
reject(error);
} else {
resolve(value);
}
});
if (!mainWindow || !mainWindow.webContents) {
reject(new Error('No main window available'));
return;
}
mainWindow.webContents.send(`settings:get:${name}`);
});
}
private installCallback<Name extends keyof IPCEventsCallbacksType>(
name: Name
): void {
ipc.on(`callbacks:call:${name}`, async (event, args) => {
const { mainWindow } = this;
const contents = event.sender;
if (!mainWindow || !mainWindow.webContents) {
return contents.send(
`callbacks:call-success:${name}`,
'Main window not found'
);
}
mainWindow.webContents.send(`callbacks:call:${name}`, args);
ipc.once(`callbacks:call-success:${name}`, (_event, error, value) => {
if (contents.isDestroyed()) {
return;
}
contents.send(`callbacks:call-success:${name}`, error, value);
});
});
}
private installSetting<Name extends keyof IPCEventsValuesType>(
name: Name,
{
getter = true,
setter = true,
isEphemeral = false,
}: { getter?: boolean; setter?: boolean; isEphemeral?: boolean } = {}
): void {
if (getter) {
ipc.on(`settings:get:${name}`, async event => {
const { mainWindow } = this;
if (mainWindow && mainWindow.webContents) {
let error: Error | undefined;
let value: unknown;
try {
value = await this.getSettingFromMainWindow(name);
} catch (caughtError) {
error = caughtError;
}
const contents = event.sender;
if (contents.isDestroyed()) {
return;
}
contents.send(`settings:get-success:${name}`, error, value);
}
});
}
if (!setter) {
return;
}
ipc.on(`settings:set:${name}`, (event, value) => {
if (isEphemeral) {
strictAssert(name === 'spellCheck', 'Only spellCheck is ephemeral');
ephemeralConfig.set('spell-check', value);
}
const { mainWindow } = this;
if (mainWindow && mainWindow.webContents) {
ipc.once(`settings:set-success:${name}`, (_event, error) => {
const contents = event.sender;
if (contents.isDestroyed()) {
return;
}
contents.send(`settings:set-success:${name}`, error);
});
mainWindow.webContents.send(`settings:set:${name}`, value);
}
});
}
}

View File

@ -23,7 +23,6 @@ import {
AvatarColorType,
ConversationColorType,
CustomColorType,
DEFAULT_CONVERSATION_COLOR,
} from '../types/Colors';
import { MessageModel } from './messages';
import { isMuted } from '../util/isMuted';
@ -950,7 +949,7 @@ export class ConversationModel extends window.Backbone
bumpTyping(): void {
// We don't send typing messages if the setting is disabled
if (!window.storage.get('typingIndicators')) {
if (!window.Events.getTypingIndicatorSetting()) {
return;
}
@ -4680,10 +4679,7 @@ export class ConversationModel extends window.Backbone
}
getConversationColor(): ConversationColorType {
const defaultConversationColor = window.storage.get(
'defaultConversationColor',
DEFAULT_CONVERSATION_COLOR
);
const defaultConversationColor = window.Events.getDefaultConversationColor();
return this.get('conversationColor') || defaultConversationColor.color;
}
@ -4692,10 +4688,7 @@ export class ConversationModel extends window.Backbone
customColor?: CustomColorType;
customColorId?: string;
} {
const defaultConversationColor = window.storage.get(
'defaultConversationColor',
DEFAULT_CONVERSATION_COLOR
);
const defaultConversationColor = window.Events.getDefaultConversationColor();
if (this.getConversationColor() !== 'custom') {
return {

View File

@ -38,11 +38,12 @@ import {
} from '../state/ducks/calling';
import { getConversationCallMode } from '../state/ducks/conversations';
import {
CallMode,
AudioDevice,
MediaDeviceSettings,
AvailableIODevicesType,
CallMode,
GroupCallConnectionState,
GroupCallJoinState,
MediaDeviceSettings,
PresentableSource,
PresentedSource,
} from '../types/Calling';
@ -1254,11 +1255,26 @@ export class CallingClass {
}
}
async getMediaDeviceSettings(): Promise<MediaDeviceSettings> {
async getAvailableIODevices(): Promise<AvailableIODevicesType> {
const availableCameras = await this.videoCapturer.enumerateDevices();
const availableMicrophones = RingRTC.getAudioInputs();
const preferredMicrophone = window.storage.get(
'preferred-audio-input-device'
);
const availableSpeakers = RingRTC.getAudioOutputs();
return {
availableCameras,
availableMicrophones,
availableSpeakers,
};
}
async getMediaDeviceSettings(): Promise<MediaDeviceSettings> {
const {
availableCameras,
availableMicrophones,
availableSpeakers,
} = await this.getAvailableIODevices();
const preferredMicrophone = window.Events.getPreferredAudioInputDevice();
const selectedMicIndex = this.findBestMatchingDeviceIndex(
availableMicrophones,
preferredMicrophone
@ -1268,10 +1284,7 @@ export class CallingClass {
? availableMicrophones[selectedMicIndex]
: undefined;
const availableSpeakers = RingRTC.getAudioOutputs();
const preferredSpeaker = window.storage.get(
'preferred-audio-output-device'
);
const preferredSpeaker = window.Events.getPreferredAudioOutputDevice();
const selectedSpeakerIndex = this.findBestMatchingDeviceIndex(
availableSpeakers,
preferredSpeaker
@ -1281,8 +1294,7 @@ export class CallingClass {
? availableSpeakers[selectedSpeakerIndex]
: undefined;
const availableCameras = await this.videoCapturer.enumerateDevices();
const preferredCamera = window.storage.get('preferred-video-input-device');
const preferredCamera = window.Events.getPreferredVideoInputDevice();
const selectedCamera = this.findBestMatchingCamera(
availableCameras,
preferredCamera
@ -1343,13 +1355,13 @@ export class CallingClass {
setPreferredMicrophone(device: AudioDevice): void {
window.log.info('MediaDevice: setPreferredMicrophone', device);
window.storage.put('preferred-audio-input-device', device);
window.Events.setPreferredAudioInputDevice(device);
RingRTC.setAudioInput(device.index);
}
setPreferredSpeaker(device: AudioDevice): void {
window.log.info('MediaDevice: setPreferredSpeaker', device);
window.storage.put('preferred-audio-output-device', device);
window.Events.setPreferredAudioOutputDevice(device);
RingRTC.setAudioOutput(device.index);
}
@ -1363,7 +1375,7 @@ export class CallingClass {
async setPreferredCamera(device: string): Promise<void> {
window.log.info('MediaDevice: setPreferredCamera', device);
window.storage.put('preferred-video-input-device', device);
window.Events.setPreferredVideoInputDevice(device);
await this.videoCapturer.setPreferredDevice(device);
}
@ -1373,7 +1385,7 @@ export class CallingClass {
): Promise<void> {
window.log.info('CallingClass.handleCallingMessage()');
const enableIncomingCalls = await window.getIncomingCallNotification();
const enableIncomingCalls = window.Events.getIncomingCallNotification();
if (callingMessage.offer && !enableIncomingCalls) {
// Drop offers silently if incoming call notifications are disabled.
window.log.info('Incoming calls are disabled, ignoring call offer.');

View File

@ -170,16 +170,14 @@ export async function toAccountRecord(
accountRecord.noteToSelfMarkedUnread = Boolean(
conversation.get('markedUnread')
);
accountRecord.readReceipts = Boolean(
window.storage.get('read-receipt-setting')
);
accountRecord.readReceipts = Boolean(window.Events.getReadReceiptSetting());
accountRecord.sealedSenderIndicators = Boolean(
window.storage.get('sealedSenderIndicators')
);
accountRecord.typingIndicators = Boolean(
window.storage.get('typingIndicators')
window.Events.getTypingIndicatorSetting()
);
accountRecord.linkPreviews = Boolean(window.storage.get('linkPreviews'));
accountRecord.linkPreviews = Boolean(window.Events.getLinkPreviewSetting());
const primarySendsSms = window.storage.get('primarySendsSms');
if (primarySendsSms !== undefined) {

View File

@ -0,0 +1,25 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { Middleware } from 'redux';
import { COLORS_CHANGED, COLOR_SELECTED } from '../state/ducks/conversations';
export const dispatchItemsMiddleware: Middleware = ({
getState,
}) => next => action => {
if (
action.type === 'items/PUT' ||
action.type === 'items/PUT_EXTERNAL' ||
action.type === 'items/REMOVE' ||
action.type === 'items/REMOVE_EXTERNAL' ||
action.type === 'items/RESET' ||
action.type === COLOR_SELECTED ||
action.type === COLORS_CHANGED
) {
ipcRenderer.send('preferences-changed', getState().items);
}
return next(action);
};

11
ts/shims/themeChanged.ts Normal file
View File

@ -0,0 +1,11 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export function themeChanged(): void {
if (window.reduxActions && window.reduxActions.user) {
const theme = window.Events.getThemeSetting();
window.reduxActions.user.userChanged({
theme: theme === 'system' ? window.systemTheme : theme,
});
}
}

View File

@ -15,6 +15,7 @@ import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { reducer, StateType } from './reducer';
import { dispatchItemsMiddleware } from '../shims/dispatchItemsMiddleware';
declare global {
// We want to extend `window`'s properties, so we need an interface.
@ -49,6 +50,7 @@ const logger = createLogger({
const middlewareList = [
promise,
thunk,
dispatchItemsMiddleware,
...(env === 'production' ? [] : [logger]),
];

View File

@ -524,7 +524,7 @@ async function showCallNotification(
isVideoCall: boolean
): Promise<void> {
const shouldNotify =
!window.isActive() && (await window.getCallSystemNotification());
!window.isActive() && window.Events.getCallSystemNotification();
if (!shouldNotify) {
return;
}

View File

@ -336,8 +336,8 @@ export const getConversationCallMode = (
// Actions
const COLORS_CHANGED = 'conversations/COLORS_CHANGED';
const COLOR_SELECTED = 'conversations/COLOR_SELECTED';
export const COLORS_CHANGED = 'conversations/COLORS_CHANGED';
export const COLOR_SELECTED = 'conversations/COLOR_SELECTED';
const COMPOSE_TOGGLE_EDITING_AVATAR =
'conversations/compose/COMPOSE_TOGGLE_EDITING_AVATAR';
const COMPOSE_ADD_AVATAR = 'conversations/compose/ADD_AVATAR';

View File

@ -4,22 +4,16 @@
// State
export type GlobalModalsStateType = {
readonly isChatColorEditorVisible: boolean;
readonly isProfileEditorVisible: boolean;
readonly profileEditorHasError: boolean;
};
// Actions
const TOGGLE_CHAT_COLOR_EDITOR = 'globalModals/TOGGLE_CHAT_COLOR_EDITOR';
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
export const TOGGLE_PROFILE_EDITOR_ERROR =
'globalModals/TOGGLE_PROFILE_EDITOR_ERROR';
type ToggleChatColorEditorActionType = {
type: typeof TOGGLE_CHAT_COLOR_EDITOR;
};
type ToggleProfileEditorActionType = {
type: typeof TOGGLE_PROFILE_EDITOR;
};
@ -29,22 +23,16 @@ export type ToggleProfileEditorErrorActionType = {
};
export type GlobalModalsActionType =
| ToggleChatColorEditorActionType
| ToggleProfileEditorActionType
| ToggleProfileEditorErrorActionType;
// Action Creators
export const actions = {
toggleChatColorEditor,
toggleProfileEditor,
toggleProfileEditorHasError,
};
function toggleChatColorEditor(): ToggleChatColorEditorActionType {
return { type: TOGGLE_CHAT_COLOR_EDITOR };
}
function toggleProfileEditor(): ToggleProfileEditorActionType {
return { type: TOGGLE_PROFILE_EDITOR };
}
@ -57,7 +45,6 @@ function toggleProfileEditorHasError(): ToggleProfileEditorErrorActionType {
export function getEmptyState(): GlobalModalsStateType {
return {
isChatColorEditorVisible: false,
isProfileEditorVisible: false,
profileEditorHasError: false,
};
@ -67,13 +54,6 @@ export function reducer(
state: Readonly<GlobalModalsStateType> = getEmptyState(),
action: Readonly<GlobalModalsActionType>
): GlobalModalsStateType {
if (action.type === TOGGLE_CHAT_COLOR_EDITOR) {
return {
...state,
isChatColorEditorVisible: !state.isChatColorEditorVisible,
};
}
if (action.type === TOGGLE_PROFILE_EDITOR) {
return {
...state,

View File

@ -16,6 +16,7 @@ import {
} from '../../types/Colors';
import { reloadSelectedConversation } from '../../shims/reloadSelectedConversation';
import { StorageAccessType } from '../../types/Storage.d';
import { actions as conversationActions } from './conversations';
// State
@ -139,7 +140,7 @@ function getDefaultCustomColorData() {
function addCustomColor(
customColor: CustomColorType,
nextAction: (uuid: string) => unknown
conversationId?: string
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
return (dispatch, getState) => {
const { customColors = getDefaultCustomColorData() } = getState().items;
@ -158,7 +159,25 @@ function addCustomColor(
};
dispatch(putItem('customColors', nextCustomColors));
nextAction(uuid);
const customColorData = {
id: uuid,
value: customColor,
};
if (conversationId) {
conversationActions.colorSelected({
conversationId,
conversationColor: 'custom',
customColorData,
})(dispatch, getState, null);
} else {
setGlobalDefaultConversationColor('custom', customColorData)(
dispatch,
getState,
null
);
}
};
}

View File

@ -43,3 +43,9 @@ export const getDefaultConversationColor = createSelector(
};
} => state.defaultConversationColor ?? DEFAULT_CONVERSATION_COLOR
);
export const getCustomColors = createSelector(
getItems,
(state: ItemsStateType): Record<string, CustomColorType> | undefined =>
state.customColors?.colors
);

View File

@ -38,9 +38,8 @@ const mapStateToProps = (
return {
...props,
customColors: customColors ? customColors.colors : {},
getConversationsWithCustomColor: getConversationsWithCustomColorSelector(
state
),
getConversationsWithCustomColor: (colorId: string) =>
Promise.resolve(getConversationsWithCustomColorSelector(state)(colorId)),
i18n: getIntl(state),
selectedColor: colorValues.conversationColor,
selectedCustomColor: {

View File

@ -6,8 +6,6 @@ import { connect } from 'react-redux';
import { mapDispatchToProps } from '../actions';
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
import { StateType } from '../reducer';
import { getIntl } from '../selectors/user';
import { SmartChatColorPicker } from './ChatColorPicker';
import { SmartProfileEditorModal } from './ProfileEditorModal';
// Workaround: A react component's required properties are filtering up through connect()
@ -16,10 +14,6 @@ import { SmartProfileEditorModal } from './ProfileEditorModal';
const FilteredSmartProfileEditorModal = SmartProfileEditorModal as any;
/* eslint-enable @typescript-eslint/no-explicit-any */
function renderChatColorPicker(): JSX.Element {
return <SmartChatColorPicker />;
}
function renderProfileEditor(): JSX.Element {
return <FilteredSmartProfileEditorModal />;
}
@ -27,8 +21,6 @@ function renderProfileEditor(): JSX.Element {
const mapStateToProps = (state: StateType) => {
return {
...state.globalModals,
i18n: getIntl(state),
renderChatColorPicker,
renderProfileEditor,
};
};

View File

@ -10,18 +10,18 @@ import {
} from '../../../state/ducks/globalModals';
describe('both/state/ducks/globalModals', () => {
describe('toggleChatColorEditor', () => {
const { toggleChatColorEditor } = actions;
describe('toggleProfileEditor', () => {
const { toggleProfileEditor } = actions;
it('toggles isChatColorEditorVisible', () => {
it('toggles isProfileEditorVisible', () => {
const state = getEmptyState();
const nextState = reducer(state, toggleChatColorEditor());
const nextState = reducer(state, toggleProfileEditor());
assert.isTrue(nextState.isChatColorEditorVisible);
assert.isTrue(nextState.isProfileEditorVisible);
const nextNextState = reducer(nextState, toggleChatColorEditor());
const nextNextState = reducer(nextState, toggleProfileEditor());
assert.isFalse(nextNextState.isChatColorEditorVisible);
assert.isFalse(nextNextState.isProfileEditorVisible);
});
});
});

View File

@ -0,0 +1,23 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { awaitObject } from '../../util/awaitObject';
describe('awaitObject', () => {
it('returns correct result', async () => {
assert.deepStrictEqual(
await awaitObject({
a: Promise.resolve(1),
b: Promise.resolve('b'),
c: Promise.resolve(null),
}),
{
a: 1,
b: 'b',
c: null,
}
);
});
});

View File

@ -156,12 +156,15 @@ export enum CallingDeviceType {
SPEAKER,
}
export type MediaDeviceSettings = {
availableMicrophones: Array<AudioDevice>;
selectedMicrophone: AudioDevice | undefined;
availableSpeakers: Array<AudioDevice>;
selectedSpeaker: AudioDevice | undefined;
export type AvailableIODevicesType = {
availableCameras: Array<MediaDeviceInfo>;
availableMicrophones: Array<AudioDevice>;
availableSpeakers: Array<AudioDevice>;
};
export type MediaDeviceSettings = AvailableIODevicesType & {
selectedMicrophone: AudioDevice | undefined;
selectedSpeaker: AudioDevice | undefined;
selectedCamera: string | undefined;
};

11
ts/types/Storage.d.ts vendored
View File

@ -24,6 +24,12 @@ export type SerializedCertificateType = {
serialized: ArrayBuffer;
};
export type ZoomFactorType = 0.75 | 1 | 1.25 | 1.5 | 2;
export type ThemeSettingType = 'system' | 'light' | 'dark';
export type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
export type StorageAccessType = {
'always-relay-calls': boolean;
'audio-notification': boolean;
@ -36,10 +42,10 @@ export type StorageAccessType = {
'system-tray-setting': SystemTraySetting;
'incoming-call-notification': boolean;
'notification-draw-attention': boolean;
'notification-setting': 'message' | 'name' | 'count' | 'off';
'notification-setting': NotificationSettingType;
'read-receipt-setting': boolean;
'spell-check': boolean;
'theme-setting': 'light' | 'dark' | 'system';
'theme-setting': ThemeSettingType;
attachmentMigration_isComplete: boolean;
attachmentMigration_lastProcessedIndex: number;
blocked: Array<string>;
@ -112,6 +118,7 @@ export type StorageAccessType = {
senderCertificate: SerializedCertificateType;
senderCertificateNoE164: SerializedCertificateType;
paymentAddress: string;
zoomFactor: ZoomFactorType;
// Deprecated
senderCertificateWithUuid: never;

View File

@ -51,3 +51,5 @@ type InternalAssertProps<
};
export type AssertProps<Result, Value> = InternalAssertProps<Result, Value>;
export type UnwrapPromise<Value> = Value extends Promise<infer T> ? T : Value;

24
ts/util/awaitObject.ts Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-restricted-syntax */
export async function awaitObject<Result extends { [key: string]: unknown }>(
settings: {
[key in keyof Result]: Promise<Result[key]>;
}
): Promise<Result> {
const keys = Object.keys(settings);
const promises = new Array<Promise<unknown>>();
for (const key of keys) {
promises.push(settings[key as keyof Result] as Promise<unknown>);
}
const values = await Promise.all(promises);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: any = {};
for (const [i, key] of keys.entries()) {
result[key] = values[i];
}
return result;
}

View File

@ -14,7 +14,7 @@ class CallingTones {
// eslint-disable-next-line class-methods-use-this
async playEndCall(): Promise<void> {
const canPlayTone = await window.getCallRingtoneNotification();
const canPlayTone = window.Events.getCallRingtoneNotification();
if (!canPlayTone) {
return;
}
@ -32,7 +32,7 @@ class CallingTones {
this.ringtone = undefined;
}
const canPlayTone = await window.getCallRingtoneNotification();
const canPlayTone = window.Events.getCallRingtoneNotification();
if (!canPlayTone) {
return;
}
@ -57,7 +57,7 @@ class CallingTones {
// eslint-disable-next-line class-methods-use-this
async someonePresenting() {
const canPlayTone = await window.getCallRingtoneNotification();
const canPlayTone = window.Events.getCallRingtoneNotification();
if (!canPlayTone) {
return;
}

487
ts/util/createIPCEvents.ts Normal file
View File

@ -0,0 +1,487 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { webFrame } from 'electron';
import { AudioDevice } from '../types/Calling';
import { ZoomFactorType } from '../types/Storage.d';
import {
DEFAULT_CONVERSATION_COLOR,
ConversationColorType,
CustomColorType,
DefaultConversationColorType,
} from '../types/Colors';
import * as Stickers from '../types/Stickers';
import {
SystemTraySetting,
parseSystemTraySetting,
} from '../types/SystemTraySetting';
import { ConversationType } from '../state/ducks/conversations';
import { calling } from '../services/calling';
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
import { getCustomColors } from '../state/selectors/items';
import { themeChanged } from '../shims/themeChanged';
import * as universalExpireTimer from './universalExpireTimer';
import { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
import { assert } from './assert';
type ThemeType = 'light' | 'dark' | 'system';
type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
export type IPCEventsValuesType = {
alwaysRelayCalls: boolean | undefined;
audioNotification: boolean | undefined;
autoLaunch: boolean;
callRingtoneNotification: boolean;
callSystemNotification: boolean;
countMutedConversations: boolean;
hideMenuBar: boolean | undefined;
incomingCallNotification: boolean;
lastSyncTime: number | undefined;
notificationDrawAttention: boolean;
notificationSetting: NotificationSettingType;
preferredAudioInputDevice: AudioDevice | undefined;
preferredAudioOutputDevice: AudioDevice | undefined;
preferredVideoInputDevice: string | undefined;
spellCheck: boolean;
systemTraySetting: SystemTraySetting;
themeSetting: ThemeType;
universalExpireTimer: number;
zoomFactor: ZoomFactorType;
// Optional
mediaPermissions: boolean;
mediaCameraPermissions: boolean;
// Only getters
blockedCount: number;
linkPreviewSetting: boolean;
phoneNumberDiscoverabilitySetting: PhoneNumberDiscoverability;
phoneNumberSharingSetting: PhoneNumberSharingMode;
readReceiptSetting: boolean;
typingIndicatorSetting: boolean;
deviceName: string | undefined;
};
export type IPCEventsCallbacksType = {
getAvailableIODevices(): Promise<{
availableCameras: Array<
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
>;
availableMicrophones: Array<AudioDevice>;
availableSpeakers: Array<AudioDevice>;
}>;
addCustomColor: (customColor: CustomColorType) => void;
addDarkOverlay: () => void;
deleteAllData: () => Promise<void>;
editCustomColor: (colorId: string, customColor: CustomColorType) => void;
getConversationsWithCustomColor: (x: string) => Array<ConversationType>;
installStickerPack: (packId: string, key: string) => Promise<void>;
isPrimary: () => boolean;
removeCustomColor: (x: string) => void;
removeCustomColorOnConversations: (x: string) => void;
removeDarkOverlay: () => void;
resetAllChatColors: () => void;
resetDefaultChatColor: () => void;
showKeyboardShortcuts: () => void;
showGroupViaLink: (x: string) => Promise<void>;
showStickerPack: (packId: string, key: string) => void;
shutdown: () => Promise<void>;
unknownSignalLink: () => void;
getCustomColors: () => Record<string, CustomColorType>;
syncRequest: () => Promise<void>;
setGlobalDefaultConversationColor: (
color: ConversationColorType,
customColor?: { id: string; value: CustomColorType }
) => void;
getDefaultConversationColor: () => DefaultConversationColorType;
};
type ValuesWithGetters = Omit<
IPCEventsValuesType,
// Optional
'mediaPermissions' | 'mediaCameraPermissions'
>;
type ValuesWithSetters = Omit<
IPCEventsValuesType,
| 'blockedCount'
| 'defaultConversationColor'
| 'linkPreviewSetting'
| 'phoneNumberDiscoverabilitySetting'
| 'phoneNumberSharingSetting'
| 'readReceiptSetting'
| 'typingIndicatorSetting'
| 'deviceName'
// Optional
| 'mediaPermissions'
| 'mediaCameraPermissions'
>;
export type IPCEventGetterType<
Key extends keyof IPCEventsValuesType
> = `get${Capitalize<Key>}`;
export type IPCEventSetterType<
Key extends keyof IPCEventsValuesType
> = `set${Capitalize<Key>}`;
export type IPCEventsGettersType = {
[Key in keyof ValuesWithGetters as IPCEventGetterType<Key>]: () => ValuesWithGetters[Key];
} & {
getMediaPermissions?: () => Promise<boolean>;
getMediaCameraPermissions?: () => Promise<boolean>;
};
export type IPCEventsSettersType = {
[Key in keyof ValuesWithSetters as IPCEventSetterType<Key>]: (
value: NonNullable<ValuesWithSetters[Key]>
) => Promise<void>;
} & {
setMediaPermissions?: (value: boolean) => Promise<void>;
setMediaCameraPermissions?: (value: boolean) => Promise<void>;
};
export type IPCEventsType = IPCEventsGettersType &
IPCEventsSettersType &
IPCEventsCallbacksType;
export function createIPCEvents(
overrideEvents: Partial<IPCEventsType> = {}
): IPCEventsType {
return {
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
getZoomFactor: () => window.storage.get('zoomFactor', 1),
setZoomFactor: (zoomFactor: ZoomFactorType) => {
const numZoomFactor = zoomFactor;
webFrame.setZoomFactor(numZoomFactor);
return window.storage.put('zoomFactor', numZoomFactor);
},
getPreferredAudioInputDevice: () =>
window.storage.get('preferred-audio-input-device'),
setPreferredAudioInputDevice: device =>
window.storage.put('preferred-audio-input-device', device),
getPreferredAudioOutputDevice: () =>
window.storage.get('preferred-audio-output-device'),
setPreferredAudioOutputDevice: device =>
window.storage.put('preferred-audio-output-device', device),
getPreferredVideoInputDevice: () =>
window.storage.get('preferred-video-input-device'),
setPreferredVideoInputDevice: device =>
window.storage.put('preferred-video-input-device', device),
// Chat Color redux hookups
getCustomColors: () => {
return getCustomColors(window.reduxStore.getState()) || {};
},
getConversationsWithCustomColor: colorId => {
return getConversationsWithCustomColorSelector(
window.reduxStore.getState()
)(colorId);
},
addCustomColor: (...args) =>
window.reduxActions.items.addCustomColor(...args),
editCustomColor: (...args) =>
window.reduxActions.items.editCustomColor(...args),
removeCustomColor: colorId =>
window.reduxActions.items.removeCustomColor(colorId),
removeCustomColorOnConversations: colorId =>
window.reduxActions.conversations.removeCustomColorOnConversations(
colorId
),
resetAllChatColors: () =>
window.reduxActions.conversations.resetAllChatColors(),
resetDefaultChatColor: () =>
window.reduxActions.items.resetDefaultChatColor(),
setGlobalDefaultConversationColor: (...args) =>
window.reduxActions.items.setGlobalDefaultConversationColor(...args),
// Getters only
getAvailableIODevices: async () => {
const {
availableCameras,
availableMicrophones,
availableSpeakers,
} = await calling.getAvailableIODevices();
return {
// mapping it to a pojo so that it is IPC friendly
availableCameras: availableCameras.map(
(inputDeviceInfo: MediaDeviceInfo) => ({
deviceId: inputDeviceInfo.deviceId,
groupId: inputDeviceInfo.groupId,
kind: inputDeviceInfo.kind,
label: inputDeviceInfo.label,
})
),
availableMicrophones,
availableSpeakers,
};
},
getBlockedCount: () =>
window.storage.blocked.getBlockedUuids().length +
window.storage.blocked.getBlockedGroups().length,
getDefaultConversationColor: () =>
window.storage.get(
'defaultConversationColor',
DEFAULT_CONVERSATION_COLOR
),
getLinkPreviewSetting: () => window.storage.get('linkPreviews', false),
getPhoneNumberDiscoverabilitySetting: () =>
window.storage.get(
'phoneNumberDiscoverability',
PhoneNumberDiscoverability.NotDiscoverable
),
getPhoneNumberSharingSetting: () =>
window.storage.get(
'phoneNumberSharingMode',
PhoneNumberSharingMode.Nobody
),
getReadReceiptSetting: () =>
window.storage.get('read-receipt-setting', false),
getTypingIndicatorSetting: () =>
window.storage.get('typingIndicators', false),
// Configurable settings
getThemeSetting: () =>
window.storage.get(
'theme-setting',
window.platform === 'darwin' ? 'system' : 'light'
),
setThemeSetting: value => {
const promise = window.storage.put('theme-setting', value);
themeChanged();
return promise;
},
getHideMenuBar: () => window.storage.get('hide-menu-bar'),
setHideMenuBar: value => {
const promise = window.storage.put('hide-menu-bar', value);
window.setAutoHideMenuBar(value);
window.setMenuBarVisibility(!value);
return promise;
},
getSystemTraySetting: () =>
parseSystemTraySetting(window.storage.get('system-tray-setting')),
setSystemTraySetting: value => {
const promise = window.storage.put('system-tray-setting', value);
window.updateSystemTraySetting(value);
return promise;
},
getNotificationSetting: () =>
window.storage.get('notification-setting', 'message'),
setNotificationSetting: (value: 'message' | 'name' | 'count' | 'off') =>
window.storage.put('notification-setting', value),
getNotificationDrawAttention: () =>
window.storage.get('notification-draw-attention', true),
setNotificationDrawAttention: value =>
window.storage.put('notification-draw-attention', value),
getAudioNotification: () => window.storage.get('audio-notification'),
setAudioNotification: value =>
window.storage.put('audio-notification', value),
getCountMutedConversations: () =>
window.storage.get('badge-count-muted-conversations', false),
setCountMutedConversations: value => {
const promise = window.storage.put(
'badge-count-muted-conversations',
value
);
window.Whisper.events.trigger('updateUnreadCount');
return promise;
},
getCallRingtoneNotification: () =>
window.storage.get('call-ringtone-notification', true),
setCallRingtoneNotification: value =>
window.storage.put('call-ringtone-notification', value),
getCallSystemNotification: () =>
window.storage.get('call-system-notification', true),
setCallSystemNotification: value =>
window.storage.put('call-system-notification', value),
getIncomingCallNotification: () =>
window.storage.get('incoming-call-notification', true),
setIncomingCallNotification: value =>
window.storage.put('incoming-call-notification', value),
getSpellCheck: () => window.storage.get('spell-check', true),
setSpellCheck: value => window.storage.put('spell-check', value),
getAlwaysRelayCalls: () => window.storage.get('always-relay-calls'),
setAlwaysRelayCalls: value =>
window.storage.put('always-relay-calls', value),
getAutoLaunch: () => window.getAutoLaunch(),
setAutoLaunch: async (value: boolean) => {
window.setAutoLaunch(value);
},
isPrimary: () => window.textsecure.storage.user.getDeviceId() === 1,
syncRequest: () =>
new Promise<void>((resolve, reject) => {
const FIVE_MINUTES = 5 * 60 * 60 * 1000;
const syncRequest = window.getSyncRequest(FIVE_MINUTES);
syncRequest.addEventListener('success', () => resolve());
syncRequest.addEventListener('timeout', () =>
reject(new Error('timeout'))
);
}),
getLastSyncTime: () => window.storage.get('synced_at'),
setLastSyncTime: value => window.storage.put('synced_at', value),
getUniversalExpireTimer: () => universalExpireTimer.get(),
setUniversalExpireTimer: async newValue => {
await universalExpireTimer.set(newValue);
// Update account in Storage Service
const conversationId = window.ConversationController.getOurConversationIdOrThrow();
const account = window.ConversationController.get(conversationId);
assert(account, "Account wasn't found");
account.captureChange('universalExpireTimer');
// Add a notification to the currently open conversation
const state = window.reduxStore.getState();
const selectedId = state.conversations.selectedConversationId;
if (selectedId) {
const conversation = window.ConversationController.get(selectedId);
assert(conversation, "Conversation wasn't found");
await conversation.updateLastMessage();
}
},
addDarkOverlay: () => {
if ($('.dark-overlay').length) {
return;
}
$(document.body).prepend('<div class="dark-overlay"></div>');
$('.dark-overlay').on('click', () => $('.dark-overlay').remove());
},
removeDarkOverlay: () => $('.dark-overlay').remove(),
showKeyboardShortcuts: () => window.showKeyboardShortcuts(),
deleteAllData: async () => {
await window.sqlInitializer.goBackToMainProcess();
const clearDataView = new window.Whisper.ClearDataView().render();
$('body').append(clearDataView.el);
},
showStickerPack: (packId, key) => {
// We can get these events even if the user has never linked this instance.
if (!window.Signal.Util.Registration.everDone()) {
window.log.warn('showStickerPack: Not registered, returning early');
return;
}
if (window.isShowingModal) {
window.log.warn(
'showStickerPack: Already showing modal, returning early'
);
return;
}
try {
window.isShowingModal = true;
// Kick off the download
Stickers.downloadEphemeralPack(packId, key);
const props = {
packId,
onClose: async () => {
window.isShowingModal = false;
stickerPreviewModalView.remove();
await Stickers.removeEphemeralPack(packId);
},
};
const stickerPreviewModalView = new window.Whisper.ReactWrapperView({
className: 'sticker-preview-modal-wrapper',
JSX: window.Signal.State.Roots.createStickerPreviewModal(
window.reduxStore,
props
),
});
} catch (error) {
window.isShowingModal = false;
window.log.error(
'showStickerPack: Ran into an error!',
error && error.stack ? error.stack : error
);
const errorView = new window.Whisper.ReactWrapperView({
className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal,
props: {
onClose: () => {
errorView.remove();
},
},
});
}
},
showGroupViaLink: async hash => {
// We can get these events even if the user has never linked this instance.
if (!window.Signal.Util.Registration.everDone()) {
window.log.warn('showGroupViaLink: Not registered, returning early');
return;
}
if (window.isShowingModal) {
window.log.warn(
'showGroupViaLink: Already showing modal, returning early'
);
return;
}
try {
await window.Signal.Groups.joinViaLink(hash);
} catch (error) {
window.log.error(
'showGroupViaLink: Ran into an error!',
error && error.stack ? error.stack : error
);
const errorView = new window.Whisper.ReactWrapperView({
className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal,
props: {
title: window.i18n('GroupV2--join--general-join-failure--title'),
description: window.i18n('GroupV2--join--general-join-failure'),
onClose: () => {
errorView.remove();
},
},
});
}
window.isShowingModal = false;
},
unknownSignalLink: () => {
window.log.warn('unknownSignalLink: Showing error dialog');
const errorView = new window.Whisper.ReactWrapperView({
className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal,
props: {
description: window.i18n('unknown-sgnl-link'),
onClose: () => {
errorView.remove();
},
},
});
},
installStickerPack: async (packId, key) => {
Stickers.downloadStickerPack(packId, key, {
finalStatus: 'installed',
});
},
shutdown: () => Promise.resolve(),
getMediaPermissions: window.getMediaPermissions,
getMediaCameraPermissions: window.getMediaCameraPermissions,
...overrideEvents,
};
}

View File

@ -200,30 +200,6 @@
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/settings_start.js",
"line": "$(document).on('keydown', e => {",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/settings_start.js",
"line": "const $body = $(document.body);",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-appendTo(",
"path": "js/settings_start.js",
"line": " window.view.$el.appendTo($body);",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/clear_data_view.js",
@ -767,317 +743,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('input').prop('checked', !!this.value);",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('input').prop('checked', Boolean(this.value));",
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T21:51:34.813Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " const value = this.$(e.target).val();",
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T21:51:34.813Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$(`#${this.name}-${this.value}`).attr('checked', 'checked');",
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T21:51:34.813Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.notification-settings'),",
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T21:51:34.813Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.theme-settings'),",
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T21:51:34.813Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " $(document.body)",
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T21:51:34.813Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('input').prop('checked', Boolean(this.value));",
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T22:20:33.618Z"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.draw-attention-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.audio-notification-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.badge-count-muted-conversations-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.spell-check-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " const $msg = this.$('.spell-check-setting-message');",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.menu-bar-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.always-relay-calls-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.call-ringtone-notification-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.call-system-notification-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.incoming-call-notification-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.media-permissions'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.media-camera-permissions'),",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync-setting').append(syncView.el);",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync').text(i18n('syncNow'));",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync').attr('disabled', 'disabled');",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.synced_at').hide();",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync_failed').hide();",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync').removeAttr('disabled');",
"reasonCategory": "usageTrusted",
"updated": "2020-09-11T17:24:56.124Z",
"reasonDetail": "Static selector argument"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync').text(i18n('syncing'));",
"reasonCategory": "usageTrusted",
"updated": "2020-09-11T17:24:56.124Z",
"reasonDetail": "Static selector argument"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync_failed').show();",
"reasonCategory": "usageTrusted",
"updated": "2020-09-11T17:24:56.124Z",
"reasonDetail": "Static selector argument"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " template: () => $('#settings').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " template: () => $('#syncSettings').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.auto-launch-setting'),",
"reasonCategory": "usageTrusted",
"updated": "2021-05-11T20:38:03.542Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " template: () => $('#disappearingMessagesSettings').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-05-27T01:33:06.541Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " this.$('.disappearing-messages-setting').append(",
"reasonCategory": "usageTrusted",
"updated": "2021-05-27T01:33:06.541Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
"line": " el: this.$('.system-tray-setting-container'),",
"reasonCategory": "usageTrusted",
"updated": "2021-06-24T23:16:24.537Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-append(",
"path": "js/views/settings_view.js",
"line": " this.$('.sync-setting').append(syncView.el);",
"reasonCategory": "usageTrusted",
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-append(",
"path": "js/views/settings_view.js",
"line": " this.$('.disappearing-messages-setting').append(",
"reasonCategory": "usageTrusted",
"updated": "2021-05-27T01:33:06.541Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-html(",
"path": "js/views/settings_view.js",
"line": " template: () => $('#settings').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-html(",
"path": "js/views/settings_view.js",
"line": " template: () => $('#syncSettings').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-html(",
"path": "js/views/settings_view.js",
"line": " template: () => $('#disappearingMessagesSettings').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-05-27T01:33:06.541Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/standalone_registration_view.js",
@ -14444,6 +14109,118 @@
"reasonCategory": "usageTrusted",
"updated": "2021-08-03T21:17:38.615Z"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.js",
"line": " if ($('.dark-overlay').length) {",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.js",
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.js",
"line": " $('.dark-overlay').on('click', () => $('.dark-overlay').remove());",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.js",
"line": " }, removeDarkOverlay: () => $('.dark-overlay').remove(), showKeyboardShortcuts: () => window.showKeyboardShortcuts(), deleteAllData: async () => {",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.js",
"line": " $('body').append(clearDataView.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-append(",
"path": "ts/util/createIPCEvents.js",
"line": " $('body').append(clearDataView.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-prepend(",
"path": "ts/util/createIPCEvents.js",
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts",
"line": " if ($('.dark-overlay').length) {",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts",
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts",
"line": " $('.dark-overlay').on('click', () => $('.dark-overlay').remove());",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts",
"line": " removeDarkOverlay: () => $('.dark-overlay').remove(),",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts",
"line": " $('body').append(clearDataView.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-append(",
"path": "ts/util/createIPCEvents.ts",
"line": " $('body').append(clearDataView.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "jQuery-prepend(",
"path": "ts/util/createIPCEvents.ts",
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
"reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z",
"reasonDetail": "Legacy code"
},
{
"rule": "React-useRef",
"path": "ts/util/hooks/index.js",

189
ts/util/preload.ts Normal file
View File

@ -0,0 +1,189 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { strictAssert } from './assert';
import { UnwrapPromise } from '../types/Util';
import type {
IPCEventsValuesType,
IPCEventsCallbacksType,
IPCEventGetterType,
IPCEventSetterType,
} from './createIPCEvents';
type SettingOptionsType = {
getter?: boolean;
setter?: boolean;
};
export type SettingType<Value> = Readonly<{
getValue: () => Promise<Value>;
setValue: (value: Value) => Promise<Value>;
}>;
function capitalize<Name extends keyof IPCEventsValuesType>(
name: Name
): Capitalize<Name> {
const result = name.slice(0, 1).toUpperCase() + name.slice(1);
return result as Capitalize<Name>;
}
function getSetterName<Key extends keyof IPCEventsValuesType>(
name: Key
): IPCEventSetterType<Key> {
return `set${capitalize(name)}`;
}
function getGetterName<Key extends keyof IPCEventsValuesType>(
name: Key
): IPCEventGetterType<Key> {
return `get${capitalize(name)}`;
}
export function createSetting<
Name extends keyof IPCEventsValuesType,
Value extends IPCEventsValuesType[Name]
>(name: Name, overrideOptions: SettingOptionsType = {}): SettingType<Value> {
const options = {
getter: true,
setter: true,
...overrideOptions,
};
function getValue(): Promise<Value> {
strictAssert(options.getter, `${name} has no getter`);
return new Promise((resolve, reject) => {
ipcRenderer.once(`settings:get-success:${name}`, (_, error, value) => {
if (error) {
return reject(error);
}
return resolve(value);
});
ipcRenderer.send(`settings:get:${name}`);
});
}
function setValue(value: Value): Promise<Value> {
strictAssert(options.setter, `${name} has no setter`);
return new Promise((resolve, reject) => {
ipcRenderer.once(`settings:set-success:${name}`, (_, error) => {
if (error) {
return reject(error);
}
return resolve(value);
});
ipcRenderer.send(`settings:set:${name}`, value);
});
}
return {
getValue,
setValue,
};
}
type UnwrapReturn<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Callback extends (...args: Array<any>) => unknown
> = UnwrapPromise<ReturnType<Callback>>;
export function createCallback<
Name extends keyof IPCEventsCallbacksType,
Callback extends IPCEventsCallbacksType[Name]
>(
name: Name
): (...args: Parameters<Callback>) => Promise<UnwrapReturn<Callback>> {
return (...args: Parameters<Callback>): Promise<UnwrapReturn<Callback>> => {
return new Promise<UnwrapReturn<Callback>>((resolve, reject) => {
ipcRenderer.once(`callbacks:call-success:${name}`, (_, error, value) => {
if (error) {
return reject(error);
}
return resolve(value);
});
ipcRenderer.send(`callbacks:call:${name}`, args);
});
};
}
export function installSetting(
name: keyof IPCEventsValuesType,
{ getter = true, setter = true }: { getter?: boolean; setter?: boolean } = {}
): void {
const getterName = getGetterName(name);
const setterName = getSetterName(name);
if (getter) {
ipcRenderer.on(`settings:get:${name}`, async () => {
const getFn = window.Events[getterName];
if (!getFn) {
ipcRenderer.send(
`settings:get:${name}`,
`installGetter: ${getterName} not found for event ${name}`
);
return;
}
try {
ipcRenderer.send(`settings:get-success:${name}`, null, await getFn());
} catch (error) {
ipcRenderer.send(
`settings:get-success:${name}`,
error && error.stack ? error.stack : error
);
}
});
}
if (setter) {
ipcRenderer.on(`settings:set:${name}`, async (_event, value: unknown) => {
// Some settings do not have setters...
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setFn = (window.Events as any)[setterName] as (
value: unknown
) => Promise<void>;
if (!setFn) {
ipcRenderer.send(
`settings:set-success:${name}`,
`installSetter: ${setterName} not found for event ${name}`
);
return;
}
try {
await setFn(value);
ipcRenderer.send(`settings:set-success:${name}`);
} catch (error) {
ipcRenderer.send(
`settings:set-success:${name}`,
error && error.stack ? error.stack : error
);
}
});
}
}
export function installCallback<Name extends keyof IPCEventsCallbacksType>(
name: Name
): void {
ipcRenderer.on(`callbacks:call:${name}`, async (_, args) => {
const hook = window.Events[name] as (
...hookArgs: Array<unknown>
) => Promise<unknown>;
try {
ipcRenderer.send(
`callbacks:call-success:${name}`,
null,
await hook(...args)
);
} catch (error) {
ipcRenderer.send(
`callbacks:call-success;${name}`,
error && error.stack ? error.stack : error
);
}
});
}

View File

@ -24,7 +24,7 @@ export async function sendReadReceiptsFor(
): Promise<void> {
// Only send read receipts for accepted conversations
if (
window.storage.get('read-receipt-setting') &&
window.Events.getReadReceiptSetting() &&
isConversationAccepted(conversationAttrs)
) {
window.log.info(`Sending ${items.length} read receipts`);

View File

@ -4079,7 +4079,7 @@ Whisper.ConversationView = Whisper.View.extend({
maybeGrabLinkPreview(message: string, caretLocation?: number) {
// Don't generate link previews if user has turned them off
if (!window.storage.get('linkPreviews', false)) {
if (!window.Events.getLinkPreviewSetting()) {
return;
}
// Do nothing if we're offline

17
ts/window.d.ts vendored
View File

@ -8,7 +8,7 @@ import * as Backbone from 'backbone';
import * as Underscore from 'underscore';
import moment from 'moment';
import PQueue from 'p-queue/dist';
import { Ref } from 'react';
import { Attributes, ComponentClass, FunctionComponent, Ref } from 'react';
import { imageToBlurHash } from './util/imageToBlurHash';
import * as Util from './util';
import {
@ -118,6 +118,7 @@ import { isValidGuid } from './util/isValidGuid';
import { StateType } from './state/reducer';
import { SystemTraySetting } from './types/SystemTraySetting';
import { CI } from './CI';
import { IPCEventsType } from './util/createIPCEvents';
export { Long } from 'long';
@ -176,6 +177,15 @@ declare global {
WhatIsThis: WhatIsThis;
SignalModule: {
registerReactRenderer: (
f: <P extends {}>(
component: FunctionComponent<P> | ComponentClass<P>,
props?: (Attributes & P) | null
) => void
) => void;
};
registerScreenShareControllerRenderer: (
f: (
component: typeof CallingScreenSharingController,
@ -194,15 +204,12 @@ declare global {
getAccountManager: () => AccountManager;
getAlwaysRelayCalls: () => Promise<boolean>;
getBuiltInImages: () => Promise<Array<string>>;
getCallRingtoneNotification: () => Promise<boolean>;
getCallSystemNotification: () => Promise<boolean>;
getConversations: () => ConversationModelCollectionType;
getCountMutedConversations: () => Promise<boolean>;
getEnvironment: typeof getEnvironment;
getExpiration: () => string;
getGuid: () => string;
getInboxCollection: () => ConversationModelCollectionType;
getIncomingCallNotification: () => Promise<boolean>;
getInteractionMode: () => 'mouse' | 'keyboard';
getLocale: () => ElectronLocaleType;
getMediaCameraPermissions: () => Promise<boolean>;
@ -489,7 +496,7 @@ declare global {
SignalContext: SignalContext;
ConversationController: ConversationController;
Events: WhatIsThis;
Events: IPCEventsType;
MessageController: MessageController;
SignalProtocolStore: typeof SignalProtocolStore;
WebAPI: WebAPIConnectType;

8
ts/windows/context.ts Normal file
View File

@ -0,0 +1,8 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer as ipc } from 'electron';
import { Context } from '../context';
window.SignalContext = new Context(ipc);

94
ts/windows/preload.ts Normal file
View File

@ -0,0 +1,94 @@
// Copyright 2017-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer as ipc } from 'electron';
import { installCallback, installSetting } from '../util/preload';
// ChatColorPicker redux hookups
installCallback('getCustomColors');
installCallback('getConversationsWithCustomColor');
installCallback('addCustomColor');
installCallback('editCustomColor');
installCallback('removeCustomColor');
installCallback('removeCustomColorOnConversations');
installCallback('resetAllChatColors');
installCallback('resetDefaultChatColor');
installCallback('setGlobalDefaultConversationColor');
installCallback('getDefaultConversationColor');
// Getters only. These are set by the primary device
installSetting('blockedCount', {
setter: false,
});
installSetting('linkPreviewSetting', {
setter: false,
});
installSetting('phoneNumberDiscoverabilitySetting', {
setter: false,
});
installSetting('phoneNumberSharingSetting', {
setter: false,
});
installSetting('readReceiptSetting', {
setter: false,
});
installSetting('typingIndicatorSetting', {
setter: false,
});
installSetting('alwaysRelayCalls');
installSetting('audioNotification');
installSetting('autoLaunch');
installSetting('countMutedConversations');
installSetting('callRingtoneNotification');
installSetting('callSystemNotification');
installSetting('deviceName');
installSetting('hideMenuBar');
installSetting('incomingCallNotification');
installCallback('isPrimary');
installCallback('syncRequest');
installSetting('notificationDrawAttention');
installSetting('notificationSetting');
installSetting('spellCheck');
installSetting('lastSyncTime');
installSetting('systemTraySetting');
installSetting('themeSetting');
installSetting('universalExpireTimer');
installSetting('zoomFactor');
// Media Settings
installCallback('getAvailableIODevices');
installSetting('preferredAudioInputDevice');
installSetting('preferredAudioOutputDevice');
installSetting('preferredVideoInputDevice');
window.getMediaPermissions = () =>
new Promise((resolve, reject) => {
ipc.once(
'settings:get-success:mediaPermissions',
(_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
}
);
ipc.send('settings:get:mediaPermissions');
});
window.getMediaCameraPermissions = () =>
new Promise((resolve, reject) => {
ipc.once(
'settings:get-success:mediaCameraPermissions',
(_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
}
);
ipc.send('settings:get:mediaCameraPermissions');
});

View File

@ -0,0 +1,11 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// This needs to use window.React & window.ReactDOM since it's
// not commonJS compatible.
window.SignalModule.registerReactRenderer((Component, props) => {
window.ReactDOM.render(
window.React.createElement(Component, props),
document.getElementById('app')
);
});

View File

@ -0,0 +1,378 @@
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import url from 'url';
import { ipcRenderer } from 'electron';
// It is important to call this as early as possible
import '../context';
import * as Settings from '../../types/Settings';
import i18n from '../../../js/modules/i18n';
import {
Preferences,
PropsType as PreferencesPropsType,
} from '../../components/Preferences';
import {
SystemTraySetting,
parseSystemTraySetting,
shouldMinimizeToSystemTray,
} from '../../types/SystemTraySetting';
import { awaitObject } from '../../util/awaitObject';
import { createSetting, createCallback } from '../../util/preload';
import {
getEnvironment,
setEnvironment,
parseEnvironment,
} from '../../environment';
import { initialize as initializeLogging } from '../../logging/set_up_renderer_logging';
import { strictAssert } from '../../util/assert';
const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
strictAssert(locale, 'locale could not be parsed from config');
strictAssert(typeof locale === 'string', 'locale is not a string');
const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment));
window.React = React;
window.ReactDOM = ReactDOM;
window.getEnvironment = getEnvironment;
window.getVersion = () => String(config.version);
window.i18n = i18n.setup(locale, localeMessages);
const settingAudioNotification = createSetting('audioNotification');
const settingAutoLaunch = createSetting('autoLaunch');
const settingCallRingtoneNotification = createSetting(
'callRingtoneNotification'
);
const settingCallSystemNotification = createSetting('callSystemNotification');
const settingCountMutedConversations = createSetting('countMutedConversations');
const settingDeviceName = createSetting('deviceName', { setter: false });
const settingHideMenuBar = createSetting('hideMenuBar');
const settingIncomingCallNotification = createSetting(
'incomingCallNotification'
);
const settingMediaCameraPermissions = createSetting('mediaCameraPermissions');
const settingMediaPermissions = createSetting('mediaPermissions');
const settingNotificationDrawAttention = createSetting(
'notificationDrawAttention'
);
const settingNotificationSetting = createSetting('notificationSetting');
const settingRelayCalls = createSetting('alwaysRelayCalls');
const settingSpellCheck = createSetting('spellCheck');
const settingTheme = createSetting('themeSetting');
const settingSystemTraySetting = createSetting('systemTraySetting');
const settingLastSyncTime = createSetting('lastSyncTime');
const settingZoomFactor = createSetting('zoomFactor');
// Getters only.
const settingBlockedCount = createSetting('blockedCount');
const settingLinkPreview = createSetting('linkPreviewSetting', {
setter: false,
});
const settingPhoneNumberDiscoverability = createSetting(
'phoneNumberDiscoverabilitySetting',
{ setter: false }
);
const settingPhoneNumberSharing = createSetting('phoneNumberSharingSetting', {
setter: false,
});
const settingReadReceipts = createSetting('readReceiptSetting', {
setter: false,
});
const settingTypingIndicators = createSetting('typingIndicatorSetting', {
setter: false,
});
// Media settings
const settingAudioInput = createSetting('preferredAudioInputDevice');
const settingAudioOutput = createSetting('preferredAudioOutputDevice');
const settingVideoInput = createSetting('preferredVideoInputDevice');
const settingUniversalExpireTimer = createSetting('universalExpireTimer');
// Callbacks
const ipcGetAvailableIODevices = createCallback('getAvailableIODevices');
const ipcGetCustomColors = createCallback('getCustomColors');
const ipcIsSyncNotSupported = createCallback('isPrimary');
const ipcMakeSyncRequest = createCallback('syncRequest');
// ChatColorPicker redux hookups
// The redux actions update over IPC through a preferences re-render
const ipcGetDefaultConversationColor = createCallback(
'getDefaultConversationColor'
);
const ipcGetConversationsWithCustomColor = createCallback(
'getConversationsWithCustomColor'
);
const ipcAddCustomColor = createCallback('addCustomColor');
const ipcEditCustomColor = createCallback('editCustomColor');
const ipcRemoveCustomColor = createCallback('removeCustomColor');
const ipcRemoveCustomColorOnConversations = createCallback(
'removeCustomColorOnConversations'
);
const ipcResetAllChatColors = createCallback('resetAllChatColors');
const ipcResetDefaultChatColor = createCallback('resetDefaultChatColor');
const ipcSetGlobalDefaultConversationColor = createCallback(
'setGlobalDefaultConversationColor'
);
const DEFAULT_NOTIFICATION_SETTING = 'message';
let renderComponent: (
component: typeof Preferences,
props: PreferencesPropsType
) => void;
window.SignalModule = {
registerReactRenderer: f => {
renderComponent = f;
},
};
function getSystemTraySettingValues(
systemTraySetting: SystemTraySetting
): {
hasMinimizeToAndStartInSystemTray: boolean;
hasMinimizeToSystemTray: boolean;
} {
const parsedSystemTraySetting = parseSystemTraySetting(systemTraySetting);
const hasMinimizeToAndStartInSystemTray =
parsedSystemTraySetting ===
SystemTraySetting.MinimizeToAndStartInSystemTray;
const hasMinimizeToSystemTray = shouldMinimizeToSystemTray(
parsedSystemTraySetting
);
return {
hasMinimizeToAndStartInSystemTray,
hasMinimizeToSystemTray,
};
}
async function renderPreferences() {
if (!renderComponent) {
setTimeout(renderPreferences, 100);
return;
}
const {
blockedCount,
deviceName,
hasAudioNotifications,
hasAutoLaunch,
hasCallNotifications,
hasCallRingtoneNotification,
hasCountMutedConversations,
hasHideMenuBar,
hasIncomingCallNotifications,
hasLinkPreviews,
hasMediaCameraPermissions,
hasMediaPermissions,
hasNotificationAttention,
hasReadReceipts,
hasRelayCalls,
hasSpellCheck,
hasTypingIndicators,
lastSyncTime,
notificationContent,
selectedCamera,
selectedMicrophone,
selectedSpeaker,
systemTraySetting,
themeSetting,
universalExpireTimer,
whoCanFindMe,
whoCanSeeMe,
zoomFactor,
availableIODevices,
customColors,
isSyncNotSupported,
defaultConversationColor,
} = await awaitObject({
blockedCount: settingBlockedCount.getValue(),
deviceName: settingDeviceName.getValue(),
hasAudioNotifications: settingAudioNotification.getValue(),
hasAutoLaunch: settingAutoLaunch.getValue(),
hasCallNotifications: settingCallSystemNotification.getValue(),
hasCallRingtoneNotification: settingCallRingtoneNotification.getValue(),
hasCountMutedConversations: settingCountMutedConversations.getValue(),
hasHideMenuBar: settingHideMenuBar.getValue(),
hasIncomingCallNotifications: settingIncomingCallNotification.getValue(),
hasLinkPreviews: settingLinkPreview.getValue(),
hasMediaCameraPermissions: settingMediaCameraPermissions.getValue(),
hasMediaPermissions: settingMediaPermissions.getValue(),
hasNotificationAttention: settingNotificationDrawAttention.getValue(),
hasReadReceipts: settingReadReceipts.getValue(),
hasRelayCalls: settingRelayCalls.getValue(),
hasSpellCheck: settingSpellCheck.getValue(),
hasTypingIndicators: settingTypingIndicators.getValue(),
lastSyncTime: settingLastSyncTime.getValue(),
notificationContent: settingNotificationSetting.getValue(),
selectedCamera: settingVideoInput.getValue(),
selectedMicrophone: settingAudioInput.getValue(),
selectedSpeaker: settingAudioOutput.getValue(),
systemTraySetting: settingSystemTraySetting.getValue(),
themeSetting: settingTheme.getValue(),
universalExpireTimer: settingUniversalExpireTimer.getValue(),
whoCanFindMe: settingPhoneNumberDiscoverability.getValue(),
whoCanSeeMe: settingPhoneNumberSharing.getValue(),
zoomFactor: settingZoomFactor.getValue(),
// Callbacks
availableIODevices: ipcGetAvailableIODevices(),
customColors: ipcGetCustomColors(),
isSyncNotSupported: ipcIsSyncNotSupported(),
defaultConversationColor: ipcGetDefaultConversationColor(),
});
const {
availableCameras,
availableMicrophones,
availableSpeakers,
} = availableIODevices;
const {
hasMinimizeToAndStartInSystemTray,
hasMinimizeToSystemTray,
} = getSystemTraySettingValues(systemTraySetting);
const props = {
// Settings
availableCameras,
availableMicrophones,
availableSpeakers,
blockedCount,
customColors,
defaultConversationColor,
deviceName,
hasAudioNotifications,
hasAutoLaunch,
hasCallNotifications,
hasCallRingtoneNotification,
hasCountMutedConversations,
hasHideMenuBar,
hasIncomingCallNotifications,
hasLinkPreviews,
hasMediaCameraPermissions,
hasMediaPermissions,
hasMinimizeToAndStartInSystemTray,
hasMinimizeToSystemTray,
hasNotificationAttention,
hasNotifications: notificationContent !== 'off',
hasReadReceipts,
hasRelayCalls,
hasSpellCheck,
hasTypingIndicators,
lastSyncTime,
notificationContent,
selectedCamera,
selectedMicrophone,
selectedSpeaker,
theme: themeSetting === 'system' ? window.systemTheme : themeSetting,
themeSetting,
universalExpireTimer,
whoCanFindMe,
whoCanSeeMe,
zoomFactor,
// Actions and other props
addCustomColor: ipcAddCustomColor,
doDeleteAllData: () => ipcRenderer.send('delete-all-data'),
editCustomColor: ipcEditCustomColor,
getConversationsWithCustomColor: ipcGetConversationsWithCustomColor,
initialSpellCheckSetting:
config.appStartInitialSpellcheckSetting === 'true',
makeSyncRequest: ipcMakeSyncRequest,
removeCustomColor: ipcRemoveCustomColor,
removeCustomColorOnConversations: ipcRemoveCustomColorOnConversations,
resetAllChatColors: ipcResetAllChatColors,
resetDefaultChatColor: ipcResetDefaultChatColor,
setGlobalDefaultConversationColor: ipcSetGlobalDefaultConversationColor,
// Limited support features
isAudioNotificationsSupported: Settings.isAudioNotificationSupported(),
isAutoLaunchSupported: Settings.isAutoLaunchSupported(),
isHideMenuBarSupported: Settings.isHideMenuBarSupported(),
isNotificationAttentionSupported: Settings.isDrawAttentionSupported(),
isSyncSupported: !isSyncNotSupported,
isSystemTraySupported: Settings.isSystemTraySupported(window.getVersion()),
// Change handlers
onAudioNotificationsChange: reRender(settingAudioNotification.setValue),
onAutoLaunchChange: reRender(settingAutoLaunch.setValue),
onCallNotificationsChange: reRender(settingCallSystemNotification.setValue),
onCallRingtoneNotificationChange: reRender(
settingCallRingtoneNotification.setValue
),
onCountMutedConversationsChange: reRender(
settingCountMutedConversations.setValue
),
onHideMenuBarChange: reRender(settingHideMenuBar.setValue),
onIncomingCallNotificationsChange: reRender(
settingIncomingCallNotification.setValue
),
onLastSyncTimeChange: reRender(settingLastSyncTime.setValue),
onMediaCameraPermissionsChange: reRender(
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(
settingNotificationDrawAttention.setValue
),
onNotificationContentChange: reRender(settingNotificationSetting.setValue),
onNotificationsChange: reRender(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),
onSpellCheckChange: reRender(settingSpellCheck.setValue),
onThemeChange: reRender(settingTheme.setValue),
onUniversalExpireTimerChange: reRender(
settingUniversalExpireTimer.setValue
),
onZoomFactorChange: reRender(settingZoomFactor.setValue),
i18n: window.i18n,
};
function reRender<Value>(f: (value: Value) => Promise<Value>) {
return async (value: Value) => {
await f(value);
renderPreferences();
};
}
renderComponent(Preferences, props);
}
ipcRenderer.on('render', renderPreferences);
initializeLogging();