summaryrefslogtreecommitdiffhomepage
path: root/app
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@codeispoetry.ru>2017-02-21 14:12:39 +0000
committerAndrej Mihajlov <and@codeispoetry.ru>2017-02-21 14:12:39 +0000
commit0fd89cfc1c91d90ec625199ba0460bb5fb8c9ec6 (patch)
tree04b2f15f7f9aef83aa749b29757458486a11fee6 /app
parent191d72546accf308696f57ee14d27c1b221ad4b9 (diff)
downloadmullvadvpn-0fd89cfc1c91d90ec625199ba0460bb5fb8c9ec6.tar.xz
mullvadvpn-0fd89cfc1c91d90ec625199ba0460bb5fb8c9ec6.zip
State management via Backend events
Diffstat (limited to 'app')
-rw-r--r--app/actions/connect.js7
-rw-r--r--app/actions/user.js38
-rw-r--r--app/app.js52
-rw-r--r--app/components/Connect.css1
-rw-r--r--app/components/Connect.js13
-rw-r--r--app/components/SelectLocation.js10
-rw-r--r--app/constants.js25
-rw-r--r--app/lib/backend.js101
-rw-r--r--app/lib/private.js10
-rw-r--r--app/reducers/connect.js6
-rw-r--r--app/reducers/settings.js7
11 files changed, 174 insertions, 96 deletions
diff --git a/app/actions/connect.js b/app/actions/connect.js
index 518822a5b4..2d629d8a1c 100644
--- a/app/actions/connect.js
+++ b/app/actions/connect.js
@@ -1,3 +1,8 @@
import { createAction } from 'redux-actions';
+import { ConnectionState } from '../constants';
-export default {};
+const connectionChange = createAction('CONNECTION_CHANGE');
+const connect = (backend, addr) => (dispatch, getState) => backend.connect(addr);
+const disconnect = (backend) => (dispatch, getState) => backend.disconect();
+
+export default { connect, disconnect, connectionChange };
diff --git a/app/actions/user.js b/app/actions/user.js
index 3e856da294..3111a0a83a 100644
--- a/app/actions/user.js
+++ b/app/actions/user.js
@@ -5,42 +5,20 @@ import { LoginState } from '../constants';
const loginChange = createAction('USER_LOGIN_CHANGE');
const login = (backend, account) => {
- return async (dispatch) => {
- try {
- dispatch(loginChange({ status: LoginState.connecting, account }));
-
- await backend.login(account);
-
- // report success to login screen
- dispatch(loginChange({ status: LoginState.ok }));
-
+ return (dispatch) => {
+ backend.login(account).then(() => {
// show connection screen after delay
setTimeout(() => dispatch(replace('/connect')), 1000);
- } catch(e) {
- dispatch(loginChange({ status: LoginState.failed, error: e }));
- }
+ });
};
};
const logout = (backend) => {
- return async (dispatch) => {
- try {
- await backend.logout();
- } catch(e) {
- console.log(`Failed to log out: ${e.message}`);
- }
-
- // reset login information
- dispatch(loginChange({ status: LoginState.none, account: '', error: null }));
-
- // redirect user to /
- dispatch(replace('/'));
+ return (dispatch) => {
+ return backend.logout().then(() => {
+ dispatch(replace('/'));
+ });
};
};
-export default {
- login,
- logout,
- loginChange
-};
-
+export default { login, logout, loginChange };
diff --git a/app/app.js b/app/app.js
index 8789608887..a7c0aca8f3 100644
--- a/app/app.js
+++ b/app/app.js
@@ -6,9 +6,10 @@ import { syncHistoryWithStore } from 'react-router-redux';
import { webFrame } from 'electron';
import routes from './routes';
import configureStore from './store';
+import userActions from './actions/user';
+import connectActions from './actions/connect';
import Backend from './lib/backend';
-
-const backend = new Backend();
+import { LoginState, ConnectionState } from './constants';
const initialState = {};
const store = configureStore(initialState);
@@ -29,6 +30,53 @@ const rootElement = document.querySelector(document.currentScript.getAttribute('
// disable smart pinch.
webFrame.setVisualZoomLevelLimits(1, 1);
+// Create backend
+const backend = new Backend();
+
+// Setup events
+
+backend.on(Backend.EventType.connecting, (serverAddress) => {
+ store.dispatch(connectActions.connectionChange({
+ status: ConnectionState.connecting,
+ error: null,
+ serverAddress
+ }));
+});
+
+backend.on(Backend.EventType.connect, (serverAddress, error) => {
+ const status = error ? ConnectionState.disconnected : ConnectionState.connected;
+ store.dispatch(connectActions.connectionChange({ error, status }));
+});
+
+backend.on(Backend.EventType.disconnect, () => {
+ store.dispatch(connectActions.connectionChange({
+ status: ConnectionState.disconnected,
+ serverAddress: null,
+ error: null
+ }));
+});
+
+backend.on(Backend.EventType.logging, (account) => {
+ store.dispatch(userActions.loginChange({
+ status: LoginState.connecting,
+ error: null,
+ account
+ }));
+});
+
+backend.on(Backend.EventType.login, (account, error) => {
+ const status = error ? LoginState.failed : LoginState.ok;
+ store.dispatch(userActions.loginChange({ status, error }));
+});
+
+backend.on(Backend.EventType.logout, () => {
+ store.dispatch(userActions.loginChange({
+ status: LoginState.none,
+ account: null,
+ error: null
+ }));
+});
+
// helper method for router to pass backend down the component tree
const createElement = (Component, props) => {
const newProps = { ...props, backend };
diff --git a/app/components/Connect.css b/app/components/Connect.css
index 7e6de03e94..37a49bd98a 100644
--- a/app/components/Connect.css
+++ b/app/components/Connect.css
@@ -59,7 +59,6 @@
font-weight: 900;
line-height: 26px;
color: #FFFFFF;
- text-transform: uppercase;
text-align: right;
}
diff --git a/app/components/Connect.js b/app/components/Connect.js
index 9dda61c2ee..ec89dd897d 100644
--- a/app/components/Connect.js
+++ b/app/components/Connect.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { Layout, Container, Header } from './Layout';
+import { servers } from '../constants';
export default class Connect extends Component {
@@ -12,6 +13,16 @@ export default class Connect extends Component {
}
render() {
+ let serverName;
+ const preferredServer = this.props.settings.preferredServer;
+
+ // special types of servers (Fastest, Nearest)
+ if(preferredServer === 'Fastest' || preferredServer === 'Nearest') {
+ serverName = preferredServer;
+ } else {
+ serverName = (servers[preferredServer] || {}).name;
+ }
+
return (
<Layout>
<Header showSettings={ true } onSettings={ ::this.onSettings } />
@@ -24,7 +35,7 @@ export default class Connect extends Component {
<div className="connect__row">
<div className="connect__server" onClick={ ::this.openLocationPicker }>
<div className="connect__server-label">Connect to</div>
- <div className="connect__server-country">{ this.props.settings.preferredServer }</div>
+ <div className="connect__server-country">{ serverName }</div>
</div>
</div>
diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js
index 702eea074b..41ef55fd33 100644
--- a/app/components/SelectLocation.js
+++ b/app/components/SelectLocation.js
@@ -33,7 +33,7 @@ export default class SelectLocation extends Component {
return key === this.props.settings.preferredServer;
}
- drawCell(name, icon, onClick) {
+ drawCell(key, name, icon, onClick) {
const classes = ['select-location__cell'];
const selected = this.isSelected(name);
@@ -44,7 +44,7 @@ export default class SelectLocation extends Component {
const cellClass = classes.join(' ');
return (
- <div key={ name } className={ cellClass } onClick={ onClick }>
+ <div key={ key } className={ cellClass } onClick={ onClick }>
<If condition={ !!icon }>
<Then>
@@ -81,12 +81,12 @@ export default class SelectLocation extends Component {
<CustomScrollbars autoHide={ true }>
<div>
- { this.drawCell('Fastest', './assets/images/icon-fastest.svg', ::this.handleFastest) }
- { this.drawCell('Nearest', './assets/images/icon-nearest.svg', ::this.handleNearest) }
+ { this.drawCell('fastest', 'Fastest', './assets/images/icon-fastest.svg', ::this.handleFastest) }
+ { this.drawCell('nearest', 'Nearest', './assets/images/icon-nearest.svg', ::this.handleNearest) }
<div className="select-location__separator"></div>
- { servers.map((name) => this.drawCell(name, null, this.handleSelection.bind(this, name))) }
+ { Object.keys(servers).map((key) => this.drawCell(key, servers[key].name, null, this.handleSelection.bind(this, key))) }
</div>
</CustomScrollbars>
diff --git a/app/constants.js b/app/constants.js
index 75b29e1e3e..76a69ff558 100644
--- a/app/constants.js
+++ b/app/constants.js
@@ -1,6 +1,7 @@
import Enum from './lib/enum';
const LoginState = Enum('none', 'connecting', 'failed', 'ok');
+const ConnectionState = Enum('disconnected', 'connecting', 'connected', 'failed');
module.exports = {
links: {
@@ -9,11 +10,21 @@ module.exports = {
guides: 'https://mullvad.net/guides/',
supportEmail: 'mailto:support@mullvad.net'
},
- servers: [
- 'Canada', 'Canada (Quebec)', 'Denmark', 'Germany',
- 'Lithuania', 'The Netherlands', 'Norway', 'Romania',
- 'Singapore', 'Spain', 'Sweden', 'Switzerland',
- 'United Kingdom', 'USA'
- ],
- LoginState
+ servers: {
+ 'ca1.mullvad.net': { name: 'Canada' },
+ 'ca2.mullvad.net': { name: 'Canada (Quebec)' },
+ 'da.mullvad.net': { name: 'Denmark' },
+ 'de.mullvad.net': { name: 'Germany' },
+ 'lt.mullvad.net': { name: 'Lithuania' },
+ 'nl.mullvad.net': { name: 'The Netherlands' },
+ 'no.mullvad.net': { name: 'Norway' },
+ 'ro.mullvad.net': { name: 'Romania' },
+ 'sg.mullvad.net': { name: 'Singapore' },
+ 'es.mullvad.net': { name: 'Spain' },
+ 'se1.mullvad.net': { name: 'Sweden', isDefault: true },
+ 'ch.mullvad.net': { name: 'Switzerland' },
+ 'uk.mullvad.net': { name: 'United Kingdom' },
+ 'us1.mullvad.net': { name: 'USA' }
+ },
+ LoginState, ConnectionState
};
diff --git a/app/lib/backend.js b/app/lib/backend.js
index 55560cca2c..288ea6d181 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -1,37 +1,53 @@
-import privateData from './private';
+import Enum from './enum';
+import { EventEmitter } from 'events';
-/**
- * Private data
- */
-const { get: getImpl, set: setImpl } = privateData();
+const EventType = Enum('connect', 'connecting', 'disconnect', 'login', 'logging', 'logout');
/**
- * Remote backend implementation
+ * Backend implementation
*
- * @class BackendImpl
+ * @class Backend
*/
-class BackendImpl {
+export default class Backend extends EventEmitter {
+
+ static EventType = EventType;
+
constructor() {
+ super();
this._account = null;
this._loggedIn = false;
+ this._serverAddress = null;
}
- get account() {
- return this._account;
- }
+ // Accessors
- get loggedIn() {
- return this._loggedIn;
- }
+ get account() { return this._account; }
+ get loggedIn() { return this._loggedIn; }
+ get serverAddress() { return this._serverAddress; }
+
+ // Public methods
login(account) {
return new Promise((resolve, reject) => {
+ this._account = account;
+
+ // emit: logging in
+ this.emit(EventType.logging, account);
+
// @TODO: Add login call
setTimeout(() => {
if(account.startsWith('1111')) {
+ // emit: login
+ this.emit(EventType.login, account);
+
resolve(true);
} else {
- reject(new Error('Invalid account number.'));
+ const err = new Error('Invalid account number.');
+
+ // emit: login
+ this.emit(EventType.login, account, err);
+
+ reject(err);
}
}, 2000);
});
@@ -39,38 +55,49 @@ class BackendImpl {
logout() {
return new Promise((resolve, reject) => {
+ this._account = null;
+
+ // emit event
+ this.emit(EventType.logout);
+
// @TODO: Add logout call
resolve();
});
}
-}
-/**
- * Backend implementation
- *
- * @export
- * @class Backend
- */
-export default class Backend {
+ connect(addr) {
+ return new Promise((resolve, reject) => {
+ this._serverAddress = addr;
+
+ // @TODO: Add connect call
+ setTimeout(() => {
+ if(/se\d+\.mullvad\.net/.test(addr)) {
- constructor() {
- setImpl(this, new BackendImpl());
- }
+ // emit: connect
+ this.emit(EventType.connect, addr, err);
+
+ resolve(true);
+ } else {
+ const err = new Error('Server is unreachable');
- get account() {
- return getImpl(this).account;
- }
+ // emit: connect
+ this.emit(EventType.connect, addr, err);
- get loggedIn() {
- return getImpl(this).loggedIn;
+ reject(err);
+ }
+ }, 2000);
+ });
}
- login(account) {
- return getImpl(this).login(account);
- }
+ disconnect() {
+ return new Promise((resolve, reject) => {
+ this._serverAddress = null;
- logout() {
- return getImpl(this).logout();
- }
+ // emit: disconnect
+ this.emit(EventType.disconnect);
+ // @TODO: Add disconnect call
+ resolve();
+ });
+ }
}
diff --git a/app/lib/private.js b/app/lib/private.js
deleted file mode 100644
index 61e574ee12..0000000000
--- a/app/lib/private.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Data incapsulation helper
- */
-export default () => {
- const store = new WeakMap();
- return {
- get: (k) => store.get(k),
- set: (k, v) => store.set(k, v)
- };
-};
diff --git a/app/reducers/connect.js b/app/reducers/connect.js
index 97e47c5e72..e3ed877b55 100644
--- a/app/reducers/connect.js
+++ b/app/reducers/connect.js
@@ -4,4 +4,8 @@ import actions from '../actions/connect';
const initialState = {};
-export default handleActions({ test: (state) => { return state; } }, initialState);
+export default handleActions({
+ [actions.updateConnectionState]: (state, action) => {
+ return { ...state, ...action.payload };
+ }
+}, initialState);
diff --git a/app/reducers/settings.js b/app/reducers/settings.js
index 022288b90c..6436b2c560 100644
--- a/app/reducers/settings.js
+++ b/app/reducers/settings.js
@@ -1,10 +1,15 @@
import { handleActions } from 'redux-actions';
+import { servers } from '../constants';
+
import actions from '../actions/settings';
+const addrs = Object.keys(servers);
+const defaultServer = addrs.find((k) => servers[k].isDefault) || servers[addrs[0]] || {};
+
const initialState = {
autoSecure: false,
- preferredServer: 'Sweden'
+ preferredServer: defaultServer
};
export default handleActions({