Faster preferences window
This commit is contained in:
parent
ac55b8d643
commit
91af0dad78
71 changed files with 3567 additions and 2093 deletions
|
@ -1246,7 +1246,7 @@
|
||||||
"message": "Link New Device",
|
"message": "Link New Device",
|
||||||
"description": "The menu option shown in Signal iOS to add a new linked device"
|
"description": "The menu option shown in Signal iOS to add a new linked device"
|
||||||
},
|
},
|
||||||
"deviceName": {
|
"Preferences--device-name": {
|
||||||
"message": "Device name",
|
"message": "Device name",
|
||||||
"description": "The label in settings panel shown for the user-provided name for this desktop instance"
|
"description": "The label in settings panel shown for the user-provided name for this desktop instance"
|
||||||
},
|
},
|
||||||
|
@ -1278,7 +1278,7 @@
|
||||||
"installTryAgain": {
|
"installTryAgain": {
|
||||||
"message": "Try again"
|
"message": "Try again"
|
||||||
},
|
},
|
||||||
"theme": {
|
"Preferences--theme": {
|
||||||
"message": "Theme",
|
"message": "Theme",
|
||||||
"description": "Header for theme settings"
|
"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.",
|
"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"
|
"description": "Details describing the always relay calls setting"
|
||||||
},
|
},
|
||||||
"permissions": {
|
"Preferences--permissions": {
|
||||||
"message": "Permissions",
|
"message": "Permissions",
|
||||||
"description": "Header for permissions section of settings"
|
"description": "Header for permissions section of settings"
|
||||||
},
|
},
|
||||||
|
@ -1631,7 +1631,7 @@
|
||||||
"description": "Header for general options on the settings screen"
|
"description": "Header for general options on the settings screen"
|
||||||
},
|
},
|
||||||
"spellCheckDescription": {
|
"spellCheckDescription": {
|
||||||
"message": "Enable spell check",
|
"message": "Spell check text entered in message composition box",
|
||||||
"description": "Description of the spell check setting"
|
"description": "Description of the spell check setting"
|
||||||
},
|
},
|
||||||
"spellCheckWillBeEnabled": {
|
"spellCheckWillBeEnabled": {
|
||||||
|
@ -1655,7 +1655,7 @@
|
||||||
"description": "Description for the automatic launch setting"
|
"description": "Description for the automatic launch setting"
|
||||||
},
|
},
|
||||||
"clearDataHeader": {
|
"clearDataHeader": {
|
||||||
"message": "Clear Data",
|
"message": "Clear application data",
|
||||||
"description": "Header in the settings dialog for the section dealing with data deletion"
|
"description": "Header in the settings dialog for the section dealing with data deletion"
|
||||||
},
|
},
|
||||||
"clearDataExplanation": {
|
"clearDataExplanation": {
|
||||||
|
@ -1803,7 +1803,7 @@
|
||||||
"description": "Error message displayed when sending to an unregistered user."
|
"description": "Error message displayed when sending to an unregistered user."
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"message": "Contacts",
|
"message": "Import contacts",
|
||||||
"description": "Label for contact and group sync settings"
|
"description": "Label for contact and group sync settings"
|
||||||
},
|
},
|
||||||
"syncExplanation": {
|
"syncExplanation": {
|
||||||
|
@ -1963,7 +1963,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"audioNotificationDescription": {
|
"audioNotificationDescription": {
|
||||||
"message": "Play audio notification",
|
"message": "Play notification sounds",
|
||||||
"description": "Description for audio notification setting"
|
"description": "Description for audio notification setting"
|
||||||
},
|
},
|
||||||
"callRingtoneNotificationDescription": {
|
"callRingtoneNotificationDescription": {
|
||||||
|
@ -5009,7 +5009,7 @@
|
||||||
"description": "Displayed while checking if the contact is SMS-only"
|
"description": "Displayed while checking if the contact is SMS-only"
|
||||||
},
|
},
|
||||||
"countMutedConversationsDescription": {
|
"countMutedConversationsDescription": {
|
||||||
"message": "Count muted conversations in badge count",
|
"message": "Include muted conversations in badge count",
|
||||||
"description": "Description for counting muted conversations in badge setting"
|
"description": "Description for counting muted conversations in badge setting"
|
||||||
},
|
},
|
||||||
"ContactModal--message": {
|
"ContactModal--message": {
|
||||||
|
@ -5965,5 +5965,131 @@
|
||||||
"LeftPaneSetGroupMetadataHelper__avatar-modal-title": {
|
"LeftPaneSetGroupMetadataHelper__avatar-modal-title": {
|
||||||
"message": "Group Avatar",
|
"message": "Group Avatar",
|
||||||
"description": "Title for the avatar picker in the group creation flow"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { start } from './base_config';
|
||||||
const userDataPath = app.getPath('userData');
|
const userDataPath = app.getPath('userData');
|
||||||
const targetPath = join(userDataPath, 'ephemeral.json');
|
const targetPath = join(userDataPath, 'ephemeral.json');
|
||||||
|
|
||||||
const ephemeralConfig = start('ephemeral', targetPath, {
|
export const ephemeralConfig = start('ephemeral', targetPath, {
|
||||||
allowMalformedOnStartup: true,
|
allowMalformedOnStartup: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ const PERMISSIONS: Record<string, boolean> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function _createPermissionHandler(
|
function _createPermissionHandler(
|
||||||
userConfig: ConfigType
|
userConfig: Pick<ConfigType, 'get'>
|
||||||
): Parameters<typeof ElectronSession.prototype.setPermissionRequestHandler>[0] {
|
): Parameters<typeof ElectronSession.prototype.setPermissionRequestHandler>[0] {
|
||||||
return (_webContents, permission, callback, details): void => {
|
return (_webContents, permission, callback, details): void => {
|
||||||
// We default 'media' permission to false, but the user can override that for
|
// We default 'media' permission to false, but the user can override that for
|
||||||
|
@ -75,7 +75,7 @@ export function installPermissionsHandler({
|
||||||
userConfig,
|
userConfig,
|
||||||
}: {
|
}: {
|
||||||
session: typeof ElectronSession;
|
session: typeof ElectronSession;
|
||||||
userConfig: ConfigType;
|
userConfig: Pick<ConfigType, 'get'>;
|
||||||
}): void {
|
}): void {
|
||||||
// Setting the permission request handler to null first forces any permissions to be
|
// Setting the permission request handler to null first forces any permissions to be
|
||||||
// requested again. Without this, revoked permissions might still be available if
|
// requested again. Without this, revoked permissions might still be available if
|
||||||
|
|
|
@ -24,7 +24,7 @@ console.log(`userData: ${app.getPath('userData')}`);
|
||||||
const userDataPath = app.getPath('userData');
|
const userDataPath = app.getPath('userData');
|
||||||
const targetPath = join(userDataPath, 'config.json');
|
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 get = userConfig.get.bind(userConfig);
|
||||||
export const remove = userConfig.remove.bind(userConfig);
|
export const remove = userConfig.remove.bind(userConfig);
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const copyText = require('copy-text-to-clipboard');
|
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 i18n = require('./js/modules/i18n');
|
||||||
const {
|
const {
|
||||||
getEnvironment,
|
getEnvironment,
|
||||||
|
@ -13,10 +17,6 @@ const {
|
||||||
parseEnvironment,
|
parseEnvironment,
|
||||||
} = require('./ts/environment');
|
} = require('./ts/environment');
|
||||||
|
|
||||||
const { Context: SignalContext } = require('./ts/context');
|
|
||||||
|
|
||||||
window.SignalContext = new SignalContext(ipcRenderer);
|
|
||||||
|
|
||||||
const config = url.parse(window.location.toString(), true).query;
|
const config = url.parse(window.location.toString(), true).query;
|
||||||
const { locale } = config;
|
const { locale } = config;
|
||||||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||||
|
|
1
images/icons/v2/appearance-outline-24.svg
Normal file
1
images/icons/v2/appearance-outline-24.svg
Normal 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 |
1
images/icons/v2/appearance-solid-24.svg
Normal file
1
images/icons/v2/appearance-solid-24.svg
Normal 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 |
1
images/icons/v2/bell-outline-24.svg
Normal file
1
images/icons/v2/bell-outline-24.svg
Normal 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 |
1
images/icons/v2/bell-solid-24.svg
Normal file
1
images/icons/v2/bell-solid-24.svg
Normal 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 |
1
images/icons/v2/lock-solid-24.svg
Normal file
1
images/icons/v2/lock-solid-24.svg
Normal 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 |
|
@ -12,7 +12,7 @@ $(document).on('keydown', e => {
|
||||||
const $body = $(document.body);
|
const $body = $(document.body);
|
||||||
|
|
||||||
async function applyTheme() {
|
async function applyTheme() {
|
||||||
const theme = await window.getThemeSetting();
|
const theme = await window.Settings.themeSetting.getValue();
|
||||||
$body.removeClass('light-theme');
|
$body.removeClass('light-theme');
|
||||||
$body.removeClass('dark-theme');
|
$body.removeClass('dark-theme');
|
||||||
$body.addClass(`${theme === 'system' ? window.systemTheme : theme}-theme`);
|
$body.addClass(`${theme === 'system' ? window.systemTheme : theme}-theme`);
|
||||||
|
@ -41,9 +41,9 @@ window.showConfirmationDialog({
|
||||||
okText: i18n('allowAccess'),
|
okText: i18n('allowAccess'),
|
||||||
resolve: () => {
|
resolve: () => {
|
||||||
if (!window.forCamera) {
|
if (!window.forCamera) {
|
||||||
window.setMediaPermissions(true);
|
window.Settings.mediaPermissions.setValue(true);
|
||||||
} else {
|
} else {
|
||||||
window.setMediaCameraPermissions(true);
|
window.Settings.mediaCameraPermissions.setValue(true);
|
||||||
}
|
}
|
||||||
window.closePermissionsPopup();
|
window.closePermissionsPopup();
|
||||||
},
|
},
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -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
167
main.js
|
@ -37,7 +37,6 @@ const {
|
||||||
ipcMain: ipc,
|
ipcMain: ipc,
|
||||||
Menu,
|
Menu,
|
||||||
protocol: electronProtocol,
|
protocol: electronProtocol,
|
||||||
session,
|
|
||||||
shell,
|
shell,
|
||||||
systemPreferences,
|
systemPreferences,
|
||||||
} = electron;
|
} = electron;
|
||||||
|
@ -102,7 +101,6 @@ const {
|
||||||
installFileHandler,
|
installFileHandler,
|
||||||
installWebHandler,
|
installWebHandler,
|
||||||
} = require('./app/protocol_filter');
|
} = require('./app/protocol_filter');
|
||||||
const { installPermissionsHandler } = require('./app/permissions');
|
|
||||||
const OS = require('./ts/OS');
|
const OS = require('./ts/OS');
|
||||||
const { isProduction } = require('./ts/util/version');
|
const { isProduction } = require('./ts/util/version');
|
||||||
const {
|
const {
|
||||||
|
@ -124,6 +122,7 @@ const { Environment, isTestEnvironment } = require('./ts/environment');
|
||||||
const { ChallengeMainHandler } = require('./ts/main/challengeMain');
|
const { ChallengeMainHandler } = require('./ts/main/challengeMain');
|
||||||
const { NativeThemeNotifier } = require('./ts/main/NativeThemeNotifier');
|
const { NativeThemeNotifier } = require('./ts/main/NativeThemeNotifier');
|
||||||
const { PowerChannel } = require('./ts/main/powerChannel');
|
const { PowerChannel } = require('./ts/main/powerChannel');
|
||||||
|
const { SettingsChannel } = require('./ts/main/settingsChannel');
|
||||||
const { maybeParseUrl, setUrlSearchParams } = require('./ts/util/url');
|
const { maybeParseUrl, setUrlSearchParams } = require('./ts/util/url');
|
||||||
const { getHeicConverter } = require('./ts/workers/heicConverterMain');
|
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
|
// Both of these will be set after app fires the 'ready' event
|
||||||
let logger;
|
let logger;
|
||||||
let locale;
|
let locale;
|
||||||
|
let settingsChannel;
|
||||||
|
|
||||||
function prepareFileUrl(
|
function prepareFileUrl(
|
||||||
pathSegments /* : ReadonlyArray<string> */,
|
pathSegments /* : ReadonlyArray<string> */,
|
||||||
|
@ -425,6 +425,8 @@ async function createWindow() {
|
||||||
|
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
mainWindow = new BrowserWindow(windowOptions);
|
mainWindow = new BrowserWindow(windowOptions);
|
||||||
|
settingsChannel.setMainWindow(mainWindow);
|
||||||
|
|
||||||
mainWindowCreated = true;
|
mainWindowCreated = true;
|
||||||
setupSpellChecker(mainWindow, locale.messages);
|
setupSpellChecker(mainWindow, locale.messages);
|
||||||
if (!startInTray && windowConfig && windowConfig.maximized) {
|
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
|
// in an array if your app supports multi windows, this is the time
|
||||||
// when you should delete the corresponding element.
|
// when you should delete the corresponding element.
|
||||||
mainWindow = undefined;
|
mainWindow = undefined;
|
||||||
|
settingsChannel.setMainWindow(mainWindow);
|
||||||
if (systemTrayService) {
|
if (systemTrayService) {
|
||||||
systemTrayService.setMainWindow(mainWindow);
|
systemTrayService.setMainWindow(mainWindow);
|
||||||
}
|
}
|
||||||
|
@ -875,40 +878,26 @@ function showSettingsWindow() {
|
||||||
settingsWindow.show();
|
settingsWindow.show();
|
||||||
return;
|
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 = {
|
const options = {
|
||||||
x: Math.round(mainPos[0] + mainSize[0] / 2 - settingwidth / 2),
|
width: 700,
|
||||||
y: Math.round(mainPos[1] + mainSize[1] / 2 - settingheight / 2),
|
height: 700,
|
||||||
width: settingwidth,
|
frame: true,
|
||||||
height: settingheight,
|
|
||||||
frame: false,
|
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: locale.messages.signalDesktopPreferences.message,
|
title: locale.messages.signalDesktopPreferences.message,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
backgroundColor: '#3a76f0',
|
backgroundColor: '#3a76f0',
|
||||||
show: false,
|
show: false,
|
||||||
modal: true,
|
modal: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
...defaultWebPrefs,
|
...defaultWebPrefs,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
nodeIntegrationInWorker: false,
|
nodeIntegrationInWorker: false,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
enableRemoteModule: true,
|
enableRemoteModule: true,
|
||||||
preload: path.join(__dirname, 'settings_preload.js'),
|
preload: path.join(__dirname, 'ts', 'windows', 'settings', 'preload.js'),
|
||||||
nativeWindowOpen: true,
|
nativeWindowOpen: true,
|
||||||
},
|
},
|
||||||
parent: mainWindow,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
settingsWindow = new BrowserWindow(options);
|
settingsWindow = new BrowserWindow(options);
|
||||||
|
@ -924,6 +913,11 @@ function showSettingsWindow() {
|
||||||
|
|
||||||
settingsWindow.once('ready-to-show', () => {
|
settingsWindow.once('ready-to-show', () => {
|
||||||
settingsWindow.show();
|
settingsWindow.show();
|
||||||
|
settingsWindow.webContents.send('render');
|
||||||
|
|
||||||
|
if (config.get('openDevTools')) {
|
||||||
|
settingsWindow.webContents.openDevTools();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1015,7 +1009,7 @@ async function showDebugLogWindow() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = await pify(getDataFromMainWindow)('theme-setting');
|
const theme = await settingsChannel.getSettingFromMainWindow('themeSetting');
|
||||||
const size = mainWindow.getSize();
|
const size = mainWindow.getSize();
|
||||||
const options = {
|
const options = {
|
||||||
width: Math.max(size[0] - 100, MIN_WIDTH),
|
width: Math.max(size[0] - 100, MIN_WIDTH),
|
||||||
|
@ -1068,7 +1062,9 @@ function showPermissionsPopupWindow(forCalling, forCamera) {
|
||||||
reject(new Error('No main window'));
|
reject(new Error('No main window'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = await pify(getDataFromMainWindow)('theme-setting');
|
const theme = await settingsChannel.getSettingFromMainWindow(
|
||||||
|
'themeSetting'
|
||||||
|
);
|
||||||
const size = mainWindow.getSize();
|
const size = mainWindow.getSize();
|
||||||
const options = {
|
const options = {
|
||||||
width: Math.min(400, size[0]),
|
width: Math.min(400, size[0]),
|
||||||
|
@ -1154,6 +1150,9 @@ let ready = false;
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
const startTime = Date.now();
|
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
|
// 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.
|
// from when it's first ready until the loading screen disappears.
|
||||||
ipc.once('signal-app-loaded', (event, info) => {
|
ipc.once('signal-app-loaded', (event, info) => {
|
||||||
|
@ -1201,8 +1200,6 @@ app.on('ready', async () => {
|
||||||
protocol: electronProtocol,
|
protocol: electronProtocol,
|
||||||
});
|
});
|
||||||
|
|
||||||
installPermissionsHandler({ session, userConfig });
|
|
||||||
|
|
||||||
logger = await logging.initialize(getMainWindow);
|
logger = await logging.initialize(getMainWindow);
|
||||||
logger.info('app ready');
|
logger.info('app ready');
|
||||||
logger.info(`starting version ${packageJson.version}`);
|
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', () => {
|
ipc.on('delete-all-data', () => {
|
||||||
if (mainWindow && mainWindow.webContents) {
|
if (mainWindow && mainWindow.webContents) {
|
||||||
mainWindow.webContents.send('delete-all-data');
|
mainWindow.webContents.send('delete-all-data');
|
||||||
|
@ -1776,47 +1700,12 @@ ipc.on('get-user-data-path', event => {
|
||||||
event.returnValue = app.getPath('userData');
|
event.returnValue = app.getPath('userData');
|
||||||
});
|
});
|
||||||
|
|
||||||
function getDataFromMainWindow(name, callback) {
|
// Refresh the settings window whenever preferences change
|
||||||
ipc.once(`get-success-${name}`, (_event, error, value) =>
|
ipc.on('preferences-changed', () => {
|
||||||
callback(error, value)
|
if (settingsWindow && settingsWindow.webContents) {
|
||||||
);
|
settingsWindow.webContents.send('render');
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIncomingHref(argv) {
|
function getIncomingHref(argv) {
|
||||||
return argv.find(arg => isSgnlHref(arg, logger));
|
return argv.find(arg => isSgnlHref(arg, logger));
|
||||||
|
|
|
@ -416,7 +416,6 @@
|
||||||
"preload_utils.js",
|
"preload_utils.js",
|
||||||
"about_preload.js",
|
"about_preload.js",
|
||||||
"screenShare_preload.js",
|
"screenShare_preload.js",
|
||||||
"settings_preload.js",
|
|
||||||
"permissions_popup_preload.js",
|
"permissions_popup_preload.js",
|
||||||
"debug_log_preload.js",
|
"debug_log_preload.js",
|
||||||
"main.js",
|
"main.js",
|
||||||
|
|
|
@ -8,23 +8,24 @@ window.ReactDOM = require('react-dom');
|
||||||
|
|
||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
const url = require('url');
|
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 i18n = require('./js/modules/i18n');
|
||||||
const { ConfirmationDialog } = require('./ts/components/ConfirmationDialog');
|
const { ConfirmationDialog } = require('./ts/components/ConfirmationDialog');
|
||||||
const { makeGetter, makeSetter } = require('./preload_utils');
|
|
||||||
const {
|
const {
|
||||||
getEnvironment,
|
getEnvironment,
|
||||||
setEnvironment,
|
setEnvironment,
|
||||||
parseEnvironment,
|
parseEnvironment,
|
||||||
} = require('./ts/environment');
|
} = require('./ts/environment');
|
||||||
|
|
||||||
const { Context: SignalContext } = require('./ts/context');
|
|
||||||
|
|
||||||
const config = url.parse(window.location.toString(), true).query;
|
const config = url.parse(window.location.toString(), true).query;
|
||||||
const { locale } = config;
|
const { locale } = config;
|
||||||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||||
setEnvironment(parseEnvironment(config.environment));
|
setEnvironment(parseEnvironment(config.environment));
|
||||||
|
|
||||||
window.SignalContext = new SignalContext(ipcRenderer);
|
const { createSetting } = require('./ts/util/preload');
|
||||||
|
|
||||||
window.getEnvironment = getEnvironment;
|
window.getEnvironment = getEnvironment;
|
||||||
window.getVersion = () => config.version;
|
window.getVersion = () => config.version;
|
||||||
|
@ -43,8 +44,14 @@ require('./ts/logging/set_up_renderer_logging').initialize();
|
||||||
window.closePermissionsPopup = () =>
|
window.closePermissionsPopup = () =>
|
||||||
ipcRenderer.send('close-permissions-popup');
|
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.Backbone = require('backbone');
|
||||||
|
|
||||||
|
window.Settings = {
|
||||||
|
mediaCameraPermissions: createSetting('mediaCameraPermissions', {
|
||||||
|
getter: false,
|
||||||
|
}),
|
||||||
|
mediaPermissions: createSetting('mediaPermissions', {
|
||||||
|
getter: false,
|
||||||
|
}),
|
||||||
|
themeSetting: createSetting('themeSetting', { setter: false }),
|
||||||
|
};
|
||||||
|
|
157
preload.js
157
preload.js
|
@ -12,7 +12,10 @@ try {
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { installGetter, installSetter } = require('./preload_utils');
|
|
||||||
|
// It is important to call this as early as possible
|
||||||
|
require('./ts/windows/context');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getEnvironment,
|
getEnvironment,
|
||||||
setEnvironment,
|
setEnvironment,
|
||||||
|
@ -24,10 +27,6 @@ try {
|
||||||
const { remote } = electron;
|
const { remote } = electron;
|
||||||
const { app } = remote;
|
const { app } = remote;
|
||||||
|
|
||||||
const { Context: SignalContext } = require('./ts/context');
|
|
||||||
|
|
||||||
window.SignalContext = new SignalContext(ipc);
|
|
||||||
|
|
||||||
window.sqlInitializer = require('./ts/sql/initialize');
|
window.sqlInitializer = require('./ts/sql/initialize');
|
||||||
|
|
||||||
const config = require('url').parse(window.location.toString(), true).query;
|
const config = require('url').parse(window.location.toString(), true).query;
|
||||||
|
@ -254,146 +253,7 @@ try {
|
||||||
window.Events.removeDarkOverlay();
|
window.Events.removeDarkOverlay();
|
||||||
});
|
});
|
||||||
|
|
||||||
installGetter('device-name', 'getDeviceName');
|
require('./ts/windows/preload');
|
||||||
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
window.getBuiltInImages = () =>
|
window.getBuiltInImages = () =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
|
@ -407,13 +267,6 @@ try {
|
||||||
ipc.send('get-built-in-images');
|
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 () => {
|
ipc.on('delete-all-data', async () => {
|
||||||
const { deleteAllData } = window.Events;
|
const { deleteAllData } = window.Events;
|
||||||
if (!deleteAllData) {
|
if (!deleteAllData) {
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -8,6 +8,9 @@ const ReactDOM = require('react-dom');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const { ipcRenderer } = require('electron');
|
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 i18n = require('./js/modules/i18n');
|
||||||
const {
|
const {
|
||||||
getEnvironment,
|
getEnvironment,
|
||||||
|
@ -18,10 +21,6 @@ const {
|
||||||
CallingScreenSharingController,
|
CallingScreenSharingController,
|
||||||
} = require('./ts/components/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 config = url.parse(window.location.toString(), true).query;
|
||||||
const { locale } = config;
|
const { locale } = config;
|
||||||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||||
|
|
209
settings.html
209
settings.html
|
@ -1,4 +1,4 @@
|
||||||
<!-- Copyright 2018-2021 Signal Messenger, LLC -->
|
<!-- Copyright 2021 Signal Messenger, LLC -->
|
||||||
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
|
@ -6,11 +6,7 @@
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'none';
|
content="default-src 'none';
|
||||||
child-src 'self';
|
|
||||||
connect-src 'self' https: wss:;
|
|
||||||
font-src 'self';
|
font-src 'self';
|
||||||
form-action 'self';
|
|
||||||
frame-src 'none';
|
|
||||||
img-src 'self' blob: data:;
|
img-src 'self' blob: data:;
|
||||||
media-src 'self' blob:;
|
media-src 'self' blob:;
|
||||||
object-src 'none';
|
object-src 'none';
|
||||||
|
@ -23,201 +19,12 @@
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||||
<style></style>
|
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body>
|
||||||
<script type="text/x-tmpl-mustache" id="syncSettings">
|
<div id="app"></div>
|
||||||
<hr>
|
<script
|
||||||
<h3>{{ sync }}</h3>
|
type="application/javascript"
|
||||||
<div>
|
src="ts/windows/settings/init.js"
|
||||||
<button class='grey sync'>{{ syncNow }}</button>
|
></script>
|
||||||
<p>
|
</body>
|
||||||
{{ 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>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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();
|
|
|
@ -10,6 +10,10 @@ const config = require('url').parse(window.location.toString(), true).query;
|
||||||
const { noop, uniqBy } = require('lodash');
|
const { noop, uniqBy } = require('lodash');
|
||||||
const pMap = require('p-map');
|
const pMap = require('p-map');
|
||||||
const client = require('@signalapp/signal-client');
|
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 { deriveStickerPackKey } = require('../ts/Crypto');
|
||||||
const { SignalService: Proto } = require('../ts/protobuf');
|
const { SignalService: Proto } = require('../ts/protobuf');
|
||||||
const {
|
const {
|
||||||
|
@ -17,20 +21,16 @@ const {
|
||||||
setEnvironment,
|
setEnvironment,
|
||||||
parseEnvironment,
|
parseEnvironment,
|
||||||
} = require('../ts/environment');
|
} = require('../ts/environment');
|
||||||
const { makeGetter } = require('../preload_utils');
|
const { createSetting } = require('../ts/util/preload');
|
||||||
|
|
||||||
const { dialog } = remote;
|
const { dialog } = remote;
|
||||||
|
|
||||||
const { Context: SignalContext } = require('../ts/context');
|
|
||||||
|
|
||||||
const STICKER_SIZE = 512;
|
const STICKER_SIZE = 512;
|
||||||
const MIN_STICKER_DIMENSION = 10;
|
const MIN_STICKER_DIMENSION = 10;
|
||||||
const MAX_STICKER_DIMENSION = STICKER_SIZE;
|
const MAX_STICKER_DIMENSION = STICKER_SIZE;
|
||||||
const MAX_WEBP_STICKER_BYTE_LENGTH = 100 * 1024;
|
const MAX_WEBP_STICKER_BYTE_LENGTH = 100 * 1024;
|
||||||
const MAX_ANIMATED_STICKER_BYTE_LENGTH = 300 * 1024;
|
const MAX_ANIMATED_STICKER_BYTE_LENGTH = 300 * 1024;
|
||||||
|
|
||||||
window.SignalContext = new SignalContext(ipc);
|
|
||||||
|
|
||||||
setEnvironment(parseEnvironment(config.environment));
|
setEnvironment(parseEnvironment(config.environment));
|
||||||
|
|
||||||
window.sqlInitializer = require('../ts/sql/initialize');
|
window.sqlInitializer = require('../ts/sql/initialize');
|
||||||
|
@ -274,10 +274,10 @@ async function encrypt(data, key, iv) {
|
||||||
return ciphertext;
|
return ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getThemeSetting = makeGetter('theme-setting');
|
const getThemeSetting = createSetting('theme-setting');
|
||||||
|
|
||||||
async function resolveTheme() {
|
async function resolveTheme() {
|
||||||
const theme = (await getThemeSetting()) || 'system';
|
const theme = (await getThemeSetting.getValue()) || 'system';
|
||||||
if (process.platform === 'darwin' && theme === 'system') {
|
if (process.platform === 'darwin' && theme === 'system') {
|
||||||
const { theme: nativeTheme } = window.SignalContext.nativeThemeListener;
|
const { theme: nativeTheme } = window.SignalContext.nativeThemeListener;
|
||||||
return nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
|
return nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
|
||||||
|
|
|
@ -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 {
|
.module-avatar-popup__item__icon-archive {
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
@include color-svg(
|
@include color-svg(
|
||||||
|
|
30
stylesheets/components/Checkbox.scss
Normal file
30
stylesheets/components/Checkbox.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
241
stylesheets/components/Preferences.scss
Normal file
241
stylesheets/components/Preferences.scss
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,7 @@
|
||||||
@import './components/CallingScreenSharingController.scss';
|
@import './components/CallingScreenSharingController.scss';
|
||||||
@import './components/CallingSelectPresentingSourcesModal.scss';
|
@import './components/CallingSelectPresentingSourcesModal.scss';
|
||||||
@import './components/ChatColorPicker.scss';
|
@import './components/ChatColorPicker.scss';
|
||||||
|
@import './components/Checkbox.scss';
|
||||||
@import './components/CompositionArea.scss';
|
@import './components/CompositionArea.scss';
|
||||||
@import './components/ContactName.scss';
|
@import './components/ContactName.scss';
|
||||||
@import './components/ContactPill.scss';
|
@import './components/ContactPill.scss';
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
@import './components/MessageAudio.scss';
|
@import './components/MessageAudio.scss';
|
||||||
@import './components/MessageDetail.scss';
|
@import './components/MessageDetail.scss';
|
||||||
@import './components/Modal.scss';
|
@import './components/Modal.scss';
|
||||||
|
@import './components/Preferences.scss';
|
||||||
@import './components/ProfileEditor.scss';
|
@import './components/ProfileEditor.scss';
|
||||||
@import './components/SafetyNumberChangeDialog.scss';
|
@import './components/SafetyNumberChangeDialog.scss';
|
||||||
@import './components/SafetyNumberViewer.scss';
|
@import './components/SafetyNumberViewer.scss';
|
||||||
|
|
260
ts/background.ts
260
ts/background.ts
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { webFrame } from 'electron';
|
||||||
import { isNumber, noop } from 'lodash';
|
import { isNumber, noop } from 'lodash';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { render, unstable_batchedUpdates as batchedUpdates } from 'react-dom';
|
import { render, unstable_batchedUpdates as batchedUpdates } from 'react-dom';
|
||||||
|
@ -64,7 +65,6 @@ import {
|
||||||
EnvelopeEvent,
|
EnvelopeEvent,
|
||||||
} from './textsecure/messageReceiverEvents';
|
} from './textsecure/messageReceiverEvents';
|
||||||
import type { WebAPIType } from './textsecure/WebAPI';
|
import type { WebAPIType } from './textsecure/WebAPI';
|
||||||
import * as universalExpireTimer from './util/universalExpireTimer';
|
|
||||||
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
||||||
import { getSendOptions } from './util/getSendOptions';
|
import { getSendOptions } from './util/getSendOptions';
|
||||||
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
|
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
|
||||||
|
@ -88,13 +88,11 @@ import {
|
||||||
SendStatus,
|
SendStatus,
|
||||||
} from './messages/MessageSendState';
|
} from './messages/MessageSendState';
|
||||||
import * as AttachmentDownloads from './messageModifiers/AttachmentDownloads';
|
import * as AttachmentDownloads from './messageModifiers/AttachmentDownloads';
|
||||||
import {
|
|
||||||
SystemTraySetting,
|
|
||||||
parseSystemTraySetting,
|
|
||||||
} from './types/SystemTraySetting';
|
|
||||||
import * as Stickers from './types/Stickers';
|
import * as Stickers from './types/Stickers';
|
||||||
import { SignalService as Proto } from './protobuf';
|
import { SignalService as Proto } from './protobuf';
|
||||||
import { onRetryRequest, onDecryptionError } from './util/handleRetry';
|
import { onRetryRequest, onDecryptionError } from './util/handleRetry';
|
||||||
|
import { themeChanged } from './shims/themeChanged';
|
||||||
|
import { createIPCEvents } from './util/createIPCEvents';
|
||||||
|
|
||||||
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
||||||
|
|
||||||
|
@ -582,7 +580,11 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.info('Storage fetch');
|
window.log.info('Storage fetch');
|
||||||
window.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) {
|
switch (theme) {
|
||||||
case 'dark':
|
case 'dark':
|
||||||
case 'light':
|
case 'light':
|
||||||
|
@ -611,129 +613,7 @@ export async function startApp(): Promise<void> {
|
||||||
cleanupSessionResets();
|
cleanupSessionResets();
|
||||||
|
|
||||||
// These make key operations available to IPC handlers created in preload.js
|
// These make key operations available to IPC handlers created in preload.js
|
||||||
window.Events = {
|
window.Events = createIPCEvents({
|
||||||
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);
|
|
||||||
},
|
|
||||||
|
|
||||||
shutdown: async () => {
|
shutdown: async () => {
|
||||||
window.log.info('background/shutdown');
|
window.log.info('background/shutdown');
|
||||||
// Stop background processing
|
// Stop background processing
|
||||||
|
@ -763,112 +643,9 @@ export async function startApp(): Promise<void> {
|
||||||
// Shut down the data interface cleanly
|
// Shut down the data interface cleanly
|
||||||
await window.Signal.Data.shutdown();
|
await window.Signal.Data.shutdown();
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
showStickerPack: (packId: string, key: string) => {
|
webFrame.setZoomFactor(window.Events.getZoomFactor());
|
||||||
// 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',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// How long since we were last running?
|
// How long since we were last running?
|
||||||
const lastHeartbeat = window.storage.get('lastHeartbeat', 0);
|
const lastHeartbeat = window.storage.get('lastHeartbeat', 0);
|
||||||
|
@ -2373,7 +2150,7 @@ export async function startApp(): Promise<void> {
|
||||||
'theme-setting',
|
'theme-setting',
|
||||||
await window.Events.getThemeSetting()
|
await window.Events.getThemeSetting()
|
||||||
);
|
);
|
||||||
onChangeTheme();
|
themeChanged();
|
||||||
}
|
}
|
||||||
const syncRequest = window.getSyncRequest();
|
const syncRequest = window.getSyncRequest();
|
||||||
window.Whisper.events.trigger('contactsync:begin');
|
window.Whisper.events.trigger('contactsync:begin');
|
||||||
|
@ -2457,18 +2234,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeTheme() {
|
window.SignalContext.nativeThemeListener.subscribe(themeChanged);
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
name: text('name', overrideProps.name || ''),
|
name: text('name', overrideProps.name || ''),
|
||||||
noteToSelf: boolean('noteToSelf', overrideProps.noteToSelf || false),
|
noteToSelf: boolean('noteToSelf', overrideProps.noteToSelf || false),
|
||||||
onEditProfile: action('onEditProfile'),
|
onEditProfile: action('onEditProfile'),
|
||||||
onSetChatColor: action('onSetChatColor'),
|
|
||||||
onViewArchive: action('onViewArchive'),
|
onViewArchive: action('onViewArchive'),
|
||||||
onViewPreferences: action('onViewPreferences'),
|
onViewPreferences: action('onViewPreferences'),
|
||||||
phoneNumber: text('phoneNumber', overrideProps.phoneNumber || ''),
|
phoneNumber: text('phoneNumber', overrideProps.phoneNumber || ''),
|
||||||
|
|
|
@ -13,7 +13,6 @@ export type Props = {
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
|
|
||||||
onEditProfile: () => unknown;
|
onEditProfile: () => unknown;
|
||||||
onSetChatColor: () => unknown;
|
|
||||||
onViewPreferences: () => unknown;
|
onViewPreferences: () => unknown;
|
||||||
onViewArchive: () => unknown;
|
onViewArchive: () => unknown;
|
||||||
|
|
||||||
|
@ -30,7 +29,6 @@ export const AvatarPopup = (props: Props): JSX.Element => {
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
title,
|
title,
|
||||||
onEditProfile,
|
onEditProfile,
|
||||||
onSetChatColor,
|
|
||||||
onViewPreferences,
|
onViewPreferences,
|
||||||
onViewArchive,
|
onViewArchive,
|
||||||
style,
|
style,
|
||||||
|
@ -79,21 +77,6 @@ export const AvatarPopup = (props: Props): JSX.Element => {
|
||||||
{i18n('mainMenuSettings')}
|
{i18n('mainMenuSettings')}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="module-avatar-popup__item"
|
className="module-avatar-popup__item"
|
||||||
|
|
|
@ -26,7 +26,7 @@ const createProps = (): PropsType => ({
|
||||||
addCustomColor: action('addCustomColor'),
|
addCustomColor: action('addCustomColor'),
|
||||||
colorSelected: action('colorSelected'),
|
colorSelected: action('colorSelected'),
|
||||||
editCustomColor: action('editCustomColor'),
|
editCustomColor: action('editCustomColor'),
|
||||||
getConversationsWithCustomColor: (_: string) => [],
|
getConversationsWithCustomColor: (_: string) => Promise.resolve([]),
|
||||||
i18n,
|
i18n,
|
||||||
removeCustomColor: action('removeCustomColor'),
|
removeCustomColor: action('removeCustomColor'),
|
||||||
removeCustomColorOnConversations: action('removeCustomColorOnConversations'),
|
removeCustomColorOnConversations: action('removeCustomColorOnConversations'),
|
||||||
|
|
|
@ -26,7 +26,9 @@ type CustomColorDataType = {
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
conversationId?: string;
|
conversationId?: string;
|
||||||
customColors?: Record<string, CustomColorType>;
|
customColors?: Record<string, CustomColorType>;
|
||||||
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
getConversationsWithCustomColor: (
|
||||||
|
colorId: string
|
||||||
|
) => Promise<Array<ConversationType>>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isGlobal?: boolean;
|
isGlobal?: boolean;
|
||||||
selectedColor?: ConversationColorType;
|
selectedColor?: ConversationColorType;
|
||||||
|
@ -34,10 +36,7 @@ export type PropsDataType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type PropsActionType = {
|
type PropsActionType = {
|
||||||
addCustomColor: (
|
addCustomColor: (color: CustomColorType, conversationId?: string) => unknown;
|
||||||
color: CustomColorType,
|
|
||||||
nextAction: (uuid: string) => unknown
|
|
||||||
) => unknown;
|
|
||||||
colorSelected: (payload: {
|
colorSelected: (payload: {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
conversationColor?: ConversationColorType;
|
conversationColor?: ConversationColorType;
|
||||||
|
@ -100,20 +99,10 @@ export const ChatColorPicker = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onColorAdded = (value: CustomColorType) => {
|
|
||||||
return (id: string) => {
|
|
||||||
onSelectColor('custom', {
|
|
||||||
id,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderCustomColorEditorWrapper = () => (
|
const renderCustomColorEditorWrapper = () => (
|
||||||
<CustomColorEditorWrapper
|
<CustomColorEditorWrapper
|
||||||
customColorToEdit={customColorToEdit}
|
customColorToEdit={customColorToEdit}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isGlobal={isGlobal}
|
|
||||||
onClose={() => setCustomColorToEdit(undefined)}
|
onClose={() => setCustomColorToEdit(undefined)}
|
||||||
onSave={(color: CustomColorType) => {
|
onSave={(color: CustomColorType) => {
|
||||||
if (customColorToEdit?.id) {
|
if (customColorToEdit?.id) {
|
||||||
|
@ -123,16 +112,12 @@ export const ChatColorPicker = ({
|
||||||
value: color,
|
value: color,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
addCustomColor(color, onColorAdded(color));
|
addCustomColor(color, conversationId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isGlobal && customColorToEdit) {
|
|
||||||
return renderCustomColorEditorWrapper();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ChatColorPicker__container">
|
<div className="ChatColorPicker__container">
|
||||||
{customColorToEdit ? renderCustomColorEditorWrapper() : null}
|
{customColorToEdit ? renderCustomColorEditorWrapper() : null}
|
||||||
|
@ -228,7 +213,7 @@ export const ChatColorPicker = ({
|
||||||
removeCustomColorOnConversations(colorId);
|
removeCustomColorOnConversations(colorId);
|
||||||
}}
|
}}
|
||||||
onDupe={() => {
|
onDupe={() => {
|
||||||
addCustomColor(colorValues, onColorAdded(colorValues));
|
addCustomColor(colorValues, conversationId);
|
||||||
}}
|
}}
|
||||||
onEdit={() => {
|
onEdit={() => {
|
||||||
setCustomColorToEdit({ id: colorId, value: colorValues });
|
setCustomColorToEdit({ id: colorId, value: colorValues });
|
||||||
|
@ -279,7 +264,9 @@ export const ChatColorPicker = ({
|
||||||
type CustomColorBubblePropsType = {
|
type CustomColorBubblePropsType = {
|
||||||
color: CustomColorType;
|
color: CustomColorType;
|
||||||
colorId: string;
|
colorId: string;
|
||||||
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
getConversationsWithCustomColor: (
|
||||||
|
colorId: string
|
||||||
|
) => Promise<Array<ConversationType>>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
onDelete: () => unknown;
|
onDelete: () => unknown;
|
||||||
|
@ -398,11 +385,13 @@ const CustomColorBubble = ({
|
||||||
attributes={{
|
attributes={{
|
||||||
className: 'ChatColorPicker__context--delete',
|
className: 'ChatColorPicker__context--delete',
|
||||||
}}
|
}}
|
||||||
onClick={(event: MouseEvent) => {
|
onClick={async (event: MouseEvent) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const conversations = getConversationsWithCustomColor(colorId);
|
const conversations = await getConversationsWithCustomColor(
|
||||||
|
colorId
|
||||||
|
);
|
||||||
if (!conversations.length) {
|
if (!conversations.length) {
|
||||||
onDelete();
|
onDelete();
|
||||||
} else {
|
} else {
|
||||||
|
@ -420,7 +409,6 @@ const CustomColorBubble = ({
|
||||||
type CustomColorEditorWrapperPropsType = {
|
type CustomColorEditorWrapperPropsType = {
|
||||||
customColorToEdit?: CustomColorDataType;
|
customColorToEdit?: CustomColorDataType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isGlobal: boolean;
|
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
onSave: (color: CustomColorType) => unknown;
|
onSave: (color: CustomColorType) => unknown;
|
||||||
};
|
};
|
||||||
|
@ -428,7 +416,6 @@ type CustomColorEditorWrapperPropsType = {
|
||||||
const CustomColorEditorWrapper = ({
|
const CustomColorEditorWrapper = ({
|
||||||
customColorToEdit,
|
customColorToEdit,
|
||||||
i18n,
|
i18n,
|
||||||
isGlobal,
|
|
||||||
onClose,
|
onClose,
|
||||||
onSave,
|
onSave,
|
||||||
}: CustomColorEditorWrapperPropsType): JSX.Element => {
|
}: CustomColorEditorWrapperPropsType): JSX.Element => {
|
||||||
|
@ -441,20 +428,16 @@ const CustomColorEditorWrapper = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isGlobal) {
|
return (
|
||||||
return (
|
<Modal
|
||||||
<Modal
|
hasXButton
|
||||||
hasXButton
|
i18n={i18n}
|
||||||
i18n={i18n}
|
moduleClassName="ChatColorPicker__modal"
|
||||||
moduleClassName="ChatColorPicker__modal"
|
noMouseClose
|
||||||
noMouseClose
|
onClose={onClose}
|
||||||
onClose={onClose}
|
title={i18n('CustomColorEditor__title')}
|
||||||
title={i18n('CustomColorEditor__title')}
|
>
|
||||||
>
|
{editor}
|
||||||
{editor}
|
</Modal>
|
||||||
</Modal>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return editor;
|
|
||||||
};
|
};
|
||||||
|
|
27
ts/components/Checkbox.stories.tsx
Normal file
27
ts/components/Checkbox.stories.tsx
Normal 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 />);
|
47
ts/components/Checkbox.tsx
Normal file
47
ts/components/Checkbox.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,50 +1,17 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Modal } from './Modal';
|
|
||||||
import { LocalizerType } from '../types/Util';
|
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
i18n: LocalizerType;
|
|
||||||
|
|
||||||
// ChatColorPicker
|
|
||||||
isChatColorEditorVisible: boolean;
|
|
||||||
renderChatColorPicker: () => JSX.Element;
|
|
||||||
toggleChatColorEditor: () => unknown;
|
|
||||||
|
|
||||||
// ProfileEditor
|
// ProfileEditor
|
||||||
isProfileEditorVisible: boolean;
|
isProfileEditorVisible: boolean;
|
||||||
renderProfileEditor: () => JSX.Element;
|
renderProfileEditor: () => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GlobalModalContainer = ({
|
export const GlobalModalContainer = ({
|
||||||
i18n,
|
|
||||||
|
|
||||||
// ChatColorPicker
|
|
||||||
isChatColorEditorVisible,
|
|
||||||
renderChatColorPicker,
|
|
||||||
toggleChatColorEditor,
|
|
||||||
|
|
||||||
// ProfileEditor
|
// ProfileEditor
|
||||||
isProfileEditorVisible,
|
isProfileEditorVisible,
|
||||||
renderProfileEditor,
|
renderProfileEditor,
|
||||||
}: PropsType): JSX.Element | null => {
|
}: 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) {
|
if (isProfileEditorVisible) {
|
||||||
return renderProfileEditor();
|
return renderProfileEditor();
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,6 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
|
|
||||||
showArchivedConversations: action('showArchivedConversations'),
|
showArchivedConversations: action('showArchivedConversations'),
|
||||||
startComposing: action('startComposing'),
|
startComposing: action('startComposing'),
|
||||||
toggleChatColorEditor: action('toggleChatColorEditor'),
|
|
||||||
toggleProfileEditor: action('toggleProfileEditor'),
|
toggleProfileEditor: action('toggleProfileEditor'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,6 @@ export type PropsType = {
|
||||||
|
|
||||||
showArchivedConversations: () => void;
|
showArchivedConversations: () => void;
|
||||||
startComposing: () => void;
|
startComposing: () => void;
|
||||||
toggleChatColorEditor: () => void;
|
|
||||||
toggleProfileEditor: () => void;
|
toggleProfileEditor: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -353,7 +352,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
searchConversationName,
|
searchConversationName,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
showArchivedConversations,
|
showArchivedConversations,
|
||||||
toggleChatColorEditor,
|
|
||||||
toggleProfileEditor,
|
toggleProfileEditor,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { showingAvatarPopup, popperRoot } = this.state;
|
const { showingAvatarPopup, popperRoot } = this.state;
|
||||||
|
@ -416,10 +414,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
toggleProfileEditor();
|
toggleProfileEditor();
|
||||||
this.hideAvatarPopup();
|
this.hideAvatarPopup();
|
||||||
}}
|
}}
|
||||||
onSetChatColor={() => {
|
|
||||||
toggleChatColorEditor();
|
|
||||||
this.hideAvatarPopup();
|
|
||||||
}}
|
|
||||||
onViewPreferences={() => {
|
onViewPreferences={() => {
|
||||||
showSettings();
|
showSettings();
|
||||||
this.hideAvatarPopup();
|
this.hideAvatarPopup();
|
||||||
|
|
171
ts/components/Preferences.stories.tsx
Normal file
171
ts/components/Preferences.stories.tsx
Normal 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} />
|
||||||
|
));
|
1069
ts/components/Preferences.tsx
Normal file
1069
ts/components/Preferences.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
@ -11,34 +11,48 @@ export type Option = Readonly<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type PropsType = Readonly<{
|
export type PropsType = Readonly<{
|
||||||
|
disabled?: boolean;
|
||||||
moduleClassName?: string;
|
moduleClassName?: string;
|
||||||
|
name?: string;
|
||||||
options: ReadonlyArray<Option>;
|
options: ReadonlyArray<Option>;
|
||||||
onChange(value: string): void;
|
onChange(value: string): void;
|
||||||
value: string | number;
|
value?: string | number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function Select(props: PropsType): JSX.Element {
|
export function Select({
|
||||||
const { moduleClassName, value, options, onChange } = props;
|
disabled,
|
||||||
|
moduleClassName,
|
||||||
|
name,
|
||||||
|
onChange,
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
}: PropsType): JSX.Element {
|
||||||
const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
onChange(event.target.value);
|
onChange(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(['module-select', moduleClassName])}>
|
<div className={classNames(['module-select', moduleClassName])}>
|
||||||
<select value={value} onChange={onSelectChange}>
|
<select
|
||||||
{options.map(({ disabled, text, value: optionValue }) => {
|
disabled={disabled}
|
||||||
return (
|
name={name}
|
||||||
<option
|
value={value}
|
||||||
disabled={disabled}
|
onChange={onSelectChange}
|
||||||
value={optionValue}
|
>
|
||||||
key={optionValue}
|
{options.map(
|
||||||
aria-label={text}
|
({ disabled: optionDisabled, text, value: optionValue }) => {
|
||||||
>
|
return (
|
||||||
{text}
|
<option
|
||||||
</option>
|
disabled={optionDisabled}
|
||||||
);
|
value={optionValue}
|
||||||
})}
|
key={optionValue}
|
||||||
|
aria-label={text}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
} from './shared';
|
} from './shared';
|
||||||
import * as log from './log';
|
import * as log from './log';
|
||||||
import { reallyJsonStringify } from '../util/reallyJsonStringify';
|
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
|
// To make it easier to visually scan logs, we make all levels the same length
|
||||||
const levelFromName = pino().levels.values;
|
const levelFromName = pino().levels.values;
|
||||||
|
@ -81,7 +82,7 @@ const getHeader = ({
|
||||||
Time: Date.now(),
|
Time: Date.now(),
|
||||||
'User agent': window.navigator.userAgent,
|
'User agent': window.navigator.userAgent,
|
||||||
'Node version': window.getNodeVersion(),
|
'Node version': window.getNodeVersion(),
|
||||||
Environment: window.getEnvironment(),
|
Environment: getEnvironment(),
|
||||||
'App version': window.getVersion(),
|
'App version': window.getVersion(),
|
||||||
}),
|
}),
|
||||||
headerSection('User info', user),
|
headerSection('User info', user),
|
||||||
|
@ -179,11 +180,8 @@ const publish = uploadDebugLogs;
|
||||||
|
|
||||||
// A modern logging interface for the browser
|
// A modern logging interface for the browser
|
||||||
|
|
||||||
const env = window.getEnvironment();
|
|
||||||
const IS_PRODUCTION = env === 'production';
|
|
||||||
|
|
||||||
function logAtLevel(level: LogLevel, ...args: ReadonlyArray<unknown>): void {
|
function logAtLevel(level: LogLevel, ...args: ReadonlyArray<unknown>): void {
|
||||||
if (!IS_PRODUCTION) {
|
if (getEnvironment() !== Environment.Production) {
|
||||||
const prefix = getLogLevelString(level)
|
const prefix = getLogLevelString(level)
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.padEnd(levelMaxLength, ' ');
|
.padEnd(levelMaxLength, ' ');
|
||||||
|
|
218
ts/main/settingsChannel.ts
Normal file
218
ts/main/settingsChannel.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,6 @@ import {
|
||||||
AvatarColorType,
|
AvatarColorType,
|
||||||
ConversationColorType,
|
ConversationColorType,
|
||||||
CustomColorType,
|
CustomColorType,
|
||||||
DEFAULT_CONVERSATION_COLOR,
|
|
||||||
} from '../types/Colors';
|
} from '../types/Colors';
|
||||||
import { MessageModel } from './messages';
|
import { MessageModel } from './messages';
|
||||||
import { isMuted } from '../util/isMuted';
|
import { isMuted } from '../util/isMuted';
|
||||||
|
@ -950,7 +949,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
bumpTyping(): void {
|
bumpTyping(): void {
|
||||||
// We don't send typing messages if the setting is disabled
|
// We don't send typing messages if the setting is disabled
|
||||||
if (!window.storage.get('typingIndicators')) {
|
if (!window.Events.getTypingIndicatorSetting()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4680,10 +4679,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
getConversationColor(): ConversationColorType {
|
getConversationColor(): ConversationColorType {
|
||||||
const defaultConversationColor = window.storage.get(
|
const defaultConversationColor = window.Events.getDefaultConversationColor();
|
||||||
'defaultConversationColor',
|
|
||||||
DEFAULT_CONVERSATION_COLOR
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.get('conversationColor') || defaultConversationColor.color;
|
return this.get('conversationColor') || defaultConversationColor.color;
|
||||||
}
|
}
|
||||||
|
@ -4692,10 +4688,7 @@ export class ConversationModel extends window.Backbone
|
||||||
customColor?: CustomColorType;
|
customColor?: CustomColorType;
|
||||||
customColorId?: string;
|
customColorId?: string;
|
||||||
} {
|
} {
|
||||||
const defaultConversationColor = window.storage.get(
|
const defaultConversationColor = window.Events.getDefaultConversationColor();
|
||||||
'defaultConversationColor',
|
|
||||||
DEFAULT_CONVERSATION_COLOR
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.getConversationColor() !== 'custom') {
|
if (this.getConversationColor() !== 'custom') {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -38,11 +38,12 @@ import {
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
import { getConversationCallMode } from '../state/ducks/conversations';
|
import { getConversationCallMode } from '../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
CallMode,
|
|
||||||
AudioDevice,
|
AudioDevice,
|
||||||
MediaDeviceSettings,
|
AvailableIODevicesType,
|
||||||
|
CallMode,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
|
MediaDeviceSettings,
|
||||||
PresentableSource,
|
PresentableSource,
|
||||||
PresentedSource,
|
PresentedSource,
|
||||||
} from '../types/Calling';
|
} 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 availableMicrophones = RingRTC.getAudioInputs();
|
||||||
const preferredMicrophone = window.storage.get(
|
const availableSpeakers = RingRTC.getAudioOutputs();
|
||||||
'preferred-audio-input-device'
|
|
||||||
);
|
return {
|
||||||
|
availableCameras,
|
||||||
|
availableMicrophones,
|
||||||
|
availableSpeakers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMediaDeviceSettings(): Promise<MediaDeviceSettings> {
|
||||||
|
const {
|
||||||
|
availableCameras,
|
||||||
|
availableMicrophones,
|
||||||
|
availableSpeakers,
|
||||||
|
} = await this.getAvailableIODevices();
|
||||||
|
|
||||||
|
const preferredMicrophone = window.Events.getPreferredAudioInputDevice();
|
||||||
const selectedMicIndex = this.findBestMatchingDeviceIndex(
|
const selectedMicIndex = this.findBestMatchingDeviceIndex(
|
||||||
availableMicrophones,
|
availableMicrophones,
|
||||||
preferredMicrophone
|
preferredMicrophone
|
||||||
|
@ -1268,10 +1284,7 @@ export class CallingClass {
|
||||||
? availableMicrophones[selectedMicIndex]
|
? availableMicrophones[selectedMicIndex]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const availableSpeakers = RingRTC.getAudioOutputs();
|
const preferredSpeaker = window.Events.getPreferredAudioOutputDevice();
|
||||||
const preferredSpeaker = window.storage.get(
|
|
||||||
'preferred-audio-output-device'
|
|
||||||
);
|
|
||||||
const selectedSpeakerIndex = this.findBestMatchingDeviceIndex(
|
const selectedSpeakerIndex = this.findBestMatchingDeviceIndex(
|
||||||
availableSpeakers,
|
availableSpeakers,
|
||||||
preferredSpeaker
|
preferredSpeaker
|
||||||
|
@ -1281,8 +1294,7 @@ export class CallingClass {
|
||||||
? availableSpeakers[selectedSpeakerIndex]
|
? availableSpeakers[selectedSpeakerIndex]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const availableCameras = await this.videoCapturer.enumerateDevices();
|
const preferredCamera = window.Events.getPreferredVideoInputDevice();
|
||||||
const preferredCamera = window.storage.get('preferred-video-input-device');
|
|
||||||
const selectedCamera = this.findBestMatchingCamera(
|
const selectedCamera = this.findBestMatchingCamera(
|
||||||
availableCameras,
|
availableCameras,
|
||||||
preferredCamera
|
preferredCamera
|
||||||
|
@ -1343,13 +1355,13 @@ export class CallingClass {
|
||||||
|
|
||||||
setPreferredMicrophone(device: AudioDevice): void {
|
setPreferredMicrophone(device: AudioDevice): void {
|
||||||
window.log.info('MediaDevice: setPreferredMicrophone', device);
|
window.log.info('MediaDevice: setPreferredMicrophone', device);
|
||||||
window.storage.put('preferred-audio-input-device', device);
|
window.Events.setPreferredAudioInputDevice(device);
|
||||||
RingRTC.setAudioInput(device.index);
|
RingRTC.setAudioInput(device.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreferredSpeaker(device: AudioDevice): void {
|
setPreferredSpeaker(device: AudioDevice): void {
|
||||||
window.log.info('MediaDevice: setPreferredSpeaker', device);
|
window.log.info('MediaDevice: setPreferredSpeaker', device);
|
||||||
window.storage.put('preferred-audio-output-device', device);
|
window.Events.setPreferredAudioOutputDevice(device);
|
||||||
RingRTC.setAudioOutput(device.index);
|
RingRTC.setAudioOutput(device.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1363,7 +1375,7 @@ export class CallingClass {
|
||||||
|
|
||||||
async setPreferredCamera(device: string): Promise<void> {
|
async setPreferredCamera(device: string): Promise<void> {
|
||||||
window.log.info('MediaDevice: setPreferredCamera', device);
|
window.log.info('MediaDevice: setPreferredCamera', device);
|
||||||
window.storage.put('preferred-video-input-device', device);
|
window.Events.setPreferredVideoInputDevice(device);
|
||||||
await this.videoCapturer.setPreferredDevice(device);
|
await this.videoCapturer.setPreferredDevice(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1373,7 +1385,7 @@ export class CallingClass {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
window.log.info('CallingClass.handleCallingMessage()');
|
window.log.info('CallingClass.handleCallingMessage()');
|
||||||
|
|
||||||
const enableIncomingCalls = await window.getIncomingCallNotification();
|
const enableIncomingCalls = window.Events.getIncomingCallNotification();
|
||||||
if (callingMessage.offer && !enableIncomingCalls) {
|
if (callingMessage.offer && !enableIncomingCalls) {
|
||||||
// Drop offers silently if incoming call notifications are disabled.
|
// Drop offers silently if incoming call notifications are disabled.
|
||||||
window.log.info('Incoming calls are disabled, ignoring call offer.');
|
window.log.info('Incoming calls are disabled, ignoring call offer.');
|
||||||
|
|
|
@ -170,16 +170,14 @@ export async function toAccountRecord(
|
||||||
accountRecord.noteToSelfMarkedUnread = Boolean(
|
accountRecord.noteToSelfMarkedUnread = Boolean(
|
||||||
conversation.get('markedUnread')
|
conversation.get('markedUnread')
|
||||||
);
|
);
|
||||||
accountRecord.readReceipts = Boolean(
|
accountRecord.readReceipts = Boolean(window.Events.getReadReceiptSetting());
|
||||||
window.storage.get('read-receipt-setting')
|
|
||||||
);
|
|
||||||
accountRecord.sealedSenderIndicators = Boolean(
|
accountRecord.sealedSenderIndicators = Boolean(
|
||||||
window.storage.get('sealedSenderIndicators')
|
window.storage.get('sealedSenderIndicators')
|
||||||
);
|
);
|
||||||
accountRecord.typingIndicators = Boolean(
|
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');
|
const primarySendsSms = window.storage.get('primarySendsSms');
|
||||||
if (primarySendsSms !== undefined) {
|
if (primarySendsSms !== undefined) {
|
||||||
|
|
25
ts/shims/dispatchItemsMiddleware.ts
Normal file
25
ts/shims/dispatchItemsMiddleware.ts
Normal 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
11
ts/shims/themeChanged.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import thunk from 'redux-thunk';
|
||||||
import { createLogger } from 'redux-logger';
|
import { createLogger } from 'redux-logger';
|
||||||
|
|
||||||
import { reducer, StateType } from './reducer';
|
import { reducer, StateType } from './reducer';
|
||||||
|
import { dispatchItemsMiddleware } from '../shims/dispatchItemsMiddleware';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// We want to extend `window`'s properties, so we need an interface.
|
// We want to extend `window`'s properties, so we need an interface.
|
||||||
|
@ -49,6 +50,7 @@ const logger = createLogger({
|
||||||
const middlewareList = [
|
const middlewareList = [
|
||||||
promise,
|
promise,
|
||||||
thunk,
|
thunk,
|
||||||
|
dispatchItemsMiddleware,
|
||||||
...(env === 'production' ? [] : [logger]),
|
...(env === 'production' ? [] : [logger]),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -524,7 +524,7 @@ async function showCallNotification(
|
||||||
isVideoCall: boolean
|
isVideoCall: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const shouldNotify =
|
const shouldNotify =
|
||||||
!window.isActive() && (await window.getCallSystemNotification());
|
!window.isActive() && window.Events.getCallSystemNotification();
|
||||||
if (!shouldNotify) {
|
if (!shouldNotify) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,8 +336,8 @@ export const getConversationCallMode = (
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
const COLORS_CHANGED = 'conversations/COLORS_CHANGED';
|
export const COLORS_CHANGED = 'conversations/COLORS_CHANGED';
|
||||||
const COLOR_SELECTED = 'conversations/COLOR_SELECTED';
|
export const COLOR_SELECTED = 'conversations/COLOR_SELECTED';
|
||||||
const COMPOSE_TOGGLE_EDITING_AVATAR =
|
const COMPOSE_TOGGLE_EDITING_AVATAR =
|
||||||
'conversations/compose/COMPOSE_TOGGLE_EDITING_AVATAR';
|
'conversations/compose/COMPOSE_TOGGLE_EDITING_AVATAR';
|
||||||
const COMPOSE_ADD_AVATAR = 'conversations/compose/ADD_AVATAR';
|
const COMPOSE_ADD_AVATAR = 'conversations/compose/ADD_AVATAR';
|
||||||
|
|
|
@ -4,22 +4,16 @@
|
||||||
// State
|
// State
|
||||||
|
|
||||||
export type GlobalModalsStateType = {
|
export type GlobalModalsStateType = {
|
||||||
readonly isChatColorEditorVisible: boolean;
|
|
||||||
readonly isProfileEditorVisible: boolean;
|
readonly isProfileEditorVisible: boolean;
|
||||||
readonly profileEditorHasError: boolean;
|
readonly profileEditorHasError: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
const TOGGLE_CHAT_COLOR_EDITOR = 'globalModals/TOGGLE_CHAT_COLOR_EDITOR';
|
|
||||||
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
|
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
|
||||||
export const TOGGLE_PROFILE_EDITOR_ERROR =
|
export const TOGGLE_PROFILE_EDITOR_ERROR =
|
||||||
'globalModals/TOGGLE_PROFILE_EDITOR_ERROR';
|
'globalModals/TOGGLE_PROFILE_EDITOR_ERROR';
|
||||||
|
|
||||||
type ToggleChatColorEditorActionType = {
|
|
||||||
type: typeof TOGGLE_CHAT_COLOR_EDITOR;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ToggleProfileEditorActionType = {
|
type ToggleProfileEditorActionType = {
|
||||||
type: typeof TOGGLE_PROFILE_EDITOR;
|
type: typeof TOGGLE_PROFILE_EDITOR;
|
||||||
};
|
};
|
||||||
|
@ -29,22 +23,16 @@ export type ToggleProfileEditorErrorActionType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalModalsActionType =
|
export type GlobalModalsActionType =
|
||||||
| ToggleChatColorEditorActionType
|
|
||||||
| ToggleProfileEditorActionType
|
| ToggleProfileEditorActionType
|
||||||
| ToggleProfileEditorErrorActionType;
|
| ToggleProfileEditorErrorActionType;
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
toggleChatColorEditor,
|
|
||||||
toggleProfileEditor,
|
toggleProfileEditor,
|
||||||
toggleProfileEditorHasError,
|
toggleProfileEditorHasError,
|
||||||
};
|
};
|
||||||
|
|
||||||
function toggleChatColorEditor(): ToggleChatColorEditorActionType {
|
|
||||||
return { type: TOGGLE_CHAT_COLOR_EDITOR };
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleProfileEditor(): ToggleProfileEditorActionType {
|
function toggleProfileEditor(): ToggleProfileEditorActionType {
|
||||||
return { type: TOGGLE_PROFILE_EDITOR };
|
return { type: TOGGLE_PROFILE_EDITOR };
|
||||||
}
|
}
|
||||||
|
@ -57,7 +45,6 @@ function toggleProfileEditorHasError(): ToggleProfileEditorErrorActionType {
|
||||||
|
|
||||||
export function getEmptyState(): GlobalModalsStateType {
|
export function getEmptyState(): GlobalModalsStateType {
|
||||||
return {
|
return {
|
||||||
isChatColorEditorVisible: false,
|
|
||||||
isProfileEditorVisible: false,
|
isProfileEditorVisible: false,
|
||||||
profileEditorHasError: false,
|
profileEditorHasError: false,
|
||||||
};
|
};
|
||||||
|
@ -67,13 +54,6 @@ export function reducer(
|
||||||
state: Readonly<GlobalModalsStateType> = getEmptyState(),
|
state: Readonly<GlobalModalsStateType> = getEmptyState(),
|
||||||
action: Readonly<GlobalModalsActionType>
|
action: Readonly<GlobalModalsActionType>
|
||||||
): GlobalModalsStateType {
|
): GlobalModalsStateType {
|
||||||
if (action.type === TOGGLE_CHAT_COLOR_EDITOR) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isChatColorEditorVisible: !state.isChatColorEditorVisible,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === TOGGLE_PROFILE_EDITOR) {
|
if (action.type === TOGGLE_PROFILE_EDITOR) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
} from '../../types/Colors';
|
} from '../../types/Colors';
|
||||||
import { reloadSelectedConversation } from '../../shims/reloadSelectedConversation';
|
import { reloadSelectedConversation } from '../../shims/reloadSelectedConversation';
|
||||||
import { StorageAccessType } from '../../types/Storage.d';
|
import { StorageAccessType } from '../../types/Storage.d';
|
||||||
|
import { actions as conversationActions } from './conversations';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -139,7 +140,7 @@ function getDefaultCustomColorData() {
|
||||||
|
|
||||||
function addCustomColor(
|
function addCustomColor(
|
||||||
customColor: CustomColorType,
|
customColor: CustomColorType,
|
||||||
nextAction: (uuid: string) => unknown
|
conversationId?: string
|
||||||
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
|
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { customColors = getDefaultCustomColorData() } = getState().items;
|
const { customColors = getDefaultCustomColorData() } = getState().items;
|
||||||
|
@ -158,7 +159,25 @@ function addCustomColor(
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(putItem('customColors', nextCustomColors));
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,3 +43,9 @@ export const getDefaultConversationColor = createSelector(
|
||||||
};
|
};
|
||||||
} => state.defaultConversationColor ?? DEFAULT_CONVERSATION_COLOR
|
} => state.defaultConversationColor ?? DEFAULT_CONVERSATION_COLOR
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getCustomColors = createSelector(
|
||||||
|
getItems,
|
||||||
|
(state: ItemsStateType): Record<string, CustomColorType> | undefined =>
|
||||||
|
state.customColors?.colors
|
||||||
|
);
|
||||||
|
|
|
@ -38,9 +38,8 @@ const mapStateToProps = (
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
customColors: customColors ? customColors.colors : {},
|
customColors: customColors ? customColors.colors : {},
|
||||||
getConversationsWithCustomColor: getConversationsWithCustomColorSelector(
|
getConversationsWithCustomColor: (colorId: string) =>
|
||||||
state
|
Promise.resolve(getConversationsWithCustomColorSelector(state)(colorId)),
|
||||||
),
|
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
selectedColor: colorValues.conversationColor,
|
selectedColor: colorValues.conversationColor,
|
||||||
selectedCustomColor: {
|
selectedCustomColor: {
|
||||||
|
|
|
@ -6,8 +6,6 @@ import { connect } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { mapDispatchToProps } from '../actions';
|
||||||
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
|
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
|
||||||
import { StateType } from '../reducer';
|
import { StateType } from '../reducer';
|
||||||
import { getIntl } from '../selectors/user';
|
|
||||||
import { SmartChatColorPicker } from './ChatColorPicker';
|
|
||||||
import { SmartProfileEditorModal } from './ProfileEditorModal';
|
import { SmartProfileEditorModal } from './ProfileEditorModal';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// 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;
|
const FilteredSmartProfileEditorModal = SmartProfileEditorModal as any;
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
function renderChatColorPicker(): JSX.Element {
|
|
||||||
return <SmartChatColorPicker />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderProfileEditor(): JSX.Element {
|
function renderProfileEditor(): JSX.Element {
|
||||||
return <FilteredSmartProfileEditorModal />;
|
return <FilteredSmartProfileEditorModal />;
|
||||||
}
|
}
|
||||||
|
@ -27,8 +21,6 @@ function renderProfileEditor(): JSX.Element {
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
return {
|
return {
|
||||||
...state.globalModals,
|
...state.globalModals,
|
||||||
i18n: getIntl(state),
|
|
||||||
renderChatColorPicker,
|
|
||||||
renderProfileEditor,
|
renderProfileEditor,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,18 +10,18 @@ import {
|
||||||
} from '../../../state/ducks/globalModals';
|
} from '../../../state/ducks/globalModals';
|
||||||
|
|
||||||
describe('both/state/ducks/globalModals', () => {
|
describe('both/state/ducks/globalModals', () => {
|
||||||
describe('toggleChatColorEditor', () => {
|
describe('toggleProfileEditor', () => {
|
||||||
const { toggleChatColorEditor } = actions;
|
const { toggleProfileEditor } = actions;
|
||||||
|
|
||||||
it('toggles isChatColorEditorVisible', () => {
|
it('toggles isProfileEditorVisible', () => {
|
||||||
const state = getEmptyState();
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
23
ts/test-both/util/awaitObject_test.ts
Normal file
23
ts/test-both/util/awaitObject_test.ts
Normal 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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -156,12 +156,15 @@ export enum CallingDeviceType {
|
||||||
SPEAKER,
|
SPEAKER,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MediaDeviceSettings = {
|
export type AvailableIODevicesType = {
|
||||||
availableMicrophones: Array<AudioDevice>;
|
|
||||||
selectedMicrophone: AudioDevice | undefined;
|
|
||||||
availableSpeakers: Array<AudioDevice>;
|
|
||||||
selectedSpeaker: AudioDevice | undefined;
|
|
||||||
availableCameras: Array<MediaDeviceInfo>;
|
availableCameras: Array<MediaDeviceInfo>;
|
||||||
|
availableMicrophones: Array<AudioDevice>;
|
||||||
|
availableSpeakers: Array<AudioDevice>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MediaDeviceSettings = AvailableIODevicesType & {
|
||||||
|
selectedMicrophone: AudioDevice | undefined;
|
||||||
|
selectedSpeaker: AudioDevice | undefined;
|
||||||
selectedCamera: string | undefined;
|
selectedCamera: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
11
ts/types/Storage.d.ts
vendored
11
ts/types/Storage.d.ts
vendored
|
@ -24,6 +24,12 @@ export type SerializedCertificateType = {
|
||||||
serialized: ArrayBuffer;
|
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 = {
|
export type StorageAccessType = {
|
||||||
'always-relay-calls': boolean;
|
'always-relay-calls': boolean;
|
||||||
'audio-notification': boolean;
|
'audio-notification': boolean;
|
||||||
|
@ -36,10 +42,10 @@ export type StorageAccessType = {
|
||||||
'system-tray-setting': SystemTraySetting;
|
'system-tray-setting': SystemTraySetting;
|
||||||
'incoming-call-notification': boolean;
|
'incoming-call-notification': boolean;
|
||||||
'notification-draw-attention': boolean;
|
'notification-draw-attention': boolean;
|
||||||
'notification-setting': 'message' | 'name' | 'count' | 'off';
|
'notification-setting': NotificationSettingType;
|
||||||
'read-receipt-setting': boolean;
|
'read-receipt-setting': boolean;
|
||||||
'spell-check': boolean;
|
'spell-check': boolean;
|
||||||
'theme-setting': 'light' | 'dark' | 'system';
|
'theme-setting': ThemeSettingType;
|
||||||
attachmentMigration_isComplete: boolean;
|
attachmentMigration_isComplete: boolean;
|
||||||
attachmentMigration_lastProcessedIndex: number;
|
attachmentMigration_lastProcessedIndex: number;
|
||||||
blocked: Array<string>;
|
blocked: Array<string>;
|
||||||
|
@ -112,6 +118,7 @@ export type StorageAccessType = {
|
||||||
senderCertificate: SerializedCertificateType;
|
senderCertificate: SerializedCertificateType;
|
||||||
senderCertificateNoE164: SerializedCertificateType;
|
senderCertificateNoE164: SerializedCertificateType;
|
||||||
paymentAddress: string;
|
paymentAddress: string;
|
||||||
|
zoomFactor: ZoomFactorType;
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
senderCertificateWithUuid: never;
|
senderCertificateWithUuid: never;
|
||||||
|
|
|
@ -51,3 +51,5 @@ type InternalAssertProps<
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AssertProps<Result, Value> = InternalAssertProps<Result, Value>;
|
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
24
ts/util/awaitObject.ts
Normal 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;
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ class CallingTones {
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
async playEndCall(): Promise<void> {
|
async playEndCall(): Promise<void> {
|
||||||
const canPlayTone = await window.getCallRingtoneNotification();
|
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class CallingTones {
|
||||||
this.ringtone = undefined;
|
this.ringtone = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canPlayTone = await window.getCallRingtoneNotification();
|
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class CallingTones {
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
async someonePresenting() {
|
async someonePresenting() {
|
||||||
const canPlayTone = await window.getCallRingtoneNotification();
|
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
487
ts/util/createIPCEvents.ts
Normal file
487
ts/util/createIPCEvents.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -200,30 +200,6 @@
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-09-19T21:59:32.770Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"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-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/clear_data_view.js",
|
"path": "js/views/clear_data_view.js",
|
||||||
|
@ -767,317 +743,6 @@
|
||||||
"updated": "2021-02-26T18:44:56.450Z",
|
"updated": "2021-02-26T18:44:56.450Z",
|
||||||
"reasonDetail": "Static selector, read-only access"
|
"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-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/standalone_registration_view.js",
|
"path": "js/views/standalone_registration_view.js",
|
||||||
|
@ -14444,6 +14109,118 @@
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-08-03T21:17:38.615Z"
|
"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",
|
"rule": "React-useRef",
|
||||||
"path": "ts/util/hooks/index.js",
|
"path": "ts/util/hooks/index.js",
|
||||||
|
|
189
ts/util/preload.ts
Normal file
189
ts/util/preload.ts
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ export async function sendReadReceiptsFor(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Only send read receipts for accepted conversations
|
// Only send read receipts for accepted conversations
|
||||||
if (
|
if (
|
||||||
window.storage.get('read-receipt-setting') &&
|
window.Events.getReadReceiptSetting() &&
|
||||||
isConversationAccepted(conversationAttrs)
|
isConversationAccepted(conversationAttrs)
|
||||||
) {
|
) {
|
||||||
window.log.info(`Sending ${items.length} read receipts`);
|
window.log.info(`Sending ${items.length} read receipts`);
|
||||||
|
|
|
@ -4079,7 +4079,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
|
|
||||||
maybeGrabLinkPreview(message: string, caretLocation?: number) {
|
maybeGrabLinkPreview(message: string, caretLocation?: number) {
|
||||||
// Don't generate link previews if user has turned them off
|
// Don't generate link previews if user has turned them off
|
||||||
if (!window.storage.get('linkPreviews', false)) {
|
if (!window.Events.getLinkPreviewSetting()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Do nothing if we're offline
|
// Do nothing if we're offline
|
||||||
|
|
17
ts/window.d.ts
vendored
17
ts/window.d.ts
vendored
|
@ -8,7 +8,7 @@ import * as Backbone from 'backbone';
|
||||||
import * as Underscore from 'underscore';
|
import * as Underscore from 'underscore';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import PQueue from 'p-queue/dist';
|
import PQueue from 'p-queue/dist';
|
||||||
import { Ref } from 'react';
|
import { Attributes, ComponentClass, FunctionComponent, Ref } from 'react';
|
||||||
import { imageToBlurHash } from './util/imageToBlurHash';
|
import { imageToBlurHash } from './util/imageToBlurHash';
|
||||||
import * as Util from './util';
|
import * as Util from './util';
|
||||||
import {
|
import {
|
||||||
|
@ -118,6 +118,7 @@ import { isValidGuid } from './util/isValidGuid';
|
||||||
import { StateType } from './state/reducer';
|
import { StateType } from './state/reducer';
|
||||||
import { SystemTraySetting } from './types/SystemTraySetting';
|
import { SystemTraySetting } from './types/SystemTraySetting';
|
||||||
import { CI } from './CI';
|
import { CI } from './CI';
|
||||||
|
import { IPCEventsType } from './util/createIPCEvents';
|
||||||
|
|
||||||
export { Long } from 'long';
|
export { Long } from 'long';
|
||||||
|
|
||||||
|
@ -176,6 +177,15 @@ declare global {
|
||||||
|
|
||||||
WhatIsThis: WhatIsThis;
|
WhatIsThis: WhatIsThis;
|
||||||
|
|
||||||
|
SignalModule: {
|
||||||
|
registerReactRenderer: (
|
||||||
|
f: <P extends {}>(
|
||||||
|
component: FunctionComponent<P> | ComponentClass<P>,
|
||||||
|
props?: (Attributes & P) | null
|
||||||
|
) => void
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
registerScreenShareControllerRenderer: (
|
registerScreenShareControllerRenderer: (
|
||||||
f: (
|
f: (
|
||||||
component: typeof CallingScreenSharingController,
|
component: typeof CallingScreenSharingController,
|
||||||
|
@ -194,15 +204,12 @@ declare global {
|
||||||
getAccountManager: () => AccountManager;
|
getAccountManager: () => AccountManager;
|
||||||
getAlwaysRelayCalls: () => Promise<boolean>;
|
getAlwaysRelayCalls: () => Promise<boolean>;
|
||||||
getBuiltInImages: () => Promise<Array<string>>;
|
getBuiltInImages: () => Promise<Array<string>>;
|
||||||
getCallRingtoneNotification: () => Promise<boolean>;
|
|
||||||
getCallSystemNotification: () => Promise<boolean>;
|
|
||||||
getConversations: () => ConversationModelCollectionType;
|
getConversations: () => ConversationModelCollectionType;
|
||||||
getCountMutedConversations: () => Promise<boolean>;
|
getCountMutedConversations: () => Promise<boolean>;
|
||||||
getEnvironment: typeof getEnvironment;
|
getEnvironment: typeof getEnvironment;
|
||||||
getExpiration: () => string;
|
getExpiration: () => string;
|
||||||
getGuid: () => string;
|
getGuid: () => string;
|
||||||
getInboxCollection: () => ConversationModelCollectionType;
|
getInboxCollection: () => ConversationModelCollectionType;
|
||||||
getIncomingCallNotification: () => Promise<boolean>;
|
|
||||||
getInteractionMode: () => 'mouse' | 'keyboard';
|
getInteractionMode: () => 'mouse' | 'keyboard';
|
||||||
getLocale: () => ElectronLocaleType;
|
getLocale: () => ElectronLocaleType;
|
||||||
getMediaCameraPermissions: () => Promise<boolean>;
|
getMediaCameraPermissions: () => Promise<boolean>;
|
||||||
|
@ -489,7 +496,7 @@ declare global {
|
||||||
SignalContext: SignalContext;
|
SignalContext: SignalContext;
|
||||||
|
|
||||||
ConversationController: ConversationController;
|
ConversationController: ConversationController;
|
||||||
Events: WhatIsThis;
|
Events: IPCEventsType;
|
||||||
MessageController: MessageController;
|
MessageController: MessageController;
|
||||||
SignalProtocolStore: typeof SignalProtocolStore;
|
SignalProtocolStore: typeof SignalProtocolStore;
|
||||||
WebAPI: WebAPIConnectType;
|
WebAPI: WebAPIConnectType;
|
||||||
|
|
8
ts/windows/context.ts
Normal file
8
ts/windows/context.ts
Normal 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
94
ts/windows/preload.ts
Normal 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');
|
||||||
|
});
|
11
ts/windows/settings/init.ts
Normal file
11
ts/windows/settings/init.ts
Normal 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')
|
||||||
|
);
|
||||||
|
});
|
378
ts/windows/settings/preload.ts
Normal file
378
ts/windows/settings/preload.ts
Normal 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();
|
Loading…
Reference in a new issue