summaryrefslogtreecommitdiffhomepage
path: root/gui/src/main
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-01-14 14:56:43 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-01-14 14:56:43 +0100
commit58b11439f6828b7f6973af49090be55a8dbf6aaa (patch)
treeb7f6f8fa4973a9b205d1bd3f7ded6278963726cc /gui/src/main
parentfa62c258f0777b85e94b7df59f384f49686b04f9 (diff)
parentfb9242653d87daaffb1363f780942605af6cb490 (diff)
downloadmullvadvpn-58b11439f6828b7f6973af49090be55a8dbf6aaa.tar.xz
mullvadvpn-58b11439f6828b7f6973af49090be55a8dbf6aaa.zip
Merge branch 'refactor-optimistic-state'
Diffstat (limited to 'gui/src/main')
-rw-r--r--gui/src/main/index.ts100
1 files changed, 68 insertions, 32 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 05ee5cb3b3..b5743b7c76 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -76,6 +76,7 @@ import ReconnectionBackoff from './reconnection-backoff';
import TrayIconController, { TrayIconType } from './tray-icon-controller';
import WindowController from './window-controller';
import { ITranslations, MacOsScrollbarVisibility } from '../shared/ipc-schema';
+import { connectEnabled, disconnectEnabled, reconnectEnabled } from '../shared/connect-helper';
const execAsync = util.promisify(exec);
@@ -132,10 +133,16 @@ class ApplicationMain {
private accountData?: IAccountData = undefined;
private accountHistory?: AccountToken = undefined;
+
+ // The current tunnel state
private tunnelState: TunnelState = { state: 'disconnected' };
- private lastIgnoredTunnelState?: TunnelState;
- private optimisticTunnelState?: TunnelState['state'];
- private optimisticTunnelStateFallbackScheduler = new Scheduler();
+ // When pressing connect/disconnect/reconnect the app assumes what the next state will be before
+ // it get's the new state from the daemon. The latest state from the daemon is saved as fallback
+ // if the assumed state isn't reached.
+ private tunnelStateFallback?: TunnelState;
+ // Scheduler for discarding the assumed next state.
+ private tunnelStateFallbackScheduler = new Scheduler();
+
private settings: ISettings = {
accountToken: undefined,
allowLan: false,
@@ -701,8 +708,7 @@ class ApplicationMain {
// Reset the daemon event listener since it's going to be invalidated on disconnect
this.daemonEventListener = undefined;
- this.optimisticTunnelState = undefined;
- this.lastIgnoredTunnelState = undefined;
+ this.tunnelStateFallback = undefined;
this.autoConnectFallbackScheduler.cancel();
if (wasConnected) {
@@ -772,6 +778,31 @@ class ApplicationMain {
return daemonEventListener;
}
+ private connectTunnel = async (): Promise<void> => {
+ if (
+ connectEnabled(this.connectedToDaemon, this.settings.accountToken, this.tunnelState.state)
+ ) {
+ this.setOptimisticTunnelState('connecting');
+ await this.daemonRpc.connectTunnel();
+ }
+ };
+
+ private reconnectTunnel = async (): Promise<void> => {
+ if (
+ reconnectEnabled(this.connectedToDaemon, this.settings.accountToken, this.tunnelState.state)
+ ) {
+ this.setOptimisticTunnelState('connecting');
+ await this.daemonRpc.reconnectTunnel();
+ }
+ };
+
+ private disconnectTunnel = async (): Promise<void> => {
+ if (disconnectEnabled(this.connectedToDaemon, this.tunnelState.state)) {
+ this.setOptimisticTunnelState('disconnecting');
+ await this.daemonRpc.disconnectTunnel();
+ }
+ };
+
private setAccountHistory(accountHistory?: AccountToken) {
this.accountHistory = accountHistory;
@@ -811,33 +842,47 @@ class ApplicationMain {
this.wireguardKeygenEventAutoConnect();
}
+ // This function sets a new tunnel state as an assumed next state and saves the current state as
+ // fallback. The fallback is used if the assumed next state isn't reached.
private setOptimisticTunnelState(state: 'connecting' | 'disconnecting') {
- const tunnelState =
- state === 'disconnecting' ? ({ state, details: 'nothing' } as const) : { state };
- this.updateTrayIcon(tunnelState, this.settings.blockWhenDisconnected);
+ this.tunnelStateFallback = this.tunnelState;
+
+ this.setTunnelStateImpl(
+ state === 'disconnecting' ? { state, details: 'nothing' as const } : { state },
+ );
- this.optimisticTunnelState = state;
- this.optimisticTunnelStateFallbackScheduler.schedule(() => {
- if (this.lastIgnoredTunnelState) {
- this.optimisticTunnelState = undefined;
- this.setTunnelState(this.lastIgnoredTunnelState);
- this.lastIgnoredTunnelState = undefined;
+ this.tunnelStateFallbackScheduler.schedule(() => {
+ if (this.tunnelStateFallback) {
+ this.setTunnelStateImpl(this.tunnelStateFallback);
+ this.tunnelStateFallback = undefined;
}
}, 3000);
}
private setTunnelState(newState: TunnelState) {
- if (this.optimisticTunnelState) {
- if (this.optimisticTunnelState === newState.state) {
- this.optimisticTunnelStateFallbackScheduler.cancel();
- this.optimisticTunnelState = undefined;
- this.lastIgnoredTunnelState = undefined;
+ // If there's a fallback state set then the app is in an assumed next state and need to check
+ // if it's now reached or if the current state should be ignored and set as the fallback state.
+ if (this.tunnelStateFallback) {
+ if (this.tunnelState.state === newState.state) {
+ this.tunnelStateFallbackScheduler.cancel();
+ this.tunnelStateFallback = undefined;
} else {
- this.lastIgnoredTunnelState = newState;
+ this.tunnelStateFallback = newState;
return;
}
}
+ if (newState.state === 'disconnecting' && newState.details === 'reconnect') {
+ // When reconnecting there's no need of showing the disconnecting state. This switches to the
+ // connecting state immediately.
+ this.setOptimisticTunnelState('connecting');
+ this.tunnelStateFallback = newState;
+ } else {
+ this.setTunnelStateImpl(newState);
+ }
+ }
+
+ private setTunnelStateImpl(newState: TunnelState) {
this.tunnelState = newState;
this.updateTrayIcon(newState, this.settings.blockWhenDisconnected);
@@ -1209,18 +1254,9 @@ class ApplicationMain {
IpcMainEventChannel.location.handleGet(() => this.daemonRpc.getLocation());
- IpcMainEventChannel.tunnel.handleConnect(() => {
- this.setOptimisticTunnelState('connecting');
- return this.daemonRpc.connectTunnel();
- });
- IpcMainEventChannel.tunnel.handleDisconnect(() => {
- this.setOptimisticTunnelState('disconnecting');
- return this.daemonRpc.disconnectTunnel();
- });
- IpcMainEventChannel.tunnel.handleReconnect(() => {
- this.setOptimisticTunnelState('connecting');
- return this.daemonRpc.reconnectTunnel();
- });
+ IpcMainEventChannel.tunnel.handleConnect(this.connectTunnel);
+ IpcMainEventChannel.tunnel.handleReconnect(this.reconnectTunnel);
+ IpcMainEventChannel.tunnel.handleDisconnect(this.disconnectTunnel);
IpcMainEventChannel.guiSettings.handleSetEnableSystemNotifications((flag: boolean) => {
this.guiSettings.enableSystemNotifications = flag;