summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--app/app.android.js10
-rw-r--r--app/app.js10
-rw-r--r--app/components/Connect.js17
-rw-r--r--app/lib/backend.js135
-rw-r--r--app/lib/jsonrpc-ws-ipc.js29
-rw-r--r--app/redux/account/actions.js6
-rw-r--r--app/redux/account/reducers.js3
7 files changed, 113 insertions, 97 deletions
diff --git a/app/app.android.js b/app/app.android.js
index 38bb927a93..d362f25283 100644
--- a/app/app.android.js
+++ b/app/app.android.js
@@ -8,7 +8,7 @@ import { createMemoryHistory } from 'history';
import makeRoutes from './routes';
import configureStore from './redux/store';
import { log } from './lib/platform';
-import { Backend, BackendError } from './lib/backend';
+import { Backend, NoAccountError } from './lib/backend';
import { DeviceEventEmitter } from 'react-native';
import { MobileAppBridge } from 'NativeModules';
import { Dimensions } from 'react-native';
@@ -31,11 +31,9 @@ DeviceEventEmitter.addListener('com.mullvad.backend-info', async (_event, args)
await backend.fetchSecurityState();
await backend.connect();
} catch (e) {
- if (e instanceof BackendError) {
- if (e.type === 'NO_ACCOUNT') {
- log.debug('No user set in the backend, showing window');
- MobileAppBridge.showWindow();
- }
+ if (e instanceof NoAccountError) {
+ log.debug('No previously configured account set, showing window');
+ MobileAppBridge.showWindow();
}
}
});
diff --git a/app/app.js b/app/app.js
index 055972fdf5..dadc2177c2 100644
--- a/app/app.js
+++ b/app/app.js
@@ -9,7 +9,7 @@ import { webFrame, ipcRenderer } from 'electron';
import { log } from './lib/platform';
import makeRoutes from './routes';
import configureStore from './redux/store';
-import { Backend, BackendError } from './lib/backend';
+import { Backend, NoAccountError } from './lib/backend';
import { setShutdownHandler } from './shutdown-handler';
@@ -33,11 +33,9 @@ ipcRenderer.on('backend-info', async (_event, args) => {
await backend.fetchSecurityState();
await backend.connect();
} catch (e) {
- if (e instanceof BackendError) {
- if (e.type === 'NO_ACCOUNT') {
- log.debug('No user set in the backend, showing window');
- ipcRenderer.send('show-window');
- }
+ if (e instanceof NoAccountError) {
+ log.debug('No previously configured account set, showing window');
+ ipcRenderer.send('show-window');
}
}
});
diff --git a/app/components/Connect.js b/app/components/Connect.js
index 3fbe04cefa..f4647678f1 100644
--- a/app/components/Connect.js
+++ b/app/components/Connect.js
@@ -9,7 +9,7 @@ import { TransparentButton, RedTransparentButton, GreenButton, Label } from './s
import Accordion from './Accordion';
import styles from './ConnectStyles';
-import { BackendError } from '../lib/backend';
+import { NoCreditError, NoInternetError } from '../lib/backend';
import Map from './Map';
import type { HeaderBarStyle } from './HeaderBar';
@@ -79,15 +79,18 @@ export default class Connect extends Component<ConnectProps, ConnectState> {
);
}
- renderError(error: BackendError) {
+ renderError(error: Error) {
+ const title = error.userFriendlyTitle || 'Something went wrong';
+ const message = error.userFriendlyMessage || error.message;
+
return (
<View style={styles.connect}>
<View style={styles.status_icon}>
<Img source="icon-fail" height={60} width={60} alt="" />
</View>
<View style={styles.status}>
- <View style={styles.error_title}>{error.title}</View>
- <View style={styles.error_message}>{error.message}</View>
+ <View style={styles.error_title}>{title}</View>
+ <View style={styles.error_message}>{message}</View>
{error.type === 'NO_CREDIT' ? (
<View>
<GreenButton onPress={this.onExternalLink.bind(this, 'purchase')}>
@@ -339,16 +342,16 @@ export default class Connect extends Component<ConnectProps, ConnectState> {
return classes;
}
- displayError(): ?BackendError {
+ displayError(): ?Error {
// Offline?
if (!this.props.connection.isOnline) {
- return new BackendError('NO_INTERNET');
+ return new NoInternetError();
}
// No credit?
const expiry = this.props.accountExpiry;
if (expiry && moment(expiry).isSameOrBefore(moment())) {
- return new BackendError('NO_CREDIT');
+ return new NoCreditError();
}
return null;
diff --git a/app/lib/backend.js b/app/lib/backend.js
index c9f9969af3..c7b4ca53ec 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -2,6 +2,7 @@
import { log } from '../lib/platform';
import { IpcFacade, RealIpc } from './ipc-facade';
+import { JsonRpcError, TimeOutError } from './jsonrpc-ws-ipc';
import accountActions from '../redux/account/actions';
import connectionActions from '../redux/connection/actions';
import settingsActions from '../redux/settings/actions';
@@ -11,64 +12,65 @@ import type { ReduxStore } from '../redux/store';
import type { AccountToken, BackendState, RelaySettingsUpdate } from './ipc-facade';
import type { ConnectionState } from '../redux/connection/reducers';
-export type ErrorType =
- | 'NO_CREDIT'
- | 'NO_INTERNET'
- | 'NO_DAEMON'
- | 'INVALID_ACCOUNT'
- | 'NO_ACCOUNT'
- | 'COMMUNICATION_FAILURE'
- | 'UNKNOWN_ERROR';
+export class NoCreditError extends Error {
+ constructor() {
+ super("Account doesn't have enough credit available for connection");
+ }
-export class BackendError extends Error {
- type: ErrorType;
- title: string;
- message: string;
- cause: ?Error;
+ get userFriendlyTitle(): string {
+ return 'Out of time';
+ }
- constructor(type: ErrorType, cause?: Error) {
- super('');
- this.type = type;
- this.title = BackendError.localizedTitle(type);
- this.message = BackendError.localizedMessage(type, cause);
- this.cause = cause;
+ get userFriendlyMessage(): string {
+ return 'Buy more time, so you can continue using the internet securely';
}
+}
- static localizedTitle(type: ErrorType): string {
- switch (type) {
- case 'NO_CREDIT':
- return 'Out of time';
- case 'NO_INTERNET':
- return 'Offline';
- default:
- return 'Something went wrong';
- }
+export class NoInternetError extends Error {
+ constructor() {
+ super('Internet connectivity is currently unavailable');
}
- static localizedMessage(type: ErrorType, cause: ?Error): string {
- // TODO: since instanceof now works, BackendError can be replaced by a set
- // of specific error types
- switch (type) {
- case 'NO_CREDIT':
- return 'Buy more time, so you can continue using the internet securely';
- case 'NO_INTERNET':
- return 'Your internet connection will be secured when you get back online';
- case 'INVALID_ACCOUNT':
- return 'Invalid account number';
- case 'NO_ACCOUNT':
- return 'No account was set';
- case 'NO_DAEMON':
- return 'Could not connect to the Mullvad daemon';
- case 'COMMUNICATION_FAILURE':
- return 'api.mullvad.net is blocked, please check your firewall';
- case 'UNKNOWN_ERROR': {
- const message = cause ? ', ' + cause.message : '';
+ get userFriendlyTitle(): string {
+ return 'Offline';
+ }
- return 'An unknown error occurred' + message;
- }
- default:
- return '';
- }
+ get userFriendlyMessage(): string {
+ return 'Your internet connection will be secured when you get back online';
+ }
+}
+
+export class NoDaemonError extends Error {
+ constructor() {
+ super('Could not connect to Mullvad daemon');
+ }
+}
+
+export class InvalidAccountError extends Error {
+ constructor() {
+ super('Invalid account number');
+ }
+}
+
+export class NoAccountError extends Error {
+ constructor() {
+ super('No account was set');
+ }
+}
+
+export class CommunicationError extends Error {
+ constructor() {
+ super('api.mullvad.net is blocked, please check your firewall');
+ }
+}
+
+export class UnknownError extends Error {
+ constructor(cause: string) {
+ super(`An unknown error occurred, ${cause}`);
+ }
+
+ get userFriendlyTitle(): string {
+ return 'Something went wrong';
}
}
@@ -186,35 +188,30 @@ export class Backend {
} catch (e) {
log.error('Failed to log in,', e.message);
- const err = this._rpcErrorToBackendError(e);
- this._store.dispatch(accountActions.loginFailed(err));
+ const error = this._rpcErrorToBackendError(e);
+ this._store.dispatch(accountActions.loginFailed(error));
}
}
_rpcErrorToBackendError(e) {
- if (e instanceof BackendError) {
- return e;
- }
-
- const isJsonRpcError = e.hasOwnProperty('code');
- if (isJsonRpcError) {
+ if (e instanceof JsonRpcError) {
switch (e.code) {
case -200: // Account doesn't exist
- return new BackendError('INVALID_ACCOUNT');
+ return new InvalidAccountError();
case -32603: // Internal error
// We treat all internal backend errors as the user cannot reach
// api.mullvad.net. This is not always true of course, but it is
// true so often that we choose to disregard the other edge cases
// for now.
- return new BackendError('COMMUNICATION_FAILURE');
+ return new CommunicationError();
}
+ } else if (e instanceof TimeOutError) {
+ return new CommunicationError();
+ } else if (e instanceof NoDaemonError) {
+ return e;
}
- if (e.name === 'TimeOutError') {
- return new BackendError('COMMUNICATION_FAILURE');
- }
-
- return new BackendError('UNKNOWN_ERROR', e);
+ return new UnknownError(e.message);
}
async autologin() {
@@ -227,7 +224,7 @@ export class Backend {
const accountToken = await this._ipc.getAccount();
if (!accountToken) {
- throw new BackendError('NO_ACCOUNT');
+ throw new NoAccountError();
}
log.debug('The backend had an account number stored: ', accountToken);
@@ -365,7 +362,7 @@ export class Backend {
const accountToken = await this._ipc.getAccount();
if (!accountToken) {
- throw new BackendError('NO_ACCOUNT');
+ throw new NoAccountError();
}
const accountData = await ipc.getAccountData(accountToken);
@@ -531,7 +528,7 @@ export class Backend {
}
return this._authenticationPromise;
} else {
- return Promise.reject(new BackendError('NO_DAEMON'));
+ return Promise.reject(new NoDaemonError());
}
}
diff --git a/app/lib/jsonrpc-ws-ipc.js b/app/lib/jsonrpc-ws-ipc.js
index ff73f4cc88..84dec96c77 100644
--- a/app/lib/jsonrpc-ws-ipc.js
+++ b/app/lib/jsonrpc-ws-ipc.js
@@ -11,11 +11,12 @@ export type UnansweredRequest = {
message: Object,
};
-export type JsonRpcError = {
+export type JsonRpcErrorResponse = {
type: 'error',
payload: {
id: string,
error: {
+ code: number,
message: string,
},
},
@@ -37,7 +38,26 @@ export type JsonRpcSuccess = {
result: mixed,
},
};
-export type JsonRpcMessage = JsonRpcError | JsonRpcNotification | JsonRpcSuccess;
+export type JsonRpcMessage = JsonRpcErrorResponse | JsonRpcNotification | JsonRpcSuccess;
+
+export class JsonRpcError extends Error {
+ _code: number;
+ _details: string;
+
+ constructor(code: number, details: string) {
+ super(`Remote JSON-RPC error ${code}: ${details}`);
+ this._code = code;
+ this._details = details;
+ }
+
+ get code(): number {
+ return this._code;
+ }
+
+ get details(): string {
+ return this._details;
+ }
+}
export class TimeOutError extends Error {
jsonRpcMessage: Object;
@@ -200,7 +220,7 @@ export default class Ipc {
}
}
- _onReply(message: JsonRpcError | JsonRpcSuccess) {
+ _onReply(message: JsonRpcErrorResponse | JsonRpcSuccess) {
const id = message.payload.id;
const request = this._unansweredRequests.get(id);
this._unansweredRequests.delete(id);
@@ -215,7 +235,8 @@ export default class Ipc {
clearTimeout(request.timerId);
if (message.type === 'error') {
- request.reject(message.payload.error);
+ const error = message.payload.error;
+ request.reject(new JsonRpcError(error.code, error.message));
} else {
const reply = message.payload.result;
request.resolve(reply);
diff --git a/app/redux/account/actions.js b/app/redux/account/actions.js
index fdb9f44281..c778b111fd 100644
--- a/app/redux/account/actions.js
+++ b/app/redux/account/actions.js
@@ -1,7 +1,7 @@
// @flow
import type { AccountToken } from '../../lib/ipc-facade';
-import type { Backend, BackendError } from '../../lib/backend';
+import type { Backend } from '../../lib/backend';
type StartLoginAction = {
type: 'START_LOGIN',
@@ -15,7 +15,7 @@ type LoginSuccessfulAction = {
type LoginFailedAction = {
type: 'LOGIN_FAILED',
- error: BackendError,
+ error: Error,
};
type LoggedOutAction = {
@@ -65,7 +65,7 @@ function loginSuccessful(expiry: string): LoginSuccessfulAction {
};
}
-function loginFailed(error: BackendError): LoginFailedAction {
+function loginFailed(error: Error): LoginFailedAction {
return {
type: 'LOGIN_FAILED',
error: error,
diff --git a/app/redux/account/reducers.js b/app/redux/account/reducers.js
index f3485028d4..094864c9a8 100644
--- a/app/redux/account/reducers.js
+++ b/app/redux/account/reducers.js
@@ -1,7 +1,6 @@
// @flow
import type { ReduxAction } from '../store';
-import type { BackendError } from '../../lib/backend';
import type { AccountToken } from '../../lib/ipc-facade';
export type LoginState = 'none' | 'logging in' | 'failed' | 'ok';
@@ -10,7 +9,7 @@ export type AccountReduxState = {
accountHistory: Array<AccountToken>,
expiry: ?string, // ISO8601
status: LoginState,
- error: ?BackendError,
+ error: ?Error,
};
const initialState: AccountReduxState = {