diff options
| author | Andrej Mihajlov <and@codeispoetry.ru> | 2017-03-10 18:49:16 +0000 |
|---|---|---|
| committer | Andrej Mihajlov <and@codeispoetry.ru> | 2017-03-10 18:49:16 +0000 |
| commit | ab936cc298a81dbf0ef13e53bd03cff7f2324d13 (patch) | |
| tree | 9aea6453b22ffa3948a16c6fb6171ecaa321ba7f | |
| parent | 767148f976f942bb68d4bc6c4d09b076aa6e39e0 (diff) | |
| download | mullvadvpn-ab936cc298a81dbf0ef13e53bd03cff7f2324d13.tar.xz mullvadvpn-ab936cc298a81dbf0ef13e53bd03cff7f2324d13.zip | |
Update documentation
| -rw-r--r-- | .vscode/settings.json | 3 | ||||
| -rw-r--r-- | app/components/HeaderBar.js | 2 | ||||
| -rw-r--r-- | app/enums.js | 8 | ||||
| -rw-r--r-- | app/lib/backend-redux-actions.js | 7 | ||||
| -rw-r--r-- | app/lib/backend-routing.js | 7 | ||||
| -rw-r--r-- | app/lib/backend.js | 248 | ||||
| -rw-r--r-- | app/lib/enum.js | 58 | ||||
| -rw-r--r-- | app/lib/formatters.js | 4 | ||||
| -rw-r--r-- | app/routes.js | 13 | ||||
| -rw-r--r-- | app/store.js | 8 | ||||
| -rw-r--r-- | test/enum.spec.js | 12 |
11 files changed, 310 insertions, 60 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c4e1bdbcb..1a1f9a53e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "search.exclude": { "build/": true, - "dist/": true + "dist/": true, + "docs/": true } }
\ No newline at end of file diff --git a/app/components/HeaderBar.js b/app/components/HeaderBar.js index 899dc02cd6..f3dfa88e53 100644 --- a/app/components/HeaderBar.js +++ b/app/components/HeaderBar.js @@ -5,7 +5,7 @@ import Enum from '../lib/enum'; export default class HeaderBar extends Component { /** Bar style */ - static Style = Enum('default', 'defaultDark', 'error', 'success'); + static Style = new Enum('default', 'defaultDark', 'error', 'success'); static propTypes = { style: PropTypes.string, diff --git a/app/enums.js b/app/enums.js index b6ed213c57..bc14ba7258 100644 --- a/app/enums.js +++ b/app/enums.js @@ -2,20 +2,20 @@ import Enum from './lib/enum'; /** * Login state enum used for React components - * @type {enum} + * @type {LoginState} * @property {string} none Initial state (not logged in) * @property {string} connecting Attempting to log in * @property {string} failed Failed to log in * @property {string} ok Logged in */ -export const LoginState = Enum('none', 'connecting', 'failed', 'ok'); +export const LoginState = new Enum('none', 'connecting', 'failed', 'ok'); /** * Connection state enum used for React components - * @type {enum} + * @type {ConnectionState} * @property {string} disconnected Initial state (disconnected) * @property {string} connecting Connecting * @property {string} connected Connected * @property {string} failed Failed to connect */ -export const ConnectionState = Enum('disconnected', 'connecting', 'connected', 'failed'); +export const ConnectionState = new Enum('disconnected', 'connecting', 'connected', 'failed'); diff --git a/app/lib/backend-redux-actions.js b/app/lib/backend-redux-actions.js index 89690da2fe..b1cc14fcc5 100644 --- a/app/lib/backend-redux-actions.js +++ b/app/lib/backend-redux-actions.js @@ -3,6 +3,13 @@ import connectActions from '../actions/connect'; import Backend from './backend'; import { LoginState, ConnectionState } from '../enums'; +/** + * Add event listeners to translate backend events to redux dispatch. + * + * @export + * @param {Backend} backend + * @param {Redux.Store} store + */ export default function mapBackendEventsToReduxActions(backend, store) { const onUpdateIp = (clientIp) => { store.dispatch(connectActions.connectionChange({ clientIp })); diff --git a/app/lib/backend-routing.js b/app/lib/backend-routing.js index 0ea354a594..4f13d48333 100644 --- a/app/lib/backend-routing.js +++ b/app/lib/backend-routing.js @@ -1,6 +1,13 @@ import { replace } from 'react-router-redux'; import Backend from './backend'; +/** + * Add listeners to translate backend events to react router actions + * + * @export + * @param {Backend} backend + * @param {Redux.Store} store + */ export default function mapBackendEventsToRouter(backend, store) { // redirect user to main screen after login backend.on(Backend.EventType.login, (account, error) => { diff --git a/app/lib/backend.js b/app/lib/backend.js index 19b47647c2..88a73e9129 100644 --- a/app/lib/backend.js +++ b/app/lib/backend.js @@ -4,8 +4,73 @@ import { EventEmitter } from 'events'; import { servers } from '../config'; import { ConnectionState as ReduxConnectionState } from '../enums'; -const EventType = Enum('connect', 'connecting', 'disconnect', 'login', 'logging', 'logout', 'updatedIp', 'updatedLocation'); -const ConnectionState = Enum('disconnected', 'connecting', 'connected'); +/** + * Server info + * @typedef {object} ServerInfo + * @property {string} address - server address + * @property {string} name - server name + * @property {string} city - location city + * @property {string} country - location country + * @property {number[]} location - geo coordinate [latitude, longitude] +* + */ + +/** + * Connect event + * + * @event Backend.EventType.connect + * @param {string} addr - server address + * @param {error|null} error - error + */ + +/** + * Connecting event + * + * @event Backend.EventType.connecting + * @param {string} addr - server address + */ + +/** + * Disconnect event + * + * @event Backend.EventType.disconnect + * @param {string} addr - server address + */ + +/** + * Login event + * + * @event Backend.EventType.login + * @param {object} response + * @param {error} error + */ + +/** + * Logging event + * + * @event Backend.EventType.logging + * @param {object} response + */ + +/** + * Logout event + * + * @event Backend.EventType.logout + */ + +/** + * Updated IP event + * + * @event Backend.EventType.updatedIp + * @param {string} new IP address + */ + +/** + * Updated location event + * + * @event Backend.EventType.updatedLocation + * @param {object} location data + */ /** * Backend implementation @@ -13,26 +78,81 @@ const ConnectionState = Enum('disconnected', 'connecting', 'connected'); * @class Backend */ export default class Backend extends EventEmitter { + + /** + * Event type enum + * + * @type {EventType} + * @extends {Enum} + * @property {string} connect + * @property {string} connecting + * @property {string} disconnect + * @property {string} login + * @property {string} logging + * @property {string} logout + * @property {string} updatedIp + * @property {string} updatedLocation + */ + static EventType = new Enum('connect', 'connecting', 'disconnect', 'login', 'logging', 'logout', 'updatedIp', 'updatedLocation'); + + /** + * Connection state enum + * + * @type {ConnectionState} + * @extends {Enum} + * @property {string} disconnected - Initial state (disconnected) + * @property {string} connecting - Connecting + * @property {string} connected - Connected + */ + static ConnectionState = new Enum('disconnected', 'connecting', 'connected'); - static EventType = EventType; - static ConnectionState = ConnectionState; - + /** + * Creates an instance of Backend. + * + * @memberOf Backend + */ constructor() { super(); this._account = null; this._paidUntil = null; this._serverAddress = null; - this._connStatus = ConnectionState.disconnected; + this._connStatus = Backend.ConnectionState.disconnected; this._cancellationHandler = null; // update IP in background - setTimeout(::this.refreshIp, 0); + setTimeout(::this._refreshIp, 0); } // Accessors + /** + * Account number + * + * @type {string} + * @readonly + * + * @memberOf Backend + */ get account() { return this._account; } + + /** + * Until when services are paid for (ISO string) + * + * @type {string} + * @readonly + * + * @memberOf Backend + */ get paidUntil() { return this._paidUntil; } + + /** + * Server IP address or domain name + * + * @type {string} + * @readonly + * + * @memberOf Backend + */ get serverAddress() { return this._serverAddress; } // Public methods @@ -47,11 +167,15 @@ export default class Backend extends EventEmitter { * sync redux state with backend. * * In future this will be the other way around. + * + * @param {Redux.Store} store - an instance of Redux store + * + * @memberOf Backend */ syncWithReduxStore(store) { const mapConnStatus = (s) => { const S = ReduxConnectionState; - const BS = ConnectionState; + const BS = Backend.ConnectionState; switch(s) { case S.connected: return BS.connected; case S.connecting: return BS.connecting; @@ -76,6 +200,15 @@ export default class Backend extends EventEmitter { this._connStatus = mapConnStatus(connect.status); } + /** + * Get server info by key + * 'fastest' or 'nearest' can be used as well + * + * @param {string} key + * @returns {ServerInfo} + * + * @memberOf Backend + */ serverInfo(key) { switch(key) { case 'fastest': return this.fastestServer(); @@ -84,6 +217,13 @@ export default class Backend extends EventEmitter { } } + /** + * Get fastest server + * + * @returns {ServerInfo} + * + * @memberOf Backend + */ fastestServer() { return { address: 'uk.mullvad.net', @@ -94,6 +234,13 @@ export default class Backend extends EventEmitter { }; } + /** + * Get nearest server info + * + * @returns {ServerInfo} + * + * @memberOf Backend + */ nearestServer() { return { address: 'es.mullvad.net', @@ -104,12 +251,21 @@ export default class Backend extends EventEmitter { }; } + /** + * Log in with mullvad account + * + * @emits Backend.EventType.logging + * @emits Backend.EventType.login + * @param {string} account + * + * @memberOf Backend + */ login(account) { this._account = account; this._paidUntil = null; // emit: logging in - this.emit(EventType.logging, { account, paidUntil: this._paidUntil }, null); + this.emit(Backend.EventType.logging, { account, paidUntil: this._paidUntil }, null); // @TODO: Add login call setTimeout(() => { @@ -127,16 +283,22 @@ export default class Backend extends EventEmitter { } // emit: login - this.emit(EventType.login, res, err); + this.emit(Backend.EventType.login, res, err); }, 2000); } + /** + * Log out + * + * @emits Backend.EventType.logout + * @memberOf Backend + */ logout() { this._account = null; this._paidUntil = null; // emit event - this.emit(EventType.logout); + this.emit(Backend.EventType.logout); // disconnect user during logout this.disconnect(); @@ -144,14 +306,23 @@ export default class Backend extends EventEmitter { // @TODO: Add logout call } + /** + * Connect to VPN server + * @emits Backend.EventType.connecting + * @emits Backend.EventType.connect + * + * @param {string} addr IP address or domain name + * + * @memberOf Backend + */ connect(addr) { this.disconnect(); - this._connStatus = ConnectionState.connecting; + this._connStatus = Backend.ConnectionState.connecting; this._serverAddress = addr; // emit: connecting - this.emit(EventType.connecting, addr); + this.emit(Backend.EventType.connecting, addr); // @TODO: Add connect call let timer = null; @@ -164,11 +335,11 @@ export default class Backend extends EventEmitter { err = new Error('Server is unreachable'); } - this._connStatus = ConnectionState.connected; + this._connStatus = Backend.ConnectionState.connected; // emit: connect - this.emit(EventType.connect, addr, err); - this.refreshIp(); + this.emit(Backend.EventType.connect, addr, err); + this._refreshIp(); // reset timer timer = null; @@ -181,45 +352,68 @@ export default class Backend extends EventEmitter { this._timer = null; } this._cancellationHandler = null; - this._connStatus = ConnectionState.disconnected; + this._connStatus = Backend.ConnectionState.disconnected; }; } + /** + * Disconnect from VPN server + * + * @emits Backend.EventType.disconnect + * @memberOf Backend + */ disconnect() { - if(this._connStatus === ConnectionState.disconnected) { return; } + if(this._connStatus === Backend.ConnectionState.disconnected) { return; } - this._connStatus = ConnectionState.disconnected; + this._connStatus = Backend.ConnectionState.disconnected; this._serverAddress = null; // cancel ongoing connection attempt if(this._cancellationHandler) { this._cancellationHandler(); } else { - this.refreshIp(); + this._refreshIp(); } // emit: disconnect - this.emit(EventType.disconnect); + this.emit(Backend.EventType.disconnect); // @TODO: Add disconnect call } - + + /** + * Fetch user location + * + * @private + * @returns {promise} + * + * @memberOf Backend + */ _fetchLocation() { return fetch('https://freegeoip.net/json/').then((res) => { return res.json(); }); } - refreshIp() { - if(this._connStatus === ConnectionState.disconnected) { + /** + * Request updates for user IP + * + * @private + * @emits Backend.EventType.updatedLocation + * @emits Backend.EventType.updatedIp + * + * @memberOf Backend + */ + _refreshIp() { + if(this._connStatus === Backend.ConnectionState.disconnected) { this._fetchLocation().then((res) => { const data = { location: [ res.latitude, res.longitude ], // lat, lng city: res.city, country: res.country_name }; - this.emit(EventType.updatedLocation, data); - this.emit(EventType.updatedIp, res.ip); + this.emit(Backend.EventType.updatedLocation, data); + this.emit(Backend.EventType.updatedIp, res.ip); }).catch((error) => { console.log('Got error: ', error); }); @@ -231,6 +425,6 @@ export default class Backend extends EventEmitter { ip.push(parseInt(Math.random() * 253 + 1)); } - this.emit(EventType.updatedIp, ip.join('.')); + this.emit(Backend.EventType.updatedIp, ip.join('.')); } } diff --git a/app/lib/enum.js b/app/lib/enum.js index 1815a551f7..a27bd37439 100644 --- a/app/lib/enum.js +++ b/app/lib/enum.js @@ -1,28 +1,46 @@ /** - * Creates enum object with keys provided as arguments - * - * @constructor - * @type Enum - * @property {bool} isValid({string}) whether key is valid - * @param {...string} ... Enum keys - * @export - * @returns Enum + * Enum + * @exports + * @class Enum */ -export default function Enum() { - let object = Object.create({}); - const keys = [...arguments]; +export default class Enum { - for(const key of keys) { - Object.defineProperty(object, key, { - enumerable: true, - value: key, + /** + * Creates an instance of EnumBase. + * + * @param {...string} ... - enum keys + * @memberOf Enum + */ + constructor() { + const keys = [...arguments]; + + for(const key of keys) { + Object.defineProperty(this, key, { + enumerable: true, + value: key, + writable: false + }); + } + + Object.defineProperty(this, 'allKeys', { + enumerable: false, + value: keys, writable: false }); + + Object.freeze(this); } - - Object.defineProperty(object, 'isValid', { - value: (e) => keys.includes(e) - }); - return Object.freeze(object); + /** + * Check if key is registered in this enum + * + * @param {string} key + * @returns {bool} + * + * @memberOf Enum + */ + isValid(key) { + return this.allKeys.includes(key); + } } + diff --git a/app/lib/formatters.js b/app/lib/formatters.js index 966d9976bf..c5357cabd9 100644 --- a/app/lib/formatters.js +++ b/app/lib/formatters.js @@ -1,5 +1,9 @@ import assert from 'assert'; +/** + * Format account number + * @param {string} val account number + */ export const formatAccount = (val) => { assert(typeof(val) === 'string'); diff --git a/app/routes.js b/app/routes.js index 7edd8ad1d9..577b8eeb9d 100644 --- a/app/routes.js +++ b/app/routes.js @@ -10,7 +10,14 @@ import SelectLocationPage from './containers/SelectLocationPage'; import { LoginState } from './enums'; -const makeRoutes = (store) => { +/** + * Create routes + * + * @export + * @param {Redux.Store} store + * @returns {React.element} + */ +export default function makeRoutes(store) { /** * Ensures that user is redirected to /connect if logged in @@ -43,6 +50,4 @@ const makeRoutes = (store) => { <Route path="select-location" component={ SelectLocationPage } onEnter={ ensureLoggedIn } /> </Route> ); -}; - -export default makeRoutes; +} diff --git a/app/store.js b/app/store.js index c2ccbf3b4a..789b786ff4 100644 --- a/app/store.js +++ b/app/store.js @@ -10,6 +10,14 @@ import userActions from './actions/user'; import connectActions from './actions/connect'; import settingsActions from './actions/settings'; +/** + * Configure redux store + * + * @export + * @param {Object} initialState + * @param {History} routerHistory + * @returns {Redux.Store} + */ export default function configureStore(initialState, routerHistory) { const router = routerMiddleware(routerHistory); diff --git a/test/enum.spec.js b/test/enum.spec.js index dec15b219c..554a35b922 100644 --- a/test/enum.spec.js +++ b/test/enum.spec.js @@ -3,20 +3,26 @@ import Enum from '../app/lib/enum'; describe('enum', () => { it('should be able to compare values', () => { - const e = Enum('NORTH', 'SOUTH', 'WEST', 'EAST'); + const e = new Enum('NORTH', 'SOUTH', 'WEST', 'EAST'); expect(e.NORTH).to.be.equal('NORTH'); }); it('should not be able to modify enum', () => { - let e = Enum('NORTH', 'SOUTH', 'WEST', 'EAST'); + const e = new Enum('NORTH', 'SOUTH', 'WEST', 'EAST'); expect(() => e.ANYWHERE = 'ANYWHERE').to.throw(); }); it('should be able to validate enum keys', () => { - let e = Enum('NORTH', 'SOUTH', 'WEST', 'EAST'); + const e = new Enum('NORTH', 'SOUTH', 'WEST', 'EAST'); expect(e.isValid('SOUTH')).to.be.true; expect(e.isValid('ANYWHERE')).to.be.false; expect(e.isValid()).to.be.false; expect(e.isValid(null)).to.be.false; }); + + it('should receive correct keys from Object.keys', () => { + const keys = ['NORTH', 'SOUTH', 'WEST', 'EAST']; + const e = new Enum(...keys); + expect(Object.keys(e)).to.be.deep.equal(keys); + }); }); |
