summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2017-12-06 12:27:45 +0100
committerAndrej Mihajlov <and@mullvad.net>2017-12-06 12:27:45 +0100
commit7f20580a18bfde633a5d2cc5d7ed5210d414a9c1 (patch)
treed6df9069d816bbf5958a25e04d0b025e6acae5df
parentc3fea60e7073586875b91ee047b98e157a3cb746 (diff)
parented18963b050fa50098dd515bb12914fceb012cea (diff)
downloadmullvadvpn-7f20580a18bfde633a5d2cc5d7ed5210d414a9c1.tar.xz
mullvadvpn-7f20580a18bfde633a5d2cc5d7ed5210d414a9c1.zip
Merge branch 'normal-constraints-pr'
-rw-r--r--app/app.js26
-rw-r--r--app/components/AdvancedSettings.js11
-rw-r--r--app/components/Connect.js99
-rw-r--r--app/components/SelectLocation.js74
-rw-r--r--app/config.json179
-rw-r--r--app/containers/AdvancedSettingsPage.js70
-rw-r--r--app/containers/ConnectPage.js4
-rw-r--r--app/containers/SelectLocationPage.js29
-rw-r--r--app/lib/backend.js122
-rw-r--r--app/lib/relay-settings-builder.js195
-rw-r--r--app/redux/connection/actions.js17
-rw-r--r--app/redux/connection/reducers.js4
-rw-r--r--app/redux/settings/actions.js6
-rw-r--r--app/redux/settings/reducers.js32
-rw-r--r--test/auth.spec.js4
-rw-r--r--test/components/Connect.spec.js204
-rw-r--r--test/components/SelectLocation.spec.js8
-rw-r--r--test/components/Settings.spec.js8
-rw-r--r--test/connect.spec.js34
-rw-r--r--test/relay-settings-builder.spec.js151
20 files changed, 850 insertions, 427 deletions
diff --git a/app/app.js b/app/app.js
index 656e7dd5c0..a10d0da07c 100644
--- a/app/app.js
+++ b/app/app.js
@@ -9,7 +9,7 @@ import { webFrame, ipcRenderer } from 'electron';
import log from 'electron-log';
import makeRoutes from './routes';
import configureStore from './redux/store';
-import { Backend } from './lib/backend';
+import { Backend, BackendError } from './lib/backend';
import type { ConnectionState } from './redux/connection/reducers';
import type { TrayIconType } from './lib/tray-icon-manager';
@@ -22,25 +22,23 @@ const store = configureStore(initialState, memoryHistory);
// Backend
//////////////////////////////////////////////////////////////////////////
const backend = new Backend(store);
-ipcRenderer.on('backend-info', (_event, args) => {
+ipcRenderer.on('backend-info', async (_event, args) => {
backend.setCredentials(args.credentials);
backend.sync();
- backend.autologin()
- .then( () => {
- return backend.syncRelaySettings();
- })
- .then( () => {
- const { settings: { relaySettings: { host, protocol, port } } } = store.getState();
-
- return backend.connect(host, protocol, port);
- })
- .catch( e => {
- if (e.type === 'NO_ACCOUNT') {
+ try {
+ await backend.autologin();
+ await backend.fetchRelaySettings();
+ 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');
}
- });
+ }
+ }
});
+
ipcRenderer.on('shutdown', () => {
log.info('Been told by the node process to shutdown');
backend.shutdown()
diff --git a/app/components/AdvancedSettings.js b/app/components/AdvancedSettings.js
index 1821309369..b4306b81c8 100644
--- a/app/components/AdvancedSettings.js
+++ b/app/components/AdvancedSettings.js
@@ -7,16 +7,14 @@ import CustomScrollbars from './CustomScrollbars';
export class AdvancedSettings extends React.Component {
props: {
- host: string,
protocol: string,
- port: string|number,
- onUpdateConstraints: (host: string, protocol: string, port: string|number) => void,
+ port: string | number,
+ onUpdate: (protocol: string, port: string | number) => void,
onClose: () => void,
};
render() {
let portSelector = null;
- const host = this.props.host;
let protocol = this.props.protocol.toUpperCase();
if (protocol === 'AUTOMATIC') {
@@ -32,7 +30,7 @@ export class AdvancedSettings extends React.Component {
values={ ['Automatic', 'UDP', 'TCP'] }
value={ protocol }
onSelect={ protocol => {
- this.props.onUpdateConstraints(host, protocol, 'Automatic');
+ this.props.onUpdate(protocol, 'Automatic');
}}/>
<div className="settings__cell-spacer"></div>
@@ -43,7 +41,6 @@ export class AdvancedSettings extends React.Component {
}
_createPortSelector() {
- const host = this.props.host;
const protocol = this.props.protocol.toUpperCase();
const ports = protocol === 'TCP'
? ['Automatic', 80, 443]
@@ -54,7 +51,7 @@ export class AdvancedSettings extends React.Component {
values={ ports }
value={ this.props.port }
onSelect={ port => {
- this.props.onUpdateConstraints(host, protocol, port);
+ this.props.onUpdate(protocol, port);
}} />;
}
}
diff --git a/app/components/Connect.js b/app/components/Connect.js
index 2569e4b4b3..0975e18a1a 100644
--- a/app/components/Connect.js
+++ b/app/components/Connect.js
@@ -11,6 +11,7 @@ import type { ServerInfo } from '../lib/backend';
import type { HeaderBarStyle } from './HeaderBar';
import type { ConnectionReduxState } from '../redux/connection/reducers';
import type { SettingsReduxState } from '../redux/settings/reducers';
+import type { RelayLocation } from '../lib/ipc-facade';
export type ConnectProps = {
accountExpiry: string,
@@ -18,11 +19,11 @@ export type ConnectProps = {
settings: SettingsReduxState,
onSettings: () => void,
onSelectLocation: () => void,
- onConnect: (host: string) => void,
+ onConnect: () => void,
onCopyIP: () => void,
onDisconnect: () => void,
onExternalLink: (type: string) => void,
- getServerInfo: (identifier: string) => ?ServerInfo
+ getServerInfo: (relayLocation: RelayLocation) => ?ServerInfo
};
@@ -93,42 +94,52 @@ export default class Connect extends Component {
);
}
- _getServerInfo() {
+ _getServerInfo(): ServerInfo {
const { relaySettings } = this.props.settings;
- if (relaySettings.host === 'any') {
+ if(relaySettings.normal) {
+ const location = relaySettings.normal.location;
+ if(location === 'any') {
+ return {
+ address: '',
+ name: 'Automatic',
+ country: 'Automatic',
+ city: 'Automatic',
+ country_code: 'any',
+ city_code: 'any',
+ location: [0, 0],
+ };
+ } else {
+ const serverInfo = this.props.getServerInfo(location);
+ if(!serverInfo) {
+ throw new Error('Server info is not available for: ' + JSON.stringify(location));
+ }
+ return serverInfo;
+ }
+ } else if(relaySettings.custom_tunnel_endpoint) {
return {
- name: 'Automatic',
- country: 'Automatic',
- city: 'Automatic',
- address: '',
+ address: relaySettings.custom_tunnel_endpoint.host,
+ name: 'Custom',
+ country: 'Custom',
+ city: '',
+ country_code: 'auto',
+ city_code: 'auto',
+ location: [0, 0],
};
+ } else {
+ throw new Error('Unsupported relay settings.');
}
-
- return this.props.getServerInfo(relaySettings.host);
}
renderMap(): React.Element<*> {
const serverInfo = this._getServerInfo();
- let isConnecting = false;
- let isConnected = false;
- let isDisconnected = false;
+ let [ isConnecting, isConnected, isDisconnected ] = [false, false, false];
switch(this.props.connection.status) {
case 'connecting': isConnecting = true; break;
case 'connected': isConnected = true; break;
case 'disconnected': isDisconnected = true; break;
}
- const { city, country } = serverInfo && (isConnecting || isConnected)
- ? serverInfo
- : { city: '\u2003', country: '\u2002' };
- const ip = serverInfo && isConnected
- ? serverInfo.address
- : '\u2003'; //this.props.connection.clientIp;
- const serverName = serverInfo
- ? serverInfo.name
- : '\u2003';
-
// We decided to not include the map in the first beta release to customers
// but it MUST be included in the following releases. Therefore we choose
// to just comment it out
@@ -147,9 +158,14 @@ export default class Connect extends Component {
let ipComponent = undefined;
if (isConnected || isDisconnected) {
if (this.state.showCopyIPMessage) {
- ipComponent = <span>{ 'IP copied to clipboard!' }</span>;
+ ipComponent = (<span>{ 'IP copied to clipboard!' }</span>);
} else {
- ipComponent = <span>{ ip }</span>;
+ // TODO: remove empty IP placeholder when implemented in backend.
+ if(isDisconnected) {
+ ipComponent = (<span>{ '\u2003' }</span>);
+ } else {
+ ipComponent = (<span>{ this.props.connection.clientIp }</span>);
+ }
}
}
return (
@@ -173,29 +189,31 @@ export default class Connect extends Component {
**********************************
*/ }
- { /* location when connecting */ }
- <If condition={ isConnecting }>
+ { /* location when disconnected.
+ TODO: merge with the isConnecting block below when implemented in backend.
+ */ }
+ <If condition={ isDisconnected }>
<Then>
<div className="connect__status-location">
- <span>{ country }</span>
+ <span>{ '\u2002' }</span>
</div>
</Then>
</If>
- { /* location when connected */ }
- <If condition={ isConnected }>
+ { /* location when connecting */ }
+ <If condition={ isConnecting }>
<Then>
<div className="connect__status-location">
- { city }<br/>{ country }
+ <span>{ this.props.connection.country }</span>
</div>
</Then>
</If>
- { /* location when disconnected */ }
- <If condition={ isDisconnected }>
+ { /* location when connected */ }
+ <If condition={ isConnected }>
<Then>
<div className="connect__status-location">
- { country }
+ { this.props.connection.city }<br/>{ this.props.connection.country }
</div>
</Then>
</If>
@@ -228,14 +246,14 @@ export default class Connect extends Component {
<div className="connect__server-label">Connect to</div>
<div className="connect__server-value">
- <div className="connect__server-name">{ serverName }</div>
+ <div className="connect__server-name">{ serverInfo.name }</div>
</div>
</div>
</div>
<div className="connect__row">
- <button className="button button--positive" onClick={ this.onConnect.bind(this) }>Secure my connection</button>
+ <button className="button button--positive" onClick={ this.props.onConnect }>Secure my connection</button>
</div>
</div>
</Then>
@@ -284,15 +302,6 @@ export default class Connect extends Component {
// Handlers
- onConnect() {
- const serverInfo = this._getServerInfo();
- if(!serverInfo) {
- return;
- }
-
- this.props.onConnect(serverInfo.address);
- }
-
onExternalLink(type: string) {
this.props.onExternalLink(type);
}
diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js
index b5f395b7ba..e0f50a12d4 100644
--- a/app/components/SelectLocation.js
+++ b/app/components/SelectLocation.js
@@ -1,71 +1,73 @@
// @flow
import React, { Component } from 'react';
-import { If, Then } from 'react-if';
import { Layout, Container, Header } from './Layout';
-import { servers } from '../config';
import CustomScrollbars from './CustomScrollbars';
+import { servers } from '../config';
+import type { ServerInfo } from '../lib/backend';
import type { SettingsReduxState } from '../redux/settings/reducers';
+import type { RelayLocation } from '../lib/ipc-facade';
export type SelectLocationProps = {
settings: SettingsReduxState,
onClose: () => void;
- onSelect: (server: string) => void;
+ onSelect: (location: RelayLocation) => void;
};
export default class SelectLocation extends Component {
props: SelectLocationProps;
_selectedCell: ?HTMLElement;
- onSelect(name: string) {
- if (!this.isSelected(name)) {
- this.props.onSelect(name);
+ _onSelect(location: RelayLocation) {
+ if (!this._isSelected(location)) {
+ this.props.onSelect(location);
}
}
- isSelected(server: string) {
- const { host } = this.props.settings.relaySettings;
- return server === host;
+ _isSelected(selectedLocation: RelayLocation) {
+ const { relaySettings } = this.props.settings;
+ if(relaySettings.normal) {
+ const otherLocation = relaySettings.normal.location;
+
+ if(selectedLocation.country && otherLocation.country &&
+ selectedLocation.country === otherLocation.country) {
+ return true;
+ }
+
+ if(Array.isArray(selectedLocation.city) && Array.isArray(otherLocation.city)) {
+ const selectedCity = selectedLocation.city;
+ const otherCity = otherLocation.city;
+
+ return selectedCity.length === otherCity.length &&
+ selectedCity.every((v, i) => v === otherCity[i]);
+ }
+ }
+ return false;
}
- drawCell(key: string, name: string, icon: ?string, onClick: (e: Event) => void): React.Element<*> {
+ drawCell(key: string, name: string, selected: bool, icon: ?string, onClick: (e: Event) => void): React.Element<*> {
const classes = ['select-location__cell'];
- const selected = this.isSelected(key);
-
if(selected) {
classes.push('select-location__cell--selected');
}
-
const cellClass = classes.join(' ');
+ const onRef = selected ? (element) => {
+ this._selectedCell = element;
+ } : undefined;
return (
- <div key={ key } className={ cellClass } onClick={ onClick } ref={ (e) => this.onCellRef(key, e) }>
+ <div key={ key } className={ cellClass } onClick={ onClick } ref={ onRef }>
- <If condition={ !!icon }>
- <Then>
- <img className="select-location__cell-icon" src={ icon } />
- </Then>
- </If>
+ { icon && <img className="select-location__cell-icon" src={ icon } />}
<div className="select-location__cell-label">{ name }</div>
- <If condition={ selected } >
- <Then>
- <img className="select-location__cell-accessory" src="./assets/images/icon-tick.svg" />
- </Then>
- </If>
+ { selected && <img className="select-location__cell-accessory" src="./assets/images/icon-tick.svg" /> }
</div>
);
}
- onCellRef(key: string, element: HTMLElement) {
- // save reference to selected cell
- if(this.isSelected(key)) {
- this._selectedCell = element;
- }
- }
-
componentDidMount() {
// restore scroll to selected cell
const cell = this._selectedCell;
@@ -99,7 +101,15 @@ export default class SelectLocation extends Component {
<div className="select-location__separator"></div>
- { Object.keys(servers).map((key) => this.drawCell(key, servers[key].name, null, this.onSelect.bind(this, key))) }
+ { (servers: Array<ServerInfo>).map((server) => {
+ const { address, name, country_code, city_code } = server;
+ const relayLocation = {
+ city: [ country_code, city_code ]
+ };
+ const selected = this._isSelected(relayLocation);
+ const clickHandler = () => this._onSelect(relayLocation);
+ return this.drawCell(address, name, selected, null, clickHandler);
+ }) }
</div>
</CustomScrollbars>
diff --git a/app/config.json b/app/config.json
index ed984c0642..73eb310d3a 100644
--- a/app/config.json
+++ b/app/config.json
@@ -11,210 +11,285 @@
"supportEmail": "mailto:support@mullvad.net"
},
"defaultServer": "193.138.218.135",
- "servers": {
- "168.1.6.5": {
+ "servers": [
+ {
"address": "168.1.6.5",
"name": "Australia",
"city": "Sydney",
- "country": "Australia"
+ "country": "Australia",
+ "country_code": "au",
+ "city_code": "syd",
+ "location": [0, 0]
},
- "217.64.127.138": {
+ {
"address": "217.64.127.138",
"name": "Austria",
"city": "Wien",
- "country": "Austria"
+ "country": "Austria",
+ "country_code": "at",
+ "city_code": "vie",
+ "location": [0, 0]
},
- "185.104.186.202": {
+ {
"address": "185.104.186.202",
"name": "Belgium",
"city": "Brussels",
- "country": "Belgium"
+ "country": "Belgium",
+ "country_code": "be",
+ "city_code": "bru",
+ "location": [0, 0]
},
- "185.94.192.42": {
+ {
"address": "185.94.192.42",
"name": "Bulgaria",
"city": "Sofia",
- "country": "Bulgaria"
+ "country": "Bulgaria",
+ "country_code": "bg",
+ "city_code": "sof",
+ "location": [0, 0]
},
- "162.219.176.250": {
+ {
"address": "162.219.176.250",
"name": "Canada",
"city": "Toronto",
"country": "Canada",
+ "country_code": "ca",
+ "city_code": "tor",
"location": [
45.42153,
-75.697193
]
},
- "185.156.174.146": {
+ {
"address": "185.156.174.146",
"name": "Czech Republic",
"city": "Prague",
- "country": "Czech Republic"
+ "country": "Czech Republic",
+ "country_code": "cz",
+ "city_code": "prg",
+ "location": [0, 0]
},
- "82.103.140.213": {
+ {
"address": "82.103.140.213",
"name": "Denmark",
"city": "Copenhagen",
"country": "Denmark",
+ "country_code": "dk",
+ "city_code": "cph",
"location": [
55.6760968,
12.5683371
]
},
- "185.103.110.69": {
+ {
"address": "185.103.110.69",
"name": "Finland",
"city": "Helsinki",
- "country": "Finland"
+ "country": "Finland",
+ "country_code": "fi",
+ "city_code": "hel",
+ "location": [0, 0]
},
- "185.156.173.218": {
+ {
"address": "185.156.173.218",
"name": "France",
"city": "Paris",
- "country": "France"
+ "country": "France",
+ "country_code": "fr",
+ "city_code": "par",
+ "location": [0, 0]
},
- "185.104.184.178": {
- "address": "185.104.184.178",
+ {
+ "address": "89.249.64.146",
"name": "Germany",
"city": "Berlin",
"country": "Germany",
+ "country_code": "de",
+ "city_code": "ber",
"location": [
- 52.52000659999999,
- 13.404954
+ 54.033333,
+ 10.45
]
},
- "161.202.48.245": {
+ {
"address": "161.202.48.245",
"name": "Hong Kong",
"city": "Hong Kong",
- "country": "Hong Kong"
+ "country": "Hong Kong",
+ "country_code": "hk",
+ "city_code": "hkg",
+ "location": [0, 0]
},
- "185.189.114.10": {
+ {
"address": "185.189.114.10",
"name": "Hungary",
"city": "Budapest",
- "country": "Hungary"
+ "country": "Hungary",
+ "country_code": "hu",
+ "city_code": "bud",
+ "location": [0, 0]
},
- "213.184.122.34": {
+ {
"address": "213.184.122.34",
"name": "Israel",
"city": "Petach-Tikva",
- "country": "Israel"
+ "country": "Israel",
+ "country_code": "il",
+ "city_code": "pet",
+ "location": [0, 0]
},
- "217.64.113.180": {
+ {
"address": "217.64.113.180",
"name": "Italy",
"city": "Milan",
- "country": "Italy"
+ "country": "Italy",
+ "country_code": "it",
+ "city_code": "mil",
+ "location": [0, 0]
},
- "161.202.144.203": {
+ {
"address": "161.202.144.203",
"name": "Japan",
"city": "Tokyo",
- "country": "Japan"
+ "country": "Japan",
+ "country_code": "jp",
+ "city_code": "tyo",
+ "location": [0, 0]
},
- "185.65.134.140": {
+ {
"address": "185.65.134.140",
"name": "Netherlands",
"city": "Amsterdam",
"country": "Netherlands",
+ "country_code": "nl",
+ "city_code": "ams",
"location": [
52.3702157,
4.895167900000001
]
},
- "31.169.51.154": {
+ {
"address": "31.169.51.154",
"name": "Norway",
"city": "Oslo",
"country": "Norway",
+ "country_code": "no",
+ "city_code": "osl",
"location": [
59.9138688,
10.7522454
]
},
- "212.7.217.30": {
+ {
"address": "212.7.217.30",
"name": "Poland",
"city": "Warsaw",
- "country": "Poland"
+ "country": "Poland",
+ "country_code": "pl",
+ "city_code": "waw",
+ "location": [0, 0]
},
- "185.45.13.10": {
+ {
"address": "185.45.13.10",
"name": "Romania",
"city": "Bucharest",
"country": "Romania",
+ "country_code": "ro",
+ "city_code": "buh",
"location": [
44.4267674,
26.1025384
]
},
- "103.57.72.30": {
+ {
"address": "103.57.72.30",
"name": "Singapore",
"city": "Singapore",
"country": "Singapore",
+ "country_code": "sg",
+ "city_code": "sin",
"location": [
1.352083,
103.819836
]
},
- "89.238.178.34": {
+ {
"address": "89.238.178.34",
"name": "Spain",
"city": "Madrid",
"country": "Spain",
+ "country_code": "es",
+ "city_code": "mad",
"location": [
40.4167754,
-3.7037902
]
},
- "185.213.152.132": {
+ {
"address": "185.213.152.132",
"name": "Sweden - Helsingborg",
"city": "Helsingborg",
- "country": "Sweden"
+ "country": "Sweden",
+ "country_code": "se",
+ "city_code": "hel",
+ "location": [0, 0]
},
- "193.138.218.135": {
+ {
"address": "193.138.218.135",
"name": "Sweden - Malmö",
"city": "Malmö",
- "country": "Sweden"
+ "country": "Sweden",
+ "country_code": "se",
+ "city_code": "mma",
+ "location": [0, 0]
},
- "185.65.135.143": {
+ {
"address": "185.65.135.143",
"name": "Sweden - Stockholm",
"city": "Stockholm",
- "country": "Sweden"
+ "country": "Sweden",
+ "country_code": "se",
+ "city_code": "sto",
+ "location": [0, 0]
},
- "179.43.128.170": {
+ {
"address": "179.43.128.170",
"name": "Switzerland",
"city": "Zürich",
- "country": "Switzerland"
+ "country": "Switzerland",
+ "country_code": "ch",
+ "city_code": "zrh",
+ "location": [0, 0]
},
- "185.16.85.170": {
+ {
"address": "185.16.85.170",
"name": "United Kingdom",
"city": "London",
"country": "United Kingdom",
+ "country_code": "gb",
+ "city_code": "lon",
"location": [
51.5073509,
-0.1277583
]
},
- "173.199.80.130": {
+ {
"address": "173.199.80.130",
"name": "USA - Los Angeles",
"city": "Los Angeles",
- "country": "USA"
+ "country": "USA",
+ "country_code": "us",
+ "city_code": "lax",
+ "location": [0, 0]
},
- "38.132.107.138": {
+ {
"address": "38.132.107.138",
"name": "USA - New York",
"city": "New York",
- "country": "USA"
+ "country": "USA",
+ "country_code": "us",
+ "city_code": "nyc",
+ "location": [0, 0]
}
- }
+ ]
}
diff --git a/app/containers/AdvancedSettingsPage.js b/app/containers/AdvancedSettingsPage.js
index 74e59ec60a..6f456abcea 100644
--- a/app/containers/AdvancedSettingsPage.js
+++ b/app/containers/AdvancedSettingsPage.js
@@ -1,46 +1,56 @@
+// @flow
+
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { AdvancedSettings } from '../components/AdvancedSettings';
-import settingsActions from '../redux/settings/actions';
+import RelaySettingsBuilder from '../lib/relay-settings-builder';
import log from 'electron-log';
-const mapStateToProps = (state) => {
- const constraints = state.settings.relaySettings;
- const { host, protocol, port } = constraints;
- return {
- host: host,
- protocol: protocol === 'any' ? 'Automatic' : protocol,
- port: port === 'any' ? 'Automatic' : port,
- };
+import type { ReduxState, ReduxDispatch } from '../redux/store';
+import type { SharedRouteProps } from '../routes';
+
+const mapStateToProps = (state: ReduxState) => {
+ const relaySettings = state.settings.relaySettings;
+ if(relaySettings.normal) {
+ const { protocol, port } = relaySettings.normal;
+ return {
+ protocol: protocol === 'any' ? 'Automatic' : protocol,
+ port: port === 'any' ? 'Automatic' : port,
+ };
+ } else if(relaySettings.custom_tunnel_endpoint) {
+ const { protocol, port } = relaySettings.custom_tunnel_endpoint;
+ return { protocol, port };
+ } else {
+ throw new Error('Unknown type of relay settings.');
+ }
};
-const mapDispatchToProps = (dispatch, props) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
const { backend } = props;
return {
onClose: () => dispatch(push('/settings')),
- onUpdateConstraints: (host, protocol, port) => {
- // TODO: udp and 1301 are automatic because we cannot pass `any` when using custom tunnel
- const protocolConstraint = protocol === 'Automatic' ? 'udp' : protocol.toLowerCase();
- const portConstraint = port === 'Automatic' ? (protocolConstraint === 'tcp' ? 443 : 1301) : port;
- const update = {
- custom_tunnel_endpoint: {
- host: host,
- tunnel: {
- openvpn: {
- protocol: protocolConstraint,
- port: portConstraint,
- }
+ onUpdate: async (protocol, port) => {
+ const relayUpdate = RelaySettingsBuilder.normal()
+ .tunnel.openvpn((openvpn) => {
+ if(protocol === 'Automatic') {
+ openvpn.protocol.any();
+ } else {
+ openvpn.protocol.exact(protocol.toLowerCase());
+ }
+ if(port === 'Automatic') {
+ openvpn.port.any();
+ } else {
+ openvpn.port.exact(port);
}
- },
- };
+ }).build();
- backend.updateRelaySettings(update)
- .then( () => dispatch(settingsActions.updateRelay({
- protocol: protocolConstraint,
- port: portConstraint,
- })))
- .catch( e => log.error('Failed updating relay constraints', e.message));
+ try {
+ await backend.updateRelaySettings(relayUpdate);
+ await backend.fetchRelaySettings();
+ } catch(e) {
+ log.error('Failed to update relay settings', e.message);
+ }
},
};
};
diff --git a/app/containers/ConnectPage.js b/app/containers/ConnectPage.js
index 5c6dc1fa60..9f2df48df4 100644
--- a/app/containers/ConnectPage.js
+++ b/app/containers/ConnectPage.js
@@ -21,11 +21,11 @@ const mapDispatchToProps = (dispatch, props) => {
return {
onSettings: () => dispatch(push('/settings')),
onSelectLocation: () => dispatch(push('/select-location')),
- onConnect: (relayEndpoint) => connect(backend, relayEndpoint),
+ onConnect: () => connect(backend),
onCopyIP: () => copyIPAddress(),
onDisconnect: () => disconnect(backend),
onExternalLink: (type) => shell.openExternal(links[type]),
- getServerInfo: (key) => backend.serverInfo(key)
+ getServerInfo: (relayLocation) => backend.serverInfo(relayLocation),
};
};
diff --git a/app/containers/SelectLocationPage.js b/app/containers/SelectLocationPage.js
index 336186a2dc..c39f76eba7 100644
--- a/app/containers/SelectLocationPage.js
+++ b/app/containers/SelectLocationPage.js
@@ -1,27 +1,30 @@
+// @flow
+
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import SelectLocation from '../components/SelectLocation';
-import settingsActions from '../redux/settings/actions';
+import RelaySettingsBuilder from '../lib/relay-settings-builder';
import log from 'electron-log';
+import type { ReduxDispatch } from '../redux/store';
+
const mapStateToProps = (state) => state;
-const mapDispatchToProps = (dispatch, props) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props) => {
const { backend } = props;
return {
onClose: () => dispatch(push('/connect')),
- onSelect: (host) => {
- dispatch(async (dispatch, getState) => {
- try {
- const { settings: { relaySettings: { protocol, port } } } = getState();
+ onSelect: async (relayLocation) => {
+ try {
+ const relayUpdate = RelaySettingsBuilder.normal().location.fromRaw(relayLocation).build();
- backend.connect(host, protocol, port);
+ await backend.updateRelaySettings(relayUpdate);
+ await backend.fetchRelaySettings();
+ await backend.connect();
- dispatch(settingsActions.updateRelay({ host: host }));
- dispatch(push('/connect'));
- } catch (e) {
- log.error('Failed to select server: ', e.message);
- }
- });
+ dispatch(push('/connect'));
+ } catch (e) {
+ log.error('Failed to select server: ', e.message);
+ }
}
};
};
diff --git a/app/lib/backend.js b/app/lib/backend.js
index 900f5b8799..778997fa38 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -10,7 +10,7 @@ import settingsActions from '../redux/settings/actions';
import { push } from 'react-router-redux';
import type { ReduxStore } from '../redux/store';
-import type { AccountToken, BackendState, RelaySettingsUpdate, RelayProtocol } from './ipc-facade';
+import type { AccountToken, BackendState, RelayLocation, RelaySettingsUpdate } from './ipc-facade';
import type { ConnectionState } from '../redux/connection/reducers';
export type ErrorType = 'NO_CREDIT' | 'NO_INTERNET' | 'INVALID_ACCOUNT' | 'NO_ACCOUNT';
@@ -20,11 +20,11 @@ export type ServerInfo = {
name: string,
city: string,
country: string,
- location: [number, number]
+ country_code: string,
+ city_code: string,
+ location: [number, number],
};
-export type ServerInfoList = { [string]: ServerInfo };
-
export class BackendError extends Error {
type: ErrorType;
title: string;
@@ -164,8 +164,22 @@ export class Backend {
this._updateAccountHistory();
}
- serverInfo(identifier: string): ?ServerInfo {
- return (servers: ServerInfoList)[identifier];
+ serverInfo(relay: RelayLocation): ?ServerInfo {
+ const list: Array<ServerInfo> = servers;
+ if(relay.country) {
+ const country = relay.country;
+ return list.find((server) => {
+ return server.country_code === country;
+ });
+ } else if(relay.city) {
+ const [country_code, city_code] = relay.city;
+ return list.find((server) => {
+ return server.country_code === country_code &&
+ server.city_code === city_code;
+ });
+ } else {
+ return null;
+ }
}
login(accountToken: AccountToken): Promise<void> {
@@ -186,18 +200,15 @@ export class Backend {
log.info('Log in complete');
this._store.dispatch(accountActions.loginSuccessful(accountData.expiry));
- return this.syncRelaySettings();
+ return this.fetchRelaySettings();
})
.then( () => {
// Redirect the user after some time to allow for
// the 'Login Successful' screen to be visible
setTimeout(() => {
- const { settings: { relaySettings: { host, protocol, port } } } = this._store.getState();
-
- log.debug(`Autoconnecting to ${host}`);
-
this._store.dispatch(push('/connect'));
- this.connect(host, protocol, port);
+ log.debug('Autoconnecting...');
+ this.connect();
}, 1000);
}).catch(e => {
log.error('Failed to log in,', e.message);
@@ -263,20 +274,9 @@ export class Backend {
});
}
- connect(host: string, protocol: RelayProtocol, port: number): Promise<void> {
- const newRelaySettings = {
- custom_tunnel_endpoint: {
- host: host,
- tunnel: {
- openvpn: { protocol, port }
- },
- },
- };
-
- this._store.dispatch(connectionActions.connectingTo(host));
-
+ connect(): Promise<void> {
+ this._store.dispatch(connectionActions.connecting());
return this._ensureAuthenticated()
- .then(() => this._ipc.updateRelaySettings(newRelaySettings))
.then(() => this._ipc.connect())
.catch((e) => {
log.error('Backend.connect failed because: ', e.message);
@@ -304,29 +304,53 @@ export class Backend {
updateRelaySettings(relaySettings: RelaySettingsUpdate): Promise<void> {
return this._ensureAuthenticated()
- .then( () => {
- return this._ipc.updateRelaySettings(relaySettings);
- });
+ .then(() => this._ipc.updateRelaySettings(relaySettings));
}
- syncRelaySettings(): Promise<void> {
- return this._ensureAuthenticated()
- .then(() => this._ipc.getRelaySettings())
- .then((constraints) => {
- log.debug('Got constraints from backend', constraints);
+ async fetchRelaySettings(): Promise<void> {
+ await this._ensureAuthenticated();
- if(constraints.normal) {
- // TODO: handle normal constraints
- log.warn('syncRelaySettings: Normal constraints are not implemented yet.');
- } else if(constraints.custom_tunnel_endpoint) {
- const custom_tunnel_endpoint = constraints.custom_tunnel_endpoint;
- const { host, tunnel: { openvpn: { port, protocol } } } = custom_tunnel_endpoint;
- this._store.dispatch(settingsActions.updateRelay({ host, port, protocol }));
- }
- })
- .catch(e => {
- log.error('Failed getting relay constraints', e);
- });
+ const relaySettings = await this._ipc.getRelaySettings();
+ log.debug('Got relay settings from backend', relaySettings);
+
+ if(relaySettings.normal) {
+ const payload = {};
+ const normal = relaySettings.normal;
+ const tunnel = normal.tunnel;
+ const location = normal.location;
+
+ if(location === 'any') {
+ payload.location = 'any';
+ } else {
+ payload.location = location.only;
+ }
+
+ if(tunnel === 'any') {
+ payload.port = 'any';
+ payload.protocol = 'any';
+ } else {
+ const { port, protocol } = tunnel.only.openvpn;
+ payload.port = port === 'any' ? port : port.only;
+ payload.protocol = protocol === 'any' ? protocol : protocol.only;
+ }
+
+ this._store.dispatch(
+ settingsActions.updateRelay({
+ normal: payload
+ })
+ );
+ } else if(relaySettings.custom_tunnel_endpoint) {
+ const custom_tunnel_endpoint = relaySettings.custom_tunnel_endpoint;
+ const { host, tunnel: { openvpn: { port, protocol } } } = custom_tunnel_endpoint;
+
+ this._store.dispatch(
+ settingsActions.updateRelay({
+ custom_tunnel_endpoint: {
+ host, port, protocol
+ }
+ })
+ );
+ }
}
removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
@@ -349,14 +373,6 @@ export class Backend {
.catch(e => log.info('Failed to fetch account history,', e.message));
}
- _apiToReduxConstraints(constraint: *): * {
- if (typeof(constraint) === 'object') {
- return constraint.only;
- } else {
- return constraint;
- }
- }
-
/**
* Start reachability monitoring for online/offline detection
* This is currently done via HTML5 APIs but will be replaced later
diff --git a/app/lib/relay-settings-builder.js b/app/lib/relay-settings-builder.js
new file mode 100644
index 0000000000..4d83956a00
--- /dev/null
+++ b/app/lib/relay-settings-builder.js
@@ -0,0 +1,195 @@
+// @flow
+
+import type {
+ RelayLocation,
+ RelayProtocol,
+ RelaySettingsUpdate,
+ RelaySettingsNormalUpdate,
+ RelaySettingsCustom
+} from './ipc-facade';
+
+type LocationBuilder<Self> = {
+ country: (country: string) => Self,
+ city: (country: string, city: string) => Self,
+ any: () => Self,
+ fromRaw: (location: 'any' | RelayLocation) => Self,
+};
+
+type OpenVPNConfigurator<Self> = {
+ port: {
+ exact: (port: number) => Self,
+ any: () => Self
+ },
+ protocol: {
+ exact: (protocol: RelayProtocol) => Self,
+ any: () => Self
+ }
+};
+
+type TunnelBuilder<Self> = {
+ openvpn: (configurator: (OpenVPNConfigurator<*>) => void) => Self
+};
+
+class NormalRelaySettingsBuilder {
+ _payload: RelaySettingsNormalUpdate = {};
+
+ build(): RelaySettingsUpdate {
+ return {
+ normal: this._payload
+ };
+ }
+
+ get location(): LocationBuilder<NormalRelaySettingsBuilder> {
+ return {
+ country: (country: string) => {
+ this._payload.location = { only: { country } };
+ return this;
+ },
+ city: (country: string, city: string) => {
+ this._payload.location = { only: { city: [country, city] } };
+ return this;
+ },
+ any: () => {
+ this._payload.location = 'any';
+ return this;
+ },
+ fromRaw: function (location: 'any' | RelayLocation) {
+ if(location === 'any') {
+ return this.any();
+ }
+
+ if(location.city) {
+ const [country, city] = location.city;
+ return this.city(country, city);
+ }
+
+ if(location.country) {
+ return this.country(location.country);
+ }
+
+ throw new Error('Unsupported value of RelayLocation' +
+ (location && JSON.stringify(location)) );
+ },
+ };
+ }
+
+ get tunnel(): TunnelBuilder<NormalRelaySettingsBuilder> {
+ const updateOpenvpn = (next) => {
+ const tunnel = this._payload.tunnel;
+ if(typeof(tunnel) === 'string' || typeof(tunnel) === 'undefined') {
+ this._payload.tunnel = {
+ only: {
+ openvpn: next
+ }
+ };
+ } else if(typeof(tunnel) === 'object') {
+ const prev = (tunnel.only && tunnel.only.openvpn) || {};
+ this._payload.tunnel = {
+ only: {
+ openvpn: { ...prev, ...next }
+ }
+ };
+ }
+ };
+
+ return {
+ openvpn: (configurator) => {
+ const openvpnBuilder = {
+ get port() {
+ const apply = (port) => {
+ updateOpenvpn({ port });
+ return this;
+ };
+ return {
+ exact: (value: number) => apply({ only: value }),
+ any: () => apply('any'),
+ };
+ },
+ get protocol() {
+ const apply = (protocol) => {
+ updateOpenvpn({ protocol });
+ return this;
+ };
+ return {
+ exact: (value: RelayProtocol) => apply({ only: value }),
+ any: () => apply('any'),
+ };
+ }
+ };
+
+ configurator(openvpnBuilder);
+
+ return this;
+ },
+ any: () => {
+ this._payload.tunnel = 'any';
+ return this;
+ }
+ };
+ }
+
+}
+
+
+type CustomOpenVPNConfigurator<Self> = {
+ port: (port: number) => Self,
+ protocol: (protocol: RelayProtocol) => Self
+};
+
+type CustomTunnelBuilder<Self> = {
+ openvpn: (configurator: (CustomOpenVPNConfigurator<*>) => void) => Self
+};
+
+class CustomRelaySettingsBuilder {
+ _payload: RelaySettingsCustom = {
+ host: '',
+ tunnel: {
+ openvpn: {
+ port: 0,
+ protocol: 'udp'
+ }
+ }
+ };
+
+ build(): RelaySettingsUpdate {
+ return {
+ custom_tunnel_endpoint: this._payload
+ };
+ }
+
+ host(value: string) {
+ this._payload.host = value;
+ return this;
+ }
+
+ get tunnel(): CustomTunnelBuilder<CustomRelaySettingsBuilder> {
+ const updateOpenvpn = (next) => {
+ const tunnel = this._payload.tunnel || {};
+ const prev = tunnel.openvpn || {};
+ this._payload.tunnel = {
+ openvpn: { ...prev, ...next }
+ };
+ };
+
+ return {
+ openvpn: (configurator) => {
+ configurator({
+ port: function (port: number) {
+ updateOpenvpn({ port });
+ return this;
+ },
+ protocol: function (protocol: RelayProtocol) {
+ updateOpenvpn({ protocol });
+ return this;
+ }
+ });
+ return this;
+ }
+ };
+ }
+}
+
+export default {
+ normal: () => new NormalRelaySettingsBuilder(),
+ custom: () => new CustomRelaySettingsBuilder(),
+}; \ No newline at end of file
diff --git a/app/redux/connection/actions.js b/app/redux/connection/actions.js
index fb4320bf3a..f2ebf2c7ba 100644
--- a/app/redux/connection/actions.js
+++ b/app/redux/connection/actions.js
@@ -6,12 +6,7 @@ import type { Backend } from '../../lib/backend';
import type { ReduxThunk } from '../store';
import type { Coordinate2d } from '../../types';
-const connect = (backend: Backend, relay: string): ReduxThunk => {
- return (_, getState) => {
- const { settings: { relaySettings: { protocol, port } } } = getState();
- backend.connect(relay, protocol, port);
- };
-};
+const connect = (backend: Backend): ReduxThunk => () => backend.connect();
const disconnect = (backend: Backend) => () => backend.disconnect();
const copyIPAddress = (): ReduxThunk => {
return (_, getState) => {
@@ -25,7 +20,6 @@ const copyIPAddress = (): ReduxThunk => {
type ConnectingAction = {
type: 'CONNECTING',
- host?: string,
};
type ConnectedAction = {
type: 'CONNECTED',
@@ -66,13 +60,6 @@ export type ConnectionAction = NewPublicIpAction
| OnlineAction
| OfflineAction;
-function connectingTo(host: string): ConnectingAction {
- return {
- type: 'CONNECTING',
- host: host,
- };
-}
-
function connecting(): ConnectingAction {
return {
type: 'CONNECTING',
@@ -118,5 +105,5 @@ function offline(): OfflineAction {
}
-export default { connect, disconnect, copyIPAddress, newPublicIp, newLocation, connectingTo, connecting, connected, disconnected, online, offline };
+export default { connect, disconnect, copyIPAddress, newPublicIp, newLocation, connecting, connected, disconnected, online, offline };
diff --git a/app/redux/connection/reducers.js b/app/redux/connection/reducers.js
index 48177fc5b6..52c1d648c1 100644
--- a/app/redux/connection/reducers.js
+++ b/app/redux/connection/reducers.js
@@ -7,7 +7,6 @@ export type ConnectionState = 'disconnected' | 'connecting' | 'connected';
export type ConnectionReduxState = {
status: ConnectionState,
isOnline: boolean,
- serverAddress: ?string,
clientIp: ?string,
location: ?Coordinate2d,
country: ?string,
@@ -17,7 +16,6 @@ export type ConnectionReduxState = {
const initialState: ConnectionReduxState = {
status: 'disconnected',
isOnline: true,
- serverAddress: null,
clientIp: null,
location: null,
country: null,
@@ -38,7 +36,7 @@ export default function(state: ConnectionReduxState = initialState, action: Redu
return { ...state, ...action.newLocation };
case 'CONNECTING':
- return { ...state, ...{ status: 'connecting', serverAddress: action.host }};
+ return { ...state, ...{ status: 'connecting' }};
case 'CONNECTED':
return { ...state, ...{ status: 'connected' }};
diff --git a/app/redux/settings/actions.js b/app/redux/settings/actions.js
index 4cc4910e01..74f8f120f7 100644
--- a/app/redux/settings/actions.js
+++ b/app/redux/settings/actions.js
@@ -1,15 +1,15 @@
// @flow
-import type { RelaySettings } from './reducers';
+import type { RelaySettingsRedux } from './reducers';
export type UpdateRelayAction = {
type: 'UPDATE_RELAY',
- relay: RelaySettings,
+ relay: RelaySettingsRedux,
};
export type SettingsAction = UpdateRelayAction;
-function updateRelay(relay: RelaySettings): UpdateRelayAction {
+function updateRelay(relay: RelaySettingsRedux): UpdateRelayAction {
return {
type: 'UPDATE_RELAY',
relay: relay,
diff --git a/app/redux/settings/reducers.js b/app/redux/settings/reducers.js
index 2754088d1b..56548a8d12 100644
--- a/app/redux/settings/reducers.js
+++ b/app/redux/settings/reducers.js
@@ -1,24 +1,33 @@
// @flow
-import { defaultServer } from '../../config';
-
import type { ReduxAction } from '../store';
+import type { RelayProtocol, RelayLocation } from '../../lib/ipc-facade';
-export type RelaySettings = {
+export type RelaySettingsRedux = {|
+ normal: {
+ location: 'any' | RelayLocation,
+ port: 'any' | number,
+ protocol: 'any' | RelayProtocol,
+ }
+|} | {|
+ custom_tunnel_endpoint: {
host: string,
port: number,
- protocol: 'tcp' | 'udp',
-};
+ protocol: RelayProtocol,
+ }
+|};
export type SettingsReduxState = {
- relaySettings: RelaySettings
+ relaySettings: RelaySettingsRedux
};
const initialState: SettingsReduxState = {
relaySettings: {
- host: defaultServer,
- port: 1301,
- protocol: 'udp',
+ normal: {
+ location: 'any',
+ port: 'any',
+ protocol: 'any',
+ }
},
};
@@ -26,10 +35,7 @@ export default function(state: SettingsReduxState = initialState, action: ReduxA
if (action.type === 'UPDATE_RELAY') {
return { ...state,
- relaySettings: {
- ...state.relaySettings,
- ...action.relay,
- },
+ relaySettings: action.relay,
};
}
diff --git a/test/auth.spec.js b/test/auth.spec.js
index 1725038acd..c3fd78c83d 100644
--- a/test/auth.spec.js
+++ b/test/auth.spec.js
@@ -29,7 +29,7 @@ describe('authentication', () => {
const backend = new Backend(store, credentials, mockIpc);
- backend.connect('example.com', 'udp', 1301);
+ backend.connect();
});
it('reauthenticates on reconnect', (done) => {
@@ -48,7 +48,7 @@ describe('authentication', () => {
}, done);
- backend.connect('example.com', 'udp', 1301);
+ backend.connect();
checkNextTick(() => {
expect(authCount).to.equal(1);
}, done);
diff --git a/test/components/Connect.spec.js b/test/components/Connect.spec.js
index d672aa77d6..748dd25761 100644
--- a/test/components/Connect.spec.js
+++ b/test/components/Connect.spec.js
@@ -7,12 +7,17 @@ import { mount } from 'enzyme';
import Connect from '../../app/components/Connect';
import Header from '../../app/components/HeaderBar';
-import type { ReactWrapper } from 'enzyme';
+import type { ConnectProps } from '../../app/components/Connect';
describe('components/Connect', () => {
- it('shows unsecured hints when not connected', () => {
- const component = renderNotConnected();
+ it('shows unsecured hints when disconnected', () => {
+ const component = renderWithProps({
+ connection: {
+ ...defaultConnection,
+ status: 'disconnected',
+ }
+ });
const header = component.find(Header);
const securityMessage = component.find('.connect__status-security--unsecured');
@@ -24,7 +29,12 @@ describe('components/Connect', () => {
});
it('shows secured hints when connected', () => {
- const component = renderConnected();
+ const component = renderWithProps({
+ connection: {
+ ...defaultConnection,
+ status: 'connected',
+ }
+ });
const header = component.find(Header);
const securityMessage = component.find('.connect__status-security--secure');
@@ -36,90 +46,106 @@ describe('components/Connect', () => {
});
it('shows the connection location when connecting', () => {
- const component = renderConnecting({
- getServerInfo: (_s) => ({
- address: '185.65.132.102',
- name: '',
- location: [0, 0],
- country: 'norway',
- city: 'oslo',
- }),
- }, {
- clientIp: '185.65.132.102',
+ const component = renderWithProps({
+ connection: {
+ ...defaultConnection,
+ status: 'connecting',
+ country: 'Norway',
+ city: 'Oslo',
+ }
});
const countryAndCity = component.find('.connect__status-location');
const ipAddr = component.find('.connect__status-ipaddress');
- expect(countryAndCity.text()).to.contain('norway');
- expect(countryAndCity.text()).not.to.contain('oslo');
+ expect(countryAndCity.text()).to.contain('Norway');
+ expect(countryAndCity.text()).not.to.contain('Oslo');
expect(ipAddr.text()).to.be.empty;
});
it('shows the connection location when connected', () => {
- const component = renderConnected({
- getServerInfo: (_s) => ({
- address: '185.65.132.102',
- name: '',
- location: [0, 0],
- country: 'sweden',
- city: 'gothenburg',
- }),
- }, {
- clientIp: '185.65.132.102',
+ const component = renderWithProps({
+ connection: {
+ ...defaultConnection,
+ status: 'connected',
+ country: 'Norway',
+ city: 'Oslo',
+ clientIp: '4.3.2.1',
+ }
});
const countryAndCity = component.find('.connect__status-location');
const ipAddr = component.find('.connect__status-ipaddress');
- expect(countryAndCity.text()).to.contain('sweden');
- expect(countryAndCity.text()).to.contain('gothenburg');
- expect(ipAddr.text()).to.contain('185.65.132.102');
+ expect(countryAndCity.text()).to.contain('Norway');
+ expect(countryAndCity.text()).to.contain('Oslo');
+ expect(ipAddr.text()).to.contain('4.3.2.1');
});
it('shows the connection location when disconnected', () => {
- const component = renderNotConnected({
- getServerInfo: (_s) => ({
- address: '\u2003',
- name: '',
- location: [0, 0],
- country: 'sweden',
- city: 'gothenburg',
- }),
- }, {
- clientIp: '\u2003',
+ const component = renderWithProps({
+ connection: {
+ ...defaultConnection,
+ status: 'disconnected',
+ country: 'Norway',
+ city: 'Oslo',
+ clientIp: '4.3.2.1',
+ }
});
const countryAndCity = component.find('.connect__status-location');
const ipAddr = component.find('.connect__status-ipaddress');
expect(countryAndCity.text()).to.contain('\u2002');
- expect(countryAndCity.text()).to.not.contain('\u2003');
+ expect(countryAndCity.text()).to.not.contain('Oslo');
expect(ipAddr.text()).to.contain('\u2003');
});
it('shows the country name in the location switcher', () => {
- const servers = {
- 'se1.mullvad.net': { name: 'Sweden' },
- };
- const getServerInfo = (key) => servers[key] || defaultServer;
- const component = renderNotConnected({
- getServerInfo: getServerInfo,
- });
- const locationSwitcher = component.find('.connect__server');
+ const servers = [{
+ address: '1.2.3.4',
+ name: 'Sweden - Malmö',
+ city: 'Malmö',
+ country: 'Sweden',
+ country_code: 'se',
+ city_code: 'mma',
+ location: [0, 0]
+ }];
- component.setProps({
+ const component = renderWithProps({
+ connection: {
+ ...defaultConnection,
+ status: 'disconnected',
+ },
settings: {
relaySettings: {
- host: 'se1.mullvad.net',
- protocol: 'udp',
- port: 1301,
+ normal: {
+ location: { city: ['se', 'mma'] },
+ protocol: 'any',
+ port: 'any',
+ }
},
},
+ getServerInfo: (location) => {
+ return servers.find((server) => {
+ if(location.city) {
+ const [country_code, city_code] = location.city;
+ return (server.city_code === city_code &&
+ server.country_code === country_code);
+ }
+ return false;
+ });
+ },
});
- expect(locationSwitcher.text()).to.contain(servers['se1.mullvad.net'].name);
+
+ const locationSwitcher = component.find('.connect__server');
+ expect(locationSwitcher.text()).to.contain(servers[0].name);
});
it('invokes the onConnect prop', (done) => {
- const component = renderNotConnected({
+ const component = renderWithProps({
onConnect: () => done(),
+ connection: {
+ ...defaultConnection,
+ status: 'disconnected',
+ }
});
const connectButton = component.find('.button .button--positive');
@@ -127,72 +153,38 @@ describe('components/Connect', () => {
});
});
-function renderNotConnected(customProps, customConnectionProps) {
- const connection = Object.assign({}, defaultConnection, {
- status: 'disconnected',
- }, customConnectionProps);
-
- const props = Object.assign({}, customProps, {connection});
- return renderWithProps(props);
-}
-
-function renderConnecting(customProps, customConnectionProps) {
- const connection = Object.assign({}, defaultConnection, {
- status: 'connecting',
- }, customConnectionProps);
-
- const props = Object.assign({}, customProps, {connection});
- return renderWithProps(props);
-}
-
-function renderConnected(customProps, customConnectionProps) {
- const connection = Object.assign({}, defaultConnection, {
- status: 'connected',
- }, customConnectionProps);
-
- const props = Object.assign({}, customProps, {connection});
- return renderWithProps(props);
-}
-
-function renderWithProps(customProps): ReactWrapper {
- const props = Object.assign({}, defaultProps, customProps);
- return mount( <Connect { ...props } /> );
-}
-const noop = () => {};
-const defaultServer = {
- address: '',
- name: '',
- city: '',
- country: '',
- location: [0, 0],
-};
const defaultConnection = {
status: 'disconnected',
isOnline: true,
- serverAddress: null,
clientIp: null,
location: null,
country: null,
city: null,
};
-const defaultProps = {
- onSettings: noop,
- onSelectLocation: noop,
- onConnect: noop,
- onCopyIP: noop,
- onDisconnect: noop,
- onExternalLink: noop,
- getServerInfo: (_) => { return defaultServer; },
-
+const defaultProps: ConnectProps = {
+ onSettings: () => {},
+ onSelectLocation: () => {},
+ onConnect: () => {},
+ onCopyIP: () => {},
+ onDisconnect: () => {},
+ onExternalLink: () => {},
+ getServerInfo: _ => null,
accountExpiry: '',
settings: {
relaySettings: {
- host: 'www.example.com',
- protocol: 'udp',
- port: 1301,
+ normal: {
+ location: 'any',
+ protocol: 'any',
+ port: 'any',
+ }
},
},
connection: defaultConnection,
};
+
+function renderWithProps(customProps: $Shape<ConnectProps>) {
+ const props = { ...defaultProps, ...customProps };
+ return mount( <Connect { ...props } /> );
+}
diff --git a/test/components/SelectLocation.spec.js b/test/components/SelectLocation.spec.js
index 937622cb36..439edc1865 100644
--- a/test/components/SelectLocation.spec.js
+++ b/test/components/SelectLocation.spec.js
@@ -11,9 +11,11 @@ import type { SelectLocationProps } from '../../app/components/SelectLocation';
describe('components/SelectLocation', () => {
const state: SettingsReduxState = {
relaySettings: {
- host: 'example.com',
- protocol: 'udp',
- port: 1301,
+ normal: {
+ location: 'any',
+ protocol: 'any',
+ port: 'any',
+ }
},
};
diff --git a/test/components/Settings.spec.js b/test/components/Settings.spec.js
index 419c3e509c..31a5fa09a9 100644
--- a/test/components/Settings.spec.js
+++ b/test/components/Settings.spec.js
@@ -36,9 +36,11 @@ describe('components/Settings', () => {
const settingsState: SettingsReduxState = {
relaySettings: {
- host: 'example.com',
- protocol: 'udp',
- port: 1301,
+ normal: {
+ location: 'any',
+ protocol: 'udp',
+ port: 1301,
+ },
},
};
diff --git a/test/connect.spec.js b/test/connect.spec.js
index a7f991ec50..056e1bfe15 100644
--- a/test/connect.spec.js
+++ b/test/connect.spec.js
@@ -3,34 +3,9 @@
import { expect } from 'chai';
import connectionActions from '../app/redux/connection/actions';
import { setupBackendAndStore, checkNextTick } from './helpers/ipc-helpers';
-import { IpcChain } from './helpers/IpcChain';
describe('connect', () => {
- it('should invoke update_relay_settings and then connect in the backend', (done) => {
- const { store, mockIpc, backend } = setupBackendAndStore();
-
- const chain = new IpcChain(mockIpc);
- chain.require('updateRelaySettings')
- .withInputValidation(
- relayEndpoint => {
- if (relayEndpoint) {
- expect(relayEndpoint.custom_tunnel_endpoint.host).to.equal(arbitraryRelay);
- } else {
- expect.fail();
- }
- },
- )
- .done();
-
- chain.require('connect')
- .done();
-
- chain.onSuccessOrFailure(done);
-
- store.dispatch(connectionActions.connect(backend, arbitraryRelay));
- });
-
it('should set the connection state to \'disconnected\' on failed attempts', (done) => {
const { store, mockIpc, backend } = setupBackendAndStore();
@@ -41,7 +16,7 @@ describe('connect', () => {
expect(store.getState().connection.status).not.to.equal('disconnected');
- store.dispatch(connectionActions.connect(backend, arbitraryRelay));
+ store.dispatch(connectionActions.connect(backend));
checkNextTick(() => {
@@ -52,11 +27,10 @@ describe('connect', () => {
it('should update the state with the server address', () => {
const { store, backend } = setupBackendAndStore();
- return backend.connect('www.example.com', 'udp', 1301)
+ return backend.connect()
.then( () => {
const state = store.getState().connection;
expect(state.status).to.equal('connecting');
- expect(state.serverAddress).to.equal('www.example.com');
});
});
@@ -90,6 +64,4 @@ describe('connect', () => {
expect(store.getState().connection.status).to.equal('disconnected');
}, done);
});
-});
-
-const arbitraryRelay = 'www.example.com';
+}); \ No newline at end of file
diff --git a/test/relay-settings-builder.spec.js b/test/relay-settings-builder.spec.js
new file mode 100644
index 0000000000..3f27d2df23
--- /dev/null
+++ b/test/relay-settings-builder.spec.js
@@ -0,0 +1,151 @@
+// @flow
+
+import { expect } from 'chai';
+import RelaySettingsBuilder from '../app/lib/relay-settings-builder';
+
+describe('Relay settings builder', () => {
+
+ it('should set location to any', () => {
+ expect(
+ RelaySettingsBuilder.normal()
+ .location
+ .any()
+ .build()
+ ).to.deep.equal({
+ normal: {
+ location: 'any'
+ }
+ });
+ });
+
+ it('should bound location to city', () => {
+ expect(
+ RelaySettingsBuilder.normal()
+ .location.city('se', 'mma').build()
+ ).to.deep.equal({
+ normal: {
+ location: {
+ only: {
+ city: ['se', 'mma']
+ }
+ }
+ }
+ });
+ });
+
+ it('should bound location to country', () => {
+ expect(
+ RelaySettingsBuilder.normal()
+ .location.country('se').build()
+ ).to.deep.equal({
+ normal: {
+ location: {
+ only: { country: 'se' }
+ }
+ }
+ });
+ });
+
+ it('should set openvpn settings to any', () => {
+ expect(
+ RelaySettingsBuilder.normal()
+ .tunnel.openvpn(openvpn => {
+ openvpn.port.any()
+ .protocol.any();
+ })
+ .build()
+ ).to.deep.equal({
+ normal: {
+ tunnel: {
+ only: {
+ openvpn: {
+ port: 'any',
+ protocol: 'any'
+ }
+ }
+ }
+ }
+ });
+ });
+
+ it('should set openvpn settings to exact values', () => {
+ expect(
+ RelaySettingsBuilder.normal()
+ .tunnel.openvpn(openvpn => {
+ openvpn.port.exact(80)
+ .protocol.exact('tcp');
+ })
+ .build()
+ ).to.deep.equal({
+ normal: {
+ tunnel: {
+ only: {
+ openvpn: {
+ port: { only: 80 },
+ protocol: { only: 'tcp' }
+ }
+ }
+ }
+ }
+ });
+ });
+
+ it('should set location from raw RelayLocation', () => {
+ expect(
+ RelaySettingsBuilder.normal()
+ .location.fromRaw('any')
+ .build()
+ ).to.deep.equal({
+ normal: {
+ location: 'any'
+ }
+ });
+
+ expect(
+ RelaySettingsBuilder.normal()
+ .location.fromRaw({ country: 'se'})
+ .build()
+ ).to.deep.equal({
+ normal: {
+ location: {
+ only: { country: 'se' }
+ }
+ }
+ });
+
+ expect(
+ RelaySettingsBuilder.normal()
+ .location.fromRaw({ city: ['se', 'mma']})
+ .build()
+ ).to.deep.equal({
+ normal: {
+ location: {
+ only: { city: ['se', 'mma'] }
+ }
+ }
+ });
+ });
+
+ it('should set custom endpoint settings', () => {
+ expect(
+ RelaySettingsBuilder.custom()
+ .host('se2.mullvad.net')
+ .tunnel.openvpn((openvpn) => {
+ openvpn.port(80)
+ .protocol('tcp');
+ })
+ .build()
+ ).to.deep.equal({
+ custom_tunnel_endpoint: {
+ host: 'se2.mullvad.net',
+ tunnel: {
+ openvpn: {
+ port: 80,
+ protocol: 'tcp'
+ }
+ }
+ }
+ });
+ });
+
+}); \ No newline at end of file