// 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 = Readonly<{ getValue: () => Promise; setValue: (value: Value) => Promise; }>; function capitalize( name: Name ): Capitalize { const result = name.slice(0, 1).toUpperCase() + name.slice(1); return result as Capitalize; } function getSetterName( name: Key ): IPCEventSetterType { return `set${capitalize(name)}`; } function getGetterName( name: Key ): IPCEventGetterType { return `get${capitalize(name)}`; } export function createSetting< Name extends keyof IPCEventsValuesType, Value extends IPCEventsValuesType[Name] >(name: Name, overrideOptions: SettingOptionsType = {}): SettingType { const options = { getter: true, setter: true, ...overrideOptions, }; function getValue(): Promise { 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 { 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) => unknown > = UnwrapPromise>; export function createCallback< Name extends keyof IPCEventsCallbacksType, Callback extends IPCEventsCallbacksType[Name] >( name: Name ): (...args: Parameters) => Promise> { return (...args: Parameters): Promise> => { return new Promise>((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; 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: Name ): void { ipcRenderer.on(`callbacks:call:${name}`, async (_, args) => { const hook = window.Events[name] as ( ...hookArgs: Array ) => Promise; 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 ); } }); }