summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-12-21 17:17:00 +0100
committerAndrej Mihajlov <and@mullvad.net>2018-12-21 17:17:00 +0100
commit11f6f8e7c9be27f18e674e21171ee2b87b05efce (patch)
treea015bad23644b38a6f96e373288e359a7052b1e2
parent9d657b8ac3634ad37e2d9bfd8e5c8c80975873ec (diff)
parent07b70a9c8906a2b42dd67f5cbcc5b933c4f0662b (diff)
downloadmullvadvpn-11f6f8e7c9be27f18e674e21171ee2b87b05efce.tar.xz
mullvadvpn-11f6f8e7c9be27f18e674e21171ee2b87b05efce.zip
Merge branch 'refactor-ipc'
-rw-r--r--gui/flow-libs/electron.js.flow9
-rw-r--r--gui/packages/desktop/src/main/gui-settings.js38
-rw-r--r--gui/packages/desktop/src/main/index.js97
-rw-r--r--gui/packages/desktop/src/main/window-controller.js6
-rw-r--r--gui/packages/desktop/src/renderer/app.js37
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js6
-rw-r--r--gui/packages/desktop/src/shared/ipc-event-channel.js202
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);
+ });
+ };
+}