summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
Diffstat (limited to 'gui')
-rw-r--r--gui/src/main/daemon-rpc.ts37
-rw-r--r--gui/src/main/index.ts43
-rw-r--r--gui/src/main/notification-controller.ts4
-rw-r--r--gui/src/renderer/app.tsx15
-rw-r--r--gui/src/renderer/components/NotificationArea.tsx4
-rw-r--r--gui/src/renderer/components/TunnelControl.tsx4
-rw-r--r--gui/src/renderer/containers/ConnectionPanelContainer.tsx4
-rw-r--r--gui/src/renderer/redux/connection/actions.ts14
-rw-r--r--gui/src/renderer/redux/connection/reducers.ts8
-rw-r--r--gui/src/shared/daemon-rpc-types.ts15
-rw-r--r--gui/src/shared/ipc-event-channel.ts8
-rw-r--r--gui/test/components/NotificationArea.spec.tsx14
12 files changed, 92 insertions, 78 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 215807b2a7..2c467a0d05 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -8,7 +8,7 @@ import {
IRelayList,
ISettings,
RelaySettingsUpdate,
- TunnelStateTransition,
+ TunnelState,
} from '../shared/daemon-rpc-types';
import { CommunicationError, InvalidAccountError, NoDaemonError } from './errors';
import JsonRpcClient, {
@@ -230,24 +230,27 @@ const accountDataSchema = partialObject({
expiry: string,
});
-const tunnelStateTransitionSchema = oneOf(
+const tunnelStateSchema = oneOf(
object({
state: enumeration('disconnecting'),
details: enumeration('nothing', 'block', 'reconnect'),
}),
object({
state: enumeration('connecting', 'connected'),
- details: partialObject({
- address: string,
- protocol: enumeration('tcp', 'udp'),
- tunnel_type: enumeration('wireguard', 'openvpn'),
- proxy: maybe(
- partialObject({
- address: string,
- protocol: enumeration('tcp', 'udp'),
- proxy_type: enumeration('shadowsocks', 'custom'),
- }),
- ),
+ details: object({
+ endpoint: partialObject({
+ address: string,
+ protocol: enumeration('tcp', 'udp'),
+ tunnel_type: enumeration('wireguard', 'openvpn'),
+ proxy: maybe(
+ partialObject({
+ address: string,
+ protocol: enumeration('tcp', 'udp'),
+ proxy_type: enumeration('shadowsocks', 'custom'),
+ }),
+ ),
+ }),
+ location: locationSchema,
}),
}),
object({
@@ -329,7 +332,7 @@ const settingsSchema = partialObject({
const daemonEventSchema = oneOf(
object({
- state_transition: tunnelStateTransitionSchema,
+ tunnel_state: tunnelStateSchema,
}),
object({
settings: settingsSchema,
@@ -468,12 +471,10 @@ export class DaemonRpc {
}
}
- public async getState(): Promise<TunnelStateTransition> {
+ public async getState(): Promise<TunnelState> {
const response = await this.transport.send('get_state');
try {
- return camelCaseObjectKeys(
- validate(tunnelStateTransitionSchema, response),
- ) as TunnelStateTransition;
+ return camelCaseObjectKeys(validate(tunnelStateSchema, response)) as TunnelState;
} catch (error) {
throw new ResponseParseError('Invalid response from get_state', error);
}
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index b34cc87fbc..9013c7c5cd 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -15,7 +15,7 @@ import {
ISettings,
RelaySettings,
RelaySettingsUpdate,
- TunnelStateTransition,
+ TunnelState,
} from '../shared/daemon-rpc-types';
import { loadTranslations, messages } from '../shared/gettext';
import { IpcMainEventChannel } from '../shared/ipc-event-channel';
@@ -73,7 +73,7 @@ class ApplicationMain {
private quitStage = AppQuitStage.unready;
private accountHistory: AccountToken[] = [];
- private tunnelState: TunnelStateTransition = { state: 'disconnected' };
+ private tunnelState: TunnelState = { state: 'disconnected' };
private settings: ISettings = {
accountToken: undefined,
allowLan: false,
@@ -480,8 +480,8 @@ class ApplicationMain {
private async subscribeEvents(): Promise<void> {
const daemonEventListener = new SubscriptionListener(
(daemonEvent: DaemonEvent) => {
- if ('stateTransition' in daemonEvent) {
- this.setTunnelState(daemonEvent.stateTransition);
+ if ('tunnelState' in daemonEvent) {
+ this.setTunnelState(daemonEvent.tunnelState);
} else if ('settings' in daemonEvent) {
this.setSettings(daemonEvent.settings);
} else if ('relayList' in daemonEvent) {
@@ -512,7 +512,7 @@ class ApplicationMain {
}
}
- private setTunnelState(newState: TunnelStateTransition) {
+ private setTunnelState(newState: TunnelState) {
this.tunnelState = newState;
this.updateTrayIcon(newState, this.settings.blockWhenDisconnected);
this.updateLocation();
@@ -724,16 +724,24 @@ class ApplicationMain {
}
private async updateLocation() {
- const state = this.tunnelState.state;
+ const tunnelState = this.tunnelState;
- if (state === 'connected' || state === 'disconnected' || state === 'connecting') {
- try {
- // It may take some time to fetch the new user location.
- // So take the user to the last known location when disconnected.
- if (state === 'disconnected' && this.lastDisconnectedLocation) {
- this.setLocation(this.lastDisconnectedLocation);
- }
+ if (tunnelState.state === 'connected' || tunnelState.state === 'connecting') {
+ // Location was broadcasted with the tunnel state, but it doesn't contain the relay out IP
+ // address, so it will have to be fetched afterwards
+ if (tunnelState.details) {
+ this.setLocation(tunnelState.details.location);
+ }
+ } else if (tunnelState.state === 'disconnected') {
+ // It may take some time to fetch the new user location.
+ // So take the user to the last known location when disconnected.
+ if (this.lastDisconnectedLocation) {
+ this.setLocation(this.lastDisconnectedLocation);
+ }
+ }
+ if (tunnelState.state === 'connected' || tunnelState.state === 'disconnected') {
+ try {
// Fetch the new user location
const location = await this.daemonRpc.getLocation();
// If the location is currently unavailable, do nothing! This only ever
@@ -751,7 +759,7 @@ class ApplicationMain {
// Broadcast the new location.
// There is a chance that the location is not stale if the tunnel state before the location
// request is the same as after receiving the response.
- if (this.tunnelState.state === state) {
+ if (this.tunnelState.state === tunnelState.state) {
this.setLocation(location);
}
} catch (error) {
@@ -760,10 +768,7 @@ class ApplicationMain {
}
}
- private trayIconType(
- tunnelState: TunnelStateTransition,
- blockWhenDisconnected: boolean,
- ): TrayIconType {
+ private trayIconType(tunnelState: TunnelState, blockWhenDisconnected: boolean): TrayIconType {
switch (tunnelState.state) {
case 'connected':
return 'secured';
@@ -791,7 +796,7 @@ class ApplicationMain {
}
}
- private updateTrayIcon(tunnelState: TunnelStateTransition, blockWhenDisconnected: boolean) {
+ private updateTrayIcon(tunnelState: TunnelState, blockWhenDisconnected: boolean) {
const type = this.trayIconType(tunnelState, blockWhenDisconnected);
if (this.trayIconController) {
diff --git a/gui/src/main/notification-controller.ts b/gui/src/main/notification-controller.ts
index a2e1e00a0b..771e788722 100644
--- a/gui/src/main/notification-controller.ts
+++ b/gui/src/main/notification-controller.ts
@@ -2,7 +2,7 @@ import { app, nativeImage, NativeImage, Notification, shell } from 'electron';
import path from 'path';
import { sprintf } from 'sprintf-js';
import config from '../config.json';
-import { TunnelStateTransition } from '../shared/daemon-rpc-types';
+import { TunnelState } from '../shared/daemon-rpc-types';
import { messages } from '../shared/gettext';
export default class NotificationController {
@@ -22,7 +22,7 @@ export default class NotificationController {
}
}
- public notifyTunnelState(tunnelState: TunnelStateTransition) {
+ public notifyTunnelState(tunnelState: TunnelState) {
switch (tunnelState.state) {
case 'connecting':
if (!this.reconnecting) {
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index ec7ba833e8..3d5a6eb0e5 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -38,7 +38,7 @@ import {
ISettings,
RelaySettings,
RelaySettingsUpdate,
- TunnelStateTransition,
+ TunnelState,
} from '../shared/daemon-rpc-types';
type AccountVerification = { status: 'verified' } | { status: 'deferred'; error: Error };
@@ -70,7 +70,7 @@ export default class AppRenderer {
);
private locale: string;
- private tunnelState: TunnelStateTransition;
+ private tunnelState: TunnelState;
private settings: ISettings;
private guiSettings: IGuiSettingsState;
private accountExpiry?: AccountExpiry;
@@ -109,7 +109,7 @@ export default class AppRenderer {
this.setAccountHistory(newAccountHistory);
});
- IpcRendererEventChannel.tunnel.listen((newState: TunnelStateTransition) => {
+ IpcRendererEventChannel.tunnel.listen((newState: TunnelState) => {
this.setTunnelState(newState);
this.updateBlockedState(newState, this.settings.blockWhenDisconnected);
@@ -461,7 +461,7 @@ export default class AppRenderer {
this.reduxActions.account.updateAccountHistory(accountHistory);
}
- private setTunnelState(tunnelState: TunnelStateTransition) {
+ private setTunnelState(tunnelState: TunnelState) {
const actions = this.reduxActions;
log.debug(`Tunnel state: ${tunnelState.state}`);
@@ -513,7 +513,7 @@ export default class AppRenderer {
}
}
- private updateBlockedState(tunnelState: TunnelStateTransition, blockWhenDisconnected: boolean) {
+ private updateBlockedState(tunnelState: TunnelState, blockWhenDisconnected: boolean) {
const actions = this.reduxActions.connection;
switch (tunnelState.state) {
case 'connecting':
@@ -604,10 +604,7 @@ export default class AppRenderer {
this.reduxActions.account.updateAccountExpiry(expiry);
}
- private detectStaleAccountExpiry(
- tunnelState: TunnelStateTransition,
- accountExpiry: AccountExpiry,
- ) {
+ private detectStaleAccountExpiry(tunnelState: TunnelState, accountExpiry: AccountExpiry) {
// It's likely that the account expiry is stale if the daemon managed to establish the tunnel.
if (tunnelState.state === 'connected' && accountExpiry.hasExpired()) {
log.info('Detected the stale account expiry.');
diff --git a/gui/src/renderer/components/NotificationArea.tsx b/gui/src/renderer/components/NotificationArea.tsx
index 062bb45da7..e120e25895 100644
--- a/gui/src/renderer/components/NotificationArea.tsx
+++ b/gui/src/renderer/components/NotificationArea.tsx
@@ -14,7 +14,7 @@ import {
NotificationTitle,
} from './NotificationBanner';
-import { BlockReason, TunnelStateTransition } from '../../shared/daemon-rpc-types';
+import { BlockReason, TunnelState } from '../../shared/daemon-rpc-types';
import AccountExpiry from '../lib/account-expiry';
import { parseAuthFailure } from '../lib/auth-failure';
import { IVersionReduxState } from '../redux/version/reducers';
@@ -22,7 +22,7 @@ import { IVersionReduxState } from '../redux/version/reducers';
interface IProps {
style?: Types.ViewStyleRuleSet;
accountExpiry?: AccountExpiry;
- tunnelState: TunnelStateTransition;
+ tunnelState: TunnelState;
version: IVersionReduxState;
openExternalLink: (url: string) => void;
blockWhenDisconnected: boolean;
diff --git a/gui/src/renderer/components/TunnelControl.tsx b/gui/src/renderer/components/TunnelControl.tsx
index ffb7ac2479..a03332194e 100644
--- a/gui/src/renderer/components/TunnelControl.tsx
+++ b/gui/src/renderer/components/TunnelControl.tsx
@@ -1,14 +1,14 @@
import * as React from 'react';
import { Component, Styles, Text, Types, View } from 'reactxp';
import { colors } from '../../config.json';
-import { TunnelStateTransition } from '../../shared/daemon-rpc-types';
+import { TunnelState } from '../../shared/daemon-rpc-types';
import { cities, countries, messages, relayLocations } from '../../shared/gettext';
import ConnectionPanelContainer from '../containers/ConnectionPanelContainer';
import * as AppButton from './AppButton';
import SecuredLabel, { SecuredDisplayStyle } from './SecuredLabel';
interface ITunnelControlProps {
- tunnelState: TunnelStateTransition;
+ tunnelState: TunnelState;
selectedRelayName: string;
city?: string;
country?: string;
diff --git a/gui/src/renderer/containers/ConnectionPanelContainer.tsx b/gui/src/renderer/containers/ConnectionPanelContainer.tsx
index 77b164c65b..44d3330c78 100644
--- a/gui/src/renderer/containers/ConnectionPanelContainer.tsx
+++ b/gui/src/renderer/containers/ConnectionPanelContainer.tsx
@@ -43,12 +43,12 @@ const mapStateToProps = (state: IReduxState) => {
const inAddress: IInAddress | undefined =
(status.state === 'connecting' || status.state === 'connected') && status.details
- ? tunnelEndpointToRelayInAddress(status.details)
+ ? tunnelEndpointToRelayInAddress(status.details.endpoint)
: undefined;
const bridgeInfo: IBridgeData | undefined =
(status.state === 'connecting' || status.state === 'connected') && status.details
- ? tunnelEndpointToBridgeData(status.details)
+ ? tunnelEndpointToBridgeData(status.details.endpoint)
: undefined;
return {
diff --git a/gui/src/renderer/redux/connection/actions.ts b/gui/src/renderer/redux/connection/actions.ts
index b84e85f8e6..8f5b2e62bb 100644
--- a/gui/src/renderer/redux/connection/actions.ts
+++ b/gui/src/renderer/redux/connection/actions.ts
@@ -2,17 +2,17 @@ import {
AfterDisconnect,
BlockReason,
ILocation,
- ITunnelEndpoint,
+ ITunnelStateRelayInfo,
} from '../../../shared/daemon-rpc-types';
interface IConnectingAction {
type: 'CONNECTING';
- tunnelEndpoint?: ITunnelEndpoint;
+ details?: ITunnelStateRelayInfo;
}
interface IConnectedAction {
type: 'CONNECTED';
- tunnelEndpoint: ITunnelEndpoint;
+ details: ITunnelStateRelayInfo;
}
interface IDisconnectedAction {
@@ -48,17 +48,17 @@ export type ConnectionAction =
| IBlockedAction
| IUpdateBlockStateAction;
-function connecting(tunnelEndpoint?: ITunnelEndpoint): IConnectingAction {
+function connecting(details?: ITunnelStateRelayInfo): IConnectingAction {
return {
type: 'CONNECTING',
- tunnelEndpoint,
+ details,
};
}
-function connected(tunnelEndpoint: ITunnelEndpoint): IConnectedAction {
+function connected(details: ITunnelStateRelayInfo): IConnectedAction {
return {
type: 'CONNECTED',
- tunnelEndpoint,
+ details,
};
}
diff --git a/gui/src/renderer/redux/connection/reducers.ts b/gui/src/renderer/redux/connection/reducers.ts
index 2719f3d3db..4772df8fc5 100644
--- a/gui/src/renderer/redux/connection/reducers.ts
+++ b/gui/src/renderer/redux/connection/reducers.ts
@@ -1,8 +1,8 @@
-import { Ip, TunnelStateTransition } from '../../../shared/daemon-rpc-types';
+import { Ip, TunnelState } from '../../../shared/daemon-rpc-types';
import { ReduxAction } from '../store';
export interface IConnectionReduxState {
- status: TunnelStateTransition;
+ status: TunnelState;
isBlocked: boolean;
ipv4?: Ip;
ipv6?: Ip;
@@ -41,13 +41,13 @@ export default function(
case 'CONNECTING':
return {
...state,
- status: { state: 'connecting', details: action.tunnelEndpoint },
+ status: { state: 'connecting', details: action.details },
};
case 'CONNECTED':
return {
...state,
- status: { state: 'connected', details: action.tunnelEndpoint },
+ status: { state: 'connected', details: action.details },
};
case 'DISCONNECTED':
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 120ef3a0b0..ec52d0ad5f 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -30,8 +30,6 @@ export type BlockReason =
export type AfterDisconnect = 'nothing' | 'block' | 'reconnect';
-export type TunnelState = 'connecting' | 'connected' | 'disconnecting' | 'disconnected' | 'blocked';
-
export type TunnelType = 'wireguard' | 'openvpn';
export function tunnelTypeToString(tunnel: TunnelType): string {
switch (tunnel) {
@@ -72,15 +70,20 @@ export interface IProxyEndpoint {
}
export type DaemonEvent =
- | { stateTransition: TunnelStateTransition }
+ | { tunnelState: TunnelState }
| { settings: ISettings }
| { relayList: IRelayList }
| { wireguardKey: KeygenEvent };
-export type TunnelStateTransition =
+export interface ITunnelStateRelayInfo {
+ endpoint: ITunnelEndpoint;
+ location: ILocation;
+}
+
+export type TunnelState =
| { state: 'disconnected' }
- | { state: 'connecting'; details?: ITunnelEndpoint }
- | { state: 'connected'; details: ITunnelEndpoint }
+ | { state: 'connecting'; details?: ITunnelStateRelayInfo }
+ | { state: 'connected'; details: ITunnelStateRelayInfo }
| { state: 'disconnecting'; details: AfterDisconnect }
| { state: 'blocked'; details: BlockReason };
diff --git a/gui/src/shared/ipc-event-channel.ts b/gui/src/shared/ipc-event-channel.ts
index b6292b554a..207e509779 100644
--- a/gui/src/shared/ipc-event-channel.ts
+++ b/gui/src/shared/ipc-event-channel.ts
@@ -13,7 +13,7 @@ import {
IRelayList,
ISettings,
RelaySettingsUpdate,
- TunnelStateTransition,
+ TunnelState,
} from './daemon-rpc-types';
export interface IAppStateSnapshot {
@@ -21,7 +21,7 @@ export interface IAppStateSnapshot {
isConnected: boolean;
autoStart: boolean;
accountHistory: AccountToken[];
- tunnelState: TunnelStateTransition;
+ tunnelState: TunnelState;
settings: ISettings;
location?: ILocation;
relays: IRelayList;
@@ -42,12 +42,12 @@ interface IReceiver<T> {
listen(fn: (value: T) => void): void;
}
-interface ITunnelMethods extends IReceiver<TunnelStateTransition> {
+interface ITunnelMethods extends IReceiver<TunnelState> {
connect(): Promise<void>;
disconnect(): Promise<void>;
}
-interface ITunnelHandlers extends ISender<TunnelStateTransition> {
+interface ITunnelHandlers extends ISender<TunnelState> {
handleConnect(fn: () => Promise<void>): void;
handleDisconnect(fn: () => Promise<void>): void;
}
diff --git a/gui/test/components/NotificationArea.spec.tsx b/gui/test/components/NotificationArea.spec.tsx
index 7b1a69bd8a..f2674e848c 100644
--- a/gui/test/components/NotificationArea.spec.tsx
+++ b/gui/test/components/NotificationArea.spec.tsx
@@ -63,9 +63,17 @@ describe('components/NotificationArea', () => {
tunnelState={{
state: 'connected',
details: {
- address: '1.2.3.4',
- protocol: 'tcp',
- tunnelType: 'openvpn',
+ endpoint: {
+ address: '1.2.3.4',
+ protocol: 'tcp',
+ tunnelType: 'openvpn',
+ },
+ location: {
+ country: 'Sweden',
+ latitude: 57.70887,
+ longitude: 11.97456,
+ mullvadExitIp: true,
+ },
},
}}
version={defaultVersion}