summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-11-14 16:10:10 +0100
committerAndrej Mihajlov <and@mullvad.net>2018-11-15 11:14:43 +0100
commit3f70ef611126c008202b1665fc684d5a04b7e72d (patch)
tree35c2042d8ca38be3f93ebc03202264ec1f5b8d9d
parentf004183e5e6a043afe839885643a6c7776fbb9f2 (diff)
downloadmullvadvpn-3f70ef611126c008202b1665fc684d5a04b7e72d.tar.xz
mullvadvpn-3f70ef611126c008202b1665fc684d5a04b7e72d.zip
Move DaemonRPC to main and establish a temp bridge using DaemonRPCProxy
-rw-r--r--gui/packages/desktop/src/main/daemon-rpc.js (renamed from gui/packages/desktop/src/renderer/lib/daemon-rpc.js)2
-rw-r--r--gui/packages/desktop/src/main/errors.js (renamed from gui/packages/desktop/src/renderer/errors.js)0
-rw-r--r--gui/packages/desktop/src/main/index.js130
-rw-r--r--gui/packages/desktop/src/main/jsonrpc-client.js (renamed from gui/packages/desktop/src/renderer/lib/jsonrpc-client.js)0
-rw-r--r--gui/packages/desktop/src/main/reconnection-backoff.js (renamed from gui/packages/desktop/src/renderer/lib/reconnection-backoff.js)0
-rw-r--r--gui/packages/desktop/src/main/window-controller.js4
-rw-r--r--gui/packages/desktop/src/renderer/app.js110
-rw-r--r--gui/packages/desktop/src/renderer/components/Account.js2
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.js2
-rw-r--r--gui/packages/desktop/src/renderer/components/Login.js2
-rw-r--r--gui/packages/desktop/src/renderer/components/NotificationArea.js2
-rw-r--r--gui/packages/desktop/src/renderer/components/SelectLocation.js2
-rw-r--r--gui/packages/desktop/src/renderer/components/Support.js2
-rw-r--r--gui/packages/desktop/src/renderer/components/TunnelControl.js2
-rw-r--r--gui/packages/desktop/src/renderer/index.js2
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js237
-rw-r--r--gui/packages/desktop/src/renderer/lib/notification-controller.js2
-rw-r--r--gui/packages/desktop/src/renderer/lib/relay-settings-builder.js2
-rw-r--r--gui/packages/desktop/src/renderer/lib/subscription-proxy/base-proxy.js4
-rw-r--r--gui/packages/desktop/src/renderer/lib/subscription-proxy/settings-proxy.js4
-rw-r--r--gui/packages/desktop/src/renderer/lib/subscription-proxy/tunnel-state-proxy.js4
-rw-r--r--gui/packages/desktop/src/renderer/redux/account/actions.js2
-rw-r--r--gui/packages/desktop/src/renderer/redux/account/reducers.js2
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/actions.js2
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/reducers.js2
-rw-r--r--gui/packages/desktop/src/renderer/redux/settings/reducers.js2
-rw-r--r--gui/packages/desktop/src/renderer/redux/version/actions.js2
-rw-r--r--gui/packages/desktop/test/account-data-cache.spec.js2
-rw-r--r--gui/packages/desktop/test/jsonrpc-transport.spec.js5
29 files changed, 435 insertions, 99 deletions
diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js b/gui/packages/desktop/src/main/daemon-rpc.js
index a37ebdd5a8..1811fa1e36 100644
--- a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
+++ b/gui/packages/desktop/src/main/daemon-rpc.js
@@ -5,7 +5,7 @@ import JsonRpcClient, {
TimeOutError as JsonRpcTimeOutError,
SocketTransport,
} from './jsonrpc-client';
-import { CommunicationError, InvalidAccountError, NoDaemonError } from '../errors';
+import { CommunicationError, InvalidAccountError, NoDaemonError } from './errors';
import {
object,
diff --git a/gui/packages/desktop/src/renderer/errors.js b/gui/packages/desktop/src/main/errors.js
index 67f87501dd..67f87501dd 100644
--- a/gui/packages/desktop/src/renderer/errors.js
+++ b/gui/packages/desktop/src/main/errors.js
diff --git a/gui/packages/desktop/src/main/index.js b/gui/packages/desktop/src/main/index.js
index 2950cd9e01..50a4815544 100644
--- a/gui/packages/desktop/src/main/index.js
+++ b/gui/packages/desktop/src/main/index.js
@@ -11,14 +11,24 @@ import { app, screen, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'el
import TrayIconController from './tray-icon-controller';
import WindowController from './window-controller';
import ShutdownCoordinator from './shutdown-coordinator';
+import { DaemonRpc, ConnectionObserver, SubscriptionListener } from './daemon-rpc';
+import type { TunnelStateTransition, Settings } from './daemon-rpc';
+import ReconnectionBackoff from './reconnection-backoff';
import { resolveBin } from './proc';
import type { TrayIconType } from './tray-icon-controller';
+const DAEMON_RPC_PATH =
+ process.platform === 'win32' ? '//./pipe/Mullvad VPN' : '/var/run/mullvad-vpn';
+
const ApplicationMain = {
_windowController: (null: ?WindowController),
_trayIconController: (null: ?TrayIconController),
+ _daemonRpc: new DaemonRpc(),
+ _reconnectBackoff: new ReconnectionBackoff(),
+ _connectedToDaemon: false,
+
_logFilePath: '',
_oldLogFilePath: (null: ?string),
_connectionFilePollInterval: (null: ?IntervalID),
@@ -48,6 +58,18 @@ const ApplicationMain = {
app.on('ready', () => this._onReady());
app.on('window-all-closed', () => app.quit());
app.on('before-quit', () => this._onBeforeQuit());
+
+ const connectionObserver = new ConnectionObserver(
+ () => {
+ this._onDaemonConnected();
+ },
+ (error) => {
+ this._onDaemonDisconnected(error);
+ },
+ );
+
+ this._daemonRpc.addConnectionObserver(connectionObserver);
+ this._connectToDaemon();
},
_ensureSingleInstance() {
@@ -130,9 +152,8 @@ const ApplicationMain = {
},
_onActivate() {
- const windowController = this._windowController;
- if (windowController) {
- windowController.show();
+ if (this._windowController) {
+ this._windowController.show();
}
},
@@ -140,9 +161,8 @@ const ApplicationMain = {
this._shouldQuit = true;
if (process.env.NODE_ENV === 'development') {
- const windowController = this._windowController;
- if (windowController) {
- windowController.window.closeDevTools();
+ if (this._windowController) {
+ this._windowController.window.closeDevTools();
}
}
@@ -190,6 +210,79 @@ const ApplicationMain = {
window.loadFile(path.resolve(path.join(__dirname, '../renderer/index.html')));
},
+ async _onDaemonConnected() {
+ this._connectedToDaemon = true;
+
+ // reset the reconnect backoff when connection established.
+ this._reconnectBackoff.reset();
+
+ if (this._windowController) {
+ this._windowController.send('daemon-connected');
+ }
+
+ const stateListener = new SubscriptionListener(
+ (newState: TunnelStateTransition) => {
+ this._onStateChange(newState);
+ },
+ (error: Error) => {
+ log.error(`Cannot deserialize the new state: ${error.message}`);
+ },
+ );
+
+ const settingsListener = new SubscriptionListener(
+ (newSettings: Settings) => {
+ this._onSettingsChange(newSettings);
+ },
+ (error: Error) => {
+ log.error(`Cannot deserialize the new settings: ${error.message}`);
+ },
+ );
+
+ try {
+ await Promise.all([
+ this._daemonRpc.subscribeStateListener(stateListener),
+ this._daemonRpc.subscribeSettingsListener(settingsListener),
+ ]);
+ } catch (error) {
+ log.error(`Failed to subscribe: ${error.message}`);
+ }
+ },
+
+ _onDaemonDisconnected(error: ?Error) {
+ // recover connection on error
+ if (error) {
+ log.debug(`Lost connection to daemon: ${error.message}`);
+
+ this._reconnectBackoff.attempt(() => {
+ this._connectToDaemon();
+ });
+ } else {
+ log.info(`Disconnected from the daemon`);
+ }
+
+ this._connectedToDaemon = false;
+
+ if (this._windowController) {
+ this._windowController.send('daemon-disconnected', error ? error.message : null);
+ }
+ },
+
+ _connectToDaemon() {
+ this._daemonRpc.connect({ path: DAEMON_RPC_PATH });
+ },
+
+ _onStateChange(newState: TunnelStateTransition) {
+ if (this._windowController) {
+ this._windowController.send('state-changed', newState);
+ }
+ },
+
+ _onSettingsChange(newSettings: Settings) {
+ if (this._windowController) {
+ this._windowController.send('settings-changed', newSettings);
+ }
+ },
+
_registerWindowListener(windowController: WindowController) {
const window = windowController.window;
@@ -197,6 +290,31 @@ const ApplicationMain = {
},
_registerIpcListeners() {
+ ipcMain.on('daemon-rpc-call', async (event, id: string, method: string, payload: any) => {
+ log.debug(`Got daemon-rpc-call: ${id} ${method} ${payload}`);
+
+ try {
+ // $FlowFixMe: flow does not like index accessors.
+ const result = await this._daemonRpc[method](payload);
+
+ log.debug(`Reply to ${id} ${method} with success: ${JSON.stringify(result)}`);
+ event.sender.send(`daemon-rpc-reply-${id}`, result);
+ } catch (error) {
+ log.debug(`Reply to ${id} ${method} with error: ${error.message}`);
+ event.sender.send(`daemon-rpc-reply-${id}`, undefined, {
+ className: error.constructor.name || '',
+ data: {
+ message: error.message,
+ ...JSON.parse(JSON.stringify(error)),
+ },
+ });
+ }
+ });
+
+ ipcMain.on('daemon-connection-status', (event) => {
+ event.sender.send('daemon-connection-status-reply', this._connectedToDaemon);
+ });
+
ipcMain.on('show-window', () => {
const windowController = this._windowController;
if (windowController) {
diff --git a/gui/packages/desktop/src/renderer/lib/jsonrpc-client.js b/gui/packages/desktop/src/main/jsonrpc-client.js
index 0f51ca57cb..0f51ca57cb 100644
--- a/gui/packages/desktop/src/renderer/lib/jsonrpc-client.js
+++ b/gui/packages/desktop/src/main/jsonrpc-client.js
diff --git a/gui/packages/desktop/src/renderer/lib/reconnection-backoff.js b/gui/packages/desktop/src/main/reconnection-backoff.js
index d777e86e57..d777e86e57 100644
--- a/gui/packages/desktop/src/renderer/lib/reconnection-backoff.js
+++ b/gui/packages/desktop/src/main/reconnection-backoff.js
diff --git a/gui/packages/desktop/src/main/window-controller.js b/gui/packages/desktop/src/main/window-controller.js
index 5950ab4716..0943364dab 100644
--- a/gui/packages/desktop/src/main/window-controller.js
+++ b/gui/packages/desktop/src/main/window-controller.js
@@ -175,6 +175,10 @@ export default class WindowController {
}
}
+ send(event: string, ...data: Array<mixed>): void {
+ this._window.webContents.send(event, ...data);
+ }
+
_showImmediately() {
const window = this._window;
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 8e4328f66a..0b9352a5c1 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -12,10 +12,8 @@ import {
} from 'connected-react-router';
import { createMemoryHistory } from 'history';
-import { InvalidAccountError } from './errors';
+import { InvalidAccountError } from '../main/errors';
import makeRoutes from './routes';
-import ReconnectionBackoff from './lib/reconnection-backoff';
-import { DaemonRpc, ConnectionObserver } from './lib/daemon-rpc';
import NotificationController from './lib/notification-controller';
import setShutdownHandler from './lib/shutdown-handler';
@@ -38,9 +36,11 @@ import type {
RelaySettingsUpdate,
RelaySettings,
TunnelState,
- DaemonRpcProtocol,
AccountData,
-} from './lib/daemon-rpc';
+} from './lib/daemon-rpc-proxy';
+
+import DaemonRpcProxy from './lib/daemon-rpc-proxy';
+
import type { ReduxStore } from './redux/store';
import type { TrayIconType } from '../main/tray-icon-controller';
@@ -48,19 +48,10 @@ const RELAY_LIST_UPDATE_INTERVAL = 60 * 60 * 1000;
export default class AppRenderer {
_notificationController = new NotificationController();
- _daemonRpc: DaemonRpcProtocol = new DaemonRpc();
- _connectionObserver = new ConnectionObserver(
- () => {
- this._onOpenConnection();
- },
- (error) => {
- this._onCloseConnection(error);
- },
- );
- _reconnectBackoff = new ReconnectionBackoff();
_memoryHistory = createMemoryHistory();
_reduxStore: ReduxStore;
_reduxActions: *;
+ _daemonRpc = new DaemonRpcProxy();
_accountDataCache = new AccountDataCache(
(accountToken) => {
return this._daemonRpc.getAccountData(accountToken);
@@ -106,8 +97,6 @@ export default class AppRenderer {
),
};
- this._daemonRpc.addConnectionObserver(this._connectionObserver);
-
setShutdownHandler(async () => {
log.info('Executing a shutdown handler');
try {
@@ -125,10 +114,30 @@ export default class AppRenderer {
});
ipcRenderer.on('window-shown', () => {
- this.updateAccountExpiry();
+ if (this._connectedToDaemon) {
+ this.updateAccountExpiry();
+ }
+
this._notificationController.cancelPendingNotifications();
});
+ ipcRenderer.on('daemon-connected', () => {
+ this._onDaemonConnected();
+ });
+
+ ipcRenderer.on('daemon-disconnected', (errorMessage: ?string) => {
+ this._onDaemonDisconnected(errorMessage ? new Error(errorMessage) : null);
+ });
+
+ // Request the initial daemon connection status
+ ipcRenderer.once('daemon-connection-status-reply', (isConnected: boolean) => {
+ if (isConnected) {
+ this._onDaemonConnected();
+ }
+ });
+
+ ipcRenderer.send('daemon-connection-status');
+
// disable pinch to zoom
webFrame.setVisualZoomLevelLimits(1, 1);
}
@@ -141,14 +150,6 @@ export default class AppRenderer {
);
}
- connect() {
- this._daemonRpc.connect({ path: getIpcPath() });
- }
-
- disconnect() {
- this._daemonRpc.disconnect();
- }
-
async login(accountToken: AccountToken) {
const actions = this._reduxActions;
const history = this._memoryHistory;
@@ -294,10 +295,9 @@ export default class AppRenderer {
async updateAccountExpiry() {
const settings = await this._settingsProxy.fetch();
- const accountDataCache = this._accountDataCache;
if (settings && settings.accountToken) {
- accountDataCache.fetch(settings.accountToken);
+ this._accountDataCache.fetch(settings.accountToken);
}
}
@@ -435,12 +435,9 @@ export default class AppRenderer {
});
}
- async _onOpenConnection() {
+ async _onDaemonConnected() {
this._connectedToDaemon = true;
- // reset the reconnect backoff when connection established.
- this._reconnectBackoff.reset();
-
try {
await this._runPrimaryApplicationFlow();
} catch (error) {
@@ -448,6 +445,23 @@ export default class AppRenderer {
}
}
+ _onDaemonDisconnected(error: ?Error) {
+ const actions = this._reduxActions;
+
+ this._relayListCache.stopUpdating();
+
+ // recover connection on error
+ if (error) {
+ // only send to the connecting to daemon view if the daemon was
+ // connnected previously
+ if (this._connectedToDaemon) {
+ actions.history.replace('/');
+ }
+ }
+
+ this._connectedToDaemon = false;
+ }
+
async _runPrimaryApplicationFlow() {
// fetch initial state and subscribe for changes
try {
@@ -554,30 +568,6 @@ export default class AppRenderer {
}
}
- _onCloseConnection(error: ?Error) {
- const actions = this._reduxActions;
-
- this._relayListCache.stopUpdating();
-
- // recover connection on error
- if (error) {
- log.debug(`Lost connection to daemon: ${error.message}`);
-
- this._reconnectBackoff.attempt(() => {
- this.connect();
- });
-
- // only send to the connecting to daemon view if the daemon was
- // connnected previously
- if (this._connectedToDaemon) {
- actions.history.replace('/');
- }
- } else {
- log.info(`Disconnected from the daemon`);
- }
- this._connectedToDaemon = false;
- }
-
_setTunnelState(tunnelState: TunnelStateTransition) {
log.debug(`Tunnel state: ${tunnelState.state}`);
@@ -797,11 +787,3 @@ class RelayListCache {
}
}
}
-
-function getIpcPath(): string {
- if (process.platform === 'win32') {
- return '//./pipe/Mullvad VPN';
- } else {
- return '/var/run/mullvad-vpn';
- }
-}
diff --git a/gui/packages/desktop/src/renderer/components/Account.js b/gui/packages/desktop/src/renderer/components/Account.js
index 0792e58343..8db9887993 100644
--- a/gui/packages/desktop/src/renderer/components/Account.js
+++ b/gui/packages/desktop/src/renderer/components/Account.js
@@ -9,7 +9,7 @@ import { Layout, Container } from './Layout';
import { NavigationBar, BackBarItem } from './NavigationBar';
import styles from './AccountStyles';
-import type { AccountToken } from '../lib/daemon-rpc';
+import type { AccountToken } from '../lib/daemon-rpc-proxy';
type Props = {
accountToken: AccountToken,
diff --git a/gui/packages/desktop/src/renderer/components/Connect.js b/gui/packages/desktop/src/renderer/components/Connect.js
index baacce3a8b..021dbd579f 100644
--- a/gui/packages/desktop/src/renderer/components/Connect.js
+++ b/gui/packages/desktop/src/renderer/components/Connect.js
@@ -10,7 +10,7 @@ import * as AppButton from './AppButton';
import TunnelControl from './TunnelControl';
import Map from './Map';
import styles from './ConnectStyles';
-import { NoCreditError, NoInternetError } from '../errors';
+import { NoCreditError, NoInternetError } from '../../main/errors';
import type { RelayOutAddress, RelayInAddress } from './TunnelControl';
import type { ConnectionReduxState } from '../redux/connection/reducers';
diff --git a/gui/packages/desktop/src/renderer/components/Login.js b/gui/packages/desktop/src/renderer/components/Login.js
index c89ec100d3..972b0e3f18 100644
--- a/gui/packages/desktop/src/renderer/components/Login.js
+++ b/gui/packages/desktop/src/renderer/components/Login.js
@@ -10,7 +10,7 @@ import styles from './LoginStyles';
import { colors } from '../../config';
import type { LoginState } from '../redux/account/reducers';
-import type { AccountToken } from '../lib/daemon-rpc';
+import type { AccountToken } from '../lib/daemon-rpc-proxy';
type Props = {
accountToken: ?AccountToken,
diff --git a/gui/packages/desktop/src/renderer/components/NotificationArea.js b/gui/packages/desktop/src/renderer/components/NotificationArea.js
index a3d4625be8..8c99c31e26 100644
--- a/gui/packages/desktop/src/renderer/components/NotificationArea.js
+++ b/gui/packages/desktop/src/renderer/components/NotificationArea.js
@@ -12,7 +12,7 @@ import {
} from './NotificationBanner';
import { AuthFailure } from '../lib/auth-failure';
-import type { BlockReason, TunnelStateTransition } from '../lib/daemon-rpc';
+import type { BlockReason, TunnelStateTransition } from '../lib/daemon-rpc-proxy';
import type { VersionReduxState } from '../redux/version/reducers';
type Props = {
diff --git a/gui/packages/desktop/src/renderer/components/SelectLocation.js b/gui/packages/desktop/src/renderer/components/SelectLocation.js
index d1933a37e5..153984529c 100644
--- a/gui/packages/desktop/src/renderer/components/SelectLocation.js
+++ b/gui/packages/desktop/src/renderer/components/SelectLocation.js
@@ -21,7 +21,7 @@ import type {
RelayLocationCityRedux,
RelayLocationRelayRedux,
} from '../redux/settings/reducers';
-import type { RelayLocation } from '../lib/daemon-rpc';
+import type { RelayLocation } from '../lib/daemon-rpc-proxy';
import { colors } from '../../config';
type Props = {
diff --git a/gui/packages/desktop/src/renderer/components/Support.js b/gui/packages/desktop/src/renderer/components/Support.js
index 9a61fb0a1c..cdc0346fee 100644
--- a/gui/packages/desktop/src/renderer/components/Support.js
+++ b/gui/packages/desktop/src/renderer/components/Support.js
@@ -8,7 +8,7 @@ import { Layout, Container } from './Layout';
import { NavigationBar, BackBarItem } from './NavigationBar';
import styles from './SupportStyles';
-import type { AccountToken } from '../lib/daemon-rpc';
+import type { AccountToken } from '../lib/daemon-rpc-proxy';
import type { SupportReportForm } from '../redux/support/actions';
type SupportState = {
email: string,
diff --git a/gui/packages/desktop/src/renderer/components/TunnelControl.js b/gui/packages/desktop/src/renderer/components/TunnelControl.js
index b8e0a45b98..c2788501fe 100644
--- a/gui/packages/desktop/src/renderer/components/TunnelControl.js
+++ b/gui/packages/desktop/src/renderer/components/TunnelControl.js
@@ -6,7 +6,7 @@ import { ConnectionInfo, SecuredLabel, SecuredDisplayStyle, ImageView } from '@m
import * as AppButton from './AppButton';
import { colors } from '../../config';
-import type { TunnelState, RelayProtocol } from '../lib/daemon-rpc';
+import type { TunnelState, RelayProtocol } from '../lib/daemon-rpc-proxy';
export type RelayInAddress = {
ip: string,
diff --git a/gui/packages/desktop/src/renderer/index.js b/gui/packages/desktop/src/renderer/index.js
index 39553123fc..eea0c96fc9 100644
--- a/gui/packages/desktop/src/renderer/index.js
+++ b/gui/packages/desktop/src/renderer/index.js
@@ -6,8 +6,6 @@ import App from './app';
const app = new App();
const view = app.renderView();
-app.connect();
-
RX.App.initialize(true, true);
RX.UserInterface.setMainView(view);
RX.UserInterface.useCustomScrollbars(true);
diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js
new file mode 100644
index 0000000000..df2c5b72b6
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc-proxy.js
@@ -0,0 +1,237 @@
+// @flow
+
+import { ipcRenderer } from 'electron';
+import log from 'electron-log';
+import uuid from 'uuid';
+
+export type {
+ AccountToken,
+ AccountData,
+ AppVersionInfo,
+ Settings,
+ TunnelStateTransition,
+ RelayList,
+ RelaySettingsUpdate,
+ RelaySettings,
+ RelaySettingsCustom,
+ RelaySettingsNormalUpdate,
+ RelayLocation,
+ RelayProtocol,
+ Ip,
+ Location,
+ TunnelEndpoint,
+ BlockReason,
+ AfterDisconnect,
+ ResponseParseError,
+ TunnelState,
+ DaemonRpcProtocol,
+} from '../../main/daemon-rpc';
+
+export { ConnectionObserver, SubscriptionListener } from '../../main/daemon-rpc';
+
+import {
+ NoCreditError,
+ NoInternetError,
+ NoDaemonError,
+ InvalidAccountError,
+ CommunicationError,
+} from '../../main/errors';
+
+import { TimeOutError, RemoteError } from '../../main/jsonrpc-client';
+
+import type {
+ AccountToken,
+ AccountData,
+ AppVersionInfo,
+ DaemonRpcProtocol,
+ RelaySettingsUpdate,
+ RelayList,
+ TunnelStateTransition,
+ Settings,
+ Location,
+} from '../../main/daemon-rpc';
+import { ConnectionObserver, SubscriptionListener } from '../../main/daemon-rpc';
+
+type ErrorInfo = {
+ className: string,
+ data: Object,
+};
+
+export default class DaemonRpcProxy implements DaemonRpcProtocol {
+ _connectionObservers: Array<ConnectionObserver> = [];
+ _stateListeners: Array<SubscriptionListener<TunnelStateTransition>> = [];
+ _settingsListeners: Array<SubscriptionListener<Settings>> = [];
+
+ constructor() {
+ ipcRenderer.on('connected-daemon', () => {
+ for (const observer of this._connectionObservers) {
+ observer._onOpen();
+ }
+ });
+
+ ipcRenderer.on('disconnected-daemon', (_event: Event, error: ?Error) => {
+ for (const observer of this._connectionObservers) {
+ observer._onClose(error);
+ }
+ });
+
+ ipcRenderer.on('state-changed', (_event: Event, newState: TunnelStateTransition) => {
+ for (const listener of this._stateListeners) {
+ listener._onEvent(newState);
+ }
+ });
+
+ ipcRenderer.on('settings-changed', (_event: Event, newSettings: Settings) => {
+ for (const listener of this._settingsListeners) {
+ listener._onEvent(newSettings);
+ }
+ });
+ }
+
+ connect(_connectionInfo: { path: string }): void {
+ throw new Error('Do not call this method.');
+ }
+
+ disconnect(): void {
+ throw new Error('Do not call this method.');
+ }
+
+ getAccountData(accountToken: AccountToken): Promise<AccountData> {
+ return this._sendMessage('getAccountData', accountToken);
+ }
+
+ getRelayLocations(): Promise<RelayList> {
+ return this._sendMessage('getRelayLocations');
+ }
+
+ setAccount(accountToken: ?AccountToken): Promise<void> {
+ return this._sendMessage('setAccount', accountToken);
+ }
+
+ updateRelaySettings(update: RelaySettingsUpdate): Promise<void> {
+ return this._sendMessage('updateRelaySettings', update);
+ }
+
+ setAllowLan(allowLan: boolean): Promise<void> {
+ return this._sendMessage('setAllowLan', allowLan);
+ }
+
+ setEnableIpv6(enableIpv6: boolean): Promise<void> {
+ return this._sendMessage('setEnableIpv6', enableIpv6);
+ }
+
+ setOpenVpnMssfix(mssfix: ?number): Promise<void> {
+ return this._sendMessage('setOpenVpnMssfix', mssfix);
+ }
+
+ setAutoConnect(autoConnect: boolean): Promise<void> {
+ return this._sendMessage('setAutoConnect', autoConnect);
+ }
+
+ connectTunnel(): Promise<void> {
+ return this._sendMessage('connectTunnel');
+ }
+
+ disconnectTunnel(): Promise<void> {
+ return this._sendMessage('disconnectTunnel');
+ }
+
+ getLocation(): Promise<Location> {
+ return this._sendMessage('getLocation');
+ }
+
+ getState(): Promise<TunnelStateTransition> {
+ return this._sendMessage('getState');
+ }
+
+ getSettings(): Promise<Settings> {
+ return this._sendMessage('getSettings');
+ }
+
+ subscribeStateListener(listener: SubscriptionListener<TunnelStateTransition>): Promise<void> {
+ this._stateListeners.push(listener);
+ return Promise.resolve();
+ }
+
+ subscribeSettingsListener(listener: SubscriptionListener<Settings>): Promise<void> {
+ this._settingsListeners.push(listener);
+ return Promise.resolve();
+ }
+
+ addConnectionObserver(observer: ConnectionObserver): void {
+ this._connectionObservers.push(observer);
+ }
+
+ removeConnectionObserver(observer: ConnectionObserver): void {
+ const index = this._connectionObservers.indexOf(observer);
+ if (index !== -1) {
+ this._connectionObservers.splice(index, 1);
+ }
+ }
+
+ getAccountHistory(): Promise<Array<AccountToken>> {
+ return this._sendMessage('getAccountHistory');
+ }
+
+ removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
+ return this._sendMessage('removeAccountFromHistory', accountToken);
+ }
+
+ getCurrentVersion(): Promise<string> {
+ return this._sendMessage('getCurrentVersion');
+ }
+
+ getVersionInfo(): Promise<AppVersionInfo> {
+ return this._sendMessage('getVersionInfo');
+ }
+
+ _sendMessage<T, R>(method: string, payload: ?T): Promise<R> {
+ const promise: Promise<R> = new Promise((resolve, reject) => {
+ const id = uuid.v4();
+
+ ipcRenderer.once(
+ `daemon-rpc-reply-${id}`,
+ (_event: Event, result: R, errorInfo: ?ErrorInfo) => {
+ log.debug(
+ `Got daemon-rpc-reply: ${id} ${method} ${JSON.stringify(result)} ${JSON.stringify(
+ errorInfo,
+ )}`,
+ );
+
+ if (errorInfo) {
+ const error = this._deserializeError(errorInfo.className, errorInfo.data);
+ log.debug(`Deserialized an error to instance of ${error.constructor.name}`);
+ reject(error);
+ } else {
+ resolve(result);
+ }
+ },
+ );
+
+ ipcRenderer.send(`daemon-rpc-call`, id, method, payload);
+ });
+
+ return promise;
+ }
+
+ _deserializeError(className: string, data: Object): Error {
+ switch (className) {
+ case 'RemoteError':
+ return new RemoteError(data.code, data.details);
+ case 'TimeOutError':
+ return new TimeOutError(data._jsonRpcMessage);
+ case 'NoCreditError':
+ return new NoCreditError();
+ case 'NoInternetError':
+ return new NoInternetError();
+ case 'NoDaemonError':
+ return new NoDaemonError();
+ case 'InvalidAccountError':
+ return new InvalidAccountError();
+ case 'CommunicationError':
+ return new CommunicationError();
+ default:
+ return new Error(data.message || '');
+ }
+ }
+}
diff --git a/gui/packages/desktop/src/renderer/lib/notification-controller.js b/gui/packages/desktop/src/renderer/lib/notification-controller.js
index cd829de90e..06118b3562 100644
--- a/gui/packages/desktop/src/renderer/lib/notification-controller.js
+++ b/gui/packages/desktop/src/renderer/lib/notification-controller.js
@@ -4,7 +4,7 @@ import { remote } from 'electron';
import log from 'electron-log';
import config from '../../config';
-import type { TunnelStateTransition } from './daemon-rpc';
+import type { TunnelStateTransition } from './daemon-rpc-proxy';
export default class NotificationController {
_lastTunnelStateNotification: ?Notification;
diff --git a/gui/packages/desktop/src/renderer/lib/relay-settings-builder.js b/gui/packages/desktop/src/renderer/lib/relay-settings-builder.js
index dc865472d5..ba85fe26ed 100644
--- a/gui/packages/desktop/src/renderer/lib/relay-settings-builder.js
+++ b/gui/packages/desktop/src/renderer/lib/relay-settings-builder.js
@@ -6,7 +6,7 @@ import type {
RelaySettingsUpdate,
RelaySettingsNormalUpdate,
RelaySettingsCustom,
-} from './daemon-rpc';
+} from './daemon-rpc-proxy';
type LocationBuilder<Self> = {
country: (country: string) => Self,
diff --git a/gui/packages/desktop/src/renderer/lib/subscription-proxy/base-proxy.js b/gui/packages/desktop/src/renderer/lib/subscription-proxy/base-proxy.js
index b3ebfd2595..5d34ff8839 100644
--- a/gui/packages/desktop/src/renderer/lib/subscription-proxy/base-proxy.js
+++ b/gui/packages/desktop/src/renderer/lib/subscription-proxy/base-proxy.js
@@ -1,8 +1,8 @@
// @flow
import log from 'electron-log';
-import { ConnectionObserver, SubscriptionListener, ResponseParseError } from '../daemon-rpc';
-import type { DaemonRpcProtocol } from '../daemon-rpc';
+import { ConnectionObserver, SubscriptionListener, ResponseParseError } from '../daemon-rpc-proxy';
+import type { DaemonRpcProtocol } from '../daemon-rpc-proxy';
export default class BaseSubscriptionProxy<T> {
_rpc: DaemonRpcProtocol;
diff --git a/gui/packages/desktop/src/renderer/lib/subscription-proxy/settings-proxy.js b/gui/packages/desktop/src/renderer/lib/subscription-proxy/settings-proxy.js
index 6dc3e7e9b7..c7fb9b3b3b 100644
--- a/gui/packages/desktop/src/renderer/lib/subscription-proxy/settings-proxy.js
+++ b/gui/packages/desktop/src/renderer/lib/subscription-proxy/settings-proxy.js
@@ -1,8 +1,8 @@
// @flow
import BaseSubscriptionProxy from './base-proxy';
-import { SubscriptionListener } from '../daemon-rpc';
-import type { DaemonRpcProtocol, Settings } from '../daemon-rpc';
+import { SubscriptionListener } from '../daemon-rpc-proxy';
+import type { DaemonRpcProtocol, Settings } from '../daemon-rpc-proxy';
export default class SettingsProxy extends BaseSubscriptionProxy<Settings> {
static subscribeValueListener(rpc: DaemonRpcProtocol, listener: SubscriptionListener<Settings>) {
diff --git a/gui/packages/desktop/src/renderer/lib/subscription-proxy/tunnel-state-proxy.js b/gui/packages/desktop/src/renderer/lib/subscription-proxy/tunnel-state-proxy.js
index 520885fe98..4c1afa66d6 100644
--- a/gui/packages/desktop/src/renderer/lib/subscription-proxy/tunnel-state-proxy.js
+++ b/gui/packages/desktop/src/renderer/lib/subscription-proxy/tunnel-state-proxy.js
@@ -1,8 +1,8 @@
// @flow
import BaseSubscriptionProxy from './base-proxy';
-import { SubscriptionListener } from '../daemon-rpc';
-import type { DaemonRpcProtocol, TunnelStateTransition } from '../daemon-rpc';
+import { SubscriptionListener } from '../daemon-rpc-proxy';
+import type { DaemonRpcProtocol, TunnelStateTransition } from '../daemon-rpc-proxy';
export default class TunnelStateProxy extends BaseSubscriptionProxy<TunnelStateTransition> {
static subscribeValueListener(
diff --git a/gui/packages/desktop/src/renderer/redux/account/actions.js b/gui/packages/desktop/src/renderer/redux/account/actions.js
index 5f336b5955..a5dfd13412 100644
--- a/gui/packages/desktop/src/renderer/redux/account/actions.js
+++ b/gui/packages/desktop/src/renderer/redux/account/actions.js
@@ -1,6 +1,6 @@
// @flow
-import type { AccountToken } from '../../lib/daemon-rpc';
+import type { AccountToken } from '../../lib/daemon-rpc-proxy';
type StartLoginAction = {
type: 'START_LOGIN',
diff --git a/gui/packages/desktop/src/renderer/redux/account/reducers.js b/gui/packages/desktop/src/renderer/redux/account/reducers.js
index 7263b51166..01405d0fef 100644
--- a/gui/packages/desktop/src/renderer/redux/account/reducers.js
+++ b/gui/packages/desktop/src/renderer/redux/account/reducers.js
@@ -1,7 +1,7 @@
// @flow
import type { ReduxAction } from '../store';
-import type { AccountToken } from '../../lib/daemon-rpc';
+import type { AccountToken } from '../../lib/daemon-rpc-proxy';
export type LoginState = 'none' | 'logging in' | 'failed' | 'ok';
export type AccountReduxState = {
diff --git a/gui/packages/desktop/src/renderer/redux/connection/actions.js b/gui/packages/desktop/src/renderer/redux/connection/actions.js
index 6f8a297678..54ae56746a 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/actions.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/actions.js
@@ -1,6 +1,6 @@
// @flow
-import type { AfterDisconnect, BlockReason, TunnelEndpoint } from '../../lib/daemon-rpc';
+import type { AfterDisconnect, BlockReason, TunnelEndpoint } from '../../lib/daemon-rpc-proxy';
type ConnectingAction = {
type: 'CONNECTING',
diff --git a/gui/packages/desktop/src/renderer/redux/connection/reducers.js b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
index 622663dc14..ab9c8df75c 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/reducers.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
@@ -1,7 +1,7 @@
// @flow
import type { ReduxAction } from '../store';
-import type { TunnelStateTransition, Ip } from '../../lib/daemon-rpc';
+import type { TunnelStateTransition, Ip } from '../../lib/daemon-rpc-proxy';
export type ConnectionReduxState = {
status: TunnelStateTransition,
diff --git a/gui/packages/desktop/src/renderer/redux/settings/reducers.js b/gui/packages/desktop/src/renderer/redux/settings/reducers.js
index c733475cb6..eba3341e7a 100644
--- a/gui/packages/desktop/src/renderer/redux/settings/reducers.js
+++ b/gui/packages/desktop/src/renderer/redux/settings/reducers.js
@@ -1,7 +1,7 @@
// @flow
import type { ReduxAction } from '../store';
-import type { RelayProtocol, RelayLocation } from '../../lib/daemon-rpc';
+import type { RelayProtocol, RelayLocation } from '../../lib/daemon-rpc-proxy';
export type RelaySettingsRedux =
| {|
diff --git a/gui/packages/desktop/src/renderer/redux/version/actions.js b/gui/packages/desktop/src/renderer/redux/version/actions.js
index 4eee3fcc61..a3e07502d4 100644
--- a/gui/packages/desktop/src/renderer/redux/version/actions.js
+++ b/gui/packages/desktop/src/renderer/redux/version/actions.js
@@ -1,6 +1,6 @@
// @flow
-import type { AppVersionInfo } from '../../lib/daemon-rpc';
+import type { AppVersionInfo } from '../../lib/daemon-rpc-proxy';
type UpdateLatestActionPayload = {
upToDate: boolean,
diff --git a/gui/packages/desktop/test/account-data-cache.spec.js b/gui/packages/desktop/test/account-data-cache.spec.js
index 821de1af9b..17770fac77 100644
--- a/gui/packages/desktop/test/account-data-cache.spec.js
+++ b/gui/packages/desktop/test/account-data-cache.spec.js
@@ -1,7 +1,7 @@
// @flow
import { AccountDataCache } from '../src/renderer/app';
-import type { AccountData } from '../src/renderer/lib/daemon-rpc';
+import type { AccountData } from '../src/renderer/lib/daemon-rpc-proxy';
describe('AccountData cache', () => {
const dummyAccountToken = '9876543210';
diff --git a/gui/packages/desktop/test/jsonrpc-transport.spec.js b/gui/packages/desktop/test/jsonrpc-transport.spec.js
index ea07e060b8..42d6de7c23 100644
--- a/gui/packages/desktop/test/jsonrpc-transport.spec.js
+++ b/gui/packages/desktop/test/jsonrpc-transport.spec.js
@@ -1,10 +1,7 @@
// @flow
import jsonrpc from 'jsonrpc-lite';
import { Server } from 'mock-socket';
-import JsonRpcClient, {
- WebsocketTransport,
- TimeOutError,
-} from '../src/renderer/lib/jsonrpc-client';
+import JsonRpcClient, { WebsocketTransport, TimeOutError } from '../src/main/jsonrpc-client';
describe('JSON RPC transport', () => {
const WEBSOCKET_URL = 'ws://localhost:8080';