summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-09-06 17:28:57 +0300
committerAndrej Mihajlov <and@mullvad.net>2018-09-10 17:49:14 +0300
commit854e3ae2e4fdfefb0c56faa56807e5e163238542 (patch)
treebfa292400304ac6ed63ce8c60aa4035c39abb9b4
parent91d3fe07688df60b435f934fce06e19a88560fb4 (diff)
downloadmullvadvpn-854e3ae2e4fdfefb0c56faa56807e5e163238542.tar.xz
mullvadvpn-854e3ae2e4fdfefb0c56faa56807e5e163238542.zip
Add disconnecting state
-rw-r--r--gui/packages/desktop/src/renderer/app.js108
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.js16
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/actions.js12
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/reducers.js10
4 files changed, 86 insertions, 60 deletions
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 54275ec76a..04cbc9f6da 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -48,6 +48,7 @@ export default class AppRenderer {
_reduxActions: *;
_accountDataState = new AccountDataState();
_connectedToDaemon = false;
+ _tunnelState: ?TunnelState;
constructor() {
const store = configureStore(null, this._memoryHistory);
@@ -229,18 +230,22 @@ export default class AppRenderer {
async connectTunnel() {
const actions = this._reduxActions;
- try {
- const currentState = await this._daemonRpc.getState();
- if (currentState === 'connected' || currentState === 'connecting') {
- log.debug('Refusing to connect as connection is already secured');
- actions.connection.connected();
- } else {
- actions.connection.connecting();
- await this._daemonRpc.connectTunnel();
- }
- } catch (error) {
- actions.connection.disconnected();
- throw error;
+ // avoid connecting when there is no account set in daemon.
+ const accountToken = await this._daemonRpc.getAccount();
+ if (!accountToken) {
+ throw new NoAccountError();
+ }
+
+ // connect only if tunnel is disconnected or blocked.
+ if (
+ this._tunnelState &&
+ (this._tunnelState.state === 'disconnected' || this._tunnelState.state === 'blocked')
+ ) {
+ // switch to connecting state immediately to prevent a lag that may be caused by RPC
+ // communication delay
+ actions.connection.connecting();
+
+ await this._daemonRpc.connectTunnel();
}
}
@@ -415,8 +420,11 @@ export default class AppRenderer {
}
async _fetchTunnelState() {
- const tunnelState = await this._daemonRpc.getState();
- this._updateConnectionState(tunnelState);
+ const state = await this._daemonRpc.getState();
+
+ log.debug(`Got state: ${JSON.stringify(state)}`);
+
+ this._onChangeTunnelState(state);
}
async _fetchTunnelOptions() {
@@ -511,14 +519,13 @@ export default class AppRenderer {
async _subscribeStateListener() {
await this._daemonRpc.subscribeStateListener((newState, error) => {
if (error) {
- log.error(`Received an error when processing the incoming state change: ${error.message}`);
+ log.error(`Failed to deserialize the new state: ${error.message}`);
}
if (newState) {
- log.debug(`Got new state from daemon '${JSON.stringify(newState)}'`);
+ log.debug(`Got state update: '${JSON.stringify(newState)}'`);
- this._updateConnectionState(newState);
- this._refreshStateOnChange();
+ this._onChangeTunnelState(newState);
}
});
}
@@ -537,44 +544,28 @@ export default class AppRenderer {
]);
}
- _updateTrayIcon(connectionState: ConnectionState) {
- const iconTypes: { [ConnectionState]: TrayIconType } = {
- connected: 'secured',
- connecting: 'securing',
- blocked: 'securing',
- };
- const type = iconTypes[connectionState] || 'unsecured';
-
- ipcRenderer.send('change-tray-icon', type);
- }
+ _onChangeTunnelState(tunnelState: TunnelState) {
+ this._tunnelState = tunnelState;
- async _refreshStateOnChange() {
- try {
- await this._fetchLocation();
- } catch (error) {
- log.error(`Failed to fetch the location: ${error.message}`);
- }
+ this._updateConnectionStatus(tunnelState);
+ this._updateConnectionLocation(tunnelState.state);
+ this._updateTrayIcon(tunnelState.state);
+ this._showNotification(tunnelState.state);
}
- _tunnelStateToConnectionState(tunnelState: TunnelState): ConnectionState {
- switch (tunnelState.state) {
- case 'disconnected':
- // Fall through
- case 'disconnecting':
- return 'disconnected';
- case 'connected':
- return 'connected';
- case 'connecting':
- return 'connecting';
- case 'blocked':
- return 'blocked';
- default:
- throw new Error('Unknown tunnel state: ' + (tunnelState: empty));
+ async _updateConnectionLocation(connectionState: ConnectionState) {
+ if (connectionState === 'connecting' || connectionState === 'disconnected') {
+ try {
+ await this._fetchLocation();
+ } catch (error) {
+ log.error(`Failed to update the location: ${error.message}`);
+ }
}
}
- _updateConnectionState(tunnelState: TunnelState) {
+ _updateConnectionStatus(tunnelState: TunnelState) {
const actions = this._reduxActions;
+
switch (tunnelState.state) {
case 'connecting':
actions.connection.connecting();
@@ -583,7 +574,8 @@ export default class AppRenderer {
actions.connection.connected();
break;
case 'disconnecting':
- // Fall through
+ actions.connection.disconnecting();
+ break;
case 'disconnected':
actions.connection.disconnected();
break;
@@ -593,10 +585,17 @@ export default class AppRenderer {
default:
log.error(`Unexpected TunnelState: ${(tunnelState: empty)}`);
}
+ }
+
+ _updateTrayIcon(connectionState: ConnectionState) {
+ const iconTypes: { [ConnectionState]: TrayIconType } = {
+ connected: 'secured',
+ connecting: 'securing',
+ blocked: 'securing',
+ };
+ const type = iconTypes[connectionState] || 'unsecured';
- const connectionState = this._tunnelStateToConnectionState(tunnelState);
- this._updateTrayIcon(connectionState);
- this._showNotification(connectionState);
+ ipcRenderer.send('change-tray-icon', type);
}
_showNotification(connectionState: ConnectionState) {
@@ -613,6 +612,9 @@ export default class AppRenderer {
case 'blocked':
this._notificationController.show('Blocked all connections');
break;
+ case 'disconnecting':
+ // no-op
+ break;
default:
log.error(`Unexpected ConnectionState: ${(connectionState: empty)}`);
return;
diff --git a/gui/packages/desktop/src/renderer/components/Connect.js b/gui/packages/desktop/src/renderer/components/Connect.js
index 078883a23b..f06ea86eb1 100644
--- a/gui/packages/desktop/src/renderer/components/Connect.js
+++ b/gui/packages/desktop/src/renderer/components/Connect.js
@@ -154,7 +154,7 @@ export default class Connect extends Component<Props, State> {
}
renderMap() {
- let [isConnecting, isConnected, isDisconnected] = [false, false, false];
+ let [isConnecting, isConnected, isDisconnected, isDisconnecting] = [false, false, false, false];
switch (this.props.connection.status) {
case 'connecting':
isConnecting = true;
@@ -165,6 +165,9 @@ export default class Connect extends Component<Props, State> {
case 'disconnected':
isDisconnected = true;
break;
+ case 'disconnecting':
+ isDisconnecting = true;
+ break;
}
return (
@@ -193,8 +196,8 @@ export default class Connect extends Component<Props, State> {
**********************************
*/}
- {/* location when connecting or disconnected */}
- {isConnecting || isDisconnected ? (
+ {/* location when connecting, disconnecting or disconnected */}
+ {isConnecting || isDisconnecting || isDisconnected ? (
<Text style={styles.status_location} testName="location">
{this.props.connection.country}
</Text>
@@ -216,7 +219,7 @@ export default class Connect extends Component<Props, State> {
*/}
<Text style={this.ipAddressStyle()} onPress={this.onIPAddressClick.bind(this)}>
- {isConnected || isDisconnected ? (
+ {isConnected || isDisconnecting || isDisconnected ? (
<Text testName="ipAddress">
{this.state.showCopyIPMessage
? 'IP copied to clipboard!'
@@ -232,8 +235,8 @@ export default class Connect extends Component<Props, State> {
**********************************
*/}
- {/* footer when disconnected */}
- {isDisconnected ? (
+ {/* footer when disconnecting or disconnected */}
+ {isDisconnecting || isDisconnected ? (
<View style={styles.footer}>
<AppButton.TransparentButton
style={styles.switch_location_button}
@@ -324,6 +327,7 @@ export default class Connect extends Component<Props, State> {
headerBarStyle(): HeaderBarStyle {
const { status } = this.props.connection;
switch (status) {
+ case 'disconnecting':
case 'disconnected':
return 'error';
case 'connecting':
diff --git a/gui/packages/desktop/src/renderer/redux/connection/actions.js b/gui/packages/desktop/src/renderer/redux/connection/actions.js
index 052d206986..fb920673e0 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/actions.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/actions.js
@@ -14,6 +14,10 @@ type DisconnectedAction = {
type: 'DISCONNECTED',
};
+type DisconnectingAction = {
+ type: 'DISCONNECTING',
+};
+
type BlockedAction = {
type: 'BLOCKED',
reason: string,
@@ -44,6 +48,7 @@ export type ConnectionAction =
| ConnectingAction
| ConnectedAction
| DisconnectedAction
+ | DisconnectingAction
| BlockedAction
| OnlineAction
| OfflineAction;
@@ -66,6 +71,12 @@ function disconnected(): DisconnectedAction {
};
}
+function disconnecting(): DisconnectingAction {
+ return {
+ type: 'DISCONNECTING',
+ };
+}
+
function blocked(reason: string): BlockedAction {
return {
type: 'BLOCKED',
@@ -97,6 +108,7 @@ export default {
connecting,
connected,
disconnected,
+ disconnecting,
blocked,
online,
offline,
diff --git a/gui/packages/desktop/src/renderer/redux/connection/reducers.js b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
index d425dec438..71f53b3a4c 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/reducers.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
@@ -3,7 +3,12 @@
import type { ReduxAction } from '../store';
import type { Ip } from '../../lib/daemon-rpc';
-export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'blocked';
+export type ConnectionState =
+ | 'disconnected'
+ | 'disconnecting'
+ | 'connecting'
+ | 'connected'
+ | 'blocked';
export type ConnectionReduxState = {
status: ConnectionState,
isOnline: boolean,
@@ -43,6 +48,9 @@ export default function(
case 'DISCONNECTED':
return { ...state, ...{ status: 'disconnected', blockReason: null } };
+ case 'DISCONNECTING':
+ return { ...state, ...{ status: 'disconnecting', blockReason: null } };
+
case 'BLOCKED':
return { ...state, ...{ status: 'blocked', blockReason: action.reason } };