// Copyright 2017-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { BrowserWindow } from 'electron'; import { ipcMain as ipc, 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 type { IPCEventsValuesType, IPCEventsCallbacksType, } from '../util/createIPCEvents'; const EPHEMERAL_NAME_MAP = new Map([ ['spellCheck', 'spell-check'], ['systemTraySetting', 'system-tray-setting'], ]); export class SettingsChannel { private mainWindow?: BrowserWindow; public setMainWindow(mainWindow: BrowserWindow | undefined): 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'); this.installCallback('isPhoneNumberSharingEnabled'); // 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', { isEphemeral: true, }); this.installSetting('notificationSetting'); this.installSetting('notificationDrawAttention'); this.installSetting('audioNotification'); this.installSetting('countMutedConversations'); this.installSetting('spellCheck', { isEphemeral: true, }); this.installSetting('autoDownloadUpdate'); 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: Name ): Promise { 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: 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: 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) { const ephemeralName = EPHEMERAL_NAME_MAP.get(name); strictAssert( ephemeralName !== undefined, `${name} is not an ephemeral setting` ); ephemeralConfig.set(ephemeralName, 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); } }); } }