summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--app/app.js22
-rw-r--r--app/lib/backend.js38
-rw-r--r--app/lib/ipc-facade.js162
-rw-r--r--app/lib/jsonrpc-ws-ipc.js9
-rw-r--r--app/main.js9
-rw-r--r--test/mocks/ipc.js7
6 files changed, 142 insertions, 105 deletions
diff --git a/app/app.js b/app/app.js
index 452e940a2b..c0682343c3 100644
--- a/app/app.js
+++ b/app/app.js
@@ -12,7 +12,6 @@ import makeRoutes from './routes';
import configureStore from './redux/store';
import { Backend } from './lib/backend';
-import type { IpcCredentials } from './lib/backend';
import type { ConnectionState } from './redux/connection/reducers';
import type { TrayIconType } from './lib/tray-icon-manager';
@@ -25,19 +24,14 @@ const store = configureStore(initialState, memoryHistory);
//////////////////////////////////////////////////////////////////////////
const backend = new Backend(store);
ipcRenderer.on('backend-info', (_event, args) => {
- const credentials: IpcCredentials = args.credentials;
-
- backend.setLocation(credentials.connectionString);
- backend.authenticate(credentials.sharedSecret)
- .then(() => {
- backend.sync();
- backend.autologin()
- .catch( e => {
- if (e.type === 'NO_ACCOUNT') {
- log.debug('No user set in the backend, showing window');
- ipcRenderer.send('show-window');
- }
- });
+ backend.setCredentials(args.credentials);
+ backend.sync();
+ backend.autologin()
+ .catch( e => {
+ if (e.type === 'NO_ACCOUNT') {
+ log.debug('No user set in the backend, showing window');
+ ipcRenderer.send('show-window');
+ }
});
});
ipcRenderer.on('disconnect', () => {
diff --git a/app/lib/backend.js b/app/lib/backend.js
index ebd5a8dec5..a8b560812f 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -9,9 +9,8 @@ import connectionActions from '../redux/connection/actions';
import type { ReduxStore } from '../redux/store';
import { push } from 'react-router-redux';
-import type { BackendState } from './ipc-facade';
+import type { BackendState, RelayEndpoint, IpcCredentials } from './ipc-facade';
import type { ConnectionState } from '../redux/connection/reducers';
-import type { RelayEndpoint } from './ipc-facade';
export type EventType = 'connect' | 'connecting' | 'disconnect' | 'login' | 'logging' | 'logout' | 'updatedIp' | 'updatedLocation' | 'updatedReachability';
export type ErrorType = 'NO_CREDIT' | 'NO_INTERNET' | 'INVALID_ACCOUNT' | 'NO_ACCOUNT';
@@ -66,22 +65,6 @@ export class BackendError extends Error {
}
-export type IpcCredentials = {
- connectionString: string,
- sharedSecret: string,
-};
-
-export function parseIpcCredentials(data: string): ?IpcCredentials {
- const [connectionString, sharedSecret] = data.split('\n', 2);
- if(connectionString && sharedSecret) {
- return {
- connectionString,
- sharedSecret,
- };
- } else {
- return null;
- }
-}
/**
* Backend implementation
@@ -101,28 +84,17 @@ export class Backend {
}
}
- setLocation(loc: string) {
- log.info('Got connection info to backend', loc);
+ setCredentials(credentials: IpcCredentials) {
+ log.info('Got connection info to backend', credentials);
if (this._ipc) {
- this._ipc.setConnectionString(loc);
+ this._ipc.setCredentials(credentials);
} else {
- this._ipc = new RealIpc(loc);
+ this._ipc = new RealIpc(credentials);
}
this._registerIpcListeners();
}
- authenticate(sharedSecret: string): Promise<void> {
- return this._ipc.authenticate(sharedSecret)
- .then(() => {
- log.info('Authenticated with backend');
- })
- .catch((e) => {
- log.error('Backend authentication failed', e.message);
- throw e;
- });
- }
-
sync() {
log.info('Syncing with the backend...');
diff --git a/app/lib/ipc-facade.js b/app/lib/ipc-facade.js
index 246c864ad0..9e191ddf30 100644
--- a/app/lib/ipc-facade.js
+++ b/app/lib/ipc-facade.js
@@ -3,6 +3,7 @@
import JsonRpcWs, { InvalidReply } from './jsonrpc-ws-ipc';
import { object, string, arrayOf, number } from 'validated/schema';
import { validate } from 'validated/object';
+import log from 'electron-log';
import type { Coordinate2d } from '../types';
@@ -31,10 +32,26 @@ export type RelayEndpoint = {
protocol: 'tcp' | 'udp',
};
+export type IpcCredentials = {
+ connectionString: string,
+ sharedSecret: string,
+};
+
+export function parseIpcCredentials(data: string): ?IpcCredentials {
+ const [connectionString, sharedSecret] = data.split('\n', 2);
+ if(connectionString && sharedSecret) {
+ return {
+ connectionString,
+ sharedSecret,
+ };
+ } else {
+ return null;
+ }
+}
+
export interface IpcFacade {
- setConnectionString(string): void,
- authenticate(string): Promise<void>,
+ setCredentials(IpcCredentials): void,
getAccountData(AccountToken): Promise<AccountData>,
getAccount(): Promise<?AccountToken>,
setAccount(accountToken: ?AccountToken): Promise<void>,
@@ -50,18 +67,22 @@ export interface IpcFacade {
export class RealIpc implements IpcFacade {
_ipc: JsonRpcWs;
+ _credentials: ?IpcCredentials;
+ _authenticationPromise: ?Promise<void>;
- constructor(connectionString: string) {
- this._ipc = new JsonRpcWs(connectionString);
- }
+ constructor(credentials: IpcCredentials) {
+ this._credentials = credentials;
+ this._ipc = new JsonRpcWs(credentials.connectionString);
- setConnectionString(str: string) {
- this._ipc.setConnectionString(str);
+ // force to re-authenticate when connection closed
+ this._ipc.setCloseConnectionHandler(() => {
+ this._authenticationPromise = null;
+ });
}
- authenticate(sharedSecret: string): Promise<void> {
- return this._ipc.send('auth', sharedSecret)
- .then(this._ignoreResponse);
+ setCredentials(credentials: IpcCredentials) {
+ this._credentials = credentials;
+ this._ipc.setConnectionString(credentials.connectionString);
}
getAccountData(accountToken: AccountToken): Promise<AccountData> {
@@ -79,19 +100,23 @@ export class RealIpc implements IpcFacade {
}
getAccount(): Promise<?AccountToken> {
- return this._ipc.send('get_account')
- .then( raw => {
- if (raw === undefined || raw === null || typeof raw === 'string') {
- return raw;
- } else {
- throw new InvalidReply(raw);
- }
- });
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('get_account')
+ .then( raw => {
+ if (raw === undefined || raw === null || typeof raw === 'string') {
+ return raw;
+ } else {
+ throw new InvalidReply(raw);
+ }
+ });
+ });
}
setAccount(accountToken: ?AccountToken): Promise<void> {
- return this._ipc.send('set_account', accountToken)
- .then(this._ignoreResponse);
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('set_account', accountToken)
+ .then(this._ignoreResponse);
+ });
}
_ignoreResponse(_response: mixed): void {
@@ -99,48 +124,60 @@ export class RealIpc implements IpcFacade {
}
setCustomRelay(relayEndpoint: RelayEndpoint): Promise<void> {
- return this._ipc.send('set_custom_relay', [relayEndpoint])
- .then(this._ignoreResponse);
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('set_custom_relay', [relayEndpoint])
+ .then(this._ignoreResponse);
+ });
}
connect(): Promise<void> {
- return this._ipc.send('connect')
- .then(this._ignoreResponse);
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('connect')
+ .then(this._ignoreResponse);
+ });
}
disconnect(): Promise<void> {
- return this._ipc.send('disconnect')
- .then(this._ignoreResponse);
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('disconnect')
+ .then(this._ignoreResponse);
+ });
}
getIp(): Promise<Ip> {
- return this._ipc.send('get_ip')
- .then(raw => {
- if (typeof raw === 'string' && raw) {
- return raw;
- } else {
- throw new InvalidReply(raw, 'Expected a string');
- }
- });
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('get_ip')
+ .then(raw => {
+ if (typeof raw === 'string' && raw) {
+ return raw;
+ } else {
+ throw new InvalidReply(raw, 'Expected a string');
+ }
+ });
+ });
}
getLocation(): Promise<Location> {
- return this._ipc.send('get_location')
- .then(raw => {
- try {
- const validated: any = validate(LocationSchema, raw);
- return (validated: Location);
- } catch (e) {
- throw new InvalidReply(raw, e);
- }
- });
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('get_location')
+ .then(raw => {
+ try {
+ const validated: any = validate(LocationSchema, raw);
+ return (validated: Location);
+ } catch (e) {
+ throw new InvalidReply(raw, e);
+ }
+ });
+ });
}
getState(): Promise<BackendState> {
- return this._ipc.send('get_state')
- .then(raw => {
- return this._parseBackendState(raw);
- });
+ return this._ensureAuthenticated().then(() => {
+ return this._ipc.send('get_state')
+ .then(raw => {
+ return this._parseBackendState(raw);
+ });
+ });
}
_parseBackendState(raw: mixed): BackendState {
@@ -163,10 +200,35 @@ export class RealIpc implements IpcFacade {
}
registerStateListener(listener: (BackendState) => void) {
- this._ipc.on('new_state', (rawEvent) => {
- const parsedEvent : BackendState = this._parseBackendState(rawEvent);
+ this._ensureAuthenticated().then(() => {
+ this._ipc.on('new_state', (rawEvent) => {
+ const parsedEvent : BackendState = this._parseBackendState(rawEvent);
- listener(parsedEvent);
+ listener(parsedEvent);
+ });
});
}
+
+ _ensureAuthenticated(): Promise<void> {
+ if(this._credentials) {
+ const credentials = this._credentials;
+ if(!this._authenticationPromise) {
+ this._authenticationPromise = this._authenticate(credentials.sharedSecret);
+ }
+ return this._authenticationPromise;
+ } else {
+ return Promise.reject(new Error('Missing authentication credentials.'));
+ }
+ }
+
+ _authenticate(sharedSecret: string): Promise<void> {
+ return this._ipc.send('auth', sharedSecret)
+ .then(() => {
+ log.info('Authenticated with backend');
+ })
+ .catch((e) => {
+ log.error('Failed to authenticate with backend: ', e.message);
+ throw e;
+ });
+ }
}
diff --git a/app/lib/jsonrpc-ws-ipc.js b/app/lib/jsonrpc-ws-ipc.js
index 2609d1514c..deefce3d66 100644
--- a/app/lib/jsonrpc-ws-ipc.js
+++ b/app/lib/jsonrpc-ws-ipc.js
@@ -75,6 +75,7 @@ export default class Ipc {
_websocket: WebSocket;
_backoff: ReconnectionBackoff;
_websocketFactory: (string) => WebSocket;
+ _closeConnectionHandler: ?() => void;
constructor(connectionString: string, websocketFactory: ?(string)=>WebSocket) {
this._connectionString = connectionString;
@@ -91,6 +92,10 @@ export default class Ipc {
this._connectionString = str;
}
+ setCloseConnectionHandler(handler: ?() => void) {
+ this._closeConnectionHandler = handler;
+ }
+
on(event: string, listener: (mixed) => void): Promise<*> {
log.info('Adding a listener to', event);
@@ -246,6 +251,10 @@ export default class Ipc {
};
this._websocket.onclose = () => {
+ if(this._closeConnectionHandler) {
+ this._closeConnectionHandler();
+ }
+
const delay = this._backoff.getIncreasedBackoff();
log.warn('The websocket connetion closed, attempting to reconnect it in', delay, 'milliseconds');
setTimeout(() => this._reconnect(), delay);
diff --git a/app/main.js b/app/main.js
index a18bba32b9..1ca22fc115 100644
--- a/app/main.js
+++ b/app/main.js
@@ -7,7 +7,7 @@ import { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron';
import TrayIconManager from './lib/tray-icon-manager';
import ElectronSudo from 'electron-sudo';
import { version } from '../package.json';
-import { parseIpcCredentials } from './lib/backend';
+import { parseIpcCredentials } from './lib/ipc-facade';
import type { TrayIconType } from './lib/tray-icon-manager';
@@ -89,6 +89,7 @@ const appDelegate = {
browserWindowReady = true;
appDelegate._pollForConnectionInfoFile();
});
+
ipcMain.on('show-window', () => {
appDelegate._showWindow(window, appDelegate._tray);
});
@@ -102,6 +103,8 @@ const appDelegate = {
// create tray icon on macOS
if(isMacOS) {
appDelegate._tray = appDelegate._createTray(window);
+ } else {
+ appDelegate._showWindow(window, null);
}
appDelegate._setAppMenu();
@@ -259,6 +262,7 @@ const appDelegate = {
resizable: false,
maximizable: false,
fullscreenable: false,
+ show: false,
webPreferences: {
// prevents renderer process code from not running when window is hidden
backgroundThrottling: false,
@@ -272,8 +276,7 @@ const appDelegate = {
options = Object.assign({}, options, {
height: contentHeight + 12, // 12 is the size of transparent area around arrow
frame: false,
- transparent: true,
- show: false
+ transparent: true
});
}
diff --git a/test/mocks/ipc.js b/test/mocks/ipc.js
index fc5dede75e..8f0b82ba37 100644
--- a/test/mocks/ipc.js
+++ b/test/mocks/ipc.js
@@ -1,5 +1,5 @@
// @flow
-import type { IpcFacade, BackendState } from '../../app/lib/ipc-facade';
+import type { IpcFacade, BackendState, IpcCredentials } from '../../app/lib/ipc-facade';
interface MockIpc {
sendNewState: (BackendState) => void;
@@ -14,10 +14,7 @@ export function newMockIpc() {
const mockIpc: IpcFacade & MockIpc = {
- setConnectionString: (_str: string) => {},
- authenticate: (_sharedSecret) => {
- return new Promise(r => r());
- },
+ setCredentials: (_credentials: IpcCredentials) => {},
getAccountData: (accountToken) => {
return new Promise(r => r({
accountToken: accountToken,