summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorErik Larkö <erik@mullvad.net>2017-11-01 07:33:17 +0100
committerErik Larkö <erik@mullvad.net>2017-11-08 15:21:06 +0100
commita77e01a50b165fd8d6e2db96652a1fcc9a220723 (patch)
tree4866f64a6956f1cfc2a4a47f7dac792af3b07a47
parente53e809b9b674e70dc1f540ed25076ca5bc6fae2 (diff)
downloadmullvadvpn-a77e01a50b165fd8d6e2db96652a1fcc9a220723.tar.xz
mullvadvpn-a77e01a50b165fd8d6e2db96652a1fcc9a220723.zip
tcp and port
-rw-r--r--app/app.js3
-rw-r--r--app/components/AdvancedSettings.js127
-rw-r--r--app/components/Connect.js38
-rw-r--r--app/components/SelectLocation.js6
-rw-r--r--app/components/Settings.css22
-rw-r--r--app/components/Settings.js15
-rw-r--r--app/containers/AdvancedSettingsPage.js51
-rw-r--r--app/containers/ConnectPage.js2
-rw-r--r--app/containers/SelectLocationPage.js21
-rw-r--r--app/containers/SettingsPage.js1
-rw-r--r--app/lib/backend.js93
-rw-r--r--app/lib/ipc-facade.js54
-rw-r--r--app/lib/jsonrpc-ws-ipc.js2
-rw-r--r--app/main.js2
-rw-r--r--app/redux/connection/actions.js9
-rw-r--r--app/redux/connection/reducers.js4
-rw-r--r--app/redux/settings/actions.js18
-rw-r--r--app/redux/settings/reducers.js20
-rw-r--r--app/redux/store.js4
-rw-r--r--app/routes.js2
-rw-r--r--app/transitions.js1
-rw-r--r--test/components/Connect.spec.js16
-rw-r--r--test/components/SelectLocation.spec.js10
-rw-r--r--test/components/Settings.spec.js10
-rw-r--r--test/connect.spec.js25
-rw-r--r--test/mocks/ipc.js10
-rw-r--r--test/reducers.spec.js21
27 files changed, 449 insertions, 138 deletions
diff --git a/app/app.js b/app/app.js
index c07f267617..6536de4983 100644
--- a/app/app.js
+++ b/app/app.js
@@ -27,6 +27,9 @@ ipcRenderer.on('backend-info', (_event, args) => {
backend.setCredentials(args.credentials);
backend.sync();
backend.autologin()
+ .then( () => {
+ return backend.syncRelayConstraints();
+ })
.catch( e => {
if (e.type === 'NO_ACCOUNT') {
log.debug('No user set in the backend, showing window');
diff --git a/app/components/AdvancedSettings.js b/app/components/AdvancedSettings.js
new file mode 100644
index 0000000000..1ba25a924f
--- /dev/null
+++ b/app/components/AdvancedSettings.js
@@ -0,0 +1,127 @@
+// @flow
+
+import React from 'react';
+import { Layout, Container, Header } from './Layout';
+import CustomScrollbars from './CustomScrollbars';
+
+type Props = {
+ onClose: () => void,
+ protocol: string,
+ port: string|number,
+ updateConstraints: (string, string|number) => void,
+};
+export function AdvancedSettings(props: Props) {
+
+ let portSelector = null;
+ let protocol = props.protocol.toUpperCase();
+
+ if (protocol === 'AUTOMATIC') {
+ protocol = 'Automatic';
+ } else {
+ portSelector = createPortSelector(props);
+ }
+
+ return <BaseLayout onClose={ props.onClose }>
+
+ <Selector
+ title={ 'Network protocols' }
+ values={ ['Automatic', 'UDP', 'TCP'] }
+ value={ protocol }
+ onSelect={ protocol => {
+ // $FlowFixMe
+ props.updateConstraints(protocol, 'Automatic');
+ }}/>
+
+ <div className="settings__cell-spacer"></div>
+
+ { portSelector }
+
+ </BaseLayout>;
+
+}
+
+function createPortSelector(props) {
+ const protocol = props.protocol.toUpperCase();
+ const ports = protocol === 'TCP'
+ ? ['Automatic', 80, 443]
+ : ['Automatic', 1194, 1195, 1196, 1197, 1300, 1301, 1302];
+
+ return <Selector
+ title={ protocol + ' port' }
+ values={ ports }
+ value={ props.port }
+ onSelect={ port => {
+ props.updateConstraints(protocol, port);
+ }} />;
+}
+
+function Selector(props) {
+ return <div>
+ <Cell
+ label={ props.title }
+ />
+
+ { props.values.map(value => renderCell(value)) }
+ </div>;
+
+ function renderCell(value) {
+ const selected = value === props.value;
+
+ let className = 'settings__sub-cell';
+ let tick = null;
+ if (selected) {
+ className = 'settings__cell--selected';
+ tick = <img src='./assets/images/icon-tick.svg' />;
+ }
+ const label = <div className={ 'settings__sub-cell--label' }>
+ { tick }
+ { value }
+ </div>;
+
+ const onCellClick = () => props.onSelect(value);
+
+ return <Cell
+ key={ value }
+ label={ label }
+ className={ className }
+ onClick={ onCellClick } />;
+ }
+}
+
+function BaseLayout(props) {
+ return <Layout>
+ <Header hidden={ true } style={ 'defaultDark' } />
+ <Container>
+ <div className="settings">
+ <button className="settings__close" onClick={ props.onClose } />
+ <div className="settings__container">
+ <div className="settings__header">
+ <h2 className="settings__title">Advanced Settings</h2>
+ </div>
+ <CustomScrollbars autoHide={ true }>
+ <div className="settings__content">
+ <div className="settings__main">
+ <div className="settings__advanced">
+ { props.children }
+ </div>
+ </div>
+ </div>
+ </CustomScrollbars>
+ </div>
+ </div>
+ </Container>
+ </Layout>;
+}
+
+function Cell(props) {
+
+ const className = props.className || '';
+ return <div
+ className={ className + ' settings__cell' }
+ onClick={ props.onClick || null } >
+ <div className="settings__cell-label">{ props.label }</div>
+ <div className="settings__cell-value">
+ { props.value || null }
+ </div>
+ </div>;
+}
diff --git a/app/components/Connect.js b/app/components/Connect.js
index c49912349d..531174b464 100644
--- a/app/components/Connect.js
+++ b/app/components/Connect.js
@@ -10,15 +10,15 @@ import ExternalLinkSVG from '../assets/images/icon-extLink.svg';
import type { ServerInfo } from '../lib/backend';
import type { HeaderBarStyle } from './HeaderBar';
import type { ConnectionReduxState } from '../redux/connection/reducers';
-import type { RelayEndpoint } from '../lib/ipc-facade';
+import type { SettingsReduxState } from '../redux/settings/reducers';
export type ConnectProps = {
accountExpiry: string,
connection: ConnectionReduxState,
- preferredServer: string,
+ settings: SettingsReduxState,
onSettings: () => void,
onSelectLocation: () => void,
- onConnect: (relayEndpoint: RelayEndpoint) => void,
+ onConnect: (host: string) => void,
onCopyIP: () => void,
onDisconnect: () => void,
onExternalLink: (type: string) => void,
@@ -93,9 +93,23 @@ export default class Connect extends Component {
);
}
+ _getServerInfo() {
+ const { relayConstraints } = this.props.settings;
+ if (relayConstraints.host === 'any') {
+ return {
+ name: 'Automatic',
+ country: 'Automatic',
+ city: 'Automatic',
+ address: '',
+ };
+ }
+
+ return this.props.getServerInfo(relayConstraints.host.only);
+
+ }
+
renderMap(): React.Element<*> {
- const preferredServer = this.props.preferredServer;
- const serverInfo = this.props.getServerInfo(preferredServer);
+ const serverInfo = this._getServerInfo();
if(!serverInfo) {
throw new Error('Server info cannot be null.');
}
@@ -272,16 +286,12 @@ export default class Connect extends Component {
// Handlers
onConnect() {
- const preferredServer = this.props.preferredServer;
- const serverInfo = this.props.getServerInfo(preferredServer);
- if(serverInfo) {
- // TODO: Don't use these hardcoded values
- this.props.onConnect({
- host: serverInfo.address,
- port: 1300,
- protocol: 'udp',
- });
+ const serverInfo = this._getServerInfo();
+ if(!serverInfo) {
+ return;
}
+
+ this.props.onConnect(serverInfo.address);
}
onExternalLink(type: string) {
diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js
index 5df5603f71..c5180a3ffa 100644
--- a/app/components/SelectLocation.js
+++ b/app/components/SelectLocation.js
@@ -24,7 +24,11 @@ export default class SelectLocation extends Component {
}
isSelected(server: string) {
- return server === this.props.settings.preferredServer;
+ const { host } = this.props.settings.relayConstraints;
+ if (host === 'any') {
+ return false;
+ }
+ return server === host.only;
}
drawCell(key: string, name: string, icon: ?string, onClick: (e: Event) => void): React.Element<*> {
diff --git a/app/components/Settings.css b/app/components/Settings.css
index 7bb3dfcec7..00067a1074 100644
--- a/app/components/Settings.css
+++ b/app/components/Settings.css
@@ -63,6 +63,11 @@
height: 24px;
}
+.settings__cell--selected,
+.settings__cell--selected:hover {
+ background-color: #44AD4D;
+}
+
.settings__cell--active:hover {
background-color: rgba(41,71,115,0.9);
}
@@ -91,6 +96,21 @@
flex: 0 0 auto;
}
+.settings__sub-cell {
+ background-color: rgb(36, 57, 84);
+}
+.settings__sub-cell:hover {
+ background-color: rgba(41,71,115,0.9);
+}
+
+.settings__sub-cell--label {
+ padding-left: 15px;
+}
+
+.settings__sub-cell--label img {
+ padding-right: 8px;
+}
+
.settings__account-paid-until-label {
font-family: "Open Sans";
font-size: 13px;
@@ -117,4 +137,4 @@
padding: 24px;
}
-.settings__footer .button + .button { margin-top: 16px; } \ No newline at end of file
+.settings__footer .button + .button { margin-top: 16px; }
diff --git a/app/components/Settings.js b/app/components/Settings.js
index 4c906a9dc1..fd9fbebebd 100644
--- a/app/components/Settings.js
+++ b/app/components/Settings.js
@@ -15,6 +15,7 @@ export type SettingsProps = {
onClose: () => void,
onViewAccount: () => void,
onViewSupport: () => void,
+ onViewAdvancedSettings: () => void,
onExternalLink: (type: string) => void
};
@@ -67,8 +68,20 @@ export default class Settings extends Component {
<img className="settings__cell-disclosure" src="assets/images/icon-chevron.svg" />
</div>
<div className="settings__cell-spacer"></div>
+ </div>
+ </Then>
+ </If>
- <div className="settings__cell-footer"></div>
+ <If condition={ isLoggedIn }>
+ <Then>
+ <div className="settings__advanced">
+ <div className="settings__cell settings__cell--active" onClick={ this.props.onViewAdvancedSettings }>
+ <div className="settings__cell-label">Advanced</div>
+ <div className="settings__cell-value">
+ <img className="settings__cell-disclosure" src="assets/images/icon-chevron.svg" />
+ </div>
+ </div>
+ <div className="settings__cell-spacer"></div>
</div>
</Then>
</If>
diff --git a/app/containers/AdvancedSettingsPage.js b/app/containers/AdvancedSettingsPage.js
new file mode 100644
index 0000000000..22dbdf8ec1
--- /dev/null
+++ b/app/containers/AdvancedSettingsPage.js
@@ -0,0 +1,51 @@
+import { connect } from 'react-redux';
+import { push } from 'react-router-redux';
+import { AdvancedSettings } from '../components/AdvancedSettings';
+import settingsActions from '../redux/settings/actions';
+import log from 'electron-log';
+
+const mapStateToProps = (state) => {
+ return {
+ protocol: tryOrElse( () => state.settings.relayConstraints.tunnel.openvpn.protocol.only, 'Automatic'),
+ port: tryOrElse( () => state.settings.relayConstraints.tunnel.openvpn.port.only, 'Automatic'),
+ };
+};
+
+function tryOrElse(toTry, orElse) {
+ try {
+ return toTry() || orElse;
+ } catch (e) {
+ return orElse;
+ }
+}
+
+const mapDispatchToProps = (dispatch, props) => {
+ const { backend } = props;
+ return {
+ onClose: () => dispatch(push('/settings')),
+
+ updateConstraints: (protocol, port) => {
+
+ const protConstraint = protocol === 'Automatic'
+ ? 'any'
+ : { only: protocol.toLowerCase() };
+
+ const portConstraint = port === 'Automatic'
+ ? 'any'
+ : { only: port };
+
+ const update = {
+ tunnel: { openvpn: {
+ protocol: protConstraint,
+ port: portConstraint,
+ }},
+ };
+
+ backend.updateRelayConstraints(update)
+ .then( () => dispatch(settingsActions.updateRelay(update)))
+ .catch( e => log.error('Failed updating relay constraints', e.message));
+ },
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(AdvancedSettings);
diff --git a/app/containers/ConnectPage.js b/app/containers/ConnectPage.js
index d629e83fc7..5c6dc1fa60 100644
--- a/app/containers/ConnectPage.js
+++ b/app/containers/ConnectPage.js
@@ -10,7 +10,7 @@ const mapStateToProps = (state) => {
return {
accountExpiry: state.account.expiry,
connection: state.connection,
- preferredServer: state.settings.preferredServer,
+ settings: state.settings,
};
};
diff --git a/app/containers/SelectLocationPage.js b/app/containers/SelectLocationPage.js
index c11c4b59dd..9e5551130c 100644
--- a/app/containers/SelectLocationPage.js
+++ b/app/containers/SelectLocationPage.js
@@ -1,30 +1,31 @@
import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
import { push } from 'react-router-redux';
import SelectLocation from '../components/SelectLocation';
import settingsActions from '../redux/settings/actions';
+import log from 'electron-log';
const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch, props) => {
const { backend } = props;
- const settings = bindActionCreators(settingsActions, dispatch);
return {
onClose: () => dispatch(push('/connect')),
onSelect: (preferredServer) => {
- const server = backend.serverInfo(preferredServer);
dispatch(push('/connect'));
// add delay to let the map load
setTimeout(() => {
- settings.updateSettings({ preferredServer });
+ const update = {
+ host: { only: preferredServer },
+ tunnel: { openvpn: {
+ }},
+ };
+
+ backend.updateRelayConstraints(update)
+ .then( () => dispatch(settingsActions.updateRelay(update)))
+ .then( () => backend.connect())
+ .catch( e => log.error('Failed updating relay constraints', e.message));
- // TODO: Don't use these hardcoded values
- backend.connect({
- host: server.address,
- port: 1300,
- protocol: 'udp',
- });
}, 600);
}
};
diff --git a/app/containers/SettingsPage.js b/app/containers/SettingsPage.js
index 520f27ce6e..72eafb9431 100644
--- a/app/containers/SettingsPage.js
+++ b/app/containers/SettingsPage.js
@@ -14,6 +14,7 @@ const mapDispatchToProps = (dispatch, _props) => {
onClose: () => dispatch(push('/connect')),
onViewAccount: () => dispatch(push('/settings/account')),
onViewSupport: () => dispatch(push('/settings/support')),
+ onViewAdvancedSettings: () => dispatch(push('/settings/advanced')),
onExternalLink: (type) => shell.openExternal(links[type]),
};
};
diff --git a/app/lib/backend.js b/app/lib/backend.js
index b210e65e85..89a895b4c4 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -1,16 +1,17 @@
// @flow
-//import log from 'electron-log';
-const log = console;
+import log from 'electron-log';
import EventEmitter from 'events';
import { servers } from '../config';
import { IpcFacade, RealIpc } from './ipc-facade';
import accountActions from '../redux/account/actions';
import connectionActions from '../redux/connection/actions';
-import type { ReduxStore } from '../redux/store';
+import settingsActions from '../redux/settings/actions';
import { push } from 'react-router-redux';
+import { defaultServer } from '../config';
-import type { BackendState, RelayEndpoint } from './ipc-facade';
+import type { ReduxStore } from '../redux/store';
+import type { BackendState, RelayConstraintsUpdate } from './ipc-facade';
import type { ConnectionState } from '../redux/connection/reducers';
export type EventType = 'connect' | 'connecting' | 'disconnect' | 'login' | 'logging' | 'logout' | 'updatedIp' | 'updatedLocation' | 'updatedReachability';
@@ -114,7 +115,7 @@ export class Backend {
}
setCredentials(credentials: IpcCredentials) {
- log.info('Got connection info to backend', credentials.connectionString);
+ log.debug('Got connection info to backend', credentials.connectionString);
this._credentials = credentials;
if (this._ipc) {
@@ -168,7 +169,7 @@ export class Backend {
}
login(accountToken: string): Promise<void> {
- log.info('Attempting to login with account number', accountToken);
+ log.debug('Attempting to login with account number', accountToken);
this._store.dispatch(accountActions.startLogin(accountToken));
@@ -176,7 +177,7 @@ export class Backend {
.then( () => {
return this._ipc.getAccountData(accountToken)
.then( response => {
- log.info('Account exists', response);
+ log.debug('Account exists', response);
return this._ipc.setAccount(accountToken)
.then( () => response );
@@ -203,7 +204,7 @@ export class Backend {
}
autologin() {
- log.info('Attempting to log in automatically');
+ log.debug('Attempting to log in automatically');
this._store.dispatch(accountActions.startLogin());
@@ -220,7 +221,7 @@ export class Backend {
return this._ipc.getAccountData(accountToken);
})
.then( accountData => {
- log.info('The stored account number still exists', accountData);
+ log.debug('The stored account number still exists', accountData);
this._store.dispatch(accountActions.loginSuccessful(accountData.expiry));
@@ -259,33 +260,26 @@ export class Backend {
});
}
- connect(aRelayEndpoint?: RelayEndpoint): Promise<void> {
-
- const relayEndpoint = aRelayEndpoint;
- if (relayEndpoint) {
- this._store.dispatch(connectionActions.connectingTo(relayEndpoint));
+ connect(aHost?: string): Promise<void> {
+ const host = aHost;
- return this._ensureAuthenticated()
- .then( () => {
- return this._ipc.setCustomRelay(relayEndpoint)
- .then( () => {
- return this._ipc.connect();
- })
- .catch(e => {
- log.info('Failed connecting to', relayEndpoint.host, '-', e.message);
- this._store.dispatch(connectionActions.disconnected());
- });
- });
- } else {
- return this._ensureAuthenticated()
- .then( () => {
- return this._ipc.connect()
- .catch(e => {
- log.info('Failed connecting to the relay set in the backend, ', e.message);
- this._store.dispatch(connectionActions.disconnected());
- });
- });
+ let setHostPromise = () => Promise.resolve();
+ if (host) {
+ this._store.dispatch(connectionActions.connectingTo(host || 'unknown'));
+ setHostPromise = () => this._ipc.updateRelayConstraints({
+ host: { only: host },
+ tunnel: { openvpn: {
+ }},
+ });
}
+
+ return this._ensureAuthenticated()
+ .then( setHostPromise )
+ .then( () => this._ipc.connect() )
+ .catch(e => {
+ log.info('Failed connecting to the relay set in the backend, ', e.message);
+ this._store.dispatch(connectionActions.disconnected());
+ });
}
disconnect(): Promise<void> {
@@ -306,6 +300,35 @@ export class Backend {
});
}
+ updateRelayConstraints(relayConstraints: RelayConstraintsUpdate): Promise<void> {
+ return this._ensureAuthenticated()
+ .then( () => {
+ return this._ipc.updateRelayConstraints(relayConstraints);
+ });
+ }
+
+ syncRelayConstraints(): Promise<void> {
+ return this._ensureAuthenticated()
+ .then( () => {
+ return this._ipc.getRelayContraints();
+ })
+ .then( constraints => {
+ log.debug('Got constraints from backend', constraints);
+
+ const host = constraints.host === 'any'
+ ? defaultServer
+ : constraints.host || defaultServer;
+
+ this._store.dispatch(settingsActions.updateRelay({
+ host: host,
+ tunnel: constraints.tunnel,
+ }));
+ })
+ .catch( e => {
+ log.error('Failed getting relay constraints', e);
+ });
+ }
+
/**
* Start reachability monitoring for online/offline detection
* This is currently done via HTML5 APIs but will be replaced later
@@ -335,7 +358,7 @@ export class Backend {
return this._ensureAuthenticated()
.then( () => {
return this._ipc.registerStateListener(newState => {
- log.info('Got new state from backend', newState);
+ log.debug('Got new state from backend', newState);
const newStatus = this._securityStateToConnectionState(newState);
switch(newStatus) {
diff --git a/app/lib/ipc-facade.js b/app/lib/ipc-facade.js
index 8939e82823..9f3f900331 100644
--- a/app/lib/ipc-facade.js
+++ b/app/lib/ipc-facade.js
@@ -1,7 +1,7 @@
// @flow
import JsonRpcWs, { InvalidReply } from './jsonrpc-ws-ipc';
-import { object, string, arrayOf, number } from 'validated/schema';
+import { object, string, arrayOf, number, enumeration, oneOf } from 'validated/schema';
import { validate } from 'validated/object';
import type { Coordinate2d } from '../types';
@@ -25,11 +25,36 @@ export type BackendState = {
state: SecurityState,
target_state: SecurityState,
};
-export type RelayEndpoint = {
- host: string,
- port: number,
- protocol: 'tcp' | 'udp',
+export type RelayConstraints = {
+ host: 'any' | { only: string },
+ tunnel: {
+ openvpn: {
+ port: 'any' | { only: number },
+ protocol: 'any' | { only: 'tcp' | 'udp' },
+ },
+ },
};
+export type RelayConstraintsUpdate = {
+ host?: 'any' | { only: string },
+ tunnel: {
+ openvpn: {
+ port?: 'any' | { only: number },
+ protocol?: 'any' | { only: 'tcp' | 'udp' },
+ },
+ },
+};
+const Constraint = (v) => oneOf(string, object({
+ only: v,
+}));
+const RelayConstraintsSchema = object({
+ host: Constraint(string),
+ tunnel: object({
+ openvpn: object({
+ port: Constraint(number),
+ protocol: Constraint(enumeration('udp', 'tcp')),
+ }),
+ }),
+});
export interface IpcFacade {
@@ -37,7 +62,8 @@ export interface IpcFacade {
getAccountData(AccountToken): Promise<AccountData>,
getAccount(): Promise<?AccountToken>,
setAccount(accountToken: ?AccountToken): Promise<void>,
- setCustomRelay(RelayEndpoint): Promise<void>,
+ updateRelayConstraints(RelayConstraintsUpdate): Promise<void>,
+ getRelayContraints(): Promise<RelayConstraints>,
connect(): Promise<void>,
disconnect(): Promise<void>,
shutdown(): Promise<void>,
@@ -95,11 +121,23 @@ export class RealIpc implements IpcFacade {
return;
}
- setCustomRelay(relayEndpoint: RelayEndpoint): Promise<void> {
- return this._ipc.send('set_custom_relay', [relayEndpoint])
+ updateRelayConstraints(relayConstraints: RelayConstraintsUpdate): Promise<void> {
+ return this._ipc.send('update_relay_constraints', [relayConstraints])
.then(this._ignoreResponse);
}
+ getRelayContraints(): Promise<RelayConstraints> {
+ return this._ipc.send('get_relay_constraints')
+ .then( raw => {
+ try {
+ const validated: any = validate(RelayConstraintsSchema, raw);
+ return (validated: RelayConstraints);
+ } catch (e) {
+ throw new InvalidReply(raw, e);
+ }
+ });
+ }
+
connect(): Promise<void> {
return this._ipc.send('connect')
.then(this._ignoreResponse);
diff --git a/app/lib/jsonrpc-ws-ipc.js b/app/lib/jsonrpc-ws-ipc.js
index deefce3d66..909b4b0775 100644
--- a/app/lib/jsonrpc-ws-ipc.js
+++ b/app/lib/jsonrpc-ws-ipc.js
@@ -98,7 +98,7 @@ export default class Ipc {
on(event: string, listener: (mixed) => void): Promise<*> {
- log.info('Adding a listener to', event);
+ log.debug('Adding a listener to', event);
return this.send(event + '_subscribe')
.then(subscriptionId => {
if (typeof subscriptionId === 'string' || typeof subscriptionId === 'number') {
diff --git a/app/main.js b/app/main.js
index cc47a57cb2..3af6a4863e 100644
--- a/app/main.js
+++ b/app/main.js
@@ -233,7 +233,7 @@ const appDelegate = {
const credentials = parseIpcCredentials(data);
if(credentials) {
- log.info('Read IPC connection info', credentials.connectionString);
+ log.debug('Read IPC connection info', credentials.connectionString);
window.webContents.send('backend-info', { credentials });
} else {
log.error('Could not parse IPC credentials.');
diff --git a/app/redux/connection/actions.js b/app/redux/connection/actions.js
index 73a94fcc06..dcc90f7273 100644
--- a/app/redux/connection/actions.js
+++ b/app/redux/connection/actions.js
@@ -3,12 +3,11 @@
import { clipboard } from 'electron';
import type { Backend } from '../../lib/backend';
-import type { RelayEndpoint } from '../../lib/ipc-facade';
import type { ReduxGetState, ReduxDispatch } from '../store';
import type { Coordinate2d } from '../../types';
-const connect = (backend: Backend, relay: RelayEndpoint) => () => backend.connect(relay);
+const connect = (backend: Backend, relay: string) => () => backend.connect(relay);
const disconnect = (backend: Backend) => () => backend.disconnect();
const copyIPAddress = () => {
return (_dispatch: ReduxDispatch, getState: ReduxGetState) => {
@@ -22,7 +21,7 @@ const copyIPAddress = () => {
type ConnectingAction = {
type: 'CONNECTING',
- relayEndpoint?: RelayEndpoint,
+ host?: string,
};
type ConnectedAction = {
type: 'CONNECTED',
@@ -63,10 +62,10 @@ export type ConnectionAction = NewPublicIpAction
| OnlineAction
| OfflineAction;
-function connectingTo(relayEndpoint: RelayEndpoint): ConnectingAction {
+function connectingTo(host: string): ConnectingAction {
return {
type: 'CONNECTING',
- relayEndpoint: relayEndpoint,
+ host: host,
};
}
diff --git a/app/redux/connection/reducers.js b/app/redux/connection/reducers.js
index 8adfac594a..ce4cb79344 100644
--- a/app/redux/connection/reducers.js
+++ b/app/redux/connection/reducers.js
@@ -62,8 +62,8 @@ function onConnecting(state, action) {
status: 'connecting',
};
- if (action.relayEndpoint) {
- newState.serverAddress = action.relayEndpoint.host;
+ if (action.host) {
+ newState.serverAddress = action.host;
}
return { ...state, ...newState};
}
diff --git a/app/redux/settings/actions.js b/app/redux/settings/actions.js
index 7b806096b8..041da76389 100644
--- a/app/redux/settings/actions.js
+++ b/app/redux/settings/actions.js
@@ -1,17 +1,19 @@
// @flow
-import type { SettingsReduxState } from './reducers';
+import type { RelayConstraints } from '../../lib/ipc-facade';
-export type UpdateSettingsAction = {
- type: 'UPDATE_SETTINGS',
- newSettings: $Shape<SettingsReduxState>,
+export type UpdateRelayAction = {
+ type: 'UPDATE_RELAY',
+ relay: RelayConstraints,
};
-function updateSettings(newSettings: $Shape<SettingsReduxState>): UpdateSettingsAction {
+export type SettingsAction = UpdateRelayAction;
+
+function updateRelay(relay: RelayConstraints): UpdateRelayAction {
return {
- type: 'UPDATE_SETTINGS',
- newSettings: newSettings,
+ type: 'UPDATE_RELAY',
+ relay: relay,
};
}
-export default { updateSettings };
+export default { updateRelay };
diff --git a/app/redux/settings/reducers.js b/app/redux/settings/reducers.js
index da23a5a6ce..665a4c465e 100644
--- a/app/redux/settings/reducers.js
+++ b/app/redux/settings/reducers.js
@@ -3,19 +3,31 @@
import { defaultServer } from '../../config';
import type { ReduxAction } from '../store';
+import type { RelayConstraints } from '../../lib/ipc-facade';
export type SettingsReduxState = {
- preferredServer: string
+ relayConstraints: RelayConstraints,
};
const initialState: SettingsReduxState = {
- preferredServer: defaultServer
+ relayConstraints: {
+ host: { only: defaultServer },
+ tunnel: { openvpn: {
+ port: 'any',
+ protocol: 'any',
+ }},
+ },
};
export default function(state: SettingsReduxState = initialState, action: ReduxAction): SettingsReduxState {
- if (action.type === 'UPDATE_SETTINGS') {
- return { ...state, ...action.newSettings };
+ if (action.type === 'UPDATE_RELAY') {
+ return { ...state,
+ relayConstraints: {
+ ...state.relayConstraints,
+ ...action.relay,
+ },
+ };
}
return state;
diff --git a/app/redux/store.js b/app/redux/store.js
index b4aa0375e1..3f73574103 100644
--- a/app/redux/store.js
+++ b/app/redux/store.js
@@ -18,7 +18,7 @@ import type { SettingsReduxState } from './settings/reducers.js';
import type { ConnectionAction } from './connection/actions.js';
import type { AccountAction } from './account/actions.js';
-import type { UpdateSettingsAction } from './settings/actions.js';
+import type { SettingsAction } from './settings/actions.js';
export type ReduxState = {
account: AccountReduxState,
@@ -27,7 +27,7 @@ export type ReduxState = {
};
export type ReduxAction = AccountAction
- | UpdateSettingsAction
+ | SettingsAction
| ConnectionAction;
export type ReduxStore = Store<ReduxState, ReduxAction, ReduxDispatch>;
diff --git a/app/routes.js b/app/routes.js
index 5736e7f37a..f428c8093b 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -7,6 +7,7 @@ import WindowChrome from './components/WindowChrome';
import LoginPage from './containers/LoginPage';
import ConnectPage from './containers/ConnectPage';
import SettingsPage from './containers/SettingsPage';
+import AdvancedSettingsPage from './containers/AdvancedSettingsPage';
import AccountPage from './containers/AccountPage';
import SupportPage from './containers/SupportPage';
import SelectLocationPage from './containers/SelectLocationPage';
@@ -97,6 +98,7 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar
<PublicRoute exact path="/settings" component={ SettingsPage } />
<PrivateRoute exact path="/settings/account" component={ AccountPage } />
<PublicRoute exact path="/settings/support" component={ SupportPage } />
+ <PublicRoute exact path="/settings/advanced" component={ AdvancedSettingsPage } />
<PrivateRoute exact path="/select-location" component={ SelectLocationPage } />
</Switch>
</CSSTransitionGroup>
diff --git a/app/transitions.js b/app/transitions.js
index 0ce94e2d16..fe6da53563 100644
--- a/app/transitions.js
+++ b/app/transitions.js
@@ -50,6 +50,7 @@ const transitions: TransitionMap = {
const transitionRules = [
r('/settings', '/settings/account', transitions.push),
r('/settings', '/settings/support', transitions.push),
+ r('/settings', '/settings/advanced', transitions.push),
r(null, '/settings', transitions.slide),
r(null, '/select-location', transitions.slide)
];
diff --git a/test/components/Connect.spec.js b/test/components/Connect.spec.js
index 07c67a57bc..5c342bff12 100644
--- a/test/components/Connect.spec.js
+++ b/test/components/Connect.spec.js
@@ -106,7 +106,11 @@ describe('components/Connect', () => {
const locationSwitcher = component.find('.connect__server');
component.setProps({
- preferredServer: 'se1.mullvad.net',
+ settings: {
+ relayConstraints: {
+ host: { only: 'se1.mullvad.net' },
+ },
+ },
});
expect(locationSwitcher.text()).to.contain(servers['se1.mullvad.net'].name);
});
@@ -181,6 +185,14 @@ const defaultProps = {
getServerInfo: (_) => { return defaultServer; },
accountExpiry: '',
- preferredServer: '',
+ settings: {
+ relayConstraints: {
+ host: { only: 'www.example.com' },
+ tunnel: { openvpn: {
+ port: 'any',
+ protocol: 'any',
+ }},
+ },
+ },
connection: defaultConnection,
};
diff --git a/test/components/SelectLocation.spec.js b/test/components/SelectLocation.spec.js
index 783a3adbf9..a3565a6704 100644
--- a/test/components/SelectLocation.spec.js
+++ b/test/components/SelectLocation.spec.js
@@ -4,15 +4,19 @@ import { expect } from 'chai';
import React from 'react';
import ReactTestUtils, { Simulate } from 'react-dom/test-utils';
import SelectLocation from '../../app/components/SelectLocation';
-import { defaultServer } from '../../app/config';
import type { SettingsReduxState } from '../../app/redux/settings/reducers';
import type { SelectLocationProps } from '../../app/components/SelectLocation';
describe('components/SelectLocation', () => {
const state: SettingsReduxState = {
- autoSecure: true,
- preferredServer: defaultServer
+ relayConstraints: {
+ host: 'any',
+ tunnel: { openvpn: {
+ port: 'any',
+ protocol: 'any',
+ }},
+ },
};
const makeProps = (state: SettingsReduxState, mergeProps: $Shape<SelectLocationProps>): SelectLocationProps => {
diff --git a/test/components/Settings.spec.js b/test/components/Settings.spec.js
index f90d658f9b..2f562051b5 100644
--- a/test/components/Settings.spec.js
+++ b/test/components/Settings.spec.js
@@ -4,7 +4,6 @@ import { expect } from 'chai';
import React from 'react';
import ReactTestUtils, { Simulate } from 'react-dom/test-utils';
import Settings from '../../app/components/Settings';
-import { defaultServer } from '../../app/config';
import type { AccountReduxState } from '../../app/redux/account/reducers';
import type { SettingsReduxState } from '../../app/redux/settings/reducers';
@@ -33,7 +32,13 @@ describe('components/Settings', () => {
};
const settingsState: SettingsReduxState = {
- preferredServer: defaultServer
+ relayConstraints: {
+ host: 'any',
+ tunnel: { openvpn: {
+ port: 'any',
+ protocol: 'any',
+ }},
+ },
};
const makeProps = (anAccountState: AccountReduxState, aSettingsState: SettingsReduxState, mergeProps: $Shape<SettingsProps> = {}): SettingsProps => {
@@ -44,6 +49,7 @@ describe('components/Settings', () => {
onClose: () => {},
onViewAccount: () => {},
onViewSupport: () => {},
+ onViewAdvancedSettings: () => {},
onExternalLink: (_type) => {}
};
return Object.assign({}, defaultProps, mergeProps);
diff --git a/test/connect.spec.js b/test/connect.spec.js
index 07e3091c6e..586df45980 100644
--- a/test/connect.spec.js
+++ b/test/connect.spec.js
@@ -7,14 +7,18 @@ import { IpcChain } from './helpers/IpcChain';
describe('connect', () => {
- it('should invoke set_custom_relay and then connect in the backend', (done) => {
+ it('should invoke update_relay_constraints and then connect in the backend', (done) => {
const { store, mockIpc, backend } = setupBackendAndStore();
const chain = new IpcChain(mockIpc);
- chain.require('setCustomRelay')
+ chain.require('updateRelayConstraints')
.withInputValidation(
- (relayEndpoint) => {
- expect(relayEndpoint).to.equal(arbitraryRelay);
+ relayEndpoint => {
+ if (relayEndpoint) {
+ expect(relayEndpoint.host.only).to.equal(arbitraryRelay);
+ } else {
+ expect.fail();
+ }
},
)
.done();
@@ -47,13 +51,8 @@ describe('connect', () => {
it('should update the state with the server address', () => {
const { store, backend } = setupBackendAndStore();
- const relay = {
- host: 'www.example.com',
- port: 1,
- protocol: 'udp',
- };
- return backend.connect(relay)
+ return backend.connect('www.example.com')
.then( () => {
const state = store.getState().connection;
expect(state.status).to.equal('connecting');
@@ -93,8 +92,4 @@ describe('connect', () => {
});
});
-const arbitraryRelay = {
- host: 'www.example.com',
- port: 1,
- protocol: 'udp',
-};
+const arbitraryRelay = 'www.example.com';
diff --git a/test/mocks/ipc.js b/test/mocks/ipc.js
index c02eb20de4..67c11c7366 100644
--- a/test/mocks/ipc.js
+++ b/test/mocks/ipc.js
@@ -28,7 +28,15 @@ export function newMockIpc() {
setAccount: () => Promise.resolve(),
- setCustomRelay: () => Promise.resolve(),
+ updateRelayConstraints: () => Promise.resolve(),
+
+ getRelayContraints: () => Promise.resolve({
+ host: { only: 'www.example.com' },
+ tunnel: { openvpn: {
+ port: 'any',
+ protocol: 'any',
+ }},
+ }),
connect: () => Promise.resolve(),
diff --git a/test/reducers.spec.js b/test/reducers.spec.js
deleted file mode 100644
index 92392ad5d3..0000000000
--- a/test/reducers.spec.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// @flow
-
-import { expect } from 'chai';
-import settingsReducer from '../app/redux/settings/reducers';
-import { defaultServer } from '../app/config';
-
-describe('reducers', () => {
- const previousState: any = {};
-
- it('should handle SETTINGS_UPDATE', () => {
- const action = {
- type: 'UPDATE_SETTINGS',
- newSettings: {
- preferredServer: defaultServer
- }
- };
- const test = Object.assign({}, action.newSettings);
- expect(settingsReducer(previousState, action)).to.deep.equal(test);
- });
-
-});