diff options
| author | Andrej Mihajlov <and@codeispoetry.ru> | 2017-02-21 14:12:39 +0000 |
|---|---|---|
| committer | Andrej Mihajlov <and@codeispoetry.ru> | 2017-02-21 14:12:39 +0000 |
| commit | 0fd89cfc1c91d90ec625199ba0460bb5fb8c9ec6 (patch) | |
| tree | 04b2f15f7f9aef83aa749b29757458486a11fee6 | |
| parent | 191d72546accf308696f57ee14d27c1b221ad4b9 (diff) | |
| download | mullvadvpn-0fd89cfc1c91d90ec625199ba0460bb5fb8c9ec6.tar.xz mullvadvpn-0fd89cfc1c91d90ec625199ba0460bb5fb8c9ec6.zip | |
State management via Backend events
| -rw-r--r-- | app/actions/connect.js | 7 | ||||
| -rw-r--r-- | app/actions/user.js | 38 | ||||
| -rw-r--r-- | app/app.js | 52 | ||||
| -rw-r--r-- | app/components/Connect.css | 1 | ||||
| -rw-r--r-- | app/components/Connect.js | 13 | ||||
| -rw-r--r-- | app/components/SelectLocation.js | 10 | ||||
| -rw-r--r-- | app/constants.js | 25 | ||||
| -rw-r--r-- | app/lib/backend.js | 101 | ||||
| -rw-r--r-- | app/lib/private.js | 10 | ||||
| -rw-r--r-- | app/reducers/connect.js | 6 | ||||
| -rw-r--r-- | app/reducers/settings.js | 7 |
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({ |
