summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@codeispoetry.ru>2017-03-10 18:49:16 +0000
committerAndrej Mihajlov <and@codeispoetry.ru>2017-03-10 18:49:16 +0000
commitab936cc298a81dbf0ef13e53bd03cff7f2324d13 (patch)
tree9aea6453b22ffa3948a16c6fb6171ecaa321ba7f
parent767148f976f942bb68d4bc6c4d09b076aa6e39e0 (diff)
downloadmullvadvpn-ab936cc298a81dbf0ef13e53bd03cff7f2324d13.tar.xz
mullvadvpn-ab936cc298a81dbf0ef13e53bd03cff7f2324d13.zip
Update documentation
-rw-r--r--.vscode/settings.json3
-rw-r--r--app/components/HeaderBar.js2
-rw-r--r--app/enums.js8
-rw-r--r--app/lib/backend-redux-actions.js7
-rw-r--r--app/lib/backend-routing.js7
-rw-r--r--app/lib/backend.js248
-rw-r--r--app/lib/enum.js58
-rw-r--r--app/lib/formatters.js4
-rw-r--r--app/routes.js13
-rw-r--r--app/store.js8
-rw-r--r--test/enum.spec.js12
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);
+ });
});