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",
|
||||
"description": "The menu option shown in Signal iOS to add a new linked device"
|
||||
},
|
||||
"deviceName": {
|
||||
"Preferences--device-name": {
|
||||
"message": "Device name",
|
||||
"description": "The label in settings panel shown for the user-provided name for this desktop instance"
|
||||
},
|
||||
|
@ -1278,7 +1278,7 @@
|
|||
"installTryAgain": {
|
||||
"message": "Try again"
|
||||
},
|
||||
"theme": {
|
||||
"Preferences--theme": {
|
||||
"message": "Theme",
|
||||
"description": "Header for theme settings"
|
||||
},
|
||||
|
@ -1614,7 +1614,7 @@
|
|||
"message": "Relay all calls through the Signal server to avoid revealing your IP address to your contact. Enabling will reduce call quality.",
|
||||
"description": "Details describing the always relay calls setting"
|
||||
},
|
||||
"permissions": {
|
||||
"Preferences--permissions": {
|
||||
"message": "Permissions",
|
||||
"description": "Header for permissions section of settings"
|
||||
},
|
||||
|
@ -1631,7 +1631,7 @@
|
|||
"description": "Header for general options on the settings screen"
|
||||
},
|
||||
"spellCheckDescription": {
|
||||
"message": "Enable spell check",
|
||||
"message": "Spell check text entered in message composition box",
|
||||
"description": "Description of the spell check setting"
|
||||
},
|
||||
"spellCheckWillBeEnabled": {
|
||||
|
@ -1655,7 +1655,7 @@
|
|||
"description": "Description for the automatic launch setting"
|
||||
},
|
||||
"clearDataHeader": {
|
||||
"message": "Clear Data",
|
||||
"message": "Clear application data",
|
||||
"description": "Header in the settings dialog for the section dealing with data deletion"
|
||||
},
|
||||
"clearDataExplanation": {
|
||||
|
@ -1803,7 +1803,7 @@
|
|||
"description": "Error message displayed when sending to an unregistered user."
|
||||
},
|
||||
"sync": {
|
||||
"message": "Contacts",
|
||||
"message": "Import contacts",
|
||||
"description": "Label for contact and group sync settings"
|
||||
},
|
||||
"syncExplanation": {
|
||||
|
@ -1963,7 +1963,7 @@
|
|||
}
|
||||
},
|
||||
"audioNotificationDescription": {
|
||||
"message": "Play audio notification",
|
||||
"message": "Play notification sounds",
|
||||
"description": "Description for audio notification setting"
|
||||
},
|
||||
"callRingtoneNotificationDescription": {
|
||||
|
@ -5009,7 +5009,7 @@
|
|||
"description": "Displayed while checking if the contact is SMS-only"
|
||||
},
|
||||
"countMutedConversationsDescription": {
|
||||
"message": "Count muted conversations in badge count",
|
||||
"message": "Include muted conversations in badge count",
|
||||
"description": "Description for counting muted conversations in badge setting"
|
||||
},
|
||||
"ContactModal--message": {
|
||||
|
@ -5965,5 +5965,131 @@
|
|||
"LeftPaneSetGroupMetadataHelper__avatar-modal-title": {
|
||||
"message": "Group Avatar",
|
||||
"description": "Title for the avatar picker in the group creation flow"
|
||||
},
|
||||
"Preferences__button--general": {
|
||||
"message": "General",
|
||||
"description": "Button to switch the settings view"
|
||||
},
|
||||
"Preferences__button--appearance": {
|
||||
"message": "Appearance",
|
||||
"description": "Button to switch the settings view"
|
||||
},
|
||||
"Preferences__button--chats": {
|
||||
"message": "Chats",
|
||||
"description": "Button to switch the settings view"
|
||||
},
|
||||
"Preferences__button--calls": {
|
||||
"message": "Calls",
|
||||
"description": "Button to switch the settings view"
|
||||
},
|
||||
"Preferences__button--notifications": {
|
||||
"message": "Notifications",
|
||||
"description": "Button to switch the settings view"
|
||||
},
|
||||
"Preferences__button--privacy": {
|
||||
"message": "Privacy",
|
||||
"description": "Button to switch the settings view"
|
||||
},
|
||||
"Preferences--lastSynced": {
|
||||
"message": "Last import at $date$ $time$",
|
||||
"description": "Label for date and time of last sync operation",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
"example": "6/9/2020"
|
||||
},
|
||||
"time": {
|
||||
"content": "$2",
|
||||
"example": "7:37:25 PM"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Preferences--system": {
|
||||
"message": "System",
|
||||
"description": "Title for system type settings"
|
||||
},
|
||||
"Preferences--zoom": {
|
||||
"message": "Zoom level",
|
||||
"description": "Label for changing the zoom level"
|
||||
},
|
||||
"Preferences__link-previews--title": {
|
||||
"message": "Generate link previews",
|
||||
"description": "Title for the generate link previews setting"
|
||||
},
|
||||
"Preferences__link-previews--description": {
|
||||
"message": "To change this setting, open the Signal app on your mobile device and navigate to Settings > Chats",
|
||||
"description": "Description for the generate link previews setting"
|
||||
},
|
||||
"Preferences--advanced": {
|
||||
"message": "Advanced",
|
||||
"description": "Title for advanced settings"
|
||||
},
|
||||
"Preferences--notification-content": {
|
||||
"message": "Notification content",
|
||||
"description": "Label for the notification content setting select box"
|
||||
},
|
||||
"Preferences--blocked": {
|
||||
"message": "Blocked",
|
||||
"description": "Label for blocked contacts setting"
|
||||
},
|
||||
"Preferences--blocked-count-singular": {
|
||||
"message": "$num$ contact",
|
||||
"description": "Number of contacts blocked singular",
|
||||
"placeholders": {
|
||||
"num": {
|
||||
"content": "$1",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Preferences--blocked-count-plural": {
|
||||
"message": "$num$ contacts",
|
||||
"description": "Number of contacts blocked plural",
|
||||
"placeholders": {
|
||||
"num": {
|
||||
"content": "$1",
|
||||
"example": "20"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Preferences__who-can--title": {
|
||||
"message": "Who can...",
|
||||
"description": "Title for the 'who can do X' setting"
|
||||
},
|
||||
"Preferences__privacy--description": {
|
||||
"message": "To change these settings, open the Signal app on your mobile device and navigate to Settings > Privacy",
|
||||
"description": "Description for the 'who can do X' setting"
|
||||
},
|
||||
"Preferences__who-can--everybody": {
|
||||
"message": "Everybody",
|
||||
"description": "Option for who can see my X select"
|
||||
},
|
||||
"Preferences__who-can--contacts": {
|
||||
"message": "My Contacts",
|
||||
"description": "Option for who can see my X select"
|
||||
},
|
||||
"Preferences__who-can--nobody": {
|
||||
"message": "Nobody",
|
||||
"description": "Option for who can see my X select"
|
||||
},
|
||||
"Preferences--messaging": {
|
||||
"message": "Messaging",
|
||||
"description": "Title for the messaging settings"
|
||||
},
|
||||
"Preferences--see-me": {
|
||||
"message": "See my phone number",
|
||||
"description": "Label for the see my phone number setting"
|
||||
},
|
||||
"Preferences--find-me": {
|
||||
"message": "Find me by my phone number",
|
||||
"description": "Label for the find me by my phone number setting"
|
||||
},
|
||||
"Preferences--read-receipts": {
|
||||
"message": "Read receipts",
|
||||
"description": "Label for the read receipts setting"
|
||||
},
|
||||
"Preferences--typing-indicators": {
|
||||
"message": "Typing indicators",
|
||||
"description": "Label for the typing indicators setting"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { start } from './base_config';
|
|||
const userDataPath = app.getPath('userData');
|
||||
const targetPath = join(userDataPath, 'ephemeral.json');
|
||||
|
||||
const ephemeralConfig = start('ephemeral', targetPath, {
|
||||
export const ephemeralConfig = start('ephemeral', targetPath, {
|
||||
allowMalformedOnStartup: true,
|
||||
});
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ const PERMISSIONS: Record<string, boolean> = {
|
|||
};
|
||||
|
||||
function _createPermissionHandler(
|
||||
userConfig: ConfigType
|
||||
userConfig: Pick<ConfigType, 'get'>
|
||||
): Parameters<typeof ElectronSession.prototype.setPermissionRequestHandler>[0] {
|
||||
return (_webContents, permission, callback, details): void => {
|
||||
// We default 'media' permission to false, but the user can override that for
|
||||
|
@ -75,7 +75,7 @@ export function installPermissionsHandler({
|
|||
userConfig,
|
||||
}: {
|
||||
session: typeof ElectronSession;
|
||||
userConfig: ConfigType;
|
||||
userConfig: Pick<ConfigType, 'get'>;
|
||||
}): void {
|
||||
// Setting the permission request handler to null first forces any permissions to be
|
||||
// requested again. Without this, revoked permissions might still be available if
|
||||
|
|
|
@ -24,7 +24,7 @@ console.log(`userData: ${app.getPath('userData')}`);
|
|||
const userDataPath = app.getPath('userData');
|
||||
const targetPath = join(userDataPath, 'config.json');
|
||||
|
||||
const userConfig = start('user', targetPath);
|
||||
export const userConfig = start('user', targetPath);
|
||||
|
||||
export const get = userConfig.get.bind(userConfig);
|
||||
export const remove = userConfig.remove.bind(userConfig);
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
const { ipcRenderer } = require('electron');
|
||||
const url = require('url');
|
||||
const copyText = require('copy-text-to-clipboard');
|
||||
|
||||
// It is important to call this as early as possible
|
||||
require('./ts/windows/context');
|
||||
|
||||
const i18n = require('./js/modules/i18n');
|
||||
const {
|
||||
getEnvironment,
|
||||
|
@ -13,10 +17,6 @@ const {
|
|||
parseEnvironment,
|
||||
} = require('./ts/environment');
|
||||
|
||||
const { Context: SignalContext } = require('./ts/context');
|
||||
|
||||
window.SignalContext = new SignalContext(ipcRenderer);
|
||||
|
||||
const config = url.parse(window.location.toString(), true).query;
|
||||
const { locale } = config;
|
||||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||
|
|
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);
|
||||
|
||||
async function applyTheme() {
|
||||
const theme = await window.getThemeSetting();
|
||||
const theme = await window.Settings.themeSetting.getValue();
|
||||
$body.removeClass('light-theme');
|
||||
$body.removeClass('dark-theme');
|
||||
$body.addClass(`${theme === 'system' ? window.systemTheme : theme}-theme`);
|
||||
|
@ -41,9 +41,9 @@ window.showConfirmationDialog({
|
|||
okText: i18n('allowAccess'),
|
||||
resolve: () => {
|
||||
if (!window.forCamera) {
|
||||
window.setMediaPermissions(true);
|
||||
window.Settings.mediaPermissions.setValue(true);
|
||||
} else {
|
||||
window.setMediaCameraPermissions(true);
|
||||
window.Settings.mediaCameraPermissions.setValue(true);
|
||||
}
|
||||
window.closePermissionsPopup();
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
Menu,
|
||||
protocol: electronProtocol,
|
||||
session,
|
||||
shell,
|
||||
systemPreferences,
|
||||
} = electron;
|
||||
|
@ -102,7 +101,6 @@ const {
|
|||
installFileHandler,
|
||||
installWebHandler,
|
||||
} = require('./app/protocol_filter');
|
||||
const { installPermissionsHandler } = require('./app/permissions');
|
||||
const OS = require('./ts/OS');
|
||||
const { isProduction } = require('./ts/util/version');
|
||||
const {
|
||||
|
@ -124,6 +122,7 @@ const { Environment, isTestEnvironment } = require('./ts/environment');
|
|||
const { ChallengeMainHandler } = require('./ts/main/challengeMain');
|
||||
const { NativeThemeNotifier } = require('./ts/main/NativeThemeNotifier');
|
||||
const { PowerChannel } = require('./ts/main/powerChannel');
|
||||
const { SettingsChannel } = require('./ts/main/settingsChannel');
|
||||
const { maybeParseUrl, setUrlSearchParams } = require('./ts/util/url');
|
||||
const { getHeicConverter } = require('./ts/workers/heicConverterMain');
|
||||
|
||||
|
@ -235,6 +234,7 @@ const loadLocale = require('./app/locale').load;
|
|||
// Both of these will be set after app fires the 'ready' event
|
||||
let logger;
|
||||
let locale;
|
||||
let settingsChannel;
|
||||
|
||||
function prepareFileUrl(
|
||||
pathSegments /* : ReadonlyArray<string> */,
|
||||
|
@ -425,6 +425,8 @@ async function createWindow() {
|
|||
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow(windowOptions);
|
||||
settingsChannel.setMainWindow(mainWindow);
|
||||
|
||||
mainWindowCreated = true;
|
||||
setupSpellChecker(mainWindow, locale.messages);
|
||||
if (!startInTray && windowConfig && windowConfig.maximized) {
|
||||
|
@ -567,6 +569,7 @@ async function createWindow() {
|
|||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = undefined;
|
||||
settingsChannel.setMainWindow(mainWindow);
|
||||
if (systemTrayService) {
|
||||
systemTrayService.setMainWindow(mainWindow);
|
||||
}
|
||||
|
@ -875,40 +878,26 @@ function showSettingsWindow() {
|
|||
settingsWindow.show();
|
||||
return;
|
||||
}
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
addDarkOverlay();
|
||||
|
||||
const size = mainWindow.getSize();
|
||||
// center settings window over main window
|
||||
const settingwidth = Math.min(500, size[0]);
|
||||
const settingheight = Math.max(size[1] - 100, MIN_HEIGHT);
|
||||
const mainPos = mainWindow.getPosition();
|
||||
const mainSize = mainWindow.getSize();
|
||||
const options = {
|
||||
x: Math.round(mainPos[0] + mainSize[0] / 2 - settingwidth / 2),
|
||||
y: Math.round(mainPos[1] + mainSize[1] / 2 - settingheight / 2),
|
||||
width: settingwidth,
|
||||
height: settingheight,
|
||||
frame: false,
|
||||
width: 700,
|
||||
height: 700,
|
||||
frame: true,
|
||||
resizable: false,
|
||||
title: locale.messages.signalDesktopPreferences.message,
|
||||
autoHideMenuBar: true,
|
||||
backgroundColor: '#3a76f0',
|
||||
show: false,
|
||||
modal: true,
|
||||
modal: false,
|
||||
webPreferences: {
|
||||
...defaultWebPrefs,
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false,
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
preload: path.join(__dirname, 'settings_preload.js'),
|
||||
preload: path.join(__dirname, 'ts', 'windows', 'settings', 'preload.js'),
|
||||
nativeWindowOpen: true,
|
||||
},
|
||||
parent: mainWindow,
|
||||
};
|
||||
|
||||
settingsWindow = new BrowserWindow(options);
|
||||
|
@ -924,6 +913,11 @@ function showSettingsWindow() {
|
|||
|
||||
settingsWindow.once('ready-to-show', () => {
|
||||
settingsWindow.show();
|
||||
settingsWindow.webContents.send('render');
|
||||
|
||||
if (config.get('openDevTools')) {
|
||||
settingsWindow.webContents.openDevTools();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1015,7 +1009,7 @@ async function showDebugLogWindow() {
|
|||
return;
|
||||
}
|
||||
|
||||
const theme = await pify(getDataFromMainWindow)('theme-setting');
|
||||
const theme = await settingsChannel.getSettingFromMainWindow('themeSetting');
|
||||
const size = mainWindow.getSize();
|
||||
const options = {
|
||||
width: Math.max(size[0] - 100, MIN_WIDTH),
|
||||
|
@ -1068,7 +1062,9 @@ function showPermissionsPopupWindow(forCalling, forCamera) {
|
|||
reject(new Error('No main window'));
|
||||
}
|
||||
|
||||
const theme = await pify(getDataFromMainWindow)('theme-setting');
|
||||
const theme = await settingsChannel.getSettingFromMainWindow(
|
||||
'themeSetting'
|
||||
);
|
||||
const size = mainWindow.getSize();
|
||||
const options = {
|
||||
width: Math.min(400, size[0]),
|
||||
|
@ -1154,6 +1150,9 @@ let ready = false;
|
|||
app.on('ready', async () => {
|
||||
const startTime = Date.now();
|
||||
|
||||
settingsChannel = new SettingsChannel();
|
||||
settingsChannel.install();
|
||||
|
||||
// We use this event only a single time to log the startup time of the app
|
||||
// from when it's first ready until the loading screen disappears.
|
||||
ipc.once('signal-app-loaded', (event, info) => {
|
||||
|
@ -1201,8 +1200,6 @@ app.on('ready', async () => {
|
|||
protocol: electronProtocol,
|
||||
});
|
||||
|
||||
installPermissionsHandler({ session, userConfig });
|
||||
|
||||
logger = await logging.initialize(getMainWindow);
|
||||
logger.info('app ready');
|
||||
logger.info(`starting version ${packageJson.version}`);
|
||||
|
@ -1668,79 +1665,6 @@ ipc.on('close-settings', () => {
|
|||
}
|
||||
});
|
||||
|
||||
installSettingsGetter('device-name');
|
||||
|
||||
installSettingsGetter('theme-setting');
|
||||
installSettingsSetter('theme-setting');
|
||||
installSettingsGetter('hide-menu-bar');
|
||||
installSettingsSetter('hide-menu-bar');
|
||||
installSettingsGetter('system-tray-setting');
|
||||
installSettingsSetter('system-tray-setting');
|
||||
|
||||
installSettingsGetter('notification-setting');
|
||||
installSettingsSetter('notification-setting');
|
||||
installSettingsGetter('notification-draw-attention');
|
||||
installSettingsSetter('notification-draw-attention');
|
||||
installSettingsGetter('audio-notification');
|
||||
installSettingsSetter('audio-notification');
|
||||
installSettingsGetter('badge-count-muted-conversations');
|
||||
installSettingsSetter('badge-count-muted-conversations');
|
||||
|
||||
installSettingsGetter('spell-check');
|
||||
installSettingsSetter('spell-check', true);
|
||||
|
||||
installSettingsGetter('auto-launch');
|
||||
installSettingsSetter('auto-launch');
|
||||
|
||||
installSettingsGetter('always-relay-calls');
|
||||
installSettingsSetter('always-relay-calls');
|
||||
installSettingsGetter('call-ringtone-notification');
|
||||
installSettingsSetter('call-ringtone-notification');
|
||||
installSettingsGetter('call-system-notification');
|
||||
installSettingsSetter('call-system-notification');
|
||||
installSettingsGetter('incoming-call-notification');
|
||||
installSettingsSetter('incoming-call-notification');
|
||||
|
||||
// These ones are different because its single source of truth is userConfig,
|
||||
// not IndexedDB
|
||||
ipc.on('get-media-permissions', event => {
|
||||
event.sender.send(
|
||||
'get-success-media-permissions',
|
||||
null,
|
||||
userConfig.get('mediaPermissions') || false
|
||||
);
|
||||
});
|
||||
ipc.on('get-media-camera-permissions', event => {
|
||||
event.sender.send(
|
||||
'get-success-media-camera-permissions',
|
||||
null,
|
||||
userConfig.get('mediaCameraPermissions') || false
|
||||
);
|
||||
});
|
||||
ipc.on('set-media-permissions', (event, value) => {
|
||||
userConfig.set('mediaPermissions', value);
|
||||
|
||||
// We reinstall permissions handler to ensure that a revoked permission takes effect
|
||||
installPermissionsHandler({ session, userConfig });
|
||||
|
||||
event.sender.send('set-success-media-permissions', null);
|
||||
});
|
||||
ipc.on('set-media-camera-permissions', (event, value) => {
|
||||
userConfig.set('mediaCameraPermissions', value);
|
||||
|
||||
// We reinstall permissions handler to ensure that a revoked permission takes effect
|
||||
installPermissionsHandler({ session, userConfig });
|
||||
|
||||
event.sender.send('set-success-media-camera-permissions', null);
|
||||
});
|
||||
|
||||
installSettingsGetter('is-primary');
|
||||
installSettingsGetter('sync-request');
|
||||
installSettingsGetter('sync-time');
|
||||
installSettingsSetter('sync-time');
|
||||
installSettingsGetter('universal-expire-timer');
|
||||
installSettingsSetter('universal-expire-timer');
|
||||
|
||||
ipc.on('delete-all-data', () => {
|
||||
if (mainWindow && mainWindow.webContents) {
|
||||
mainWindow.webContents.send('delete-all-data');
|
||||
|
@ -1776,47 +1700,12 @@ ipc.on('get-user-data-path', event => {
|
|||
event.returnValue = app.getPath('userData');
|
||||
});
|
||||
|
||||
function getDataFromMainWindow(name, callback) {
|
||||
ipc.once(`get-success-${name}`, (_event, error, value) =>
|
||||
callback(error, value)
|
||||
);
|
||||
mainWindow.webContents.send(`get-${name}`);
|
||||
}
|
||||
|
||||
function installSettingsGetter(name) {
|
||||
ipc.on(`get-${name}`, event => {
|
||||
if (mainWindow && mainWindow.webContents) {
|
||||
getDataFromMainWindow(name, (error, value) => {
|
||||
const contents = event.sender;
|
||||
if (contents.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
contents.send(`get-success-${name}`, error, value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function installSettingsSetter(name, isEphemeral = false) {
|
||||
ipc.on(`set-${name}`, (event, value) => {
|
||||
if (isEphemeral) {
|
||||
ephemeralConfig.set('spell-check', value);
|
||||
}
|
||||
|
||||
if (mainWindow && mainWindow.webContents) {
|
||||
ipc.once(`set-success-${name}`, (_event, error) => {
|
||||
const contents = event.sender;
|
||||
if (contents.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
contents.send(`set-success-${name}`, error);
|
||||
});
|
||||
mainWindow.webContents.send(`set-${name}`, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Refresh the settings window whenever preferences change
|
||||
ipc.on('preferences-changed', () => {
|
||||
if (settingsWindow && settingsWindow.webContents) {
|
||||
settingsWindow.webContents.send('render');
|
||||
}
|
||||
});
|
||||
|
||||
function getIncomingHref(argv) {
|
||||
return argv.find(arg => isSgnlHref(arg, logger));
|
||||
|
|
|
@ -416,7 +416,6 @@
|
|||
"preload_utils.js",
|
||||
"about_preload.js",
|
||||
"screenShare_preload.js",
|
||||
"settings_preload.js",
|
||||
"permissions_popup_preload.js",
|
||||
"debug_log_preload.js",
|
||||
"main.js",
|
||||
|
|
|
@ -8,23 +8,24 @@ window.ReactDOM = require('react-dom');
|
|||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const url = require('url');
|
||||
|
||||
// It is important to call this as early as possible
|
||||
require('./ts/windows/context');
|
||||
|
||||
const i18n = require('./js/modules/i18n');
|
||||
const { ConfirmationDialog } = require('./ts/components/ConfirmationDialog');
|
||||
const { makeGetter, makeSetter } = require('./preload_utils');
|
||||
const {
|
||||
getEnvironment,
|
||||
setEnvironment,
|
||||
parseEnvironment,
|
||||
} = require('./ts/environment');
|
||||
|
||||
const { Context: SignalContext } = require('./ts/context');
|
||||
|
||||
const config = url.parse(window.location.toString(), true).query;
|
||||
const { locale } = config;
|
||||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||
setEnvironment(parseEnvironment(config.environment));
|
||||
|
||||
window.SignalContext = new SignalContext(ipcRenderer);
|
||||
const { createSetting } = require('./ts/util/preload');
|
||||
|
||||
window.getEnvironment = getEnvironment;
|
||||
window.getVersion = () => config.version;
|
||||
|
@ -43,8 +44,14 @@ require('./ts/logging/set_up_renderer_logging').initialize();
|
|||
window.closePermissionsPopup = () =>
|
||||
ipcRenderer.send('close-permissions-popup');
|
||||
|
||||
window.setMediaPermissions = makeSetter('media-permissions');
|
||||
window.setMediaCameraPermissions = makeSetter('media-camera-permissions');
|
||||
window.getThemeSetting = makeGetter('theme-setting');
|
||||
window.setThemeSetting = makeSetter('theme-setting');
|
||||
window.Backbone = require('backbone');
|
||||
|
||||
window.Settings = {
|
||||
mediaCameraPermissions: createSetting('mediaCameraPermissions', {
|
||||
getter: false,
|
||||
}),
|
||||
mediaPermissions: createSetting('mediaPermissions', {
|
||||
getter: false,
|
||||
}),
|
||||
themeSetting: createSetting('themeSetting', { setter: false }),
|
||||
};
|
||||
|
|
157
preload.js
157
preload.js
|
@ -12,7 +12,10 @@ try {
|
|||
const electron = require('electron');
|
||||
const semver = require('semver');
|
||||
const _ = require('lodash');
|
||||
const { installGetter, installSetter } = require('./preload_utils');
|
||||
|
||||
// It is important to call this as early as possible
|
||||
require('./ts/windows/context');
|
||||
|
||||
const {
|
||||
getEnvironment,
|
||||
setEnvironment,
|
||||
|
@ -24,10 +27,6 @@ try {
|
|||
const { remote } = electron;
|
||||
const { app } = remote;
|
||||
|
||||
const { Context: SignalContext } = require('./ts/context');
|
||||
|
||||
window.SignalContext = new SignalContext(ipc);
|
||||
|
||||
window.sqlInitializer = require('./ts/sql/initialize');
|
||||
|
||||
const config = require('url').parse(window.location.toString(), true).query;
|
||||
|
@ -254,146 +253,7 @@ try {
|
|||
window.Events.removeDarkOverlay();
|
||||
});
|
||||
|
||||
installGetter('device-name', 'getDeviceName');
|
||||
|
||||
installGetter('theme-setting', 'getThemeSetting');
|
||||
installSetter('theme-setting', 'setThemeSetting');
|
||||
installGetter('hide-menu-bar', 'getHideMenuBar');
|
||||
installSetter('hide-menu-bar', 'setHideMenuBar');
|
||||
installGetter('system-tray-setting', 'getSystemTraySetting');
|
||||
installSetter('system-tray-setting', 'setSystemTraySetting');
|
||||
|
||||
installGetter('notification-setting', 'getNotificationSetting');
|
||||
installSetter('notification-setting', 'setNotificationSetting');
|
||||
installGetter('notification-draw-attention', 'getNotificationDrawAttention');
|
||||
installSetter('notification-draw-attention', 'setNotificationDrawAttention');
|
||||
installGetter('audio-notification', 'getAudioNotification');
|
||||
installSetter('audio-notification', 'setAudioNotification');
|
||||
installGetter(
|
||||
'badge-count-muted-conversations',
|
||||
'getCountMutedConversations'
|
||||
);
|
||||
installSetter(
|
||||
'badge-count-muted-conversations',
|
||||
'setCountMutedConversations'
|
||||
);
|
||||
|
||||
window.getCountMutedConversations = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once(
|
||||
'get-success-badge-count-muted-conversations',
|
||||
(_event, error, value) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
}
|
||||
);
|
||||
ipc.send('get-badge-count-muted-conversations');
|
||||
});
|
||||
|
||||
installGetter('spell-check', 'getSpellCheck');
|
||||
installSetter('spell-check', 'setSpellCheck');
|
||||
|
||||
installGetter('auto-launch', 'getAutoLaunch');
|
||||
installSetter('auto-launch', 'setAutoLaunch');
|
||||
|
||||
installGetter('always-relay-calls', 'getAlwaysRelayCalls');
|
||||
installSetter('always-relay-calls', 'setAlwaysRelayCalls');
|
||||
|
||||
installGetter('call-ringtone-notification', 'getCallRingtoneNotification');
|
||||
installSetter('call-ringtone-notification', 'setCallRingtoneNotification');
|
||||
|
||||
window.getCallRingtoneNotification = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once(
|
||||
'get-success-call-ringtone-notification',
|
||||
(_event, error, value) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
}
|
||||
);
|
||||
ipc.send('get-call-ringtone-notification');
|
||||
});
|
||||
|
||||
installGetter('call-system-notification', 'getCallSystemNotification');
|
||||
installSetter('call-system-notification', 'setCallSystemNotification');
|
||||
|
||||
window.getCallSystemNotification = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once(
|
||||
'get-success-call-system-notification',
|
||||
(_event, error, value) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
}
|
||||
);
|
||||
ipc.send('get-call-system-notification');
|
||||
});
|
||||
|
||||
installGetter('incoming-call-notification', 'getIncomingCallNotification');
|
||||
installSetter('incoming-call-notification', 'setIncomingCallNotification');
|
||||
|
||||
window.getIncomingCallNotification = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once(
|
||||
'get-success-incoming-call-notification',
|
||||
(_event, error, value) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
}
|
||||
);
|
||||
ipc.send('get-incoming-call-notification');
|
||||
});
|
||||
|
||||
window.getAlwaysRelayCalls = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once('get-success-always-relay-calls', (_event, error, value) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
});
|
||||
ipc.send('get-always-relay-calls');
|
||||
});
|
||||
|
||||
window.getMediaPermissions = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once('get-success-media-permissions', (_event, error, value) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
});
|
||||
ipc.send('get-media-permissions');
|
||||
});
|
||||
|
||||
window.getMediaCameraPermissions = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once(
|
||||
'get-success-media-camera-permissions',
|
||||
(_event, error, value) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
}
|
||||
);
|
||||
ipc.send('get-media-camera-permissions');
|
||||
});
|
||||
require('./ts/windows/preload');
|
||||
|
||||
window.getBuiltInImages = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
|
@ -407,13 +267,6 @@ try {
|
|||
ipc.send('get-built-in-images');
|
||||
});
|
||||
|
||||
installGetter('is-primary', 'isPrimary');
|
||||
installGetter('sync-request', 'getSyncRequest');
|
||||
installGetter('sync-time', 'getLastSyncTime');
|
||||
installSetter('sync-time', 'setLastSyncTime');
|
||||
installGetter('universal-expire-timer', 'getUniversalExpireTimer');
|
||||
installSetter('universal-expire-timer', 'setUniversalExpireTimer');
|
||||
|
||||
ipc.on('delete-all-data', async () => {
|
||||
const { deleteAllData } = window.Events;
|
||||
if (!deleteAllData) {
|
||||
|
|
|
@ -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 { ipcRenderer } = require('electron');
|
||||
|
||||
// It is important to call this as early as possible
|
||||
require('./ts/windows/context');
|
||||
|
||||
const i18n = require('./js/modules/i18n');
|
||||
const {
|
||||
getEnvironment,
|
||||
|
@ -18,10 +21,6 @@ const {
|
|||
CallingScreenSharingController,
|
||||
} = require('./ts/components/CallingScreenSharingController');
|
||||
|
||||
const { Context: SignalContext } = require('./ts/context');
|
||||
|
||||
window.SignalContext = new SignalContext(ipcRenderer);
|
||||
|
||||
const config = url.parse(window.location.toString(), true).query;
|
||||
const { locale } = config;
|
||||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||
|
|
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 -->
|
||||
|
||||
<html>
|
||||
|
@ -6,11 +6,7 @@
|
|||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'none';
|
||||
child-src 'self';
|
||||
connect-src 'self' https: wss:;
|
||||
font-src 'self';
|
||||
form-action 'self';
|
||||
frame-src 'none';
|
||||
img-src 'self' blob: data:;
|
||||
media-src 'self' blob:;
|
||||
object-src 'none';
|
||||
|
@ -23,201 +19,12 @@
|
|||
type="text/css"
|
||||
/>
|
||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||
<style></style>
|
||||
</head>
|
||||
<body></body>
|
||||
<script type="text/x-tmpl-mustache" id="syncSettings">
|
||||
<hr>
|
||||
<h3>{{ sync }}</h3>
|
||||
<div>
|
||||
<button class='grey sync'>{{ syncNow }}</button>
|
||||
<p>
|
||||
{{ syncExplanation }}
|
||||
<div class='synced_at'>
|
||||
{{ lastSynced }} {{ syncDate }} {{ syncTime }}
|
||||
</div>
|
||||
<div class='sync_failed'>{{ syncFailed }}</div>
|
||||
<div class='clearfix'></div>
|
||||
</p>
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/x-tmpl-mustache" id="disappearingMessagesSettings">
|
||||
<h3>{{ title }}</h3>
|
||||
<div class="disappearing-messages-setting__timer {{#customInfo}}disappearing-messages-setting__timer--with-info{{/customInfo}}">
|
||||
<label
|
||||
class="disappearing-messages-setting__timer__label"
|
||||
for='disappearing-messages-timer'
|
||||
>
|
||||
{{ timerLabel }}
|
||||
</label>
|
||||
<div class="disappearing-messages-setting__timer__right">
|
||||
<div class="module-select">
|
||||
<select
|
||||
name='disappearing-messages-timer'
|
||||
id='disappearing-messages-timer'
|
||||
>
|
||||
{{#timerValues}}
|
||||
<option value="{{value}}" {{selected}}>
|
||||
{{ text }}
|
||||
</option>
|
||||
{{/timerValues}}
|
||||
<option value="-1" {{customSelected}}>
|
||||
{{customText}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
{{#customInfo}}
|
||||
<div class="disappearing-messages-setting__timer__right__info">
|
||||
{{text}}
|
||||
</div>
|
||||
{{/customInfo}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='disappearing-messages-setting__footer'>
|
||||
{{ footer }}
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/x-tmpl-mustache" id="settings">
|
||||
<div class='content'>
|
||||
<a class='x close' alt='close settings' href='#'></a>
|
||||
<h2>{{ settings }}</h2>
|
||||
<div class='device-name-settings'>
|
||||
<b>{{ deviceNameLabel }}:</b> {{ deviceName }}
|
||||
</div>
|
||||
<hr>
|
||||
<div class='theme-settings'>
|
||||
<h3>{{ theme }}</h3>
|
||||
{{#hasSystemTheme}}
|
||||
<div>
|
||||
<input type='radio' name='theme' id='theme-setting-system' value='system'>
|
||||
<label for='theme-setting-system'>{{ themeSystem }}</label>
|
||||
</div>
|
||||
{{/hasSystemTheme}}
|
||||
<div>
|
||||
<input type='radio' name='theme' id='theme-setting-light' value='light'>
|
||||
<label for='theme-setting-light'>{{ themeLight }}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type='radio' name='theme' id='theme-setting-dark' value='dark'>
|
||||
<label for='theme-setting-dark'>{{ themeDark }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
{{ #isHideMenuBarSupported }}
|
||||
<div class='menu-bar-setting'>
|
||||
<input type='checkbox' name='hide-menu-bar' id='hide-menu-bar'/>
|
||||
<label for='hide-menu-bar'>{{ hideMenuBar }}</label>
|
||||
</div>
|
||||
{{ /isHideMenuBarSupported }}
|
||||
<hr>
|
||||
<div class='notification-settings'>
|
||||
<h3>{{ notifications }}</h3>
|
||||
<p>{{ notificationSettingsDialog }}</p>
|
||||
<div>
|
||||
<input type='radio' name='notifications' id='notification-setting-message' value='message'>
|
||||
<label for='notification-setting-message'>{{ nameAndMessage }} </label>
|
||||
</div>
|
||||
<div>
|
||||
<input type='radio' name='notifications' id='notification-setting-name' value='name'/>
|
||||
<label for='notification-setting-name'>{{ nameOnly }} </label>
|
||||
</div>
|
||||
<div>
|
||||
<input type='radio' name='notifications' id='notification-setting-count' value='count'/>
|
||||
<label for='notification-setting-count'>{{ noNameOrMessage }} </label>
|
||||
</div>
|
||||
<div>
|
||||
<input type='radio' name='notifications' id='notification-setting-off' value='off'/>
|
||||
<label for='notification-setting-off'>{{ disableNotifications }} </label>
|
||||
</div>
|
||||
</div>
|
||||
{{ #isDrawAttentionSupported }}
|
||||
<br />
|
||||
<div class='draw-attention-setting'>
|
||||
<input type='checkbox' name='notification-draw-attention' id='notification-draw-attention'/>
|
||||
<label for='notification-draw-attention'>{{ notificationDrawAttention }}</label>
|
||||
</div>
|
||||
{{ /isDrawAttentionSupported }}
|
||||
<br />
|
||||
{{ #isAudioNotificationSupported }}
|
||||
<div class='audio-notification-setting'>
|
||||
<input type='checkbox' name='audio-notification' id='audio-notification'/>
|
||||
<label for='audio-notification'>{{ audioNotificationDescription }}</label>
|
||||
</div>
|
||||
{{ /isAudioNotificationSupported }}
|
||||
<div class='badge-count-muted-conversations-setting'>
|
||||
<input type='checkbox' name='badge-count-muted-conversations' id='badge-count-muted-conversations'/>
|
||||
<label for='badge-count-muted-conversations'>{{ countMutedConversationsDescription }}</label>
|
||||
</div>
|
||||
<hr>
|
||||
<h3>{{ generalHeader }}</h3>
|
||||
<div class='spell-check-setting'>
|
||||
<input type='checkbox' name='spell-check-setting' id='spell-check-setting' />
|
||||
<label for='spell-check-setting'>{{ spellCheckDescription }}</label>
|
||||
<p class='spell-check-setting-message' style='display: {{ spellCheckDisplay }};' aria-hidden='{{ spellCheckHidden }}'>
|
||||
{{ spellCheckDirtyText }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="system-tray-setting-container"></div>
|
||||
{{ #isAutoLaunchSupported }}
|
||||
<div class='auto-launch-setting'>
|
||||
<input type='checkbox' name='auto-launch-setting' id='auto-launch-setting' />
|
||||
<label for='auto-launch-setting'>{{ autoLaunchDescription }}</label>
|
||||
</div>
|
||||
{{ /isAutoLaunchSupported }}
|
||||
<hr>
|
||||
<div class='calling-setting'>
|
||||
<h3>{{ calling }}</h3>
|
||||
<div class='always-relay-calls-setting'>
|
||||
<input type='checkbox' name='always-relay-calls' id='always-relay-calls' />
|
||||
<label for='always-relay-calls'>{{ alwaysRelayCallsDescription }}</label>
|
||||
<p>
|
||||
<div class='detail'>
|
||||
{{ alwaysRelayCallsDetail }}
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div class='call-ringtone-notification-setting'>
|
||||
<input type='checkbox' name='call-ringtone-notification' id='call-ringtone-notification'/>
|
||||
<label for='call-ringtone-notification'>{{ callRingtoneNotificationDescription }}</label>
|
||||
</div>
|
||||
<div class='call-system-notification-setting'>
|
||||
<input type='checkbox' name='call-system-notification' id='call-system-notification'/>
|
||||
<label for='call-system-notification'>{{ callSystemNotificationDescription }}</label>
|
||||
</div>
|
||||
<div class='incoming-call-notification-setting'>
|
||||
<input type='checkbox' name='incoming-call-notification' id='incoming-call-notification'/>
|
||||
<label for='incoming-call-notification'>{{ incomingCallNotificationDescription }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='permissions-setting'>
|
||||
<h3>{{ permissions }}</h3>
|
||||
<div class='media-permissions'>
|
||||
<input type='checkbox' name='media-permissions' id='media-permissions' />
|
||||
<label for='media-permissions'>{{ mediaPermissionsDescription }}</label>
|
||||
</div>
|
||||
<div class='media-camera-permissions'>
|
||||
<input type='checkbox' name='media-camera-permissions' id='media-camera-permissions' />
|
||||
<label for='media-camera-permissions'>{{ mediaCameraPermissionsDescription }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class='sync-setting'></div>
|
||||
<hr>
|
||||
<div class='disappearing-messages-setting'>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='clear-data-settings'>
|
||||
<h3>{{ clearDataHeader }}</h3>
|
||||
<div>
|
||||
<button class='grey destructive clear-data'>{{ clearDataButton }}</button>
|
||||
<p>{{ clearDataExplanation }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/javascript" src="js/components.js"></script>
|
||||
<script type="text/javascript" src="ts/backboneJquery.js"></script>
|
||||
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>
|
||||
<script type="text/javascript" src="js/views/settings_view.js"></script>
|
||||
<script type="text/javascript" src="js/settings_start.js"></script>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script
|
||||
type="application/javascript"
|
||||
src="ts/windows/settings/init.js"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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 pMap = require('p-map');
|
||||
const client = require('@signalapp/signal-client');
|
||||
|
||||
// It is important to call this as early as possible
|
||||
require('../ts/windows/context');
|
||||
|
||||
const { deriveStickerPackKey } = require('../ts/Crypto');
|
||||
const { SignalService: Proto } = require('../ts/protobuf');
|
||||
const {
|
||||
|
@ -17,20 +21,16 @@ const {
|
|||
setEnvironment,
|
||||
parseEnvironment,
|
||||
} = require('../ts/environment');
|
||||
const { makeGetter } = require('../preload_utils');
|
||||
const { createSetting } = require('../ts/util/preload');
|
||||
|
||||
const { dialog } = remote;
|
||||
|
||||
const { Context: SignalContext } = require('../ts/context');
|
||||
|
||||
const STICKER_SIZE = 512;
|
||||
const MIN_STICKER_DIMENSION = 10;
|
||||
const MAX_STICKER_DIMENSION = STICKER_SIZE;
|
||||
const MAX_WEBP_STICKER_BYTE_LENGTH = 100 * 1024;
|
||||
const MAX_ANIMATED_STICKER_BYTE_LENGTH = 300 * 1024;
|
||||
|
||||
window.SignalContext = new SignalContext(ipc);
|
||||
|
||||
setEnvironment(parseEnvironment(config.environment));
|
||||
|
||||
window.sqlInitializer = require('../ts/sql/initialize');
|
||||
|
@ -274,10 +274,10 @@ async function encrypt(data, key, iv) {
|
|||
return ciphertext;
|
||||
}
|
||||
|
||||
const getThemeSetting = makeGetter('theme-setting');
|
||||
const getThemeSetting = createSetting('theme-setting');
|
||||
|
||||
async function resolveTheme() {
|
||||
const theme = (await getThemeSetting()) || 'system';
|
||||
const theme = (await getThemeSetting.getValue()) || 'system';
|
||||
if (process.platform === 'darwin' && theme === 'system') {
|
||||
const { theme: nativeTheme } = window.SignalContext.nativeThemeListener;
|
||||
return nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
|
||||
|
|
|
@ -8755,17 +8755,6 @@ button.module-image__border-overlay:focus {
|
|||
);
|
||||
}
|
||||
}
|
||||
.module-avatar-popup__item__icon-colors {
|
||||
@include light-theme {
|
||||
@include color-svg(
|
||||
'../images/icons/v2/color-outline-24.svg',
|
||||
$color-gray-75
|
||||
);
|
||||
}
|
||||
@include dark-theme {
|
||||
@include color-svg('../images/icons/v2/color-solid-24.svg', $color-gray-15);
|
||||
}
|
||||
}
|
||||
.module-avatar-popup__item__icon-archive {
|
||||
@include light-theme {
|
||||
@include color-svg(
|
||||
|
|
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/CallingSelectPresentingSourcesModal.scss';
|
||||
@import './components/ChatColorPicker.scss';
|
||||
@import './components/Checkbox.scss';
|
||||
@import './components/CompositionArea.scss';
|
||||
@import './components/ContactName.scss';
|
||||
@import './components/ContactPill.scss';
|
||||
|
@ -65,6 +66,7 @@
|
|||
@import './components/MessageAudio.scss';
|
||||
@import './components/MessageDetail.scss';
|
||||
@import './components/Modal.scss';
|
||||
@import './components/Preferences.scss';
|
||||
@import './components/ProfileEditor.scss';
|
||||
@import './components/SafetyNumberChangeDialog.scss';
|
||||
@import './components/SafetyNumberViewer.scss';
|
||||
|
|
260
ts/background.ts
260
ts/background.ts
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { webFrame } from 'electron';
|
||||
import { isNumber, noop } from 'lodash';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { render, unstable_batchedUpdates as batchedUpdates } from 'react-dom';
|
||||
|
@ -64,7 +65,6 @@ import {
|
|||
EnvelopeEvent,
|
||||
} from './textsecure/messageReceiverEvents';
|
||||
import type { WebAPIType } from './textsecure/WebAPI';
|
||||
import * as universalExpireTimer from './util/universalExpireTimer';
|
||||
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
||||
import { getSendOptions } from './util/getSendOptions';
|
||||
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
|
||||
|
@ -88,13 +88,11 @@ import {
|
|||
SendStatus,
|
||||
} from './messages/MessageSendState';
|
||||
import * as AttachmentDownloads from './messageModifiers/AttachmentDownloads';
|
||||
import {
|
||||
SystemTraySetting,
|
||||
parseSystemTraySetting,
|
||||
} from './types/SystemTraySetting';
|
||||
import * as Stickers from './types/Stickers';
|
||||
import { SignalService as Proto } from './protobuf';
|
||||
import { onRetryRequest, onDecryptionError } from './util/handleRetry';
|
||||
import { themeChanged } from './shims/themeChanged';
|
||||
import { createIPCEvents } from './util/createIPCEvents';
|
||||
|
||||
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
||||
|
||||
|
@ -582,7 +580,11 @@ export async function startApp(): Promise<void> {
|
|||
window.log.info('Storage fetch');
|
||||
window.storage.fetch();
|
||||
|
||||
function mapOldThemeToNew(theme: Readonly<unknown>) {
|
||||
function mapOldThemeToNew(
|
||||
theme: Readonly<
|
||||
'system' | 'light' | 'dark' | 'android' | 'ios' | 'android-dark'
|
||||
>
|
||||
): 'system' | 'light' | 'dark' {
|
||||
switch (theme) {
|
||||
case 'dark':
|
||||
case 'light':
|
||||
|
@ -611,129 +613,7 @@ export async function startApp(): Promise<void> {
|
|||
cleanupSessionResets();
|
||||
|
||||
// These make key operations available to IPC handlers created in preload.js
|
||||
window.Events = {
|
||||
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
||||
|
||||
getThemeSetting: (): 'light' | 'dark' | 'system' =>
|
||||
window.storage.get(
|
||||
'theme-setting',
|
||||
window.platform === 'darwin' ? 'system' : 'light'
|
||||
),
|
||||
setThemeSetting: (value: 'light' | 'dark' | 'system') => {
|
||||
window.storage.put('theme-setting', value);
|
||||
onChangeTheme();
|
||||
},
|
||||
getHideMenuBar: () => window.storage.get('hide-menu-bar'),
|
||||
setHideMenuBar: (value: boolean) => {
|
||||
window.storage.put('hide-menu-bar', value);
|
||||
window.setAutoHideMenuBar(value);
|
||||
window.setMenuBarVisibility(!value);
|
||||
},
|
||||
getSystemTraySetting: (): SystemTraySetting =>
|
||||
parseSystemTraySetting(window.storage.get('system-tray-setting')),
|
||||
setSystemTraySetting: (value: Readonly<SystemTraySetting>) => {
|
||||
window.storage.put('system-tray-setting', value);
|
||||
window.updateSystemTraySetting(value);
|
||||
},
|
||||
|
||||
getNotificationSetting: () =>
|
||||
window.storage.get('notification-setting', 'message'),
|
||||
setNotificationSetting: (value: 'message' | 'name' | 'count' | 'off') =>
|
||||
window.storage.put('notification-setting', value),
|
||||
getNotificationDrawAttention: () =>
|
||||
window.storage.get('notification-draw-attention', true),
|
||||
setNotificationDrawAttention: (value: boolean) =>
|
||||
window.storage.put('notification-draw-attention', value),
|
||||
getAudioNotification: () => window.storage.get('audio-notification'),
|
||||
setAudioNotification: (value: boolean) =>
|
||||
window.storage.put('audio-notification', value),
|
||||
getCountMutedConversations: () =>
|
||||
window.storage.get('badge-count-muted-conversations', false),
|
||||
setCountMutedConversations: (value: boolean) => {
|
||||
window.storage.put('badge-count-muted-conversations', value);
|
||||
window.Whisper.events.trigger('updateUnreadCount');
|
||||
},
|
||||
getCallRingtoneNotification: () =>
|
||||
window.storage.get('call-ringtone-notification', true),
|
||||
setCallRingtoneNotification: (value: boolean) =>
|
||||
window.storage.put('call-ringtone-notification', value),
|
||||
getCallSystemNotification: () =>
|
||||
window.storage.get('call-system-notification', true),
|
||||
setCallSystemNotification: (value: boolean) =>
|
||||
window.storage.put('call-system-notification', value),
|
||||
getIncomingCallNotification: () =>
|
||||
window.storage.get('incoming-call-notification', true),
|
||||
setIncomingCallNotification: (value: boolean) =>
|
||||
window.storage.put('incoming-call-notification', value),
|
||||
|
||||
getSpellCheck: () => window.storage.get('spell-check', true),
|
||||
setSpellCheck: (value: boolean) => {
|
||||
window.storage.put('spell-check', value);
|
||||
},
|
||||
|
||||
getAlwaysRelayCalls: () => window.storage.get('always-relay-calls'),
|
||||
setAlwaysRelayCalls: (value: boolean) =>
|
||||
window.storage.put('always-relay-calls', value),
|
||||
|
||||
getAutoLaunch: () => window.getAutoLaunch(),
|
||||
setAutoLaunch: (value: boolean) => window.setAutoLaunch(value),
|
||||
|
||||
isPrimary: () => window.textsecure.storage.user.getDeviceId() === 1,
|
||||
getSyncRequest: () =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const FIVE_MINUTES = 5 * 60 * 60 * 1000;
|
||||
const syncRequest = window.getSyncRequest(FIVE_MINUTES);
|
||||
syncRequest.addEventListener('success', () => resolve());
|
||||
syncRequest.addEventListener('timeout', () =>
|
||||
reject(new Error('timeout'))
|
||||
);
|
||||
}),
|
||||
getLastSyncTime: () => window.storage.get('synced_at'),
|
||||
setLastSyncTime: (value: number) =>
|
||||
window.storage.put('synced_at', value),
|
||||
getUniversalExpireTimer: (): number | undefined => {
|
||||
return universalExpireTimer.get();
|
||||
},
|
||||
setUniversalExpireTimer: async (
|
||||
newValue: number | undefined
|
||||
): Promise<void> => {
|
||||
await universalExpireTimer.set(newValue);
|
||||
|
||||
// Update account in Storage Service
|
||||
const conversationId = window.ConversationController.getOurConversationIdOrThrow();
|
||||
const account = window.ConversationController.get(conversationId);
|
||||
assert(account, "Account wasn't found");
|
||||
|
||||
account.captureChange('universalExpireTimer');
|
||||
|
||||
// Add a notification to the currently open conversation
|
||||
const state = window.reduxStore.getState();
|
||||
const selectedId = state.conversations.selectedConversationId;
|
||||
if (selectedId) {
|
||||
const conversation = window.ConversationController.get(selectedId);
|
||||
assert(conversation, "Conversation wasn't found");
|
||||
|
||||
await conversation.updateLastMessage();
|
||||
}
|
||||
},
|
||||
|
||||
addDarkOverlay: () => {
|
||||
if ($('.dark-overlay').length) {
|
||||
return;
|
||||
}
|
||||
$(document.body).prepend('<div class="dark-overlay"></div>');
|
||||
$('.dark-overlay').on('click', () => $('.dark-overlay').remove());
|
||||
},
|
||||
removeDarkOverlay: () => $('.dark-overlay').remove(),
|
||||
showKeyboardShortcuts: () => window.showKeyboardShortcuts(),
|
||||
|
||||
deleteAllData: async () => {
|
||||
await window.sqlInitializer.goBackToMainProcess();
|
||||
|
||||
const clearDataView = new window.Whisper.ClearDataView().render();
|
||||
$('body').append(clearDataView.el);
|
||||
},
|
||||
|
||||
window.Events = createIPCEvents({
|
||||
shutdown: async () => {
|
||||
window.log.info('background/shutdown');
|
||||
// Stop background processing
|
||||
|
@ -763,112 +643,9 @@ export async function startApp(): Promise<void> {
|
|||
// Shut down the data interface cleanly
|
||||
await window.Signal.Data.shutdown();
|
||||
},
|
||||
});
|
||||
|
||||
showStickerPack: (packId: string, key: string) => {
|
||||
// We can get these events even if the user has never linked this instance.
|
||||
if (!window.Signal.Util.Registration.everDone()) {
|
||||
window.log.warn('showStickerPack: Not registered, returning early');
|
||||
return;
|
||||
}
|
||||
if (window.isShowingModal) {
|
||||
window.log.warn(
|
||||
'showStickerPack: Already showing modal, returning early'
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.isShowingModal = true;
|
||||
|
||||
// Kick off the download
|
||||
Stickers.downloadEphemeralPack(packId, key);
|
||||
|
||||
const props = {
|
||||
packId,
|
||||
onClose: async () => {
|
||||
window.isShowingModal = false;
|
||||
stickerPreviewModalView.remove();
|
||||
await Stickers.removeEphemeralPack(packId);
|
||||
},
|
||||
};
|
||||
|
||||
const stickerPreviewModalView = new window.Whisper.ReactWrapperView({
|
||||
className: 'sticker-preview-modal-wrapper',
|
||||
JSX: window.Signal.State.Roots.createStickerPreviewModal(
|
||||
window.reduxStore,
|
||||
props
|
||||
),
|
||||
});
|
||||
} catch (error) {
|
||||
window.isShowingModal = false;
|
||||
window.log.error(
|
||||
'showStickerPack: Ran into an error!',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
const errorView = new window.Whisper.ReactWrapperView({
|
||||
className: 'error-modal-wrapper',
|
||||
Component: window.Signal.Components.ErrorModal,
|
||||
props: {
|
||||
onClose: () => {
|
||||
errorView.remove();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
showGroupViaLink: async (hash: string) => {
|
||||
// We can get these events even if the user has never linked this instance.
|
||||
if (!window.Signal.Util.Registration.everDone()) {
|
||||
window.log.warn('showGroupViaLink: Not registered, returning early');
|
||||
return;
|
||||
}
|
||||
if (window.isShowingModal) {
|
||||
window.log.warn(
|
||||
'showGroupViaLink: Already showing modal, returning early'
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await window.Signal.Groups.joinViaLink(hash);
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'showGroupViaLink: Ran into an error!',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
const errorView = new window.Whisper.ReactWrapperView({
|
||||
className: 'error-modal-wrapper',
|
||||
Component: window.Signal.Components.ErrorModal,
|
||||
props: {
|
||||
title: window.i18n('GroupV2--join--general-join-failure--title'),
|
||||
description: window.i18n('GroupV2--join--general-join-failure'),
|
||||
onClose: () => {
|
||||
errorView.remove();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
window.isShowingModal = false;
|
||||
},
|
||||
|
||||
unknownSignalLink: () => {
|
||||
window.log.warn('unknownSignalLink: Showing error dialog');
|
||||
const errorView = new window.Whisper.ReactWrapperView({
|
||||
className: 'error-modal-wrapper',
|
||||
Component: window.Signal.Components.ErrorModal,
|
||||
props: {
|
||||
description: window.i18n('unknown-sgnl-link'),
|
||||
onClose: () => {
|
||||
errorView.remove();
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
installStickerPack: async (packId: string, key: string) => {
|
||||
Stickers.downloadStickerPack(packId, key, {
|
||||
finalStatus: 'installed',
|
||||
});
|
||||
},
|
||||
};
|
||||
webFrame.setZoomFactor(window.Events.getZoomFactor());
|
||||
|
||||
// How long since we were last running?
|
||||
const lastHeartbeat = window.storage.get('lastHeartbeat', 0);
|
||||
|
@ -2373,7 +2150,7 @@ export async function startApp(): Promise<void> {
|
|||
'theme-setting',
|
||||
await window.Events.getThemeSetting()
|
||||
);
|
||||
onChangeTheme();
|
||||
themeChanged();
|
||||
}
|
||||
const syncRequest = window.getSyncRequest();
|
||||
window.Whisper.events.trigger('contactsync:begin');
|
||||
|
@ -2457,18 +2234,7 @@ export async function startApp(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function onChangeTheme() {
|
||||
if (window.reduxActions && window.reduxActions.user) {
|
||||
const theme = window.Events.getThemeSetting();
|
||||
window.reduxActions.user.userChanged({
|
||||
theme: theme === 'system' ? window.systemTheme : theme,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.SignalContext.nativeThemeListener.subscribe(() => {
|
||||
onChangeTheme();
|
||||
});
|
||||
window.SignalContext.nativeThemeListener.subscribe(themeChanged);
|
||||
|
||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
name: text('name', overrideProps.name || ''),
|
||||
noteToSelf: boolean('noteToSelf', overrideProps.noteToSelf || false),
|
||||
onEditProfile: action('onEditProfile'),
|
||||
onSetChatColor: action('onSetChatColor'),
|
||||
onViewArchive: action('onViewArchive'),
|
||||
onViewPreferences: action('onViewPreferences'),
|
||||
phoneNumber: text('phoneNumber', overrideProps.phoneNumber || ''),
|
||||
|
|
|
@ -13,7 +13,6 @@ export type Props = {
|
|||
readonly i18n: LocalizerType;
|
||||
|
||||
onEditProfile: () => unknown;
|
||||
onSetChatColor: () => unknown;
|
||||
onViewPreferences: () => unknown;
|
||||
onViewArchive: () => unknown;
|
||||
|
||||
|
@ -30,7 +29,6 @@ export const AvatarPopup = (props: Props): JSX.Element => {
|
|||
phoneNumber,
|
||||
title,
|
||||
onEditProfile,
|
||||
onSetChatColor,
|
||||
onViewPreferences,
|
||||
onViewArchive,
|
||||
style,
|
||||
|
@ -79,21 +77,6 @@ export const AvatarPopup = (props: Props): JSX.Element => {
|
|||
{i18n('mainMenuSettings')}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="module-avatar-popup__item"
|
||||
onClick={onSetChatColor}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'module-avatar-popup__item__icon',
|
||||
'module-avatar-popup__item__icon-colors'
|
||||
)}
|
||||
/>
|
||||
<div className="module-avatar-popup__item__text">
|
||||
{i18n('avatarMenuChatColors')}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="module-avatar-popup__item"
|
||||
|
|
|
@ -26,7 +26,7 @@ const createProps = (): PropsType => ({
|
|||
addCustomColor: action('addCustomColor'),
|
||||
colorSelected: action('colorSelected'),
|
||||
editCustomColor: action('editCustomColor'),
|
||||
getConversationsWithCustomColor: (_: string) => [],
|
||||
getConversationsWithCustomColor: (_: string) => Promise.resolve([]),
|
||||
i18n,
|
||||
removeCustomColor: action('removeCustomColor'),
|
||||
removeCustomColorOnConversations: action('removeCustomColorOnConversations'),
|
||||
|
|
|
@ -26,7 +26,9 @@ type CustomColorDataType = {
|
|||
export type PropsDataType = {
|
||||
conversationId?: string;
|
||||
customColors?: Record<string, CustomColorType>;
|
||||
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||
getConversationsWithCustomColor: (
|
||||
colorId: string
|
||||
) => Promise<Array<ConversationType>>;
|
||||
i18n: LocalizerType;
|
||||
isGlobal?: boolean;
|
||||
selectedColor?: ConversationColorType;
|
||||
|
@ -34,10 +36,7 @@ export type PropsDataType = {
|
|||
};
|
||||
|
||||
type PropsActionType = {
|
||||
addCustomColor: (
|
||||
color: CustomColorType,
|
||||
nextAction: (uuid: string) => unknown
|
||||
) => unknown;
|
||||
addCustomColor: (color: CustomColorType, conversationId?: string) => unknown;
|
||||
colorSelected: (payload: {
|
||||
conversationId: string;
|
||||
conversationColor?: ConversationColorType;
|
||||
|
@ -100,20 +99,10 @@ export const ChatColorPicker = ({
|
|||
}
|
||||
};
|
||||
|
||||
const onColorAdded = (value: CustomColorType) => {
|
||||
return (id: string) => {
|
||||
onSelectColor('custom', {
|
||||
id,
|
||||
value,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const renderCustomColorEditorWrapper = () => (
|
||||
<CustomColorEditorWrapper
|
||||
customColorToEdit={customColorToEdit}
|
||||
i18n={i18n}
|
||||
isGlobal={isGlobal}
|
||||
onClose={() => setCustomColorToEdit(undefined)}
|
||||
onSave={(color: CustomColorType) => {
|
||||
if (customColorToEdit?.id) {
|
||||
|
@ -123,16 +112,12 @@ export const ChatColorPicker = ({
|
|||
value: color,
|
||||
});
|
||||
} else {
|
||||
addCustomColor(color, onColorAdded(color));
|
||||
addCustomColor(color, conversationId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (isGlobal && customColorToEdit) {
|
||||
return renderCustomColorEditorWrapper();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ChatColorPicker__container">
|
||||
{customColorToEdit ? renderCustomColorEditorWrapper() : null}
|
||||
|
@ -228,7 +213,7 @@ export const ChatColorPicker = ({
|
|||
removeCustomColorOnConversations(colorId);
|
||||
}}
|
||||
onDupe={() => {
|
||||
addCustomColor(colorValues, onColorAdded(colorValues));
|
||||
addCustomColor(colorValues, conversationId);
|
||||
}}
|
||||
onEdit={() => {
|
||||
setCustomColorToEdit({ id: colorId, value: colorValues });
|
||||
|
@ -279,7 +264,9 @@ export const ChatColorPicker = ({
|
|||
type CustomColorBubblePropsType = {
|
||||
color: CustomColorType;
|
||||
colorId: string;
|
||||
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||
getConversationsWithCustomColor: (
|
||||
colorId: string
|
||||
) => Promise<Array<ConversationType>>;
|
||||
i18n: LocalizerType;
|
||||
isSelected: boolean;
|
||||
onDelete: () => unknown;
|
||||
|
@ -398,11 +385,13 @@ const CustomColorBubble = ({
|
|||
attributes={{
|
||||
className: 'ChatColorPicker__context--delete',
|
||||
}}
|
||||
onClick={(event: MouseEvent) => {
|
||||
onClick={async (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const conversations = getConversationsWithCustomColor(colorId);
|
||||
const conversations = await getConversationsWithCustomColor(
|
||||
colorId
|
||||
);
|
||||
if (!conversations.length) {
|
||||
onDelete();
|
||||
} else {
|
||||
|
@ -420,7 +409,6 @@ const CustomColorBubble = ({
|
|||
type CustomColorEditorWrapperPropsType = {
|
||||
customColorToEdit?: CustomColorDataType;
|
||||
i18n: LocalizerType;
|
||||
isGlobal: boolean;
|
||||
onClose: () => unknown;
|
||||
onSave: (color: CustomColorType) => unknown;
|
||||
};
|
||||
|
@ -428,7 +416,6 @@ type CustomColorEditorWrapperPropsType = {
|
|||
const CustomColorEditorWrapper = ({
|
||||
customColorToEdit,
|
||||
i18n,
|
||||
isGlobal,
|
||||
onClose,
|
||||
onSave,
|
||||
}: CustomColorEditorWrapperPropsType): JSX.Element => {
|
||||
|
@ -441,20 +428,16 @@ const CustomColorEditorWrapper = ({
|
|||
/>
|
||||
);
|
||||
|
||||
if (!isGlobal) {
|
||||
return (
|
||||
<Modal
|
||||
hasXButton
|
||||
i18n={i18n}
|
||||
moduleClassName="ChatColorPicker__modal"
|
||||
noMouseClose
|
||||
onClose={onClose}
|
||||
title={i18n('CustomColorEditor__title')}
|
||||
>
|
||||
{editor}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return editor;
|
||||
return (
|
||||
<Modal
|
||||
hasXButton
|
||||
i18n={i18n}
|
||||
moduleClassName="ChatColorPicker__modal"
|
||||
noMouseClose
|
||||
onClose={onClose}
|
||||
title={i18n('CustomColorEditor__title')}
|
||||
>
|
||||
{editor}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
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
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { Modal } from './Modal';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
|
||||
// ChatColorPicker
|
||||
isChatColorEditorVisible: boolean;
|
||||
renderChatColorPicker: () => JSX.Element;
|
||||
toggleChatColorEditor: () => unknown;
|
||||
|
||||
// ProfileEditor
|
||||
isProfileEditorVisible: boolean;
|
||||
renderProfileEditor: () => JSX.Element;
|
||||
};
|
||||
|
||||
export const GlobalModalContainer = ({
|
||||
i18n,
|
||||
|
||||
// ChatColorPicker
|
||||
isChatColorEditorVisible,
|
||||
renderChatColorPicker,
|
||||
toggleChatColorEditor,
|
||||
|
||||
// ProfileEditor
|
||||
isProfileEditorVisible,
|
||||
renderProfileEditor,
|
||||
}: PropsType): JSX.Element | null => {
|
||||
if (isChatColorEditorVisible) {
|
||||
return (
|
||||
<Modal
|
||||
hasXButton
|
||||
i18n={i18n}
|
||||
moduleClassName="ChatColorPicker__modal"
|
||||
noMouseClose
|
||||
onClose={toggleChatColorEditor}
|
||||
title={i18n('ChatColorPicker__global-chat-color')}
|
||||
>
|
||||
{renderChatColorPicker()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
if (isProfileEditorVisible) {
|
||||
return renderProfileEditor();
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
|
||||
showArchivedConversations: action('showArchivedConversations'),
|
||||
startComposing: action('startComposing'),
|
||||
toggleChatColorEditor: action('toggleChatColorEditor'),
|
||||
toggleProfileEditor: action('toggleProfileEditor'),
|
||||
});
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@ export type PropsType = {
|
|||
|
||||
showArchivedConversations: () => void;
|
||||
startComposing: () => void;
|
||||
toggleChatColorEditor: () => void;
|
||||
toggleProfileEditor: () => void;
|
||||
};
|
||||
|
||||
|
@ -353,7 +352,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
searchConversationName,
|
||||
searchTerm,
|
||||
showArchivedConversations,
|
||||
toggleChatColorEditor,
|
||||
toggleProfileEditor,
|
||||
} = this.props;
|
||||
const { showingAvatarPopup, popperRoot } = this.state;
|
||||
|
@ -416,10 +414,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
toggleProfileEditor();
|
||||
this.hideAvatarPopup();
|
||||
}}
|
||||
onSetChatColor={() => {
|
||||
toggleChatColorEditor();
|
||||
this.hideAvatarPopup();
|
||||
}}
|
||||
onViewPreferences={() => {
|
||||
showSettings();
|
||||
this.hideAvatarPopup();
|
||||
|
|
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<{
|
||||
disabled?: boolean;
|
||||
moduleClassName?: string;
|
||||
name?: string;
|
||||
options: ReadonlyArray<Option>;
|
||||
onChange(value: string): void;
|
||||
value: string | number;
|
||||
value?: string | number;
|
||||
}>;
|
||||
|
||||
export function Select(props: PropsType): JSX.Element {
|
||||
const { moduleClassName, value, options, onChange } = props;
|
||||
|
||||
export function Select({
|
||||
disabled,
|
||||
moduleClassName,
|
||||
name,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
}: PropsType): JSX.Element {
|
||||
const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
onChange(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(['module-select', moduleClassName])}>
|
||||
<select value={value} onChange={onSelectChange}>
|
||||
{options.map(({ disabled, text, value: optionValue }) => {
|
||||
return (
|
||||
<option
|
||||
disabled={disabled}
|
||||
value={optionValue}
|
||||
key={optionValue}
|
||||
aria-label={text}
|
||||
>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
<select
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onSelectChange}
|
||||
>
|
||||
{options.map(
|
||||
({ disabled: optionDisabled, text, value: optionValue }) => {
|
||||
return (
|
||||
<option
|
||||
disabled={optionDisabled}
|
||||
value={optionValue}
|
||||
key={optionValue}
|
||||
aria-label={text}
|
||||
>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from './shared';
|
||||
import * as log from './log';
|
||||
import { reallyJsonStringify } from '../util/reallyJsonStringify';
|
||||
import { Environment, getEnvironment } from '../environment';
|
||||
|
||||
// To make it easier to visually scan logs, we make all levels the same length
|
||||
const levelFromName = pino().levels.values;
|
||||
|
@ -81,7 +82,7 @@ const getHeader = ({
|
|||
Time: Date.now(),
|
||||
'User agent': window.navigator.userAgent,
|
||||
'Node version': window.getNodeVersion(),
|
||||
Environment: window.getEnvironment(),
|
||||
Environment: getEnvironment(),
|
||||
'App version': window.getVersion(),
|
||||
}),
|
||||
headerSection('User info', user),
|
||||
|
@ -179,11 +180,8 @@ const publish = uploadDebugLogs;
|
|||
|
||||
// A modern logging interface for the browser
|
||||
|
||||
const env = window.getEnvironment();
|
||||
const IS_PRODUCTION = env === 'production';
|
||||
|
||||
function logAtLevel(level: LogLevel, ...args: ReadonlyArray<unknown>): void {
|
||||
if (!IS_PRODUCTION) {
|
||||
if (getEnvironment() !== Environment.Production) {
|
||||
const prefix = getLogLevelString(level)
|
||||
.toUpperCase()
|
||||
.padEnd(levelMaxLength, ' ');
|
||||
|
|
218
ts/main/settingsChannel.ts
Normal file
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,
|
||||
ConversationColorType,
|
||||
CustomColorType,
|
||||
DEFAULT_CONVERSATION_COLOR,
|
||||
} from '../types/Colors';
|
||||
import { MessageModel } from './messages';
|
||||
import { isMuted } from '../util/isMuted';
|
||||
|
@ -950,7 +949,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
bumpTyping(): void {
|
||||
// We don't send typing messages if the setting is disabled
|
||||
if (!window.storage.get('typingIndicators')) {
|
||||
if (!window.Events.getTypingIndicatorSetting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4680,10 +4679,7 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
getConversationColor(): ConversationColorType {
|
||||
const defaultConversationColor = window.storage.get(
|
||||
'defaultConversationColor',
|
||||
DEFAULT_CONVERSATION_COLOR
|
||||
);
|
||||
const defaultConversationColor = window.Events.getDefaultConversationColor();
|
||||
|
||||
return this.get('conversationColor') || defaultConversationColor.color;
|
||||
}
|
||||
|
@ -4692,10 +4688,7 @@ export class ConversationModel extends window.Backbone
|
|||
customColor?: CustomColorType;
|
||||
customColorId?: string;
|
||||
} {
|
||||
const defaultConversationColor = window.storage.get(
|
||||
'defaultConversationColor',
|
||||
DEFAULT_CONVERSATION_COLOR
|
||||
);
|
||||
const defaultConversationColor = window.Events.getDefaultConversationColor();
|
||||
|
||||
if (this.getConversationColor() !== 'custom') {
|
||||
return {
|
||||
|
|
|
@ -38,11 +38,12 @@ import {
|
|||
} from '../state/ducks/calling';
|
||||
import { getConversationCallMode } from '../state/ducks/conversations';
|
||||
import {
|
||||
CallMode,
|
||||
AudioDevice,
|
||||
MediaDeviceSettings,
|
||||
AvailableIODevicesType,
|
||||
CallMode,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
MediaDeviceSettings,
|
||||
PresentableSource,
|
||||
PresentedSource,
|
||||
} from '../types/Calling';
|
||||
|
@ -1254,11 +1255,26 @@ export class CallingClass {
|
|||
}
|
||||
}
|
||||
|
||||
async getMediaDeviceSettings(): Promise<MediaDeviceSettings> {
|
||||
async getAvailableIODevices(): Promise<AvailableIODevicesType> {
|
||||
const availableCameras = await this.videoCapturer.enumerateDevices();
|
||||
const availableMicrophones = RingRTC.getAudioInputs();
|
||||
const preferredMicrophone = window.storage.get(
|
||||
'preferred-audio-input-device'
|
||||
);
|
||||
const availableSpeakers = RingRTC.getAudioOutputs();
|
||||
|
||||
return {
|
||||
availableCameras,
|
||||
availableMicrophones,
|
||||
availableSpeakers,
|
||||
};
|
||||
}
|
||||
|
||||
async getMediaDeviceSettings(): Promise<MediaDeviceSettings> {
|
||||
const {
|
||||
availableCameras,
|
||||
availableMicrophones,
|
||||
availableSpeakers,
|
||||
} = await this.getAvailableIODevices();
|
||||
|
||||
const preferredMicrophone = window.Events.getPreferredAudioInputDevice();
|
||||
const selectedMicIndex = this.findBestMatchingDeviceIndex(
|
||||
availableMicrophones,
|
||||
preferredMicrophone
|
||||
|
@ -1268,10 +1284,7 @@ export class CallingClass {
|
|||
? availableMicrophones[selectedMicIndex]
|
||||
: undefined;
|
||||
|
||||
const availableSpeakers = RingRTC.getAudioOutputs();
|
||||
const preferredSpeaker = window.storage.get(
|
||||
'preferred-audio-output-device'
|
||||
);
|
||||
const preferredSpeaker = window.Events.getPreferredAudioOutputDevice();
|
||||
const selectedSpeakerIndex = this.findBestMatchingDeviceIndex(
|
||||
availableSpeakers,
|
||||
preferredSpeaker
|
||||
|
@ -1281,8 +1294,7 @@ export class CallingClass {
|
|||
? availableSpeakers[selectedSpeakerIndex]
|
||||
: undefined;
|
||||
|
||||
const availableCameras = await this.videoCapturer.enumerateDevices();
|
||||
const preferredCamera = window.storage.get('preferred-video-input-device');
|
||||
const preferredCamera = window.Events.getPreferredVideoInputDevice();
|
||||
const selectedCamera = this.findBestMatchingCamera(
|
||||
availableCameras,
|
||||
preferredCamera
|
||||
|
@ -1343,13 +1355,13 @@ export class CallingClass {
|
|||
|
||||
setPreferredMicrophone(device: AudioDevice): void {
|
||||
window.log.info('MediaDevice: setPreferredMicrophone', device);
|
||||
window.storage.put('preferred-audio-input-device', device);
|
||||
window.Events.setPreferredAudioInputDevice(device);
|
||||
RingRTC.setAudioInput(device.index);
|
||||
}
|
||||
|
||||
setPreferredSpeaker(device: AudioDevice): void {
|
||||
window.log.info('MediaDevice: setPreferredSpeaker', device);
|
||||
window.storage.put('preferred-audio-output-device', device);
|
||||
window.Events.setPreferredAudioOutputDevice(device);
|
||||
RingRTC.setAudioOutput(device.index);
|
||||
}
|
||||
|
||||
|
@ -1363,7 +1375,7 @@ export class CallingClass {
|
|||
|
||||
async setPreferredCamera(device: string): Promise<void> {
|
||||
window.log.info('MediaDevice: setPreferredCamera', device);
|
||||
window.storage.put('preferred-video-input-device', device);
|
||||
window.Events.setPreferredVideoInputDevice(device);
|
||||
await this.videoCapturer.setPreferredDevice(device);
|
||||
}
|
||||
|
||||
|
@ -1373,7 +1385,7 @@ export class CallingClass {
|
|||
): Promise<void> {
|
||||
window.log.info('CallingClass.handleCallingMessage()');
|
||||
|
||||
const enableIncomingCalls = await window.getIncomingCallNotification();
|
||||
const enableIncomingCalls = window.Events.getIncomingCallNotification();
|
||||
if (callingMessage.offer && !enableIncomingCalls) {
|
||||
// Drop offers silently if incoming call notifications are disabled.
|
||||
window.log.info('Incoming calls are disabled, ignoring call offer.');
|
||||
|
|
|
@ -170,16 +170,14 @@ export async function toAccountRecord(
|
|||
accountRecord.noteToSelfMarkedUnread = Boolean(
|
||||
conversation.get('markedUnread')
|
||||
);
|
||||
accountRecord.readReceipts = Boolean(
|
||||
window.storage.get('read-receipt-setting')
|
||||
);
|
||||
accountRecord.readReceipts = Boolean(window.Events.getReadReceiptSetting());
|
||||
accountRecord.sealedSenderIndicators = Boolean(
|
||||
window.storage.get('sealedSenderIndicators')
|
||||
);
|
||||
accountRecord.typingIndicators = Boolean(
|
||||
window.storage.get('typingIndicators')
|
||||
window.Events.getTypingIndicatorSetting()
|
||||
);
|
||||
accountRecord.linkPreviews = Boolean(window.storage.get('linkPreviews'));
|
||||
accountRecord.linkPreviews = Boolean(window.Events.getLinkPreviewSetting());
|
||||
|
||||
const primarySendsSms = window.storage.get('primarySendsSms');
|
||||
if (primarySendsSms !== undefined) {
|
||||
|
|
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 { reducer, StateType } from './reducer';
|
||||
import { dispatchItemsMiddleware } from '../shims/dispatchItemsMiddleware';
|
||||
|
||||
declare global {
|
||||
// We want to extend `window`'s properties, so we need an interface.
|
||||
|
@ -49,6 +50,7 @@ const logger = createLogger({
|
|||
const middlewareList = [
|
||||
promise,
|
||||
thunk,
|
||||
dispatchItemsMiddleware,
|
||||
...(env === 'production' ? [] : [logger]),
|
||||
];
|
||||
|
||||
|
|
|
@ -524,7 +524,7 @@ async function showCallNotification(
|
|||
isVideoCall: boolean
|
||||
): Promise<void> {
|
||||
const shouldNotify =
|
||||
!window.isActive() && (await window.getCallSystemNotification());
|
||||
!window.isActive() && window.Events.getCallSystemNotification();
|
||||
if (!shouldNotify) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -336,8 +336,8 @@ export const getConversationCallMode = (
|
|||
|
||||
// Actions
|
||||
|
||||
const COLORS_CHANGED = 'conversations/COLORS_CHANGED';
|
||||
const COLOR_SELECTED = 'conversations/COLOR_SELECTED';
|
||||
export const COLORS_CHANGED = 'conversations/COLORS_CHANGED';
|
||||
export const COLOR_SELECTED = 'conversations/COLOR_SELECTED';
|
||||
const COMPOSE_TOGGLE_EDITING_AVATAR =
|
||||
'conversations/compose/COMPOSE_TOGGLE_EDITING_AVATAR';
|
||||
const COMPOSE_ADD_AVATAR = 'conversations/compose/ADD_AVATAR';
|
||||
|
|
|
@ -4,22 +4,16 @@
|
|||
// State
|
||||
|
||||
export type GlobalModalsStateType = {
|
||||
readonly isChatColorEditorVisible: boolean;
|
||||
readonly isProfileEditorVisible: boolean;
|
||||
readonly profileEditorHasError: boolean;
|
||||
};
|
||||
|
||||
// Actions
|
||||
|
||||
const TOGGLE_CHAT_COLOR_EDITOR = 'globalModals/TOGGLE_CHAT_COLOR_EDITOR';
|
||||
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
|
||||
export const TOGGLE_PROFILE_EDITOR_ERROR =
|
||||
'globalModals/TOGGLE_PROFILE_EDITOR_ERROR';
|
||||
|
||||
type ToggleChatColorEditorActionType = {
|
||||
type: typeof TOGGLE_CHAT_COLOR_EDITOR;
|
||||
};
|
||||
|
||||
type ToggleProfileEditorActionType = {
|
||||
type: typeof TOGGLE_PROFILE_EDITOR;
|
||||
};
|
||||
|
@ -29,22 +23,16 @@ export type ToggleProfileEditorErrorActionType = {
|
|||
};
|
||||
|
||||
export type GlobalModalsActionType =
|
||||
| ToggleChatColorEditorActionType
|
||||
| ToggleProfileEditorActionType
|
||||
| ToggleProfileEditorErrorActionType;
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const actions = {
|
||||
toggleChatColorEditor,
|
||||
toggleProfileEditor,
|
||||
toggleProfileEditorHasError,
|
||||
};
|
||||
|
||||
function toggleChatColorEditor(): ToggleChatColorEditorActionType {
|
||||
return { type: TOGGLE_CHAT_COLOR_EDITOR };
|
||||
}
|
||||
|
||||
function toggleProfileEditor(): ToggleProfileEditorActionType {
|
||||
return { type: TOGGLE_PROFILE_EDITOR };
|
||||
}
|
||||
|
@ -57,7 +45,6 @@ function toggleProfileEditorHasError(): ToggleProfileEditorErrorActionType {
|
|||
|
||||
export function getEmptyState(): GlobalModalsStateType {
|
||||
return {
|
||||
isChatColorEditorVisible: false,
|
||||
isProfileEditorVisible: false,
|
||||
profileEditorHasError: false,
|
||||
};
|
||||
|
@ -67,13 +54,6 @@ export function reducer(
|
|||
state: Readonly<GlobalModalsStateType> = getEmptyState(),
|
||||
action: Readonly<GlobalModalsActionType>
|
||||
): GlobalModalsStateType {
|
||||
if (action.type === TOGGLE_CHAT_COLOR_EDITOR) {
|
||||
return {
|
||||
...state,
|
||||
isChatColorEditorVisible: !state.isChatColorEditorVisible,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === TOGGLE_PROFILE_EDITOR) {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from '../../types/Colors';
|
||||
import { reloadSelectedConversation } from '../../shims/reloadSelectedConversation';
|
||||
import { StorageAccessType } from '../../types/Storage.d';
|
||||
import { actions as conversationActions } from './conversations';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -139,7 +140,7 @@ function getDefaultCustomColorData() {
|
|||
|
||||
function addCustomColor(
|
||||
customColor: CustomColorType,
|
||||
nextAction: (uuid: string) => unknown
|
||||
conversationId?: string
|
||||
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
|
||||
return (dispatch, getState) => {
|
||||
const { customColors = getDefaultCustomColorData() } = getState().items;
|
||||
|
@ -158,7 +159,25 @@ function addCustomColor(
|
|||
};
|
||||
|
||||
dispatch(putItem('customColors', nextCustomColors));
|
||||
nextAction(uuid);
|
||||
|
||||
const customColorData = {
|
||||
id: uuid,
|
||||
value: customColor,
|
||||
};
|
||||
|
||||
if (conversationId) {
|
||||
conversationActions.colorSelected({
|
||||
conversationId,
|
||||
conversationColor: 'custom',
|
||||
customColorData,
|
||||
})(dispatch, getState, null);
|
||||
} else {
|
||||
setGlobalDefaultConversationColor('custom', customColorData)(
|
||||
dispatch,
|
||||
getState,
|
||||
null
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -43,3 +43,9 @@ export const getDefaultConversationColor = createSelector(
|
|||
};
|
||||
} => state.defaultConversationColor ?? DEFAULT_CONVERSATION_COLOR
|
||||
);
|
||||
|
||||
export const getCustomColors = createSelector(
|
||||
getItems,
|
||||
(state: ItemsStateType): Record<string, CustomColorType> | undefined =>
|
||||
state.customColors?.colors
|
||||
);
|
||||
|
|
|
@ -38,9 +38,8 @@ const mapStateToProps = (
|
|||
return {
|
||||
...props,
|
||||
customColors: customColors ? customColors.colors : {},
|
||||
getConversationsWithCustomColor: getConversationsWithCustomColorSelector(
|
||||
state
|
||||
),
|
||||
getConversationsWithCustomColor: (colorId: string) =>
|
||||
Promise.resolve(getConversationsWithCustomColorSelector(state)(colorId)),
|
||||
i18n: getIntl(state),
|
||||
selectedColor: colorValues.conversationColor,
|
||||
selectedCustomColor: {
|
||||
|
|
|
@ -6,8 +6,6 @@ import { connect } from 'react-redux';
|
|||
import { mapDispatchToProps } from '../actions';
|
||||
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
|
||||
import { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { SmartChatColorPicker } from './ChatColorPicker';
|
||||
import { SmartProfileEditorModal } from './ProfileEditorModal';
|
||||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
|
@ -16,10 +14,6 @@ import { SmartProfileEditorModal } from './ProfileEditorModal';
|
|||
const FilteredSmartProfileEditorModal = SmartProfileEditorModal as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
function renderChatColorPicker(): JSX.Element {
|
||||
return <SmartChatColorPicker />;
|
||||
}
|
||||
|
||||
function renderProfileEditor(): JSX.Element {
|
||||
return <FilteredSmartProfileEditorModal />;
|
||||
}
|
||||
|
@ -27,8 +21,6 @@ function renderProfileEditor(): JSX.Element {
|
|||
const mapStateToProps = (state: StateType) => {
|
||||
return {
|
||||
...state.globalModals,
|
||||
i18n: getIntl(state),
|
||||
renderChatColorPicker,
|
||||
renderProfileEditor,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,18 +10,18 @@ import {
|
|||
} from '../../../state/ducks/globalModals';
|
||||
|
||||
describe('both/state/ducks/globalModals', () => {
|
||||
describe('toggleChatColorEditor', () => {
|
||||
const { toggleChatColorEditor } = actions;
|
||||
describe('toggleProfileEditor', () => {
|
||||
const { toggleProfileEditor } = actions;
|
||||
|
||||
it('toggles isChatColorEditorVisible', () => {
|
||||
it('toggles isProfileEditorVisible', () => {
|
||||
const state = getEmptyState();
|
||||
const nextState = reducer(state, toggleChatColorEditor());
|
||||
const nextState = reducer(state, toggleProfileEditor());
|
||||
|
||||
assert.isTrue(nextState.isChatColorEditorVisible);
|
||||
assert.isTrue(nextState.isProfileEditorVisible);
|
||||
|
||||
const nextNextState = reducer(nextState, toggleChatColorEditor());
|
||||
const nextNextState = reducer(nextState, toggleProfileEditor());
|
||||
|
||||
assert.isFalse(nextNextState.isChatColorEditorVisible);
|
||||
assert.isFalse(nextNextState.isProfileEditorVisible);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
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,
|
||||
}
|
||||
|
||||
export type MediaDeviceSettings = {
|
||||
availableMicrophones: Array<AudioDevice>;
|
||||
selectedMicrophone: AudioDevice | undefined;
|
||||
availableSpeakers: Array<AudioDevice>;
|
||||
selectedSpeaker: AudioDevice | undefined;
|
||||
export type AvailableIODevicesType = {
|
||||
availableCameras: Array<MediaDeviceInfo>;
|
||||
availableMicrophones: Array<AudioDevice>;
|
||||
availableSpeakers: Array<AudioDevice>;
|
||||
};
|
||||
|
||||
export type MediaDeviceSettings = AvailableIODevicesType & {
|
||||
selectedMicrophone: AudioDevice | undefined;
|
||||
selectedSpeaker: AudioDevice | undefined;
|
||||
selectedCamera: string | undefined;
|
||||
};
|
||||
|
||||
|
|
11
ts/types/Storage.d.ts
vendored
11
ts/types/Storage.d.ts
vendored
|
@ -24,6 +24,12 @@ export type SerializedCertificateType = {
|
|||
serialized: ArrayBuffer;
|
||||
};
|
||||
|
||||
export type ZoomFactorType = 0.75 | 1 | 1.25 | 1.5 | 2;
|
||||
|
||||
export type ThemeSettingType = 'system' | 'light' | 'dark';
|
||||
|
||||
export type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
|
||||
|
||||
export type StorageAccessType = {
|
||||
'always-relay-calls': boolean;
|
||||
'audio-notification': boolean;
|
||||
|
@ -36,10 +42,10 @@ export type StorageAccessType = {
|
|||
'system-tray-setting': SystemTraySetting;
|
||||
'incoming-call-notification': boolean;
|
||||
'notification-draw-attention': boolean;
|
||||
'notification-setting': 'message' | 'name' | 'count' | 'off';
|
||||
'notification-setting': NotificationSettingType;
|
||||
'read-receipt-setting': boolean;
|
||||
'spell-check': boolean;
|
||||
'theme-setting': 'light' | 'dark' | 'system';
|
||||
'theme-setting': ThemeSettingType;
|
||||
attachmentMigration_isComplete: boolean;
|
||||
attachmentMigration_lastProcessedIndex: number;
|
||||
blocked: Array<string>;
|
||||
|
@ -112,6 +118,7 @@ export type StorageAccessType = {
|
|||
senderCertificate: SerializedCertificateType;
|
||||
senderCertificateNoE164: SerializedCertificateType;
|
||||
paymentAddress: string;
|
||||
zoomFactor: ZoomFactorType;
|
||||
|
||||
// Deprecated
|
||||
senderCertificateWithUuid: never;
|
||||
|
|
|
@ -51,3 +51,5 @@ type InternalAssertProps<
|
|||
};
|
||||
|
||||
export type AssertProps<Result, Value> = InternalAssertProps<Result, Value>;
|
||||
|
||||
export type UnwrapPromise<Value> = Value extends Promise<infer T> ? T : Value;
|
||||
|
|
24
ts/util/awaitObject.ts
Normal file
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
|
||||
async playEndCall(): Promise<void> {
|
||||
const canPlayTone = await window.getCallRingtoneNotification();
|
||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class CallingTones {
|
|||
this.ringtone = undefined;
|
||||
}
|
||||
|
||||
const canPlayTone = await window.getCallRingtoneNotification();
|
||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class CallingTones {
|
|||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
async someonePresenting() {
|
||||
const canPlayTone = await window.getCallRingtoneNotification();
|
||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
}
|
||||
|
|
487
ts/util/createIPCEvents.ts
Normal file
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",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/settings_start.js",
|
||||
"line": "$(document).on('keydown', e => {",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/settings_start.js",
|
||||
"line": "const $body = $(document.body);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-appendTo(",
|
||||
"path": "js/settings_start.js",
|
||||
"line": " window.view.$el.appendTo($body);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/clear_data_view.js",
|
||||
|
@ -767,317 +743,6 @@
|
|||
"updated": "2021-02-26T18:44:56.450Z",
|
||||
"reasonDetail": "Static selector, read-only access"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('input').prop('checked', !!this.value);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('input').prop('checked', Boolean(this.value));",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-06-02T21:51:34.813Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " const value = this.$(e.target).val();",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-06-02T21:51:34.813Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$(`#${this.name}-${this.value}`).attr('checked', 'checked');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-06-02T21:51:34.813Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.notification-settings'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-06-02T21:51:34.813Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.theme-settings'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-06-02T21:51:34.813Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " $(document.body)",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-06-02T21:51:34.813Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('input').prop('checked', Boolean(this.value));",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-06-02T22:20:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.draw-attention-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.audio-notification-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.badge-count-muted-conversations-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.spell-check-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " const $msg = this.$('.spell-check-setting-message');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.menu-bar-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.always-relay-calls-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.call-ringtone-notification-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.call-system-notification-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.incoming-call-notification-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.media-permissions'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.media-camera-permissions'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync-setting').append(syncView.el);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync').text(i18n('syncNow'));",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync').attr('disabled', 'disabled');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.synced_at').hide();",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync_failed').hide();",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync').removeAttr('disabled');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-11T17:24:56.124Z",
|
||||
"reasonDetail": "Static selector argument"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync').text(i18n('syncing'));",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-11T17:24:56.124Z",
|
||||
"reasonDetail": "Static selector argument"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync_failed').show();",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-11T17:24:56.124Z",
|
||||
"reasonDetail": "Static selector argument"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " template: () => $('#settings').html(),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-02-26T18:44:56.450Z",
|
||||
"reasonDetail": "Static selector, read-only access"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " template: () => $('#syncSettings').html(),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-02-26T18:44:56.450Z",
|
||||
"reasonDetail": "Static selector, read-only access"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.auto-launch-setting'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-05-11T20:38:03.542Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " template: () => $('#disappearingMessagesSettings').html(),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-05-27T01:33:06.541Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.disappearing-messages-setting').append(",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-05-27T01:33:06.541Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " el: this.$('.system-tray-setting-container'),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-06-24T23:16:24.537Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-append(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.sync-setting').append(syncView.el);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-21T11:29:29.636Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-append(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " this.$('.disappearing-messages-setting').append(",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-05-27T01:33:06.541Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-html(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " template: () => $('#settings').html(),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-02-26T18:44:56.450Z",
|
||||
"reasonDetail": "Static selector, read-only access"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-html(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " template: () => $('#syncSettings').html(),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-02-26T18:44:56.450Z",
|
||||
"reasonDetail": "Static selector, read-only access"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-html(",
|
||||
"path": "js/views/settings_view.js",
|
||||
"line": " template: () => $('#disappearingMessagesSettings').html(),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-05-27T01:33:06.541Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/standalone_registration_view.js",
|
||||
|
@ -14444,6 +14109,118 @@
|
|||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-03T21:17:38.615Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.js",
|
||||
"line": " if ($('.dark-overlay').length) {",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.js",
|
||||
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.js",
|
||||
"line": " $('.dark-overlay').on('click', () => $('.dark-overlay').remove());",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.js",
|
||||
"line": " }, removeDarkOverlay: () => $('.dark-overlay').remove(), showKeyboardShortcuts: () => window.showKeyboardShortcuts(), deleteAllData: async () => {",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.js",
|
||||
"line": " $('body').append(clearDataView.el);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-append(",
|
||||
"path": "ts/util/createIPCEvents.js",
|
||||
"line": " $('body').append(clearDataView.el);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-prepend(",
|
||||
"path": "ts/util/createIPCEvents.js",
|
||||
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.ts",
|
||||
"line": " if ($('.dark-overlay').length) {",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.ts",
|
||||
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.ts",
|
||||
"line": " $('.dark-overlay').on('click', () => $('.dark-overlay').remove());",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.ts",
|
||||
"line": " removeDarkOverlay: () => $('.dark-overlay').remove(),",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/util/createIPCEvents.ts",
|
||||
"line": " $('body').append(clearDataView.el);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-append(",
|
||||
"path": "ts/util/createIPCEvents.ts",
|
||||
"line": " $('body').append(clearDataView.el);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-prepend(",
|
||||
"path": "ts/util/createIPCEvents.ts",
|
||||
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/index.js",
|
||||
|
|
189
ts/util/preload.ts
Normal file
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> {
|
||||
// Only send read receipts for accepted conversations
|
||||
if (
|
||||
window.storage.get('read-receipt-setting') &&
|
||||
window.Events.getReadReceiptSetting() &&
|
||||
isConversationAccepted(conversationAttrs)
|
||||
) {
|
||||
window.log.info(`Sending ${items.length} read receipts`);
|
||||
|
|
|
@ -4079,7 +4079,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
|
||||
maybeGrabLinkPreview(message: string, caretLocation?: number) {
|
||||
// Don't generate link previews if user has turned them off
|
||||
if (!window.storage.get('linkPreviews', false)) {
|
||||
if (!window.Events.getLinkPreviewSetting()) {
|
||||
return;
|
||||
}
|
||||
// Do nothing if we're offline
|
||||
|
|
17
ts/window.d.ts
vendored
17
ts/window.d.ts
vendored
|
@ -8,7 +8,7 @@ import * as Backbone from 'backbone';
|
|||
import * as Underscore from 'underscore';
|
||||
import moment from 'moment';
|
||||
import PQueue from 'p-queue/dist';
|
||||
import { Ref } from 'react';
|
||||
import { Attributes, ComponentClass, FunctionComponent, Ref } from 'react';
|
||||
import { imageToBlurHash } from './util/imageToBlurHash';
|
||||
import * as Util from './util';
|
||||
import {
|
||||
|
@ -118,6 +118,7 @@ import { isValidGuid } from './util/isValidGuid';
|
|||
import { StateType } from './state/reducer';
|
||||
import { SystemTraySetting } from './types/SystemTraySetting';
|
||||
import { CI } from './CI';
|
||||
import { IPCEventsType } from './util/createIPCEvents';
|
||||
|
||||
export { Long } from 'long';
|
||||
|
||||
|
@ -176,6 +177,15 @@ declare global {
|
|||
|
||||
WhatIsThis: WhatIsThis;
|
||||
|
||||
SignalModule: {
|
||||
registerReactRenderer: (
|
||||
f: <P extends {}>(
|
||||
component: FunctionComponent<P> | ComponentClass<P>,
|
||||
props?: (Attributes & P) | null
|
||||
) => void
|
||||
) => void;
|
||||
};
|
||||
|
||||
registerScreenShareControllerRenderer: (
|
||||
f: (
|
||||
component: typeof CallingScreenSharingController,
|
||||
|
@ -194,15 +204,12 @@ declare global {
|
|||
getAccountManager: () => AccountManager;
|
||||
getAlwaysRelayCalls: () => Promise<boolean>;
|
||||
getBuiltInImages: () => Promise<Array<string>>;
|
||||
getCallRingtoneNotification: () => Promise<boolean>;
|
||||
getCallSystemNotification: () => Promise<boolean>;
|
||||
getConversations: () => ConversationModelCollectionType;
|
||||
getCountMutedConversations: () => Promise<boolean>;
|
||||
getEnvironment: typeof getEnvironment;
|
||||
getExpiration: () => string;
|
||||
getGuid: () => string;
|
||||
getInboxCollection: () => ConversationModelCollectionType;
|
||||
getIncomingCallNotification: () => Promise<boolean>;
|
||||
getInteractionMode: () => 'mouse' | 'keyboard';
|
||||
getLocale: () => ElectronLocaleType;
|
||||
getMediaCameraPermissions: () => Promise<boolean>;
|
||||
|
@ -489,7 +496,7 @@ declare global {
|
|||
SignalContext: SignalContext;
|
||||
|
||||
ConversationController: ConversationController;
|
||||
Events: WhatIsThis;
|
||||
Events: IPCEventsType;
|
||||
MessageController: MessageController;
|
||||
SignalProtocolStore: typeof SignalProtocolStore;
|
||||
WebAPI: WebAPIConnectType;
|
||||
|
|
8
ts/windows/context.ts
Normal file
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