diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-12-21 17:17:00 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-12-21 17:17:00 +0100 |
| commit | 11f6f8e7c9be27f18e674e21171ee2b87b05efce (patch) | |
| tree | a015bad23644b38a6f96e373288e359a7052b1e2 | |
| parent | 9d657b8ac3634ad37e2d9bfd8e5c8c80975873ec (diff) | |
| parent | 07b70a9c8906a2b42dd67f5cbcc5b933c4f0662b (diff) | |
| download | mullvadvpn-11f6f8e7c9be27f18e674e21171ee2b87b05efce.tar.xz mullvadvpn-11f6f8e7c9be27f18e674e21171ee2b87b05efce.zip | |
Merge branch 'refactor-ipc'
| -rw-r--r-- | gui/flow-libs/electron.js.flow | 9 | ||||
| -rw-r--r-- | gui/packages/desktop/src/main/gui-settings.js | 38 | ||||
| -rw-r--r-- | gui/packages/desktop/src/main/index.js | 97 | ||||
| -rw-r--r-- | gui/packages/desktop/src/main/window-controller.js | 6 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/app.js | 37 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js | 6 | ||||
| -rw-r--r-- | gui/packages/desktop/src/shared/ipc-event-channel.js | 202 |
7 files changed, 232 insertions, 163 deletions
diff --git a/gui/flow-libs/electron.js.flow b/gui/flow-libs/electron.js.flow index 52df468272..d8fdd9e88f 100644 --- a/gui/flow-libs/electron.js.flow +++ b/gui/flow-libs/electron.js.flow @@ -115,12 +115,21 @@ declare module 'electron' { // http://electron.atom.io/docs/api/ipc-renderer + declare type IpcRendererEvent = { + senderId: number; + } + declare class IpcRenderer extends EventEmitter { send(channel: string, ...args: Array<mixed>): void; } // http://electron.atom.io/docs/api/ipc-main + declare type IpcMainEvent = { + sender: WebContents; + returnValue?: any; + } + declare class IpcMain extends EventEmitter {} declare class WebContents extends EventEmitter { diff --git a/gui/packages/desktop/src/main/gui-settings.js b/gui/packages/desktop/src/main/gui-settings.js index 3b05261ab9..b5e1ad2e8c 100644 --- a/gui/packages/desktop/src/main/gui-settings.js +++ b/gui/packages/desktop/src/main/gui-settings.js @@ -5,7 +5,6 @@ import log from 'electron-log'; import path from 'path'; import { app } from 'electron'; -import IpcEventChannel from '../shared/ipc-event-channel'; import type { GuiSettingsState } from '../shared/gui-settings-state'; export default class GuiSettings { @@ -14,8 +13,7 @@ export default class GuiSettings { startMinimized: false, }; - _notify: ?(GuiSettingsState) => void; - _monochromaticIconChangeListener: ?(boolean) => void; + onChange: ?(newState: GuiSettingsState, oldState: GuiSettingsState) => void; load() { try { @@ -44,44 +42,34 @@ export default class GuiSettings { return this._state; } + set monochromaticIcon(newValue: boolean) { + this._changeStateAndNotify({ ...this._state, monochromaticIcon: newValue }); + } + get monochromaticIcon(): boolean { return this._state.monochromaticIcon; } - onChangeMonochromaticIcon(listener: (boolean) => void) { - this._monochromaticIconChangeListener = listener; + set startMinimized(newValue: boolean) { + this._changeStateAndNotify({ ...this._state, startMinimized: newValue }); } get startMinimized(): boolean { return this._state.startMinimized; } - registerIpcHandlers(ipcEventChannel: IpcEventChannel) { - this._notify = ipcEventChannel.guiSettings.notify; - - ipcEventChannel.guiSettings.handleMonochromaticIcon((monochromaticIcon: boolean) => { - this._state.monochromaticIcon = monochromaticIcon; - this._settingsChanged(); - - if (this._monochromaticIconChangeListener) { - this._monochromaticIconChangeListener(monochromaticIcon); - } - }); - ipcEventChannel.guiSettings.handleStartMinimized((startMinimized: boolean) => { - this._state.startMinimized = startMinimized; - this._settingsChanged(); - }); - } - _filePath() { return path.join(app.getPath('userData'), 'gui_settings.json'); } - _settingsChanged() { + _changeStateAndNotify(newState: GuiSettingsState) { + const oldState = this._state; + this._state = newState; + this.store(); - if (this._notify) { - this._notify(this._state); + if (this.onChange) { + this.onChange({ ...newState }, oldState); } } } diff --git a/gui/packages/desktop/src/main/index.js b/gui/packages/desktop/src/main/index.js index 114dc7c508..3165729458 100644 --- a/gui/packages/desktop/src/main/index.js +++ b/gui/packages/desktop/src/main/index.js @@ -14,10 +14,11 @@ import WindowController from './window-controller'; import TrayIconController from './tray-icon-controller'; import type { TrayIconType } from './tray-icon-controller'; -import IpcEventChannel from '../shared/ipc-event-channel'; +import { IpcMainEventChannel } from '../shared/ipc-event-channel'; import { DaemonRpc, ConnectionObserver, SubscriptionListener } from './daemon-rpc'; import type { + AccountToken, AppVersionInfo, Location, RelayList, @@ -52,7 +53,6 @@ const ApplicationMain = { _notificationController: new NotificationController(), _windowController: (null: ?WindowController), _trayIconController: (null: ?TrayIconController), - _ipcEventChannel: (null: ?IpcEventChannel), _daemonRpc: new DaemonRpc(), _reconnectBackoff: new ReconnectionBackoff(), @@ -287,9 +287,21 @@ const ApplicationMain = { this._windowController = windowController; this._trayIconController = trayIconController; - this._ipcEventChannel = new IpcEventChannel(window.webContents); - this._guiSettings.registerIpcHandlers(this._ipcEventChannel); + this._guiSettings.onChange = (newState, oldState) => { + if ( + process.platform === 'darwin' && + oldState.monochromaticIcon !== newState.monochromaticIcon + ) { + if (this._trayIconController) { + this._trayIconController.useMonochromaticIcon = newState.monochromaticIcon; + } + } + + if (this._windowController) { + IpcMainEventChannel.guiSettings.notify(this._windowController.webContents, newState); + } + }; if (process.env.NODE_ENV === 'development') { await this._installDevTools(); @@ -302,7 +314,6 @@ const ApplicationMain = { break; case 'darwin': this._installMacOsMenubarAppWindowHandlers(tray, windowController); - this._installMonochromaticTrayIconHandler(); break; case 'linux': this._installGenericMenubarAppWindowHandlers(tray, windowController); @@ -388,8 +399,8 @@ const ApplicationMain = { this._reconnectBackoff.reset(); // notify renderer - if (this._ipcEventChannel) { - this._ipcEventChannel.daemonConnected.notify(); + if (this._windowController) { + IpcMainEventChannel.daemonConnected.notify(this._windowController.webContents); } }, @@ -406,8 +417,11 @@ const ApplicationMain = { this._stopLatestVersionPeriodicUpdates(); // notify renderer process - if (this._ipcEventChannel) { - this._ipcEventChannel.daemonDisconnected.notify(error ? error.message : null); + if (this._windowController) { + IpcMainEventChannel.daemonDisconnected.notify( + this._windowController.webContents, + error ? error.message : null, + ); } } @@ -477,8 +491,8 @@ const ApplicationMain = { this._notificationController.notifyTunnelState(newState); } - if (this._ipcEventChannel) { - this._ipcEventChannel.tunnelState.notify(newState); + if (this._windowController) { + IpcMainEventChannel.tunnelState.notify(this._windowController.webContents, newState); } }, @@ -486,24 +500,24 @@ const ApplicationMain = { this._settings = newSettings; this._updateTrayIcon(this._tunnelState, newSettings.blockWhenDisconnected); - if (this._ipcEventChannel) { - this._ipcEventChannel.settings.notify(newSettings); + if (this._windowController) { + IpcMainEventChannel.settings.notify(this._windowController.webContents, newSettings); } }, _setLocation(newLocation: Location) { this._location = newLocation; - if (this._ipcEventChannel) { - this._ipcEventChannel.location.notify(newLocation); + if (this._windowController) { + IpcMainEventChannel.location.notify(this._windowController.webContents, newLocation); } }, _setRelays(newRelayList: RelayList) { this._relays = newRelayList; - if (this._ipcEventChannel) { - this._ipcEventChannel.relays.notify(newRelayList); + if (this._windowController) { + IpcMainEventChannel.relays.notify(this._windowController.webContents, newRelayList); } }, @@ -541,8 +555,8 @@ const ApplicationMain = { this._currentVersion = versionInfo; // notify renderer - if (this._ipcEventChannel) { - this._ipcEventChannel.currentVersion.notify(versionInfo); + if (this._windowController) { + IpcMainEventChannel.currentVersion.notify(this._windowController.webContents, versionInfo); } }, @@ -601,8 +615,8 @@ const ApplicationMain = { this._notificationController.notifyUnsupportedVersion(upgradeVersion); } - if (this._ipcEventChannel) { - this._ipcEventChannel.upgradeVersion.notify(upgradeInfo); + if (this._windowController) { + IpcMainEventChannel.upgradeVersion.notify(this._windowController.webContents, upgradeInfo); } }, @@ -730,19 +744,30 @@ const ApplicationMain = { } }); - IpcEventChannel.state.serve(() => { - return { - isConnected: this._connectedToDaemon, - tunnelState: this._tunnelState, - settings: this._settings, - location: this._location, - relays: this._relays, - currentVersion: this._currentVersion, - upgradeVersion: this._upgradeVersion, - guiSettings: this._guiSettings.state, - }; + IpcMainEventChannel.state.handleGet(() => ({ + isConnected: this._connectedToDaemon, + tunnelState: this._tunnelState, + settings: this._settings, + location: this._location, + relays: this._relays, + currentVersion: this._currentVersion, + upgradeVersion: this._upgradeVersion, + guiSettings: this._guiSettings.state, + })); + + IpcMainEventChannel.guiSettings.handleStartMinimized((startMinimized: boolean) => { + this._guiSettings.startMinimized = startMinimized; }); + IpcMainEventChannel.guiSettings.handleMonochromaticIcon((monochromaticIcon: boolean) => { + this._guiSettings.monochromaticIcon = monochromaticIcon; + }); + + IpcMainEventChannel.accountHistory.handleGet(() => this._daemonRpc.getAccountHistory()); + IpcMainEventChannel.accountHistory.handleRemoveItem((token: AccountToken) => + this._daemonRpc.removeAccountFromHistory(token), + ); + ipcMain.on('show-window', () => { const windowController = this._windowController; if (windowController) { @@ -999,14 +1024,6 @@ const ApplicationMain = { }); }, - _installMonochromaticTrayIconHandler() { - this._guiSettings.onChangeMonochromaticIcon((monochromaticIcon) => { - if (this._trayIconController) { - this._trayIconController.useMonochromaticIcon = monochromaticIcon; - } - }); - }, - _shouldShowWindowOnStart(): boolean { switch (process.platform) { case 'win32': diff --git a/gui/packages/desktop/src/main/window-controller.js b/gui/packages/desktop/src/main/window-controller.js index faabe63c91..782f5ee770 100644 --- a/gui/packages/desktop/src/main/window-controller.js +++ b/gui/packages/desktop/src/main/window-controller.js @@ -1,7 +1,7 @@ // @flow import { screen } from 'electron'; -import type { BrowserWindow, Tray, Display } from 'electron'; +import type { BrowserWindow, Tray, Display, WebContents } from 'electron'; type Position = { x: number, y: number }; @@ -139,6 +139,10 @@ export default class WindowController { return this._window; } + get webContents(): WebContents { + return this._window.webContents; + } + constructor(window: BrowserWindow, tray: Tray) { this._window = window; const [width, height] = window.getSize(); diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js index 88e849cb1d..793b0a9224 100644 --- a/gui/packages/desktop/src/renderer/app.js +++ b/gui/packages/desktop/src/renderer/app.js @@ -25,7 +25,7 @@ import userInterfaceActions from './redux/userinterface/actions'; import type { WindowShapeParameters } from '../main/window-controller'; import type { CurrentAppVersionInfo, AppUpgradeInfo } from '../main'; import type { GuiSettingsState } from '../shared/gui-settings-state'; -import IpcEventChannel from '../shared/ipc-event-channel'; +import { IpcRendererEventChannel } from '../shared/ipc-event-channel'; import type { AccountToken, @@ -93,44 +93,44 @@ export default class AppRenderer { } }); - IpcEventChannel.daemonConnected.listen(() => { + IpcRendererEventChannel.daemonConnected.listen(() => { this._onDaemonConnected(); }); - IpcEventChannel.daemonDisconnected.listen((errorMessage: ?string) => { + IpcRendererEventChannel.daemonDisconnected.listen((errorMessage: ?string) => { this._onDaemonDisconnected(errorMessage ? new Error(errorMessage) : null); }); - IpcEventChannel.tunnelState.listen((newState: TunnelStateTransition) => { + IpcRendererEventChannel.tunnelState.listen((newState: TunnelStateTransition) => { this._setTunnelState(newState); }); - IpcEventChannel.settings.listen((newSettings: Settings) => { + IpcRendererEventChannel.settings.listen((newSettings: Settings) => { this._setSettings(newSettings); }); - IpcEventChannel.location.listen((newLocation: Location) => { + IpcRendererEventChannel.location.listen((newLocation: Location) => { this._setLocation(newLocation); }); - IpcEventChannel.relays.listen((newRelays: RelayList) => { + IpcRendererEventChannel.relays.listen((newRelays: RelayList) => { this._setRelays(newRelays); }); - IpcEventChannel.currentVersion.listen((currentVersion: CurrentAppVersionInfo) => { + IpcRendererEventChannel.currentVersion.listen((currentVersion: CurrentAppVersionInfo) => { this._setCurrentVersion(currentVersion); }); - IpcEventChannel.upgradeVersion.listen((upgradeVersion: AppUpgradeInfo) => { + IpcRendererEventChannel.upgradeVersion.listen((upgradeVersion: AppUpgradeInfo) => { this._setUpgradeVersion(upgradeVersion); }); - IpcEventChannel.guiSettings.listen((guiSettings: GuiSettingsState) => { + IpcRendererEventChannel.guiSettings.listen((guiSettings: GuiSettingsState) => { this._setGuiSettings(guiSettings); }); // Request the initial state from the main process - const initialState = IpcEventChannel.state.get(); + const initialState = IpcRendererEventChannel.state.get(); this._setTunnelState(initialState.tunnelState); this._setSettings(initialState.settings); @@ -307,15 +307,14 @@ export default class AppRenderer { } async removeAccountFromHistory(accountToken: AccountToken): Promise<void> { - await this._daemonRpc.removeAccountFromHistory(accountToken); + await IpcRendererEventChannel.accountHistory.removeItem(accountToken); await this._fetchAccountHistory(); } async _fetchAccountHistory(): Promise<void> { - const actions = this._reduxActions; + const accountHistory = await IpcRendererEventChannel.accountHistory.get(); - const accountHistory = await this._daemonRpc.getAccountHistory(); - actions.account.updateAccountHistory(accountHistory); + this._reduxActions.account.updateAccountHistory(accountHistory); } async setAllowLan(allowLan: boolean) { @@ -348,12 +347,12 @@ export default class AppRenderer { actions.settings.updateAutoConnect(autoConnect); } - async setStartMinimized(startMinimized: boolean) { - IpcEventChannel.guiSettings.setStartMinimized(startMinimized); + setStartMinimized(startMinimized: boolean) { + IpcRendererEventChannel.guiSettings.setStartMinimized(startMinimized); } - async setMonochromaticIcon(monochromaticIcon: boolean) { - IpcEventChannel.guiSettings.setMonochromaticIcon(monochromaticIcon); + setMonochromaticIcon(monochromaticIcon: boolean) { + IpcRendererEventChannel.guiSettings.setMonochromaticIcon(monochromaticIcon); } async _onDaemonConnected() { diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js index 8b2e03c3dc..a9f32b34d3 100644 --- a/gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js +++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js @@ -176,11 +176,11 @@ export default class DaemonRpcProxy implements DaemonRpcProtocol { } getAccountHistory(): Promise<Array<AccountToken>> { - return this._sendMessage('getAccountHistory'); + throw new Error('Do not use this method'); } - removeAccountFromHistory(accountToken: AccountToken): Promise<void> { - return this._sendMessage('removeAccountFromHistory', accountToken); + removeAccountFromHistory(_accountToken: AccountToken): Promise<void> { + throw new Error('Do not use this method'); } getCurrentVersion(): Promise<string> { diff --git a/gui/packages/desktop/src/shared/ipc-event-channel.js b/gui/packages/desktop/src/shared/ipc-event-channel.js index f3c629a418..06c5f9620e 100644 --- a/gui/packages/desktop/src/shared/ipc-event-channel.js +++ b/gui/packages/desktop/src/shared/ipc-event-channel.js @@ -1,12 +1,19 @@ // @flow import { ipcMain, ipcRenderer } from 'electron'; -import type { WebContents } from 'electron'; +import type { WebContents, IpcMainEvent, IpcRendererEvent } from 'electron'; +import uuid from 'uuid'; import type { GuiSettingsState } from './gui-settings-state'; import type { AppUpgradeInfo, CurrentAppVersionInfo } from '../main/index'; -import type { Location, RelayList, Settings, TunnelStateTransition } from '../main/daemon-rpc'; +import type { + AccountToken, + Location, + RelayList, + Settings, + TunnelStateTransition, +} from '../main/daemon-rpc'; export type AppStateSnapshot = { isConnected: boolean, @@ -20,7 +27,7 @@ export type AppStateSnapshot = { }; interface Sender<T> { - notify(newState: T): void; + notify(webContents: WebContents, newState: T): void; } interface Receiver<T> { @@ -37,6 +44,16 @@ interface GuiSettingsHandlers { handleMonochromaticIcon: ((boolean) => void) => void; } +interface AccountHistoryHandlers { + handleGet(fn: () => Promise<Array<AccountToken>>): void; + handleRemoveItem(fn: (token: AccountToken) => Promise<void>): void; +} + +interface AccountHistoryMethods { + get(): Promise<Array<AccountToken>>; + removeItem(token: AccountToken): Promise<void>; +} + /// Events names const DAEMON_CONNECTED = 'daemon-connected'; @@ -51,30 +68,20 @@ const GUI_SETTINGS_CHANGED = 'gui-settings-changed'; const SET_MONOCHROMATIC_ICON = 'set-monochromatic-icon'; const SET_START_MINIMIZED = 'set-start-minimized'; +const GET_APP_STATE = 'get-app-state'; +const GET_ACCOUNT_HISTORY = 'get-account-history'; +const REMOVE_ACCOUNT_HISTORY_ITEM = 'remove-account-history-item'; /// Typed IPC event channel /// /// Static methods are meant to be provide the way to send the events from a renderer process, while /// instance methods are meant to be used from a main process. /// -export default class IpcEventChannel { - _webContents: WebContents; - - constructor(webContents: WebContents) { - this._webContents = webContents; - } - +export class IpcRendererEventChannel { static state = { - /// Should be used from the main process to process state snapshot requests - serve(fn: () => AppStateSnapshot) { - ipcMain.on('get-state', (event) => { - event.returnValue = fn(); - }); - }, - /// Synchronously sends the IPC request and returns the app state snapshot get(): AppStateSnapshot { - return ipcRenderer.sendSync('get-state'); + return ipcRenderer.sendSync(GET_APP_STATE); }, }; @@ -82,95 +89,97 @@ export default class IpcEventChannel { listen: listen(DAEMON_CONNECTED), }; - get daemonConnected(): Sender<void> { - return { - notify: sender(this._webContents, DAEMON_CONNECTED), - }; - } - static daemonDisconnected: Receiver<?string> = { listen: listen(DAEMON_DISCONNECTED), }; - get daemonDisconnected(): Sender<?string> { - return { - notify: sender(this._webContents, DAEMON_DISCONNECTED), - }; - } - static tunnelState: Receiver<TunnelStateTransition> = { listen: listen(TUNNEL_STATE_CHANGED), }; - get tunnelState(): Sender<TunnelStateTransition> { - return { - notify: sender(this._webContents, TUNNEL_STATE_CHANGED), - }; - } - static settings: Receiver<Settings> = { listen: listen(SETTINGS_CHANGED), }; - get settings(): Sender<Settings> { - return { - notify: sender(this._webContents, SETTINGS_CHANGED), - }; - } - static location: Receiver<Location> = { listen: listen(LOCATION_CHANGED), }; - get location(): Sender<Location> { - return { - notify: sender(this._webContents, LOCATION_CHANGED), - }; - } - static relays: Receiver<RelayList> = { listen: listen(RELAYS_CHANGED), }; - get relays(): Sender<RelayList> { - return { - notify: sender(this._webContents, RELAYS_CHANGED), - }; - } - static currentVersion: Receiver<CurrentAppVersionInfo> = { listen: listen(CURRENT_VERSION_CHANGED), }; - get currentVersion(): Sender<CurrentAppVersionInfo> { - return { - notify: sender(this._webContents, CURRENT_VERSION_CHANGED), - }; - } - static upgradeVersion: Receiver<AppUpgradeInfo> = { listen: listen(UPGRADE_VERSION_CHANGED), }; - get upgradeVersion(): Sender<AppUpgradeInfo> { - return { - notify: sender(this._webContents, UPGRADE_VERSION_CHANGED), - }; - } - static guiSettings: Receiver<GuiSettingsState> & GuiSettingsMethods = { listen: listen(GUI_SETTINGS_CHANGED), setMonochromaticIcon: set(SET_MONOCHROMATIC_ICON), setStartMinimized: set(SET_START_MINIMIZED), }; - get guiSettings(): Sender<GuiSettingsState> & GuiSettingsHandlers { - return { - notify: sender(this._webContents, GUI_SETTINGS_CHANGED), - handleMonochromaticIcon: handler(SET_MONOCHROMATIC_ICON), - handleStartMinimized: handler(SET_START_MINIMIZED), - }; - } + static accountHistory: AccountHistoryMethods = { + get: requestSender(GET_ACCOUNT_HISTORY), + removeItem: requestSender(REMOVE_ACCOUNT_HISTORY_ITEM), + }; +} + +export class IpcMainEventChannel { + static state = { + handleGet(fn: () => AppStateSnapshot) { + ipcMain.on(GET_APP_STATE, (event) => { + event.returnValue = fn(); + }); + }, + }; + + static daemonConnected: Sender<void> = { + notify: sender(DAEMON_CONNECTED), + }; + + static daemonDisconnected: Sender<?string> = { + notify: sender(DAEMON_DISCONNECTED), + }; + + static tunnelState: Sender<TunnelStateTransition> = { + notify: sender(TUNNEL_STATE_CHANGED), + }; + + static location: Sender<Location> = { + notify: sender(LOCATION_CHANGED), + }; + + static settings: Sender<Settings> = { + notify: sender(SETTINGS_CHANGED), + }; + + static relays: Sender<RelayList> = { + notify: sender(RELAYS_CHANGED), + }; + + static currentVersion: Sender<CurrentAppVersionInfo> = { + notify: sender(CURRENT_VERSION_CHANGED), + }; + + static upgradeVersion: Sender<AppUpgradeInfo> = { + notify: sender(UPGRADE_VERSION_CHANGED), + }; + + static guiSettings: Sender<GuiSettingsState> & GuiSettingsHandlers = { + notify: sender(GUI_SETTINGS_CHANGED), + handleMonochromaticIcon: handler(SET_MONOCHROMATIC_ICON), + handleStartMinimized: handler(SET_START_MINIMIZED), + }; + + static accountHistory: AccountHistoryHandlers = { + handleGet: requestHandler(GET_ACCOUNT_HISTORY), + handleRemoveItem: requestHandler(REMOVE_ACCOUNT_HISTORY_ITEM), + }; } function listen<T>(event: string): ((T) => void) => void { @@ -185,8 +194,8 @@ function set<T>(event: string): (T) => void { }; } -function sender<T>(webContents: WebContents, event: string): (T) => void { - return function(newState: T) { +function sender<T>(event: string): (WebContents, T) => void { + return function(webContents: WebContents, newState: T) { webContents.send(event, newState); }; } @@ -198,3 +207,46 @@ function handler<T>(event: string): ((T) => void) => void { }); }; } + +type RequestResult<T> = { type: 'success', value: T } | { type: 'error', message: string }; + +function requestHandler<T>(event: string): (fn: (...args: Array<any>) => Promise<T>) => void { + return function(fn: (...args: Array<any>) => Promise<T>) { + ipcMain.on(event, async (ipcEvent: IpcMainEvent, requestId: string, ...args: Array<any>) => { + const sender = ipcEvent.sender; + const responseEvent = `${event}-${requestId}`; + try { + const result: RequestResult<T> = { type: 'success', value: await fn(...args) }; + + sender.send(responseEvent, result); + } catch (error) { + const result: RequestResult<T> = { type: 'error', message: error.message || '' }; + + sender.send(responseEvent, result); + } + }); + }; +} + +function requestSender<T>(event: string): (...args: Array<any>) => Promise<T> { + return function(...args: Array<any>): Promise<T> { + return new Promise((resolve: (result: T) => void, reject: (error: Error) => void) => { + const requestId = uuid.v4(); + const responseEvent = `${event}-${requestId}`; + + ipcRenderer.once(responseEvent, (_ipcEvent: IpcRendererEvent, result: RequestResult<T>) => { + switch (result.type) { + case 'error': + reject(new Error(result.message)); + break; + + case 'success': + resolve(result.value); + break; + } + }); + + ipcRenderer.send(event, requestId, ...args); + }); + }; +} |
