summaryrefslogtreecommitdiffhomepage
path: root/gui/packages
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-02-04 14:43:45 +0100
committerAndrej Mihajlov <and@mullvad.net>2019-02-08 12:12:10 +0100
commit3e99a6c9cdb95056bbd4c61e82fb66d40649cd6b (patch)
tree4aece2a2c47a2579a835a5d3e08d59e63e1c91e7 /gui/packages
parent552f06b3793ace66d8581d9b5249ef165bd89971 (diff)
downloadmullvadvpn-3e99a6c9cdb95056bbd4c61e82fb66d40649cd6b.tar.xz
mullvadvpn-3e99a6c9cdb95056bbd4c61e82fb66d40649cd6b.zip
Fix linter issues
Diffstat (limited to 'gui/packages')
-rw-r--r--gui/packages/components/package.json1
-rw-r--r--gui/packages/config/package.json6
-rw-r--r--gui/packages/config/tslint.json14
-rw-r--r--gui/packages/desktop/package.json9
-rw-r--r--gui/packages/desktop/src/main/autostart.ts4
-rw-r--r--gui/packages/desktop/src/main/daemon-rpc.ts261
-rw-r--r--gui/packages/desktop/src/main/gui-settings.ts92
-rw-r--r--gui/packages/desktop/src/main/index.ts688
-rw-r--r--gui/packages/desktop/src/main/jsonrpc-client.ts270
-rw-r--r--gui/packages/desktop/src/main/keyframe-animation.ts113
-rw-r--r--gui/packages/desktop/src/main/notification-controller.ts74
-rw-r--r--gui/packages/desktop/src/main/reconnection-backoff.ts18
-rw-r--r--gui/packages/desktop/src/main/tray-icon-controller.ts71
-rw-r--r--gui/packages/desktop/src/main/window-controller.ts159
-rw-r--r--gui/packages/desktop/src/renderer/app.tsx474
-rw-r--r--gui/packages/desktop/src/renderer/components/Account.tsx20
-rw-r--r--gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx98
-rw-r--r--gui/packages/desktop/src/renderer/components/AppButton.tsx52
-rw-r--r--gui/packages/desktop/src/renderer/components/Cell.tsx38
-rw-r--r--gui/packages/desktop/src/renderer/components/ChevronButton.tsx10
-rw-r--r--gui/packages/desktop/src/renderer/components/CityRow.tsx45
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.tsx217
-rw-r--r--gui/packages/desktop/src/renderer/components/CountryRow.tsx45
-rw-r--r--gui/packages/desktop/src/renderer/components/CustomScrollbars.tsx291
-rw-r--r--gui/packages/desktop/src/renderer/components/Launch.tsx14
-rw-r--r--gui/packages/desktop/src/renderer/components/Layout.tsx26
-rw-r--r--gui/packages/desktop/src/renderer/components/Login.tsx236
-rw-r--r--gui/packages/desktop/src/renderer/components/Map.tsx18
-rw-r--r--gui/packages/desktop/src/renderer/components/NavigationBar.tsx185
-rw-r--r--gui/packages/desktop/src/renderer/components/NotificationArea.tsx74
-rw-r--r--gui/packages/desktop/src/renderer/components/NotificationBanner.tsx123
-rw-r--r--gui/packages/desktop/src/renderer/components/PlatformWindow.tsx12
-rw-r--r--gui/packages/desktop/src/renderer/components/Preferences.tsx36
-rw-r--r--gui/packages/desktop/src/renderer/components/RelayRow.tsx32
-rw-r--r--gui/packages/desktop/src/renderer/components/RelayStatusIndicator.tsx10
-rw-r--r--gui/packages/desktop/src/renderer/components/SelectLocation.tsx143
-rw-r--r--gui/packages/desktop/src/renderer/components/Settings.tsx48
-rw-r--r--gui/packages/desktop/src/renderer/components/Support.tsx214
-rw-r--r--gui/packages/desktop/src/renderer/components/SvgMap.tsx94
-rw-r--r--gui/packages/desktop/src/renderer/components/Switch.tsx78
-rw-r--r--gui/packages/desktop/src/renderer/components/TransitionContainer.tsx38
-rw-r--r--gui/packages/desktop/src/renderer/components/TunnelControl.tsx40
-rw-r--r--gui/packages/desktop/src/renderer/containers/AccountPage.tsx14
-rw-r--r--gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx12
-rw-r--r--gui/packages/desktop/src/renderer/containers/ConnectPage.tsx14
-rw-r--r--gui/packages/desktop/src/renderer/containers/LaunchPage.tsx12
-rw-r--r--gui/packages/desktop/src/renderer/containers/LoginPage.tsx12
-rw-r--r--gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.tsx4
-rw-r--r--gui/packages/desktop/src/renderer/containers/PreferencesPage.tsx10
-rw-r--r--gui/packages/desktop/src/renderer/containers/SelectLocationPage.tsx12
-rw-r--r--gui/packages/desktop/src/renderer/containers/SettingsPage.tsx10
-rw-r--r--gui/packages/desktop/src/renderer/containers/SupportPage.tsx10
-rw-r--r--gui/packages/desktop/src/renderer/lib/account-expiry.ts14
-rw-r--r--gui/packages/desktop/src/renderer/lib/auth-failure.ts24
-rw-r--r--gui/packages/desktop/src/renderer/lib/problem-report.ts11
-rw-r--r--gui/packages/desktop/src/renderer/lib/relay-settings-builder.ts54
-rw-r--r--gui/packages/desktop/src/renderer/lib/transition-rule.ts38
-rw-r--r--gui/packages/desktop/src/renderer/redux/account/actions.ts68
-rw-r--r--gui/packages/desktop/src/renderer/redux/account/reducers.ts14
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/actions.ts72
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/reducers.ts12
-rw-r--r--gui/packages/desktop/src/renderer/redux/settings/actions.ts78
-rw-r--r--gui/packages/desktop/src/renderer/redux/settings/reducers.ts34
-rw-r--r--gui/packages/desktop/src/renderer/redux/store.ts63
-rw-r--r--gui/packages/desktop/src/renderer/redux/support/actions.ts20
-rw-r--r--gui/packages/desktop/src/renderer/redux/support/reducers.ts10
-rw-r--r--gui/packages/desktop/src/renderer/redux/userinterface/actions.ts16
-rw-r--r--gui/packages/desktop/src/renderer/redux/userinterface/reducers.ts10
-rw-r--r--gui/packages/desktop/src/renderer/redux/version/actions.ts22
-rw-r--r--gui/packages/desktop/src/renderer/redux/version/reducers.ts10
-rw-r--r--gui/packages/desktop/src/renderer/routes.tsx82
-rw-r--r--gui/packages/desktop/src/renderer/transitions.ts31
-rw-r--r--gui/packages/desktop/src/shared/daemon-rpc-types.ts129
-rw-r--r--gui/packages/desktop/src/shared/gui-settings-state.ts4
-rw-r--r--gui/packages/desktop/src/shared/ipc-event-channel.ts155
-rw-r--r--gui/packages/desktop/tslint.json13
76 files changed, 2803 insertions, 2782 deletions
diff --git a/gui/packages/components/package.json b/gui/packages/components/package.json
index a085f7fc38..fb96900c35 100644
--- a/gui/packages/components/package.json
+++ b/gui/packages/components/package.json
@@ -36,7 +36,6 @@
"rimraf": "^2.6.2",
"ts-node": "^7.0.1",
"tslint": "^5.12.1",
- "tslint-config-prettier": "^1.17.0",
"typescript": "^3.2.4"
},
"dependencies": {},
diff --git a/gui/packages/config/package.json b/gui/packages/config/package.json
index b876bce389..fac7c77025 100644
--- a/gui/packages/config/package.json
+++ b/gui/packages/config/package.json
@@ -1,5 +1,9 @@
{
"private": true,
"name": "@mullvad/config",
- "version": "0.1.0"
+ "version": "0.1.0",
+ "devDependencies": {
+ "tslint-config-prettier": "^1.17.0",
+ "tslint-react": "^3.6.0"
+ }
}
diff --git a/gui/packages/config/tslint.json b/gui/packages/config/tslint.json
index b5f8043a6b..1022d301af 100644
--- a/gui/packages/config/tslint.json
+++ b/gui/packages/config/tslint.json
@@ -2,12 +2,18 @@
"defaultSeverity": "error",
"extends": [
"tslint:latest",
+ "tslint-react",
"tslint-config-prettier"
],
- "jsRules": {},
"rules": {
"max-classes-per-file": false,
- "object-literal-sort-keys": false
- },
- "rulesDirectory": []
+ "object-literal-sort-keys": false,
+ "variable-name": [
+ true,
+ "ban-keywords",
+ "check-format",
+ "allow-pascal-case",
+ "allow-leading-underscore"
+ ]
+ }
}
diff --git a/gui/packages/desktop/package.json b/gui/packages/desktop/package.json
index f25c1f548b..68fe6a59fd 100644
--- a/gui/packages/desktop/package.json
+++ b/gui/packages/desktop/package.json
@@ -38,19 +38,19 @@
},
"devDependencies": {
"@types/chai": "^4.1.7",
- "@types/chai-spies": "^1.0.0",
"@types/chai-as-promised": "^7.1.0",
+ "@types/chai-spies": "^1.0.0",
"@types/d3-geo": "^1.11.0",
"@types/enzyme": "^3.1.15",
"@types/enzyme-adapter-react-16": "^1.0.3",
+ "@types/mkdirp": "^0.5.2",
"@types/node": "^10.12.3",
"@types/rbush": "^2.0.2",
"@types/react": "16.3.18",
"@types/react-dom": "16.0.7",
- "@types/react-router": "^4.4.3",
"@types/react-redux": "^7.0.0",
+ "@types/react-router": "^4.4.3",
"@types/sinon": "^7.0.5",
- "@types/mkdirp": "^0.5.2",
"@types/uuid": "^3.4.4",
"browser-sync": "^2.26.3",
"chai": "^4.2.0",
@@ -69,13 +69,12 @@
"sinon": "^7.1.1",
"ts-node": "^7.0.1",
"tslint": "^5.12.1",
- "tslint-config-prettier": "^1.17.0",
"typescript": "^3.2.4"
},
"scripts": {
"postinstall": "electron-builder install-app-deps",
"build": "run-s private:clean private:copy-assets private:compile",
- "lint": "tslint -t stylish -p . || true",
+ "lint": "tslint -t stylish -p .",
"develop": "cross-env run-s private:copy-assets private:compile:dev && run-p -r private:watch private:serve",
"test": "electron-mocha --renderer -R spec --require ts-node/register --require-main ts-node/register --require-main \"test/setup/main.ts\" --preload \"test/setup/renderer.ts\" \"test/*.spec.ts\" \"test/**/*.spec.ts\" \"test/**/*.spec.tsx\" || true",
"pack:mac": "run-s build private:build:mac private:postbuild:mac",
diff --git a/gui/packages/desktop/src/main/autostart.ts b/gui/packages/desktop/src/main/autostart.ts
index 512a53d4ce..a42691550c 100644
--- a/gui/packages/desktop/src/main/autostart.ts
+++ b/gui/packages/desktop/src/main/autostart.ts
@@ -1,8 +1,8 @@
+import { app } from 'electron';
+import log from 'electron-log';
import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
-import { app } from 'electron';
-import log from 'electron-log';
const DESKTOP_FILE_NAME = 'mullvad-vpn.desktop';
diff --git a/gui/packages/desktop/src/main/daemon-rpc.ts b/gui/packages/desktop/src/main/daemon-rpc.ts
index ff319f90a2..eac4918ed2 100644
--- a/gui/packages/desktop/src/main/daemon-rpc.ts
+++ b/gui/packages/desktop/src/main/daemon-rpc.ts
@@ -1,36 +1,35 @@
-import JsonRpcClient, {
- RemoteError as JsonRpcRemoteError,
- TimeOutError as JsonRpcTimeOutError,
- SocketTransport,
-} from './jsonrpc-client';
-import { CommunicationError, InvalidAccountError, NoDaemonError } from './errors';
import {
- AccountData,
AccountToken,
- AppVersionInfo,
- Location,
- RelayList,
+ IAccountData,
+ IAppVersionInfo,
+ ILocation,
+ IRelayList,
+ ISettings,
RelaySettingsUpdate,
- Settings,
TunnelStateTransition,
} from '../shared/daemon-rpc-types';
+import { CommunicationError, InvalidAccountError, NoDaemonError } from './errors';
+import JsonRpcClient, {
+ RemoteError as JsonRpcRemoteError,
+ SocketTransport,
+ TimeOutError as JsonRpcTimeOutError,
+} from './jsonrpc-client';
+import { validate } from 'validated/object';
import {
- object,
- partialObject,
- maybe,
- string,
- number,
+ arrayOf,
boolean,
enumeration,
- arrayOf,
+ maybe,
+ Node as SchemaNode,
+ number,
+ object,
oneOf,
+ partialObject,
+ string,
} from 'validated/schema';
-import { validate } from 'validated/object';
-import { Node as SchemaNode } from 'validated/schema';
-
-const LocationSchema = maybe(
+const locationSchema = maybe(
partialObject({
ip: maybe(string),
country: string,
@@ -51,7 +50,7 @@ const constraint = <T>(constraintValue: SchemaNode<T>) => {
);
};
-const CustomTunnelEndpoint = oneOf(
+const customTunnelEndpointSchema = oneOf(
object({
openvpn: object({
endpoint: object({
@@ -78,7 +77,7 @@ const CustomTunnelEndpoint = oneOf(
}),
);
-const RelaySettingsSchema = oneOf(
+const relaySettingsSchema = oneOf(
object({
normal: partialObject({
location: constraint(
@@ -107,12 +106,12 @@ const RelaySettingsSchema = oneOf(
object({
custom_tunnel_endpoint: partialObject({
host: string,
- config: CustomTunnelEndpoint,
+ config: customTunnelEndpointSchema,
}),
}),
);
-const RelayListSchema = partialObject({
+const relayListSchema = partialObject({
countries: arrayOf(
partialObject({
name: string,
@@ -137,7 +136,7 @@ const RelayListSchema = partialObject({
),
});
-const OpenVpnProxySchema = maybe(
+const openVpnProxySchema = maybe(
oneOf(
object({
local: partialObject({
@@ -159,10 +158,10 @@ const OpenVpnProxySchema = maybe(
),
);
-const TunnelOptionsSchema = partialObject({
+const tunnelOptionsSchema = partialObject({
openvpn: partialObject({
mssfix: maybe(number),
- proxy: OpenVpnProxySchema,
+ proxy: openVpnProxySchema,
}),
wireguard: partialObject({
mtu: maybe(number),
@@ -174,11 +173,11 @@ const TunnelOptionsSchema = partialObject({
}),
});
-const AccountDataSchema = partialObject({
+const accountDataSchema = partialObject({
expiry: string,
});
-const TunnelStateTransitionSchema = oneOf(
+const tunnelStateTransitionSchema = oneOf(
object({
state: enumeration('disconnecting'),
details: enumeration('nothing', 'block', 'reconnect'),
@@ -216,7 +215,7 @@ const TunnelStateTransitionSchema = oneOf(
}),
);
-const AppVersionInfoSchema = partialObject({
+const appVersionInfoSchema = partialObject({
current_is_supported: boolean,
latest: partialObject({
latest_stable: string,
@@ -225,60 +224,56 @@ const AppVersionInfoSchema = partialObject({
});
export class ConnectionObserver {
- _openHandler: () => void;
- _closeHandler: (error?: Error) => void;
-
- constructor(openHandler: () => void, closeHandler: (error?: Error) => void) {
- this._openHandler = openHandler;
- this._closeHandler = closeHandler;
- }
+ constructor(private openHandler: () => void, private closeHandler: (error?: Error) => void) {}
- _onOpen = () => {
- this._openHandler();
+ // Only meant to be called by DaemonRpc
+ // @internal
+ public onOpen = () => {
+ this.openHandler();
};
- _onClose = (error?: Error) => {
- this._closeHandler(error);
+ // Only meant to be called by DaemonRpc
+ // @internal
+ public onClose = (error?: Error) => {
+ this.closeHandler(error);
};
}
export class SubscriptionListener<T> {
- _eventHandler: (payload: T) => void;
- _errorHandler: (error: Error) => void;
-
- constructor(eventHandler: (payload: T) => void, errorHandler: (error: Error) => void) {
- this._eventHandler = eventHandler;
- this._errorHandler = errorHandler;
- }
+ constructor(
+ private eventHandler: (payload: T) => void,
+ private errorHandler: (error: Error) => void,
+ ) {}
- _onEvent(payload: T) {
- this._eventHandler(payload);
+ // Only meant to be called by DaemonRpc
+ // @internal
+ public onEvent(payload: T) {
+ this.eventHandler(payload);
}
- _onError(error: Error) {
- this._errorHandler(error);
+ // Only meant to be called by DaemonRpc
+ // @internal
+ public onError(error: Error) {
+ this.errorHandler(error);
}
}
-const SettingsSchema = partialObject({
+const settingsSchema = partialObject({
account_token: maybe(string),
allow_lan: boolean,
auto_connect: boolean,
block_when_disconnected: boolean,
- relay_settings: RelaySettingsSchema,
- tunnel_options: TunnelOptionsSchema,
+ relay_settings: relaySettingsSchema,
+ tunnel_options: tunnelOptionsSchema,
});
export class ResponseParseError extends Error {
- _validationError?: Error;
-
- constructor(message: string, validationError?: Error) {
+ constructor(message: string, private validationErrorValue?: Error) {
super(message);
- this._validationError = validationError;
}
get validationError(): Error | undefined {
- return this._validationError;
+ return this.validationErrorValue;
}
}
@@ -286,28 +281,28 @@ export class ResponseParseError extends Error {
const NETWORK_CALL_TIMEOUT = 10000;
export class DaemonRpc {
- _transport = new JsonRpcClient(new SocketTransport());
+ private transport = new JsonRpcClient(new SocketTransport());
- connect(connectionParams: { path: string }) {
- this._transport.connect(connectionParams);
+ public connect(connectionParams: { path: string }) {
+ this.transport.connect(connectionParams);
}
- disconnect() {
- this._transport.disconnect();
+ public disconnect() {
+ this.transport.disconnect();
}
- addConnectionObserver(observer: ConnectionObserver) {
- this._transport.on('open', observer._onOpen).on('close', observer._onClose);
+ public addConnectionObserver(observer: ConnectionObserver) {
+ this.transport.on('open', observer.onOpen).on('close', observer.onClose);
}
- removeConnectionObserver(observer: ConnectionObserver) {
- this._transport.off('open', observer._onOpen).off('close', observer._onClose);
+ public removeConnectionObserver(observer: ConnectionObserver) {
+ this.transport.off('open', observer.onOpen).off('close', observer.onClose);
}
- async getAccountData(accountToken: AccountToken): Promise<AccountData> {
+ public async getAccountData(accountToken: AccountToken): Promise<IAccountData> {
let response;
try {
- response = await this._transport.send('get_account_data', accountToken, NETWORK_CALL_TIMEOUT);
+ response = await this.transport.send('get_account_data', accountToken, NETWORK_CALL_TIMEOUT);
} catch (error) {
if (error instanceof JsonRpcRemoteError) {
switch (error.code) {
@@ -324,112 +319,114 @@ export class DaemonRpc {
}
try {
- return validate(AccountDataSchema, response);
+ return validate(accountDataSchema, response);
} catch (error) {
throw new ResponseParseError('Invalid response from get_account_data', error);
}
}
- async getRelayLocations(): Promise<RelayList> {
- const response = await this._transport.send('get_relay_locations');
+ public async getRelayLocations(): Promise<IRelayList> {
+ const response = await this.transport.send('get_relay_locations');
try {
- return camelCaseObjectKeys(validate(RelayListSchema, response)) as RelayList;
+ return camelCaseObjectKeys(validate(relayListSchema, response)) as IRelayList;
} catch (error) {
throw new ResponseParseError('Invalid response from get_relay_locations', error);
}
}
- async setAccount(accountToken?: AccountToken): Promise<void> {
- await this._transport.send('set_account', [accountToken]);
+ public async setAccount(accountToken?: AccountToken): Promise<void> {
+ await this.transport.send('set_account', [accountToken]);
}
- async updateRelaySettings(relaySettings: RelaySettingsUpdate): Promise<void> {
- await this._transport.send('update_relay_settings', [underscoreObjectKeys(relaySettings)]);
+ public async updateRelaySettings(relaySettings: RelaySettingsUpdate): Promise<void> {
+ await this.transport.send('update_relay_settings', [underscoreObjectKeys(relaySettings)]);
}
- async setAllowLan(allowLan: boolean): Promise<void> {
- await this._transport.send('set_allow_lan', [allowLan]);
+ public async setAllowLan(allowLan: boolean): Promise<void> {
+ await this.transport.send('set_allow_lan', [allowLan]);
}
- async setEnableIpv6(enableIpv6: boolean): Promise<void> {
- await this._transport.send('set_enable_ipv6', [enableIpv6]);
+ public async setEnableIpv6(enableIpv6: boolean): Promise<void> {
+ await this.transport.send('set_enable_ipv6', [enableIpv6]);
}
- async setBlockWhenDisconnected(blockWhenDisconnected: boolean): Promise<void> {
- await this._transport.send('set_block_when_disconnected', [blockWhenDisconnected]);
+ public async setBlockWhenDisconnected(blockWhenDisconnected: boolean): Promise<void> {
+ await this.transport.send('set_block_when_disconnected', [blockWhenDisconnected]);
}
- async setOpenVpnMssfix(mssfix?: number): Promise<void> {
- await this._transport.send('set_openvpn_mssfix', [mssfix]);
+ public async setOpenVpnMssfix(mssfix?: number): Promise<void> {
+ await this.transport.send('set_openvpn_mssfix', [mssfix]);
}
- async setAutoConnect(autoConnect: boolean): Promise<void> {
- await this._transport.send('set_auto_connect', [autoConnect]);
+ public async setAutoConnect(autoConnect: boolean): Promise<void> {
+ await this.transport.send('set_auto_connect', [autoConnect]);
}
- async connectTunnel(): Promise<void> {
- await this._transport.send('connect');
+ public async connectTunnel(): Promise<void> {
+ await this.transport.send('connect');
}
- async disconnectTunnel(): Promise<void> {
- await this._transport.send('disconnect');
+ public async disconnectTunnel(): Promise<void> {
+ await this.transport.send('disconnect');
}
- async getLocation(): Promise<Location | undefined> {
- const response = await this._transport.send('get_current_location', [], NETWORK_CALL_TIMEOUT);
+ public async getLocation(): Promise<ILocation | undefined> {
+ const response = await this.transport.send('get_current_location', [], NETWORK_CALL_TIMEOUT);
try {
- return camelCaseObjectKeys(validate(LocationSchema, response)) as Location;
+ return camelCaseObjectKeys(validate(locationSchema, response)) as ILocation;
} catch (error) {
throw new ResponseParseError('Invalid response from get_current_location', error);
}
}
- async getState(): Promise<TunnelStateTransition> {
- const response = await this._transport.send('get_state');
+ public async getState(): Promise<TunnelStateTransition> {
+ const response = await this.transport.send('get_state');
try {
return camelCaseObjectKeys(
- validate(TunnelStateTransitionSchema, response),
+ validate(tunnelStateTransitionSchema, response),
) as TunnelStateTransition;
} catch (error) {
throw new ResponseParseError('Invalid response from get_state', error);
}
}
- async getSettings(): Promise<Settings> {
- const response = await this._transport.send('get_settings');
+ public async getSettings(): Promise<ISettings> {
+ const response = await this.transport.send('get_settings');
try {
- return camelCaseObjectKeys(validate(SettingsSchema, response)) as Settings;
+ return camelCaseObjectKeys(validate(settingsSchema, response)) as ISettings;
} catch (error) {
throw new ResponseParseError('Invalid response from get_settings', error);
}
}
- subscribeStateListener(listener: SubscriptionListener<TunnelStateTransition>): Promise<void> {
- return this._transport.subscribe('new_state', (payload) => {
+ public subscribeStateListener(
+ listener: SubscriptionListener<TunnelStateTransition>,
+ ): Promise<void> {
+ return this.transport.subscribe('new_state', (payload) => {
try {
const newState = camelCaseObjectKeys(
- validate(TunnelStateTransitionSchema, payload),
+ validate(tunnelStateTransitionSchema, payload),
) as TunnelStateTransition;
- listener._onEvent(newState);
+ listener.onEvent(newState);
} catch (error) {
- listener._onError(new ResponseParseError('Invalid payload from new_state', error));
+ listener.onError(new ResponseParseError('Invalid payload from new_state', error));
}
});
}
- subscribeSettingsListener(listener: SubscriptionListener<Settings>): Promise<void> {
- return this._transport.subscribe('settings', (payload) => {
+ public subscribeSettingsListener(listener: SubscriptionListener<ISettings>): Promise<void> {
+ return this.transport.subscribe('settings', (payload) => {
try {
- const newSettings = camelCaseObjectKeys(validate(SettingsSchema, payload)) as Settings;
- listener._onEvent(newSettings);
+ const newSettings = camelCaseObjectKeys(validate(settingsSchema, payload)) as ISettings;
+ listener.onEvent(newSettings);
} catch (error) {
- listener._onError(new ResponseParseError('Invalid payload from settings', error));
+ listener.onError(new ResponseParseError('Invalid payload from settings', error));
}
});
}
- async getAccountHistory(): Promise<Array<AccountToken>> {
- const response = await this._transport.send('get_account_history');
+ public async getAccountHistory(): Promise<AccountToken[]> {
+ const response = await this.transport.send('get_account_history');
try {
return validate(arrayOf(string), response);
} catch (error) {
@@ -437,12 +434,12 @@ export class DaemonRpc {
}
}
- async removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
- await this._transport.send('remove_account_from_history', accountToken);
+ public async removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
+ await this.transport.send('remove_account_from_history', accountToken);
}
- async getCurrentVersion(): Promise<string> {
- const response = await this._transport.send('get_current_version');
+ public async getCurrentVersion(): Promise<string> {
+ const response = await this.transport.send('get_current_version');
try {
return validate(string, response);
} catch (error) {
@@ -450,10 +447,10 @@ export class DaemonRpc {
}
}
- async getVersionInfo(): Promise<AppVersionInfo> {
- const response = await this._transport.send('get_version_info', [], NETWORK_CALL_TIMEOUT);
+ public async getVersionInfo(): Promise<IAppVersionInfo> {
+ const response = await this.transport.send('get_version_info', [], NETWORK_CALL_TIMEOUT);
try {
- return camelCaseObjectKeys(validate(AppVersionInfoSchema, response)) as AppVersionInfo;
+ return camelCaseObjectKeys(validate(appVersionInfoSchema, response)) as IAppVersionInfo;
} catch (error) {
throw new ResponseParseError('Invalid response from get_version_info');
}
@@ -470,30 +467,30 @@ function camelCaseToUnderscore(str: string): string {
.toLowerCase();
}
-function camelCaseObjectKeys(object: { [key: string]: any }) {
- return transformObjectKeys(object, underscoreToCamelCase);
+function camelCaseObjectKeys(anObject: { [key: string]: any }) {
+ return transformObjectKeys(anObject, underscoreToCamelCase);
}
-function underscoreObjectKeys(object: { [key: string]: any }) {
- return transformObjectKeys(object, camelCaseToUnderscore);
+function underscoreObjectKeys(anObject: { [key: string]: any }) {
+ return transformObjectKeys(anObject, camelCaseToUnderscore);
}
function transformObjectKeys(
- object: { [key: string]: any },
+ anObject: { [key: string]: any },
keyTransformer: (key: string) => string,
) {
- for (const sourceKey of Object.keys(object)) {
+ for (const sourceKey of Object.keys(anObject)) {
const targetKey = keyTransformer(sourceKey);
- const sourceValue = object[sourceKey];
+ const sourceValue = anObject[sourceKey];
- object[targetKey] =
+ anObject[targetKey] =
sourceValue !== null && typeof sourceValue === 'object'
? transformObjectKeys(sourceValue, keyTransformer)
: sourceValue;
if (sourceKey !== targetKey) {
- delete object[sourceKey];
+ delete anObject[sourceKey];
}
}
- return object;
+ return anObject;
}
diff --git a/gui/packages/desktop/src/main/gui-settings.ts b/gui/packages/desktop/src/main/gui-settings.ts
index 1b68b5e13c..57c034b162 100644
--- a/gui/packages/desktop/src/main/gui-settings.ts
+++ b/gui/packages/desktop/src/main/gui-settings.ts
@@ -1,79 +1,79 @@
-import * as fs from 'fs';
-import * as path from 'path';
import { app } from 'electron';
import log from 'electron-log';
+import * as fs from 'fs';
+import * as path from 'path';
-import { GuiSettingsState } from '../shared/gui-settings-state';
+import { IGuiSettingsState } from '../shared/gui-settings-state';
export default class GuiSettings {
- _state: GuiSettingsState = {
+ get state(): IGuiSettingsState {
+ return this.stateValue;
+ }
+
+ set autoConnect(newValue: boolean) {
+ this.changeStateAndNotify({ ...this.stateValue, autoConnect: newValue });
+ }
+
+ get autoConnect(): boolean {
+ return this.stateValue.autoConnect;
+ }
+
+ set monochromaticIcon(newValue: boolean) {
+ this.changeStateAndNotify({ ...this.stateValue, monochromaticIcon: newValue });
+ }
+
+ get monochromaticIcon(): boolean {
+ return this.stateValue.monochromaticIcon;
+ }
+
+ set startMinimized(newValue: boolean) {
+ this.changeStateAndNotify({ ...this.stateValue, startMinimized: newValue });
+ }
+
+ get startMinimized(): boolean {
+ return this.stateValue.startMinimized;
+ }
+
+ public onChange?: (newState: IGuiSettingsState, oldState: IGuiSettingsState) => void;
+
+ private stateValue: IGuiSettingsState = {
autoConnect: true,
monochromaticIcon: false,
startMinimized: false,
};
- onChange?: (newState: GuiSettingsState, oldState: GuiSettingsState) => void;
-
- load() {
+ public load() {
try {
- const settingsFile = this._filePath();
+ const settingsFile = this.filePath();
const contents = fs.readFileSync(settingsFile, 'utf8');
const settings = JSON.parse(contents);
- this._state.autoConnect =
+ this.stateValue.autoConnect =
typeof settings.autoConnect === 'boolean' ? settings.autoConnect : true;
- this._state.monochromaticIcon = settings.monochromaticIcon || false;
- this._state.startMinimized = settings.startMinimized || false;
+ this.stateValue.monochromaticIcon = settings.monochromaticIcon || false;
+ this.stateValue.startMinimized = settings.startMinimized || false;
} catch (error) {
log.error(`Failed to read GUI settings file: ${error}`);
}
}
- store() {
+ public store() {
try {
- const settingsFile = this._filePath();
+ const settingsFile = this.filePath();
- fs.writeFileSync(settingsFile, JSON.stringify(this._state));
+ fs.writeFileSync(settingsFile, JSON.stringify(this.stateValue));
} catch (error) {
log.error(`Failed to write GUI settings file: ${error}`);
}
}
- get state(): GuiSettingsState {
- return this._state;
- }
-
- set autoConnect(newValue: boolean) {
- this._changeStateAndNotify({ ...this._state, autoConnect: newValue });
- }
-
- get autoConnect(): boolean {
- return this._state.autoConnect;
- }
-
- set monochromaticIcon(newValue: boolean) {
- this._changeStateAndNotify({ ...this._state, monochromaticIcon: newValue });
- }
-
- get monochromaticIcon(): boolean {
- return this._state.monochromaticIcon;
- }
-
- set startMinimized(newValue: boolean) {
- this._changeStateAndNotify({ ...this._state, startMinimized: newValue });
- }
-
- get startMinimized(): boolean {
- return this._state.startMinimized;
- }
-
- _filePath() {
+ private filePath() {
return path.join(app.getPath('userData'), 'gui_settings.json');
}
- _changeStateAndNotify(newState: GuiSettingsState) {
- const oldState = this._state;
- this._state = newState;
+ private changeStateAndNotify(newState: IGuiSettingsState) {
+ const oldState = this.stateValue;
+ this.stateValue = newState;
this.store();
diff --git a/gui/packages/desktop/src/main/index.ts b/gui/packages/desktop/src/main/index.ts
index 70890f0a2a..173be30819 100644
--- a/gui/packages/desktop/src/main/index.ts
+++ b/gui/packages/desktop/src/main/index.ts
@@ -1,34 +1,28 @@
-import * as fs from 'fs';
-import log from 'electron-log';
-import * as path from 'path';
import { execFile } from 'child_process';
+import { app, BrowserWindow, ipcMain, Menu, nativeImage, screen, Tray } from 'electron';
+import log from 'electron-log';
+import * as fs from 'fs';
import mkdirp from 'mkdirp';
+import * as path from 'path';
import * as uuid from 'uuid';
-import { app, screen, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron';
-
-import { getOpenAtLogin, setOpenAtLogin } from './autostart';
-import NotificationController from './notification-controller';
-import WindowController from './window-controller';
-
-import TrayIconController from './tray-icon-controller';
-import { TrayIconType } from './tray-icon-controller';
-
-import { IpcMainEventChannel } from '../shared/ipc-event-channel';
-
-import { DaemonRpc, ConnectionObserver, SubscriptionListener } from './daemon-rpc';
import {
AccountToken,
- AppVersionInfo,
- Location,
- RelayList,
+ IAppVersionInfo,
+ ILocation,
+ IRelayList,
+ ISettings,
RelaySettingsUpdate,
- Settings,
TunnelStateTransition,
} from '../shared/daemon-rpc-types';
-
+import { IpcMainEventChannel } from '../shared/ipc-event-channel';
+import { getOpenAtLogin, setOpenAtLogin } from './autostart';
+import { ConnectionObserver, DaemonRpc, SubscriptionListener } from './daemon-rpc';
import GuiSettings from './gui-settings';
-import ReconnectionBackoff from './reconnection-backoff';
+import NotificationController from './notification-controller';
import { resolveBin } from './proc';
+import ReconnectionBackoff from './reconnection-backoff';
+import TrayIconController, { TrayIconType } from './tray-icon-controller';
+import WindowController from './window-controller';
const RELAY_LIST_UPDATE_INTERVAL = 60 * 60 * 1000;
const VERSION_UPDATE_INTERVAL = 24 * 60 * 60 * 1000;
@@ -36,34 +30,38 @@ const VERSION_UPDATE_INTERVAL = 24 * 60 * 60 * 1000;
const DAEMON_RPC_PATH =
process.platform === 'win32' ? '//./pipe/Mullvad VPN' : '/var/run/mullvad-vpn';
-type AppQuitStage = 'unready' | 'initiated' | 'ready';
+enum AppQuitStage {
+ unready,
+ initiated,
+ ready,
+}
-export type CurrentAppVersionInfo = {
+export interface ICurrentAppVersionInfo {
gui: string;
daemon: string;
isConsistent: boolean;
-};
+}
-export type AppUpgradeInfo = {
+export interface IAppUpgradeInfo extends IAppVersionInfo {
nextUpgrade?: string;
upToDate: boolean;
-} & AppVersionInfo;
+}
-const ApplicationMain = {
- _notificationController: new NotificationController(),
- _windowController: undefined as WindowController | undefined,
- _trayIconController: undefined as TrayIconController | undefined,
+class ApplicationMain {
+ private notificationController = new NotificationController();
+ private windowController?: WindowController;
+ private trayIconController?: TrayIconController;
- _daemonRpc: new DaemonRpc(),
- _reconnectBackoff: new ReconnectionBackoff(),
- _connectedToDaemon: false,
+ private daemonRpc = new DaemonRpc();
+ private reconnectBackoff = new ReconnectionBackoff();
+ private connectedToDaemon = false;
- _logFilePath: '',
- _oldLogFilePath: undefined as undefined | string,
- _quitStage: 'unready' as AppQuitStage,
+ private logFilePath = '';
+ private oldLogFilePath?: string;
+ private quitStage = AppQuitStage.unready;
- _tunnelState: { state: 'disconnected' } as TunnelStateTransition,
- _settings: {
+ private tunnelState: TunnelStateTransition = { state: 'disconnected' };
+ private settings: ISettings = {
accountToken: undefined,
allowLan: false,
autoConnect: false,
@@ -87,21 +85,21 @@ const ApplicationMain = {
fwmark: undefined,
},
},
- } as Settings,
- _guiSettings: new GuiSettings(),
- _location: undefined as Location | undefined,
- _lastDisconnectedLocation: undefined as Location | undefined,
+ };
+ private guiSettings = new GuiSettings();
+ private location?: ILocation;
+ private lastDisconnectedLocation?: ILocation;
- _relays: { countries: [] } as RelayList,
- _relaysInterval: undefined as NodeJS.Timeout | undefined,
+ private relays: IRelayList = { countries: [] };
+ private relaysInterval?: NodeJS.Timeout;
- _currentVersion: {
+ private currentVersion: ICurrentAppVersionInfo = {
daemon: '',
gui: '',
isConsistent: true,
- } as CurrentAppVersionInfo,
+ };
- _upgradeVersion: {
+ private upgradeVersion: IAppUpgradeInfo = {
currentIsSupported: true,
latest: {
latestStable: '',
@@ -109,22 +107,22 @@ const ApplicationMain = {
},
nextUpgrade: undefined,
upToDate: true,
- } as AppUpgradeInfo,
- _latestVersionInterval: undefined as NodeJS.Timeout | undefined,
+ };
+ private latestVersionInterval?: NodeJS.Timeout;
- run() {
+ public run() {
// Since electron's GPU blacklists are broken, GPU acceleration won't work on older distros
if (process.platform === 'linux') {
app.commandLine.appendSwitch('--disable-gpu');
}
- this._overrideAppPaths();
+ this.overrideAppPaths();
- if (this._ensureSingleInstance()) {
+ if (this.ensureSingleInstance()) {
return;
}
- this._initLogging();
+ this.initLogging();
log.info(`Running version ${app.getVersion()}`);
@@ -132,31 +130,31 @@ const ApplicationMain = {
app.setAppUserModelId('net.mullvad.vpn');
}
- this._guiSettings.load();
+ this.guiSettings.load();
- app.on('activate', () => this._onActivate());
- app.on('ready', () => this._onReady());
+ app.on('activate', () => this.onActivate());
+ app.on('ready', () => this.onReady());
app.on('window-all-closed', () => app.quit());
- app.on('before-quit', (event: Event) => this._onBeforeQuit(event));
+ app.on('before-quit', (event: Event) => this.onBeforeQuit(event));
const connectionObserver = new ConnectionObserver(
() => {
- this._onDaemonConnected();
+ this.onDaemonConnected();
},
(error) => {
- this._onDaemonDisconnected(error);
+ this.onDaemonDisconnected(error);
},
);
- this._daemonRpc.addConnectionObserver(connectionObserver);
- this._connectToDaemon();
- },
+ this.daemonRpc.addConnectionObserver(connectionObserver);
+ this.connectToDaemon();
+ }
- _ensureSingleInstance() {
+ private ensureSingleInstance() {
if (app.requestSingleInstanceLock()) {
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
- if (this._windowController) {
- this._windowController.show();
+ if (this.windowController) {
+ this.windowController.show();
}
});
return false;
@@ -164,9 +162,9 @@ const ApplicationMain = {
app.quit();
return true;
}
- },
+ }
- _overrideAppPaths() {
+ private overrideAppPaths() {
// This ensures that on Windows the %LOCALAPPDATA% directory is used instead of the %ADDDATA%
// directory that has roaming contents
if (process.platform === 'win32') {
@@ -178,13 +176,13 @@ const ApplicationMain = {
throw new Error('Missing %LOCALAPPDATA% environment variable');
}
}
- },
+ }
- _initLogging() {
- const logDirectory = this._getLogsDirectory();
+ private initLogging() {
+ const logDirectory = this.getLogsDirectory();
const format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}][{level}] {text}';
- this._logFilePath = path.join(logDirectory, 'frontend.log');
+ this.logFilePath = path.join(logDirectory, 'frontend.log');
log.transports.console.format = format;
log.transports.file.format = format;
@@ -199,9 +197,9 @@ const ApplicationMain = {
// Backup previous log file if it exists
try {
- fs.accessSync(this._logFilePath);
- this._oldLogFilePath = path.join(logDirectory, 'frontend.old.log');
- fs.renameSync(this._logFilePath, this._oldLogFilePath);
+ fs.accessSync(this.logFilePath);
+ this.oldLogFilePath = path.join(logDirectory, 'frontend.old.log');
+ fs.renameSync(this.logFilePath, this.oldLogFilePath);
} catch (error) {
// No previous log file exists
}
@@ -209,17 +207,17 @@ const ApplicationMain = {
// Configure logging to file
log.transports.console.level = 'debug';
log.transports.file.level = 'debug';
- log.transports.file.file = this._logFilePath;
+ log.transports.file.file = this.logFilePath;
- log.debug(`Logging to ${this._logFilePath}`);
+ log.debug(`Logging to ${this.logFilePath}`);
}
- },
+ }
// Returns platform specific logs folder for application
// See open issue and PR on Github:
// 1. https://github.com/electron/electron/issues/10118
// 2. https://github.com/electron/electron/pull/10191
- _getLogsDirectory() {
+ private getLogsDirectory() {
switch (process.platform) {
case 'darwin':
// macOS: ~/Library/Logs/{appname}
@@ -229,43 +227,43 @@ const ApplicationMain = {
// Linux: ~/.config/{appname}/logs
return path.join(app.getPath('userData'), 'logs');
}
- },
+ }
- _onActivate() {
- if (this._windowController) {
- this._windowController.show();
+ private onActivate() {
+ if (this.windowController) {
+ this.windowController.show();
}
- },
+ }
- async _onBeforeQuit(event: Event) {
- switch (this._quitStage) {
- case 'unready':
+ private async onBeforeQuit(event: Event) {
+ switch (this.quitStage) {
+ case AppQuitStage.unready:
// postpone the app shutdown
event.preventDefault();
- this._quitStage = 'initiated';
- await this._prepareToQuit();
+ this.quitStage = AppQuitStage.initiated;
+ await this.prepareToQuit();
// terminate the app
- this._quitStage = 'ready';
+ this.quitStage = AppQuitStage.ready;
app.quit();
break;
- case 'initiated':
+ case AppQuitStage.initiated:
// prevent immediate exit, the app will quit after running the shutdown routine
event.preventDefault();
return;
- case 'ready':
+ case AppQuitStage.ready:
// let the app quit freely at this point
break;
}
- },
+ }
- async _prepareToQuit() {
- if (this._connectedToDaemon) {
+ private async prepareToQuit() {
+ if (this.connectedToDaemon) {
try {
- await this._daemonRpc.disconnectTunnel();
+ await this.daemonRpc.disconnectTunnel();
log.info('Disconnected the tunnel');
} catch (e) {
log.error(`Failed to disconnect the tunnel: ${e.message}`);
@@ -273,163 +271,163 @@ const ApplicationMain = {
} else {
log.info('Cannot close the tunnel because there is no active connection to daemon.');
}
- },
+ }
- async _onReady() {
- const window = this._createWindow();
- const tray = this._createTray();
+ private async onReady() {
+ const window = this.createWindow();
+ const tray = this.createTray();
const windowController = new WindowController(window, tray);
const trayIconController = new TrayIconController(
tray,
'unsecured',
- process.platform === 'darwin' && this._guiSettings.monochromaticIcon,
+ process.platform === 'darwin' && this.guiSettings.monochromaticIcon,
);
- this._registerWindowListener(windowController);
- this._registerIpcListeners();
- this._setAppMenu();
- this._addContextMenu(window);
+ this.registerWindowListener(windowController);
+ this.registerIpcListeners();
+ this.setAppMenu();
+ this.addContextMenu(window);
- this._windowController = windowController;
- this._trayIconController = trayIconController;
+ this.windowController = windowController;
+ this.trayIconController = trayIconController;
- this._guiSettings.onChange = (newState, oldState) => {
+ this.guiSettings.onChange = (newState, oldState) => {
if (
process.platform === 'darwin' &&
oldState.monochromaticIcon !== newState.monochromaticIcon
) {
- if (this._trayIconController) {
- this._trayIconController.useMonochromaticIcon = newState.monochromaticIcon;
+ if (this.trayIconController) {
+ this.trayIconController.useMonochromaticIcon = newState.monochromaticIcon;
}
}
if (newState.autoConnect !== oldState.autoConnect) {
- this._updateDaemonsAutoConnect();
+ this.updateDaemonsAutoConnect();
}
- if (this._windowController) {
- IpcMainEventChannel.guiSettings.notify(this._windowController.webContents, newState);
+ if (this.windowController) {
+ IpcMainEventChannel.guiSettings.notify(this.windowController.webContents, newState);
}
};
if (process.env.NODE_ENV === 'development') {
- await this._installDevTools();
+ await this.installDevTools();
window.webContents.openDevTools({ mode: 'detach' });
}
switch (process.platform) {
case 'win32':
- this._installWindowsMenubarAppWindowHandlers(tray, windowController);
+ this.installWindowsMenubarAppWindowHandlers(tray, windowController);
break;
case 'darwin':
- this._installMacOsMenubarAppWindowHandlers(tray, windowController);
+ this.installMacOsMenubarAppWindowHandlers(tray, windowController);
break;
case 'linux':
- this._installGenericMenubarAppWindowHandlers(tray, windowController);
- this._installLinuxWindowCloseHandler(windowController);
+ this.installGenericMenubarAppWindowHandlers(tray, windowController);
+ this.installLinuxWindowCloseHandler(windowController);
break;
default:
- this._installGenericMenubarAppWindowHandlers(tray, windowController);
+ this.installGenericMenubarAppWindowHandlers(tray, windowController);
break;
}
- if (this._shouldShowWindowOnStart() || process.env.NODE_ENV === 'development') {
+ if (this.shouldShowWindowOnStart() || process.env.NODE_ENV === 'development') {
windowController.show();
}
window.loadFile(path.resolve(path.join(__dirname, '../renderer/index.html')));
- },
+ }
- async _onDaemonConnected() {
- this._connectedToDaemon = true;
+ private async onDaemonConnected() {
+ this.connectedToDaemon = true;
// subscribe to events
try {
- await this._subscribeEvents();
+ await this.subscribeEvents();
} catch (error) {
log.error(`Failed to subscribe: ${error.message}`);
- return this._recoverFromBootstrapError(error);
+ return this.recoverFromBootstrapError(error);
}
// fetch the tunnel state
try {
- this._setTunnelState(await this._daemonRpc.getState());
+ this.setTunnelState(await this.daemonRpc.getState());
} catch (error) {
log.error(`Failed to fetch the tunnel state: ${error.message}`);
- return this._recoverFromBootstrapError(error);
+ return this.recoverFromBootstrapError(error);
}
// fetch settings
try {
- this._setSettings(await this._daemonRpc.getSettings());
+ this.setSettings(await this.daemonRpc.getSettings());
} catch (error) {
log.error(`Failed to fetch settings: ${error.message}`);
- return this._recoverFromBootstrapError(error);
+ return this.recoverFromBootstrapError(error);
}
// fetch relays
try {
- this._setRelays(await this._daemonRpc.getRelayLocations());
+ this.setRelays(await this.daemonRpc.getRelayLocations());
} catch (error) {
log.error(`Failed to fetch relay locations: ${error.message}`);
- return this._recoverFromBootstrapError(error);
+ return this.recoverFromBootstrapError(error);
}
// fetch the daemon's version
try {
- this._setDaemonVersion(await this._daemonRpc.getCurrentVersion());
+ this.setDaemonVersion(await this.daemonRpc.getCurrentVersion());
} catch (error) {
log.error(`Failed to fetch the daemon's version: ${error.message}`);
- return this._recoverFromBootstrapError(error);
+ return this.recoverFromBootstrapError(error);
}
// fetch the latest version info in background
- this._fetchLatestVersion();
+ this.fetchLatestVersion();
// start periodic updates
- this._startRelaysPeriodicUpdates();
- this._startLatestVersionPeriodicUpdates();
+ this.startRelaysPeriodicUpdates();
+ this.startLatestVersionPeriodicUpdates();
// notify user about inconsistent version
if (
process.env.NODE_ENV !== 'development' &&
- !this._shouldSuppressNotifications() &&
- !this._currentVersion.isConsistent
+ !this.shouldSuppressNotifications() &&
+ !this.currentVersion.isConsistent
) {
- this._notificationController.notifyInconsistentVersion();
+ this.notificationController.notifyInconsistentVersion();
}
// reset the reconnect backoff when connection established.
- this._reconnectBackoff.reset();
+ this.reconnectBackoff.reset();
// notify renderer
- if (this._windowController) {
- IpcMainEventChannel.daemonConnected.notify(this._windowController.webContents);
+ if (this.windowController) {
+ IpcMainEventChannel.daemonConnected.notify(this.windowController.webContents);
}
- },
+ }
- _onDaemonDisconnected(error?: Error) {
+ private onDaemonDisconnected(error?: Error) {
// make sure we were connected before to distinguish between a failed attempt to reconnect and
// connection loss.
- const wasConnected = this._connectedToDaemon;
+ const wasConnected = this.connectedToDaemon;
if (wasConnected) {
- this._connectedToDaemon = false;
+ this.connectedToDaemon = false;
// stop periodic updates
- this._stopRelaysPeriodicUpdates();
- this._stopLatestVersionPeriodicUpdates();
+ this.stopRelaysPeriodicUpdates();
+ this.stopLatestVersionPeriodicUpdates();
// notify renderer process
- if (this._windowController) {
+ if (this.windowController) {
IpcMainEventChannel.daemonDisconnected.notify(
- this._windowController.webContents,
+ this.windowController.webContents,
error ? error.message : undefined,
);
}
@@ -443,34 +441,34 @@ const ApplicationMain = {
log.error(`Failed to connect to daemon: ${error.message}`);
}
- this._reconnectToDaemon();
+ this.reconnectToDaemon();
} else {
log.info('Disconnected from the daemon');
}
- },
+ }
- _connectToDaemon() {
- this._daemonRpc.connect({ path: DAEMON_RPC_PATH });
- },
+ private connectToDaemon() {
+ this.daemonRpc.connect({ path: DAEMON_RPC_PATH });
+ }
- _reconnectToDaemon() {
- this._reconnectBackoff.attempt(() => {
- this._connectToDaemon();
+ private reconnectToDaemon() {
+ this.reconnectBackoff.attempt(() => {
+ this.connectToDaemon();
});
- },
+ }
- _recoverFromBootstrapError(_error?: Error) {
+ private recoverFromBootstrapError(_error?: Error) {
// Attempt to reconnect to daemon if the program fails to fetch settings, tunnel state or
// subscribe for RPC events.
- this._daemonRpc.disconnect();
+ this.daemonRpc.disconnect();
- this._reconnectToDaemon();
- },
+ this.reconnectToDaemon();
+ }
- async _subscribeEvents(): Promise<void> {
+ private async subscribeEvents(): Promise<void> {
const stateListener = new SubscriptionListener(
(newState: TunnelStateTransition) => {
- this._setTunnelState(newState);
+ this.setTunnelState(newState);
},
(error: Error) => {
log.error(`Cannot deserialize the new state: ${error.message}`);
@@ -478,8 +476,8 @@ const ApplicationMain = {
);
const settingsListener = new SubscriptionListener(
- (newSettings: Settings) => {
- this._setSettings(newSettings);
+ (newSettings: ISettings) => {
+ this.setSettings(newSettings);
},
(error: Error) => {
log.error(`Cannot deserialize the new settings: ${error.message}`);
@@ -487,74 +485,74 @@ const ApplicationMain = {
);
await Promise.all([
- this._daemonRpc.subscribeStateListener(stateListener),
- this._daemonRpc.subscribeSettingsListener(settingsListener),
+ this.daemonRpc.subscribeStateListener(stateListener),
+ this.daemonRpc.subscribeSettingsListener(settingsListener),
]);
- },
+ }
- _setTunnelState(newState: TunnelStateTransition) {
- this._tunnelState = newState;
- this._updateTrayIcon(newState, this._settings.blockWhenDisconnected);
- this._updateLocation();
+ private setTunnelState(newState: TunnelStateTransition) {
+ this.tunnelState = newState;
+ this.updateTrayIcon(newState, this.settings.blockWhenDisconnected);
+ this.updateLocation();
- if (!this._shouldSuppressNotifications()) {
- this._notificationController.notifyTunnelState(newState);
+ if (!this.shouldSuppressNotifications()) {
+ this.notificationController.notifyTunnelState(newState);
}
- if (this._windowController) {
- IpcMainEventChannel.tunnel.notify(this._windowController.webContents, newState);
+ if (this.windowController) {
+ IpcMainEventChannel.tunnel.notify(this.windowController.webContents, newState);
}
- },
+ }
- _setSettings(newSettings: Settings) {
- this._settings = newSettings;
- this._updateTrayIcon(this._tunnelState, newSettings.blockWhenDisconnected);
+ private setSettings(newSettings: ISettings) {
+ this.settings = newSettings;
+ this.updateTrayIcon(this.tunnelState, newSettings.blockWhenDisconnected);
- if (this._windowController) {
- IpcMainEventChannel.settings.notify(this._windowController.webContents, newSettings);
+ if (this.windowController) {
+ IpcMainEventChannel.settings.notify(this.windowController.webContents, newSettings);
}
- },
+ }
- _setLocation(newLocation: Location) {
- this._location = newLocation;
+ private setLocation(newLocation: ILocation) {
+ this.location = newLocation;
- if (this._windowController) {
- IpcMainEventChannel.location.notify(this._windowController.webContents, newLocation);
+ if (this.windowController) {
+ IpcMainEventChannel.location.notify(this.windowController.webContents, newLocation);
}
- },
+ }
- _setRelays(newRelayList: RelayList) {
- this._relays = newRelayList;
+ private setRelays(newRelayList: IRelayList) {
+ this.relays = newRelayList;
- if (this._windowController) {
- IpcMainEventChannel.relays.notify(this._windowController.webContents, newRelayList);
+ if (this.windowController) {
+ IpcMainEventChannel.relays.notify(this.windowController.webContents, newRelayList);
}
- },
+ }
- _startRelaysPeriodicUpdates() {
+ private startRelaysPeriodicUpdates() {
log.debug('Start relays periodic updates');
const handler = async () => {
try {
- this._setRelays(await this._daemonRpc.getRelayLocations());
+ this.setRelays(await this.daemonRpc.getRelayLocations());
} catch (error) {
log.error(`Failed to fetch relay locations: ${error.message}`);
}
};
- this._relaysInterval = setInterval(handler, RELAY_LIST_UPDATE_INTERVAL);
- },
+ this.relaysInterval = setInterval(handler, RELAY_LIST_UPDATE_INTERVAL);
+ }
- _stopRelaysPeriodicUpdates() {
- if (this._relaysInterval) {
- clearInterval(this._relaysInterval);
- this._relaysInterval = undefined;
+ private stopRelaysPeriodicUpdates() {
+ if (this.relaysInterval) {
+ clearInterval(this.relaysInterval);
+ this.relaysInterval = undefined;
log.debug('Stop relays periodic updates');
}
- },
+ }
- _setDaemonVersion(daemonVersion: string) {
+ private setDaemonVersion(daemonVersion: string) {
const guiVersion = app.getVersion().replace('.0', '');
const versionInfo = {
daemon: daemonVersion,
@@ -562,15 +560,15 @@ const ApplicationMain = {
isConsistent: daemonVersion === guiVersion,
};
- this._currentVersion = versionInfo;
+ this.currentVersion = versionInfo;
// notify renderer
- if (this._windowController) {
- IpcMainEventChannel.currentVersion.notify(this._windowController.webContents, versionInfo);
+ if (this.windowController) {
+ IpcMainEventChannel.currentVersion.notify(this.windowController.webContents, versionInfo);
}
- },
+ }
- _setLatestVersion(latestVersionInfo: AppVersionInfo) {
+ private setLatestVersion(latestVersionInfo: IAppVersionInfo) {
function isBeta(version: string) {
return version.includes('-');
}
@@ -597,7 +595,7 @@ const ApplicationMain = {
}
}
- const currentVersionInfo = this._currentVersion;
+ const currentVersionInfo = this.currentVersion;
const latestVersion = latestVersionInfo.latest.latest;
const latestStableVersion = latestVersionInfo.latest.latestStable;
@@ -616,64 +614,64 @@ const ApplicationMain = {
upToDate: isUpToDate,
};
- this._upgradeVersion = upgradeInfo;
+ this.upgradeVersion = upgradeInfo;
// notify user to update the app if it became unsupported
if (
process.env.NODE_ENV !== 'development' &&
- !this._shouldSuppressNotifications() &&
+ !this.shouldSuppressNotifications() &&
currentVersionInfo.isConsistent &&
!latestVersionInfo.currentIsSupported &&
upgradeVersion
) {
- this._notificationController.notifyUnsupportedVersion(upgradeVersion);
+ this.notificationController.notifyUnsupportedVersion(upgradeVersion);
}
- if (this._windowController) {
- IpcMainEventChannel.upgradeVersion.notify(this._windowController.webContents, upgradeInfo);
+ if (this.windowController) {
+ IpcMainEventChannel.upgradeVersion.notify(this.windowController.webContents, upgradeInfo);
}
- },
+ }
- async _fetchLatestVersion() {
+ private async fetchLatestVersion() {
try {
- this._setLatestVersion(await this._daemonRpc.getVersionInfo());
+ this.setLatestVersion(await this.daemonRpc.getVersionInfo());
} catch (error) {
- console.error(`Failed to request the version info: ${error.message}`);
+ log.error(`Failed to request the version info: ${error.message}`);
}
- },
+ }
- _startLatestVersionPeriodicUpdates() {
+ private startLatestVersionPeriodicUpdates() {
const handler = () => {
- this._fetchLatestVersion();
+ this.fetchLatestVersion();
};
- this._latestVersionInterval = setInterval(handler, VERSION_UPDATE_INTERVAL);
- },
+ this.latestVersionInterval = setInterval(handler, VERSION_UPDATE_INTERVAL);
+ }
- _stopLatestVersionPeriodicUpdates() {
- if (this._latestVersionInterval) {
- clearInterval(this._latestVersionInterval);
+ private stopLatestVersionPeriodicUpdates() {
+ if (this.latestVersionInterval) {
+ clearInterval(this.latestVersionInterval);
- this._latestVersionInterval = undefined;
+ this.latestVersionInterval = undefined;
}
- },
+ }
- _shouldSuppressNotifications(): boolean {
- return this._windowController ? this._windowController.isVisible() : false;
- },
+ private shouldSuppressNotifications(): boolean {
+ return this.windowController ? this.windowController.isVisible() : false;
+ }
- async _updateLocation() {
- const state = this._tunnelState.state;
+ private async updateLocation() {
+ const state = this.tunnelState.state;
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 (state === 'disconnected' && this.lastDisconnectedLocation) {
+ this.setLocation(this.lastDisconnectedLocation);
}
// Fetch the new user location
- const location = await this._daemonRpc.getLocation();
+ const location = await this.daemonRpc.getLocation();
// If the location is currently unavailable, do nothing! This only ever
// happens when a custom relay is set or we are in a blocked state.
if (!location) {
@@ -683,22 +681,25 @@ const ApplicationMain = {
// Cache the user location
// Note: hostname is only set for relay servers.
if (location.hostname === null) {
- this._lastDisconnectedLocation = location;
+ this.lastDisconnectedLocation = location;
}
// 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) {
- this._setLocation(location);
+ if (this.tunnelState.state === state) {
+ this.setLocation(location);
}
} catch (error) {
log.error(`Failed to update the location: ${error.message}`);
}
}
- },
+ }
- _trayIconType(tunnelState: TunnelStateTransition, blockWhenDisconnected: boolean): TrayIconType {
+ private trayIconType(
+ tunnelState: TunnelStateTransition,
+ blockWhenDisconnected: boolean,
+ ): TrayIconType {
switch (tunnelState.state) {
case 'connected':
return 'secured';
@@ -724,130 +725,127 @@ const ApplicationMain = {
return 'unsecured';
}
}
- },
+ }
- _updateTrayIcon(tunnelState: TunnelStateTransition, blockWhenDisconnected: boolean) {
- const type = this._trayIconType(tunnelState, blockWhenDisconnected);
+ private updateTrayIcon(tunnelState: TunnelStateTransition, blockWhenDisconnected: boolean) {
+ const type = this.trayIconType(tunnelState, blockWhenDisconnected);
- if (this._trayIconController) {
- this._trayIconController.animateToIcon(type);
+ if (this.trayIconController) {
+ this.trayIconController.animateToIcon(type);
}
- },
+ }
- _registerWindowListener(windowController: WindowController) {
+ private registerWindowListener(windowController: WindowController) {
windowController.window.on('show', () => {
// cancel notifications when window appears
- this._notificationController.cancelPendingNotifications();
+ this.notificationController.cancelPendingNotifications();
windowController.send('window-shown');
});
- },
+ }
- _registerIpcListeners() {
+ private registerIpcListeners() {
IpcMainEventChannel.state.handleGet(() => ({
- isConnected: this._connectedToDaemon,
+ isConnected: this.connectedToDaemon,
autoStart: getOpenAtLogin(),
- tunnelState: this._tunnelState,
- settings: this._settings,
- location: this._location,
- relays: this._relays,
- currentVersion: this._currentVersion,
- upgradeVersion: this._upgradeVersion,
- guiSettings: this._guiSettings.state,
+ tunnelState: this.tunnelState,
+ settings: this.settings,
+ location: this.location,
+ relays: this.relays,
+ currentVersion: this.currentVersion,
+ upgradeVersion: this.upgradeVersion,
+ guiSettings: this.guiSettings.state,
}));
IpcMainEventChannel.settings.handleAllowLan((allowLan: boolean) =>
- this._daemonRpc.setAllowLan(allowLan),
+ this.daemonRpc.setAllowLan(allowLan),
);
IpcMainEventChannel.settings.handleEnableIpv6((enableIpv6: boolean) =>
- this._daemonRpc.setEnableIpv6(enableIpv6),
+ this.daemonRpc.setEnableIpv6(enableIpv6),
);
IpcMainEventChannel.settings.handleBlockWhenDisconnected((blockWhenDisconnected: boolean) =>
- this._daemonRpc.setBlockWhenDisconnected(blockWhenDisconnected),
+ this.daemonRpc.setBlockWhenDisconnected(blockWhenDisconnected),
);
IpcMainEventChannel.settings.handleOpenVpnMssfix((mssfix?: number) =>
- this._daemonRpc.setOpenVpnMssfix(mssfix),
+ this.daemonRpc.setOpenVpnMssfix(mssfix),
);
IpcMainEventChannel.settings.handleUpdateRelaySettings((update: RelaySettingsUpdate) =>
- this._daemonRpc.updateRelaySettings(update),
+ this.daemonRpc.updateRelaySettings(update),
);
IpcMainEventChannel.autoStart.handleSet((autoStart: boolean) => {
- return this._setAutoStart(autoStart);
+ return this.setAutoStart(autoStart);
});
- IpcMainEventChannel.tunnel.handleConnect(() => this._daemonRpc.connectTunnel());
- IpcMainEventChannel.tunnel.handleDisconnect(() => this._daemonRpc.disconnectTunnel());
+ IpcMainEventChannel.tunnel.handleConnect(() => this.daemonRpc.connectTunnel());
+ IpcMainEventChannel.tunnel.handleDisconnect(() => this.daemonRpc.disconnectTunnel());
IpcMainEventChannel.guiSettings.handleAutoConnect((autoConnect: boolean) => {
- this._guiSettings.autoConnect = autoConnect;
+ this.guiSettings.autoConnect = autoConnect;
});
IpcMainEventChannel.guiSettings.handleStartMinimized((startMinimized: boolean) => {
- this._guiSettings.startMinimized = startMinimized;
+ this.guiSettings.startMinimized = startMinimized;
});
IpcMainEventChannel.guiSettings.handleMonochromaticIcon((monochromaticIcon: boolean) => {
- this._guiSettings.monochromaticIcon = monochromaticIcon;
+ this.guiSettings.monochromaticIcon = monochromaticIcon;
});
IpcMainEventChannel.account.handleSet((token: AccountToken) =>
- this._daemonRpc.setAccount(token),
+ this.daemonRpc.setAccount(token),
);
- IpcMainEventChannel.account.handleUnset(() => this._daemonRpc.setAccount());
+ IpcMainEventChannel.account.handleUnset(() => this.daemonRpc.setAccount());
IpcMainEventChannel.account.handleGetData((token: AccountToken) =>
- this._daemonRpc.getAccountData(token),
+ this.daemonRpc.getAccountData(token),
);
- IpcMainEventChannel.accountHistory.handleGet(() => this._daemonRpc.getAccountHistory());
+ IpcMainEventChannel.accountHistory.handleGet(() => this.daemonRpc.getAccountHistory());
IpcMainEventChannel.accountHistory.handleRemoveItem((token: AccountToken) =>
- this._daemonRpc.removeAccountFromHistory(token),
+ this.daemonRpc.removeAccountFromHistory(token),
);
ipcMain.on('show-window', () => {
- const windowController = this._windowController;
+ const windowController = this.windowController;
if (windowController) {
windowController.show();
}
});
- ipcMain.on(
- 'collect-logs',
- (event: Electron.Event, requestId: string, toRedact: Array<string>) => {
- const reportPath = path.join(app.getPath('temp'), uuid.v4() + '.log');
- const executable = resolveBin('problem-report');
- const args = ['collect', '--output', reportPath];
- if (toRedact.length > 0) {
- args.push('--redact', ...toRedact, '--');
- }
- args.push(this._logFilePath);
- if (this._oldLogFilePath) {
- args.push(this._oldLogFilePath);
- }
+ ipcMain.on('collect-logs', (event: Electron.Event, requestId: string, toRedact: string[]) => {
+ const reportPath = path.join(app.getPath('temp'), uuid.v4() + '.log');
+ const executable = resolveBin('problem-report');
+ const args = ['collect', '--output', reportPath];
+ if (toRedact.length > 0) {
+ args.push('--redact', ...toRedact, '--');
+ }
+ args.push(this.logFilePath);
+ if (this.oldLogFilePath) {
+ args.push(this.oldLogFilePath);
+ }
- execFile(executable, args, { windowsHide: true }, (error, stdout, stderr) => {
- if (error) {
- log.error(
- `Failed to collect a problem report: ${error.message}
+ execFile(executable, args, { windowsHide: true }, (error, stdout, stderr) => {
+ if (error) {
+ log.error(
+ `Failed to collect a problem report: ${error.message}
Stdout: ${stdout.toString()}
Stderr: ${stderr.toString()}`,
- );
+ );
- event.sender.send('collect-logs-reply', requestId, {
- success: false,
- error: error.message,
- });
- } else {
- log.debug(`Problem report was written to ${reportPath}`);
+ event.sender.send('collect-logs-reply', requestId, {
+ success: false,
+ error: error.message,
+ });
+ } else {
+ log.debug(`Problem report was written to ${reportPath}`);
- event.sender.send('collect-logs-reply', requestId, {
- success: true,
- reportPath,
- });
- }
- });
- },
- );
+ event.sender.send('collect-logs-reply', requestId, {
+ success: true,
+ reportPath,
+ });
+ }
+ });
+ });
ipcMain.on(
'send-problem-report',
@@ -883,33 +881,33 @@ const ApplicationMain = {
});
},
);
- },
+ }
- _updateDaemonsAutoConnect() {
- const daemonAutoConnect = this._guiSettings.autoConnect && getOpenAtLogin();
- if (daemonAutoConnect !== this._settings.autoConnect) {
- this._daemonRpc.setAutoConnect(daemonAutoConnect);
+ private updateDaemonsAutoConnect() {
+ const daemonAutoConnect = this.guiSettings.autoConnect && getOpenAtLogin();
+ if (daemonAutoConnect !== this.settings.autoConnect) {
+ this.daemonRpc.setAutoConnect(daemonAutoConnect);
}
- },
+ }
- async _setAutoStart(autoStart: boolean): Promise<void> {
+ private async setAutoStart(autoStart: boolean): Promise<void> {
try {
await setOpenAtLogin(autoStart);
- if (this._windowController) {
- IpcMainEventChannel.autoStart.notify(this._windowController.webContents, autoStart);
+ if (this.windowController) {
+ IpcMainEventChannel.autoStart.notify(this.windowController.webContents, autoStart);
}
- this._updateDaemonsAutoConnect();
+ this.updateDaemonsAutoConnect();
} catch (error) {
log.error(
`Failed to update the autostart to ${autoStart.toString()}. ${error.message.toString()}`,
);
}
return Promise.resolve();
- },
+ }
- async _installDevTools() {
+ private async installDevTools() {
const installer = require('electron-devtools-installer');
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
@@ -920,9 +918,9 @@ const ApplicationMain = {
log.info(`Error installing ${name} extension: ${e.message}`);
}
}
- },
+ }
- _createWindow(): BrowserWindow {
+ private createWindow(): BrowserWindow {
const contentHeight = 568;
// the size of transparent area around arrow on macOS
@@ -967,9 +965,9 @@ const ApplicationMain = {
default:
return new BrowserWindow(options);
}
- },
+ }
- _setAppMenu() {
+ private setAppMenu() {
const template: Electron.MenuItemConstructorOptions[] = [
{
label: 'Mullvad',
@@ -987,9 +985,9 @@ const ApplicationMain = {
},
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
- },
+ }
- _addContextMenu(window: BrowserWindow) {
+ private addContextMenu(window: BrowserWindow) {
const menuTemplate: Electron.MenuItemConstructorOptions[] = [
{ role: 'cut' },
{ role: 'copy' },
@@ -1031,9 +1029,9 @@ const ApplicationMain = {
}
},
);
- },
+ }
- _createTray(): Tray {
+ private createTray(): Tray {
const tray = new Tray(nativeImage.createEmpty());
tray.setToolTip('Mullvad VPN');
@@ -1046,9 +1044,9 @@ const ApplicationMain = {
}
return tray;
- },
+ }
- _installWindowsMenubarAppWindowHandlers(tray: Tray, windowController: WindowController) {
+ private installWindowsMenubarAppWindowHandlers(tray: Tray, windowController: WindowController) {
tray.on('click', () => windowController.toggle());
tray.on('right-click', () => windowController.hide());
@@ -1065,49 +1063,51 @@ const ApplicationMain = {
windowController.hide();
}
});
- },
+ }
// setup NSEvent monitor to fix inconsistent window.blur on macOS
// see https://github.com/electron/electron/issues/8689
- _installMacOsMenubarAppWindowHandlers(tray: Tray, windowController: WindowController) {
+ private installMacOsMenubarAppWindowHandlers(tray: Tray, windowController: WindowController) {
// $FlowFixMe: this module is only available on macOS
const { NSEventMonitor, NSEventMask } = require('nseventmonitor');
const macEventMonitor = new NSEventMonitor();
+ // tslint:disable-next-line
const eventMask = NSEventMask.leftMouseDown | NSEventMask.rightMouseDown;
const window = windowController.window;
window.on('show', () => macEventMonitor.start(eventMask, () => windowController.hide()));
window.on('hide', () => macEventMonitor.stop());
tray.on('click', () => windowController.toggle());
- },
+ }
- _installGenericMenubarAppWindowHandlers(tray: Tray, windowController: WindowController) {
+ private installGenericMenubarAppWindowHandlers(tray: Tray, windowController: WindowController) {
tray.on('click', () => {
windowController.toggle();
});
- },
+ }
- _installLinuxWindowCloseHandler(windowController: WindowController) {
+ private installLinuxWindowCloseHandler(windowController: WindowController) {
windowController.window.on('close', (closeEvent: Event) => {
- if (process.platform === 'linux' && this._quitStage !== 'ready') {
+ if (process.platform === 'linux' && this.quitStage !== AppQuitStage.ready) {
closeEvent.preventDefault();
windowController.hide();
}
});
- },
+ }
- _shouldShowWindowOnStart(): boolean {
+ private shouldShowWindowOnStart(): boolean {
switch (process.platform) {
case 'win32':
return false;
case 'darwin':
return false;
case 'linux':
- return !this._guiSettings.startMinimized;
+ return !this.guiSettings.startMinimized;
default:
return true;
}
- },
-};
+ }
+}
-ApplicationMain.run();
+const applicationMain = new ApplicationMain();
+applicationMain.run();
diff --git a/gui/packages/desktop/src/main/jsonrpc-client.ts b/gui/packages/desktop/src/main/jsonrpc-client.ts
index 811c151fb5..2faf473c3e 100644
--- a/gui/packages/desktop/src/main/jsonrpc-client.ts
+++ b/gui/packages/desktop/src/main/jsonrpc-client.ts
@@ -1,19 +1,19 @@
import assert from 'assert';
-import { EventEmitter } from 'events';
import log from 'electron-log';
+import { EventEmitter } from 'events';
import jsonrpc from 'jsonrpc-lite';
-import * as uuid from 'uuid';
-import * as net from 'net';
import JSONStream from 'JSONStream';
+import * as net from 'net';
+import * as uuid from 'uuid';
-export type UnansweredRequest = {
+export interface IUnansweredRequest {
resolve: (value: any) => void;
reject: (value: any) => void;
timerId: NodeJS.Timeout;
- message: Object;
-};
+ message: object;
+}
-export type JsonRpcErrorResponse = {
+export interface IJsonRpcErrorResponse {
type: 'error';
payload: {
id: string;
@@ -22,8 +22,8 @@ export type JsonRpcErrorResponse = {
message: string;
};
};
-};
-export type JsonRpcNotification = {
+}
+export interface IJsonRpcNotification {
type: 'notification';
payload: {
method: string;
@@ -32,78 +32,56 @@ export type JsonRpcNotification = {
result: any;
};
};
-};
-export type JsonRpcSuccess = {
+}
+export interface IJsonRpcSuccess {
type: 'success';
payload: {
id: string;
result: any;
};
-};
-export type JsonRpcMessage = JsonRpcErrorResponse | JsonRpcNotification | JsonRpcSuccess;
+}
+export type JsonRpcMessage = IJsonRpcErrorResponse | IJsonRpcNotification | IJsonRpcSuccess;
export class RemoteError extends Error {
- _code: number;
- _details: string;
-
- constructor(code: number, details: string) {
- super(`Remote JSON-RPC error ${code}: ${details}`);
- this._code = code;
- this._details = details;
+ constructor(private codeValue: number, private detailsValue: string) {
+ super(`Remote JSON-RPC error ${codeValue}: ${detailsValue}`);
}
get code(): number {
- return this._code;
+ return this.codeValue;
}
get details(): string {
- return this._details;
+ return this.detailsValue;
}
}
export class TimeOutError extends Error {
- _jsonRpcMessage: Object;
-
- constructor(jsonRpcMessage: Object) {
+ constructor(private jsonRpcMessageValue: object) {
super('Request timed out');
-
- this._jsonRpcMessage = jsonRpcMessage;
}
- get jsonRpcMessage(): Object {
- return this._jsonRpcMessage;
+ get jsonRpcMessage(): object {
+ return this.jsonRpcMessageValue;
}
}
export class SubscriptionError extends Error {
- _reply: any;
-
- constructor(message: string, reply: any) {
- const replyString = JSON.stringify(reply);
-
- super(`${message}: ${replyString}`);
-
- this._reply = reply;
+ constructor(message: string, private replyValue: any) {
+ super(`${message}: ${JSON.stringify(replyValue)}`);
}
get reply(): any {
- return this._reply;
+ return this.replyValue;
}
}
export class WebSocketError extends Error {
- _code: number;
-
- constructor(code: number) {
- super(WebSocketError.reason(code));
- this._code = code;
- }
-
get code(): number {
- return this._code;
+ return this.codeValue;
}
- static reason(code: number): string {
+ private static reason(code: number): string {
switch (code) {
case 1006:
return 'Abnormal closure';
@@ -117,6 +95,9 @@ export class WebSocketError extends Error {
return `Unknown (${code})`;
}
}
+ constructor(private codeValue: number) {
+ super(WebSocketError.reason(codeValue));
+ }
}
export class TransportError extends Error {}
@@ -124,18 +105,18 @@ export class TransportError extends Error {}
const DEFAULT_TIMEOUT_MILLIS = 5000;
export default class JsonRpcClient<T> extends EventEmitter {
- _unansweredRequests: Map<string, UnansweredRequest> = new Map();
- _subscriptions: Map<string | number, (value: any) => void> = new Map();
- _transport: Transport<T>;
+ private unansweredRequests: Map<string, IUnansweredRequest> = new Map();
+ private subscriptions: Map<string | number, (value: any) => void> = new Map();
+ private transport: ITransport<T>;
- constructor(transport: Transport<T>) {
+ constructor(transport: ITransport<T>) {
super();
- this._transport = transport;
+ this.transport = transport;
}
/// Connect websocket
- connect(connectionParams: T): Promise<void> {
+ public connect(connectionParams: T): Promise<void> {
return new Promise((resolve, reject) => {
this.disconnect();
@@ -144,10 +125,10 @@ export default class JsonRpcClient<T> extends EventEmitter {
// A flag used to determine if Promise was resolved.
let isPromiseResolved = false;
- const transport = this._transport;
+ const transport = this.transport;
transport.onOpen = () => {
- log.info('Transport is connected');
+ log.info('ITransport is connected');
this.emit('open');
// Resolve the Promise
@@ -156,12 +137,12 @@ export default class JsonRpcClient<T> extends EventEmitter {
};
transport.onMessage = (obj) => {
- this._onMessage(obj);
+ this.onMessage(obj);
};
transport.onClose = (error?: Error) => {
// Remove all subscriptions since they are connection based
- this._subscriptions.clear();
+ this.subscriptions.clear();
this.emit('close', error);
@@ -172,23 +153,23 @@ export default class JsonRpcClient<T> extends EventEmitter {
};
transport.connect(connectionParams);
- this._transport = transport;
+ this.transport = transport;
});
}
- disconnect() {
- if (this._transport) {
- this._transport.close();
+ public disconnect() {
+ if (this.transport) {
+ this.transport.close();
}
}
- async subscribe(event: string, listener: (value: any) => void): Promise<void> {
+ public async subscribe(event: string, listener: (value: any) => void): Promise<void> {
log.silly(`Adding a listener for ${event}`);
try {
const subscriptionId = await this.send(`${event}_subscribe`);
if (typeof subscriptionId === 'string' || typeof subscriptionId === 'number') {
- this._subscriptions.set(subscriptionId, listener);
+ this.subscriptions.set(subscriptionId, listener);
} else {
throw new SubscriptionError(
'The subscription id was not a string or a number',
@@ -201,19 +182,19 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
}
- send(action: string, data?: any, timeout: number = DEFAULT_TIMEOUT_MILLIS): Promise<any> {
+ public send(action: string, data?: any, timeout: number = DEFAULT_TIMEOUT_MILLIS): Promise<any> {
return new Promise((resolve, reject) => {
- const transport = this._transport;
+ const transport = this.transport;
if (!transport) {
reject(new Error('RPC client transport is not connected.'));
return;
}
const id = uuid.v4();
- const payload = this._prepareParams(data);
- const timerId = setTimeout(() => this._onTimeout(id), timeout);
+ const payload = this.prepareParams(data);
+ const timerId = setTimeout(() => this.onTimeout(id), timeout);
const message = jsonrpc.request(id, action, payload);
- this._unansweredRequests.set(id, {
+ this.unansweredRequests.set(id, {
resolve,
reject,
timerId,
@@ -227,7 +208,7 @@ export default class JsonRpcClient<T> extends EventEmitter {
log.error(`Failed sending RPC message "${action}": ${error.message}`);
// clean up on error
- this._unansweredRequests.delete(id);
+ this.unansweredRequests.delete(id);
clearTimeout(timerId);
throw error;
@@ -235,7 +216,7 @@ export default class JsonRpcClient<T> extends EventEmitter {
});
}
- _prepareParams(data?: any): Array<any> | Object {
+ private prepareParams(data?: any): any[] | object {
// JSONRPC only accepts arrays and objects as params, but
// this isn't very nice to use, so this method wraps other
// types in an array. The choice of array is based on try-and-error
@@ -251,10 +232,10 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
}
- _onTimeout(requestId: string) {
- const request = this._unansweredRequests.get(requestId);
+ private onTimeout(requestId: string) {
+ const request = this.unansweredRequests.get(requestId);
- this._unansweredRequests.delete(requestId);
+ this.unansweredRequests.delete(requestId);
if (request) {
log.warn(`Request ${requestId} timed out: `, request.message);
@@ -264,29 +245,25 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
}
- _onMessage(obj: Object) {
- let messages: Array<any> = [];
+ private onMessage(obj: object) {
+ let message: any;
try {
- // TODO: Fix the type weirdness.
// @ts-ignore
- const message = jsonrpc.parseObject(obj);
- messages = Array.isArray(message) ? message : [message];
+ message = jsonrpc.parseObject(obj);
} catch (error) {
log.error(`Failed to parse JSON-RPC message: ${error} for object`);
}
- for (const message of messages) {
- if (message.type === 'notification') {
- this._onNotification(message);
- } else {
- this._onReply(message);
- }
+ if (message.type === 'notification') {
+ this.onNotification(message);
+ } else {
+ this.onReply(message);
}
}
- _onNotification(message: JsonRpcNotification) {
+ private onNotification(message: IJsonRpcNotification) {
const subscriptionId = message.payload.params.subscription;
- const listener = this._subscriptions.get(subscriptionId);
+ const listener = this.subscriptions.get(subscriptionId);
if (listener) {
log.silly(`Got notification for ${message.payload.method}`);
@@ -296,10 +273,10 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
}
- _onReply(message: JsonRpcErrorResponse | JsonRpcSuccess) {
+ private onReply(message: IJsonRpcErrorResponse | IJsonRpcSuccess) {
const id = message.payload.id;
- const request = this._unansweredRequests.get(id);
- this._unansweredRequests.delete(id);
+ const request = this.unansweredRequests.get(id);
+ this.unansweredRequests.delete(id);
if (request) {
log.silly('Got answer to', id, message.type);
@@ -319,36 +296,44 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
}
-interface Transport<T> {
- close(): void;
+interface ITransport<T> {
onOpen: () => void;
- onMessage: (data: Object) => void;
+ onMessage: (data: object) => void;
onClose: (error?: Error) => void;
+ close(): void;
send(message: string): void;
connect(params: T): void;
}
-export class WebsocketTransport implements Transport<string> {
- ws?: WebSocket;
- onOpen = () => {};
- onMessage = (_message: Object) => {};
- onClose = (_error?: Error) => {};
+export class WebsocketTransport implements ITransport<string> {
+ public ws?: WebSocket;
constructor(ws?: WebSocket) {
this.ws = ws;
}
+ public onOpen = () => {
+ // no-op
+ };
+ public onMessage = (_message: object) => {
+ // no-op
+ };
+ public onClose = (_error?: Error) => {
+ // no-op
+ };
- close() {
- if (this.ws) this.ws.close();
+ public close() {
+ if (this.ws) {
+ this.ws.close();
+ }
}
- send(msg: string) {
+ public send(msg: string) {
if (this.ws) {
this.ws.send(msg);
}
}
- connect(params: string): void {
+ public connect(params: string): void {
if (this.ws) {
this.ws.close();
}
@@ -383,32 +368,37 @@ export class WebsocketTransport implements Transport<string> {
// Given the correct parameters, this transport supports named pipes/unix
// domain sockets, and also TCP/UDP sockets
-export class SocketTransport implements Transport<{ path: string }> {
- onMessage = (_message: Object) => {};
- onClose = (_error?: Error) => {};
- onOpen = () => {};
-
- _connection?: net.Socket;
- _socketReady = false;
- _shouldClose = false;
- _lastError?: Error;
+export class SocketTransport implements ITransport<{ path: string }> {
+ private connection?: net.Socket;
+ private socketReady = false;
+ private shouldClose = false;
+ private lastError?: Error;
+ public onMessage = (_message: object) => {
+ // no-op
+ };
+ public onClose = (_error?: Error) => {
+ // no-op
+ };
+ public onOpen = () => {
+ // no-op
+ };
- connect(options: { path: string }) {
- assert(!this._connection, 'Make sure to close the existing socket');
+ public connect(options: { path: string }) {
+ assert(!this.connection, 'Make sure to close the existing socket');
const jsonStream = JSONStream.parse(null)
- .on('data', this._onJsonStreamData)
- .on('error', this._onJsonStreamError);
+ .on('data', this.onJsonStreamData)
+ .on('error', this.onJsonStreamError);
const connection = new net.Socket()
- .on('ready', this._onSocketReady)
- .on('error', this._onSocketError)
- .on('close', this._onSocketClose);
+ .on('ready', this.onSocketReady)
+ .on('error', this.onSocketError)
+ .on('close', this.onSocketClose);
- this._connection = connection;
- this._socketReady = false;
- this._shouldClose = false;
- this._lastError = undefined;
+ this.connection = connection;
+ this.socketReady = false;
+ this.shouldClose = false;
+ this.lastError = undefined;
log.debug('Connect socket');
@@ -416,51 +406,51 @@ export class SocketTransport implements Transport<{ path: string }> {
connection.connect(options);
}
- close() {
- this._shouldClose = true;
+ public close() {
+ this.shouldClose = true;
try {
- if (this._connection) {
- this._connection.end();
+ if (this.connection) {
+ this.connection.end();
}
} catch (error) {
log.error('Failed to close the socket: ', error);
}
- this._connection = undefined;
+ this.connection = undefined;
}
- send(msg: string) {
- if (this._socketReady && this._connection) {
- this._connection.write(msg);
+ public send(msg: string) {
+ if (this.socketReady && this.connection) {
+ this.connection.write(msg);
} else {
throw new TransportError('Socket not connected');
}
}
- _onSocketReady = () => {
- this._socketReady = true;
+ private onSocketReady = () => {
+ this.socketReady = true;
log.debug('Socket is ready');
this.onOpen();
};
- _onSocketError = (error: Error) => {
- this._lastError = error;
+ private onSocketError = (error: Error) => {
+ this.lastError = error;
log.error('Socket error: ', error);
};
- _onSocketClose = (hadError: boolean) => {
- if (this._shouldClose) {
+ private onSocketClose = (hadError: boolean) => {
+ if (this.shouldClose) {
log.debug(`Socket was closed deliberately`);
this.onClose();
} else if (hadError) {
log.debug(`Socket was closed due to an error`);
- this.onClose(this._lastError);
+ this.onClose(this.lastError);
} else {
log.debug(`Socket was closed by peer`);
@@ -468,16 +458,16 @@ export class SocketTransport implements Transport<{ path: string }> {
}
};
- _onJsonStreamData = (data: Object) => {
+ private onJsonStreamData = (data: object) => {
this.onMessage(data);
};
- _onJsonStreamError = (error: Error) => {
+ private onJsonStreamError = (error: Error) => {
log.error('Socket JSON stream error: ', error);
- if (this._connection) {
+ if (this.connection) {
// This will destroy the socket and emit "error" and "close" events
- this._connection.destroy(error);
+ this.connection.destroy(error);
}
};
}
diff --git a/gui/packages/desktop/src/main/keyframe-animation.ts b/gui/packages/desktop/src/main/keyframe-animation.ts
index aad9a3d24f..6e405c6ef7 100644
--- a/gui/packages/desktop/src/main/keyframe-animation.ts
+++ b/gui/packages/desktop/src/main/keyframe-animation.ts
@@ -1,129 +1,130 @@
export type OnFrameFn = (frame: number) => void;
export type OnFinishFn = () => void;
-export type KeyframeAnimationOptions = {
+
+export interface IKeyframeAnimationOptions {
start?: number;
end: number;
-};
+}
export type KeyframeAnimationRange = [number, number];
export default class KeyframeAnimation {
- _speed: number = 200; // ms
+ private speedValue: number = 200; // ms
- _onFrame?: OnFrameFn;
- _onFinish?: OnFinishFn;
+ private onFrameValue?: OnFrameFn;
+ private onFinishValue?: OnFinishFn;
- _currentFrame: number = 0;
- _targetFrame: number = 0;
+ private currentFrame: number = 0;
+ private targetFrame: number = 0;
- _isRunning: boolean = false;
- _isFinished: boolean = false;
+ private isRunningValue: boolean = false;
+ private isFinishedValue: boolean = false;
- _timeout?: NodeJS.Timeout;
+ private timeout?: NodeJS.Timeout;
set onFrame(newValue: OnFrameFn | undefined) {
- this._onFrame = newValue;
+ this.onFrameValue = newValue;
}
get onFrame(): OnFrameFn | undefined {
- return this._onFrame;
+ return this.onFrameValue;
}
// called when animation finished
set onFinish(newValue: OnFinishFn | undefined) {
- this._onFinish = newValue;
+ this.onFinishValue = newValue;
}
get onFinish(): OnFinishFn | undefined {
- return this._onFinish;
+ return this.onFinishValue;
}
// pace per frame in ms
set speed(newValue: number) {
- this._speed = newValue;
+ this.speedValue = newValue;
}
get speed(): number {
- return this._speed;
+ return this.speedValue;
}
get isRunning(): boolean {
- return this._isRunning;
+ return this.isRunningValue;
}
get isFinished(): boolean {
- return this._isFinished;
+ return this.isFinishedValue;
}
- play(options: KeyframeAnimationOptions) {
+ public play(options: IKeyframeAnimationOptions) {
const { start, end } = options;
if (start !== undefined) {
- this._currentFrame = start;
+ this.currentFrame = start;
}
- this._targetFrame = end;
+ this.targetFrame = end;
- this._isRunning = true;
- this._isFinished = false;
+ this.isRunningValue = true;
+ this.isFinishedValue = false;
- this._unscheduleUpdate();
+ this.unscheduleUpdate();
- this._render();
- this._scheduleUpdate();
+ this.render();
+ this.scheduleUpdate();
}
- stop() {
- this._isRunning = false;
- this._unscheduleUpdate();
+ public stop() {
+ this.isRunningValue = false;
+ this.unscheduleUpdate();
}
- _unscheduleUpdate() {
- if (this._timeout) {
- clearTimeout(this._timeout);
- this._timeout = undefined;
+ private unscheduleUpdate() {
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ this.timeout = undefined;
}
}
- _scheduleUpdate() {
- this._timeout = setTimeout(() => this._onUpdateFrame(), this._speed);
+ private scheduleUpdate() {
+ this.timeout = setTimeout(() => this.onUpdateFrame(), this.speedValue);
}
- _render() {
- if (this._onFrame) {
- this._onFrame(this._currentFrame);
+ private render() {
+ if (this.onFrameValue) {
+ this.onFrameValue(this.currentFrame);
}
}
- _didFinish() {
- this._isFinished = true;
- this._isRunning = false;
+ private didFinish() {
+ this.isFinishedValue = true;
+ this.isRunningValue = false;
- if (this._onFinish) {
- this._onFinish();
+ if (this.onFinishValue) {
+ this.onFinishValue();
}
}
- _onUpdateFrame() {
- this._advanceFrame();
+ private onUpdateFrame() {
+ this.advanceFrame();
- if (!this._isFinished) {
- this._render();
+ if (!this.isFinishedValue) {
+ this.render();
// check once again since onFrame() may stop animation
- if (this._isRunning) {
- this._scheduleUpdate();
+ if (this.isRunningValue) {
+ this.scheduleUpdate();
}
}
}
- _advanceFrame() {
- if (this._isFinished) {
+ private advanceFrame() {
+ if (this.isFinishedValue) {
return;
}
- if (this._currentFrame === this._targetFrame) {
- this._didFinish();
- } else if (this._currentFrame < this._targetFrame) {
- this._currentFrame += 1;
+ if (this.currentFrame === this.targetFrame) {
+ this.didFinish();
+ } else if (this.currentFrame < this.targetFrame) {
+ this.currentFrame += 1;
} else {
- this._currentFrame -= 1;
+ this.currentFrame -= 1;
}
}
}
diff --git a/gui/packages/desktop/src/main/notification-controller.ts b/gui/packages/desktop/src/main/notification-controller.ts
index 2825775e97..86f23a8b4f 100644
--- a/gui/packages/desktop/src/main/notification-controller.ts
+++ b/gui/packages/desktop/src/main/notification-controller.ts
@@ -1,34 +1,34 @@
-import { shell, Notification } from 'electron';
+import { Notification, shell } from 'electron';
import config from '../config.json';
import { TunnelStateTransition } from '../shared/daemon-rpc-types';
export default class NotificationController {
- _lastTunnelStateAnnouncement?: { body: string; notification: Notification };
- _reconnecting = false;
- _presentedNotifications: { [key: string]: boolean } = {};
- _pendingNotifications: Array<Notification> = [];
+ private lastTunnelStateAnnouncement?: { body: string; notification: Notification };
+ private reconnecting = false;
+ private presentedNotifications: { [key: string]: boolean } = {};
+ private pendingNotifications: Notification[] = [];
- notifyTunnelState(tunnelState: TunnelStateTransition) {
+ public notifyTunnelState(tunnelState: TunnelStateTransition) {
switch (tunnelState.state) {
case 'connecting':
- if (!this._reconnecting) {
- this._showTunnelStateNotification('Connecting');
+ if (!this.reconnecting) {
+ this.showTunnelStateNotification('Connecting');
}
break;
case 'connected':
- this._showTunnelStateNotification('Secured');
+ this.showTunnelStateNotification('Secured');
break;
case 'disconnected':
- this._showTunnelStateNotification('Unsecured');
+ this.showTunnelStateNotification('Unsecured');
break;
case 'blocked':
switch (tunnelState.details.reason) {
case 'set_firewall_policy_error':
- this._showTunnelStateNotification('Critical failure - Unsecured');
+ this.showTunnelStateNotification('Critical failure - Unsecured');
break;
default:
- this._showTunnelStateNotification('Blocked all connections');
+ this.showTunnelStateNotification('Blocked all connections');
break;
}
break;
@@ -39,29 +39,29 @@ export default class NotificationController {
// no-op
break;
case 'reconnect':
- this._showTunnelStateNotification('Reconnecting');
- this._reconnecting = true;
+ this.showTunnelStateNotification('Reconnecting');
+ this.reconnecting = true;
return;
}
break;
}
- this._reconnecting = false;
+ this.reconnecting = false;
}
- notifyInconsistentVersion() {
- this._presentNotificationOnce('inconsistent-version', () => {
+ public notifyInconsistentVersion() {
+ this.presentNotificationOnce('inconsistent-version', () => {
const notification = new Notification({
title: '',
body: 'Inconsistent internal version information, please restart the app',
silent: true,
});
- this._scheduleNotification(notification);
+ this.scheduleNotification(notification);
});
}
- notifyUnsupportedVersion(upgradeVersion: string) {
- this._presentNotificationOnce('unsupported-version', () => {
+ public notifyUnsupportedVersion(upgradeVersion: string) {
+ this.presentNotificationOnce('unsupported-version', () => {
const notification = new Notification({
title: '',
body: `You are running an unsupported app version. Please upgrade to ${upgradeVersion} now to ensure your security`,
@@ -72,18 +72,18 @@ export default class NotificationController {
shell.openExternal(config.links.download);
});
- this._scheduleNotification(notification);
+ this.scheduleNotification(notification);
});
}
- cancelPendingNotifications() {
- for (const notification of this._pendingNotifications) {
+ public cancelPendingNotifications() {
+ for (const notification of this.pendingNotifications) {
notification.close();
}
}
- _showTunnelStateNotification(message: string) {
- const lastAnnouncement = this._lastTunnelStateAnnouncement;
+ private showTunnelStateNotification(message: string) {
+ const lastAnnouncement = this.lastTunnelStateAnnouncement;
const sameAsLastNotification = lastAnnouncement && lastAnnouncement.body === message;
if (sameAsLastNotification) {
@@ -100,42 +100,42 @@ export default class NotificationController {
lastAnnouncement.notification.close();
}
- this._lastTunnelStateAnnouncement = {
+ this.lastTunnelStateAnnouncement = {
body: message,
notification: newNotification,
};
- this._scheduleNotification(newNotification);
+ this.scheduleNotification(newNotification);
}
- _presentNotificationOnce(notificationName: string, presentNotification: () => void) {
- const presented = this._presentedNotifications;
+ private presentNotificationOnce(notificationName: string, presentNotification: () => void) {
+ const presented = this.presentedNotifications;
if (!presented[notificationName]) {
presented[notificationName] = true;
presentNotification();
}
}
- _scheduleNotification(notification: Notification) {
- this._addPendingNotification(notification);
+ private scheduleNotification(notification: Notification) {
+ this.addPendingNotification(notification);
notification.show();
setTimeout(() => notification.close(), 4000);
}
- _addPendingNotification(notification: Notification) {
+ private addPendingNotification(notification: Notification) {
notification.on('close', () => {
- this._removePendingNotification(notification);
+ this.removePendingNotification(notification);
});
- this._pendingNotifications.push(notification);
+ this.pendingNotifications.push(notification);
}
- _removePendingNotification(notification: Notification) {
- const index = this._pendingNotifications.indexOf(notification);
+ private removePendingNotification(notification: Notification) {
+ const index = this.pendingNotifications.indexOf(notification);
if (index !== -1) {
- this._pendingNotifications.splice(index, 1);
+ this.pendingNotifications.splice(index, 1);
}
}
}
diff --git a/gui/packages/desktop/src/main/reconnection-backoff.ts b/gui/packages/desktop/src/main/reconnection-backoff.ts
index a9a2cd9c75..5709f053d7 100644
--- a/gui/packages/desktop/src/main/reconnection-backoff.ts
+++ b/gui/packages/desktop/src/main/reconnection-backoff.ts
@@ -3,20 +3,20 @@
* It uses a linear backoff function that goes from 500ms to 3000ms.
*/
export default class ReconnectionBackoff {
- _attempt = 0;
+ private attemptValue = 0;
- attempt(handler: () => void) {
- setTimeout(handler, this._getIncreasedBackoff());
+ public attempt(handler: () => void) {
+ setTimeout(handler, this.getIncreasedBackoff());
}
- reset() {
- this._attempt = 0;
+ public reset() {
+ this.attemptValue = 0;
}
- _getIncreasedBackoff() {
- if (this._attempt < 6) {
- this._attempt++;
+ private getIncreasedBackoff() {
+ if (this.attemptValue < 6) {
+ this.attemptValue++;
}
- return this._attempt * 500;
+ return this.attemptValue * 500;
}
}
diff --git a/gui/packages/desktop/src/main/tray-icon-controller.ts b/gui/packages/desktop/src/main/tray-icon-controller.ts
index 1a8c5b28c8..f3333a2636 100644
--- a/gui/packages/desktop/src/main/tray-icon-controller.ts
+++ b/gui/packages/desktop/src/main/tray-icon-controller.ts
@@ -1,78 +1,77 @@
+import { nativeImage, NativeImage, Tray } from 'electron';
import path from 'path';
import KeyframeAnimation from './keyframe-animation';
-import { nativeImage } from 'electron';
-import { NativeImage, Tray } from 'electron';
export type TrayIconType = 'unsecured' | 'securing' | 'secured';
export default class TrayIconController {
- _animation?: KeyframeAnimation;
- _iconType: TrayIconType;
- _iconImages: Array<NativeImage> = [];
- _monochromaticIconImages: Array<NativeImage> = [];
- _useMonochromaticIcon: boolean;
+ private animation?: KeyframeAnimation;
+ private iconImages: NativeImage[] = [];
+ private monochromaticIconImages: NativeImage[] = [];
- constructor(tray: Tray, initialType: TrayIconType, useMonochromaticIcon: boolean) {
- this._loadImages();
- this._iconType = initialType;
- this._useMonochromaticIcon = useMonochromaticIcon;
+ constructor(
+ tray: Tray,
+ private iconTypeValue: TrayIconType,
+ private useMonochromaticIconValue: boolean,
+ ) {
+ this.loadImages();
- const initialFrame = this._targetFrame();
+ const initialFrame = this.targetFrame();
const animation = new KeyframeAnimation();
animation.speed = 100;
- animation.onFrame = (frameNumber) => tray.setImage(this._imageForFrame(frameNumber));
+ animation.onFrame = (frameNumber) => tray.setImage(this.imageForFrame(frameNumber));
animation.play({ start: initialFrame, end: initialFrame });
- this._animation = animation;
+ this.animation = animation;
}
- dispose() {
- if (this._animation) {
- this._animation.stop();
- this._animation = undefined;
+ public dispose() {
+ if (this.animation) {
+ this.animation.stop();
+ this.animation = undefined;
}
}
get iconType(): TrayIconType {
- return this._iconType;
+ return this.iconTypeValue;
}
set useMonochromaticIcon(useMonochromaticIcon: boolean) {
- this._useMonochromaticIcon = useMonochromaticIcon;
+ this.useMonochromaticIconValue = useMonochromaticIcon;
- if (this._animation && !this._animation.isRunning) {
- this._animation.play({ end: this._targetFrame() });
+ if (this.animation && !this.animation.isRunning) {
+ this.animation.play({ end: this.targetFrame() });
}
}
- animateToIcon(type: TrayIconType) {
- if (this._iconType === type || !this._animation) {
+ public animateToIcon(type: TrayIconType) {
+ if (this.iconTypeValue === type || !this.animation) {
return;
}
- this._iconType = type;
+ this.iconTypeValue = type;
- const animation = this._animation;
- const frame = this._targetFrame();
+ const animation = this.animation;
+ const frame = this.targetFrame();
animation.play({ end: frame });
}
- _loadImages() {
+ private loadImages() {
const basePath = path.resolve(path.join(__dirname, '../../assets/images/menubar icons'));
const frames = Array.from({ length: 10 }, (_, i) => i + 1);
- this._iconImages = frames.map((frame) =>
+ this.iconImages = frames.map((frame) =>
nativeImage.createFromPath(path.join(basePath, `lock-${frame}.png`)),
);
- this._monochromaticIconImages = frames.map((frame) =>
+ this.monochromaticIconImages = frames.map((frame) =>
nativeImage.createFromPath(path.join(basePath, `lock-${frame}Template.png`)),
);
}
- _targetFrame(): number {
- switch (this._iconType) {
+ private targetFrame(): number {
+ switch (this.iconTypeValue) {
case 'unsecured':
return 0;
case 'securing':
@@ -82,9 +81,9 @@ export default class TrayIconController {
}
}
- _imageForFrame(frame: number): NativeImage {
- return this._useMonochromaticIcon
- ? this._monochromaticIconImages[frame]
- : this._iconImages[frame];
+ private imageForFrame(frame: number): NativeImage {
+ return this.useMonochromaticIconValue
+ ? this.monochromaticIconImages[frame]
+ : this.iconImages[frame];
}
}
diff --git a/gui/packages/desktop/src/main/window-controller.ts b/gui/packages/desktop/src/main/window-controller.ts
index e740f216ab..a0cbd77399 100644
--- a/gui/packages/desktop/src/main/window-controller.ts
+++ b/gui/packages/desktop/src/main/window-controller.ts
@@ -1,19 +1,21 @@
-import { screen } from 'electron';
-import { BrowserWindow, Tray, Display, WebContents } from 'electron';
+import { BrowserWindow, Display, screen, Tray, WebContents } from 'electron';
-type Position = { x: number; y: number };
+interface IPosition {
+ x: number;
+ y: number;
+}
-export type WindowShapeParameters = {
+export interface IWindowShapeParameters {
arrowPosition?: number;
-};
+}
-interface WindowPositioning {
- getPosition(window: BrowserWindow): Position;
- getWindowShapeParameters(window: BrowserWindow): WindowShapeParameters;
+interface IWindowPositioning {
+ getPosition(window: BrowserWindow): IPosition;
+ getWindowShapeParameters(window: BrowserWindow): IWindowShapeParameters;
}
-class StandaloneWindowPositioning implements WindowPositioning {
- getPosition(window: BrowserWindow): Position {
+class StandaloneWindowPositioning implements IWindowPositioning {
+ public getPosition(window: BrowserWindow): IPosition {
const windowBounds = window.getBounds();
const primaryDisplay = screen.getPrimaryDisplay();
@@ -27,33 +29,34 @@ class StandaloneWindowPositioning implements WindowPositioning {
return { x, y };
}
- getWindowShapeParameters(_window: BrowserWindow): WindowShapeParameters {
+ public getWindowShapeParameters(_window: BrowserWindow): IWindowShapeParameters {
return {};
}
}
-class AttachedToTrayWindowPositioning implements WindowPositioning {
- _tray: Tray;
+class AttachedToTrayWindowPositioning implements IWindowPositioning {
+ private tray: Tray;
constructor(tray: Tray) {
- this._tray = tray;
+ this.tray = tray;
}
- getPosition(window: BrowserWindow): Position {
+ public getPosition(window: BrowserWindow): IPosition {
const windowBounds = window.getBounds();
- const trayBounds = this._tray.getBounds();
+ const trayBounds = this.tray.getBounds();
const activeDisplay = screen.getDisplayNearestPoint({
x: trayBounds.x,
y: trayBounds.y,
});
const workArea = activeDisplay.workArea;
- const placement = this._getTrayPlacement();
+ const placement = this.getTrayPlacement();
const maxX = workArea.x + workArea.width - windowBounds.width;
const maxY = workArea.y + workArea.height - windowBounds.height;
- let x = 0,
- y = 0;
+ let x = 0;
+ let y = 0;
+
switch (placement) {
case 'top':
x = trayBounds.x + (trayBounds.width - windowBounds.width) * 0.5;
@@ -90,8 +93,8 @@ class AttachedToTrayWindowPositioning implements WindowPositioning {
};
}
- getWindowShapeParameters(window: BrowserWindow): WindowShapeParameters {
- const trayBounds = this._tray.getBounds();
+ public getWindowShapeParameters(window: BrowserWindow): IWindowShapeParameters {
+ const trayBounds = this.tray.getBounds();
const windowBounds = window.getBounds();
const arrowPosition = trayBounds.x - windowBounds.x + trayBounds.width * 0.5;
return {
@@ -99,7 +102,7 @@ class AttachedToTrayWindowPositioning implements WindowPositioning {
};
}
- _getTrayPlacement() {
+ private getTrayPlacement() {
switch (process.platform) {
case 'darwin':
// macOS has menubar always placed at the top
@@ -127,122 +130,118 @@ class AttachedToTrayWindowPositioning implements WindowPositioning {
}
export default class WindowController {
- _window: BrowserWindow;
- _width: number;
- _height: number;
- _windowPositioning: WindowPositioning;
- _isWindowReady = false;
+ private width: number;
+ private height: number;
+ private windowPositioning: IWindowPositioning;
+ private isWindowReady = false;
get window(): BrowserWindow {
- return this._window;
+ return this.windowValue;
}
get webContents(): WebContents {
- return this._window.webContents;
+ return this.windowValue.webContents;
}
- constructor(window: BrowserWindow, tray: Tray) {
- this._window = window;
- const [width, height] = window.getSize();
- this._width = width;
- this._height = height;
-
- if (process.platform === 'linux') {
- this._windowPositioning = new StandaloneWindowPositioning();
- } else {
- this._windowPositioning = new AttachedToTrayWindowPositioning(tray);
- }
+ constructor(private windowValue: BrowserWindow, tray: Tray) {
+ const [width, height] = windowValue.getSize();
+ this.width = width;
+ this.height = height;
+ this.windowPositioning =
+ process.platform === 'linux'
+ ? new StandaloneWindowPositioning()
+ : new AttachedToTrayWindowPositioning(tray);
- this._installDisplayMetricsHandler();
- this._installWindowReadyHandlers();
+ this.installDisplayMetricsHandler();
+ this.installWindowReadyHandlers();
}
- show(whenReady: boolean = true) {
+ public show(whenReady: boolean = true) {
if (whenReady) {
- this._executeWhenWindowIsReady(() => this._showImmediately());
+ this.executeWhenWindowIsReady(() => this.showImmediately());
} else {
- this._showImmediately();
+ this.showImmediately();
}
}
- hide() {
- this._window.hide();
+ public hide() {
+ this.windowValue.hide();
}
- toggle() {
- if (this._window.isVisible()) {
+ public toggle() {
+ if (this.windowValue.isVisible()) {
this.hide();
} else {
this.show();
}
}
- isVisible(): boolean {
- return this._window.isVisible();
+ public isVisible(): boolean {
+ return this.windowValue.isVisible();
}
- send(event: string, ...data: any[]): void {
- this._window.webContents.send(event, ...data);
+ public send(event: string, ...data: any[]): void {
+ this.windowValue.webContents.send(event, ...data);
}
- _showImmediately() {
- const window = this._window;
+ private showImmediately() {
+ const window = this.windowValue;
- this._updatePosition();
- this._notifyUpdateWindowShape();
+ this.updatePosition();
+ this.notifyUpdateWindowShape();
window.show();
window.focus();
}
- _updatePosition() {
- const { x, y } = this._windowPositioning.getPosition(this._window);
- this._window.setPosition(x, y, false);
+ private updatePosition() {
+ const { x, y } = this.windowPositioning.getPosition(this.windowValue);
+ this.windowValue.setPosition(x, y, false);
}
- _notifyUpdateWindowShape() {
- const shapeParameters = this._windowPositioning.getWindowShapeParameters(this._window);
- this._window.webContents.send('update-window-shape', shapeParameters);
+ private notifyUpdateWindowShape() {
+ const shapeParameters = this.windowPositioning.getWindowShapeParameters(this.windowValue);
+ this.windowValue.webContents.send('update-window-shape', shapeParameters);
}
// Installs display event handlers to update the window position on any changes in the display or
// workarea dimensions.
- _installDisplayMetricsHandler() {
- screen.addListener('display-metrics-changed', this._onDisplayMetricsChanged);
- this._window.once('closed', () => {
- screen.removeListener('display-metrics-changed', this._onDisplayMetricsChanged);
+ private installDisplayMetricsHandler() {
+ screen.addListener('display-metrics-changed', this.onDisplayMetricsChanged);
+ this.windowValue.once('closed', () => {
+ screen.removeListener('display-metrics-changed', this.onDisplayMetricsChanged);
});
}
- _onDisplayMetricsChanged = (_event: any, _display: Display, changedMetrics: Array<string>) => {
- if (changedMetrics.includes('workArea') && this._window.isVisible()) {
- this._updatePosition();
- this._notifyUpdateWindowShape();
+ private onDisplayMetricsChanged = (_event: any, _display: Display, changedMetrics: string[]) => {
+ if (changedMetrics.includes('workArea') && this.windowValue.isVisible()) {
+ this.updatePosition();
+ this.notifyUpdateWindowShape();
}
// On linux, the window won't be properly rescaled back to it's original
// size if the DPI scaling factor is changed.
// https://github.com/electron/electron/issues/11050
if (process.platform === 'linux' && changedMetrics.includes('scaleFactor')) {
- this._forceResizeWindow();
+ this.forceResizeWindow();
}
};
- _forceResizeWindow() {
- this._window.setSize(this._width, this._height);
+ private forceResizeWindow() {
+ this.windowValue.setSize(this.width, this.height);
}
- _installWindowReadyHandlers() {
- this._window.once('ready-to-show', () => {
- this._isWindowReady = true;
+ private installWindowReadyHandlers() {
+ this.windowValue.once('ready-to-show', () => {
+ this.isWindowReady = true;
});
}
- _executeWhenWindowIsReady(closure: () => any) {
- if (this._isWindowReady) {
+ private executeWhenWindowIsReady(closure: () => any) {
+ if (this.isWindowReady) {
closure();
} else {
- this._window.once('ready-to-show', () => {
+ this.windowValue.once('ready-to-show', () => {
closure();
});
}
diff --git a/gui/packages/desktop/src/renderer/app.tsx b/gui/packages/desktop/src/renderer/app.tsx
index 50c4773ca0..9ba305c7d0 100644
--- a/gui/packages/desktop/src/renderer/app.tsx
+++ b/gui/packages/desktop/src/renderer/app.tsx
@@ -1,67 +1,67 @@
-import log from 'electron-log';
-import { webFrame, ipcRenderer } from 'electron';
-import * as React from 'react';
-import { bindActionCreators } from 'redux';
-import { Provider } from 'react-redux';
import {
ConnectedRouter,
push as pushHistory,
replace as replaceHistory,
} from 'connected-react-router';
+import { ipcRenderer, webFrame } from 'electron';
+import log from 'electron-log';
import { createMemoryHistory } from 'history';
+import * as React from 'react';
+import { Provider } from 'react-redux';
+import { bindActionCreators } from 'redux';
import { InvalidAccountError } from '../main/errors';
import makeRoutes from './routes';
-import configureStore from './redux/store';
import accountActions from './redux/account/actions';
import connectionActions from './redux/connection/actions';
import settingsActions from './redux/settings/actions';
-import versionActions from './redux/version/actions';
+import configureStore from './redux/store';
import userInterfaceActions from './redux/userinterface/actions';
+import versionActions from './redux/version/actions';
-import { WindowShapeParameters } from '../main/window-controller';
-import { CurrentAppVersionInfo, AppUpgradeInfo } from '../main';
-import { GuiSettingsState } from '../shared/gui-settings-state';
+import { IAppUpgradeInfo, ICurrentAppVersionInfo } from '../main';
+import { IWindowShapeParameters } from '../main/window-controller';
+import { IGuiSettingsState } from '../shared/gui-settings-state';
import { IpcRendererEventChannel } from '../shared/ipc-event-channel';
import {
AccountToken,
- AccountData,
ConnectionConfig,
- Location,
- RelayList,
- RelaySettingsUpdate,
+ IAccountData,
+ ILocation,
+ IRelayList,
+ ISettings,
RelaySettings,
- Settings,
+ RelaySettingsUpdate,
TunnelStateTransition,
} from '../shared/daemon-rpc-types';
export default class AppRenderer {
- _memoryHistory = createMemoryHistory();
- _reduxStore = configureStore(null, this._memoryHistory);
- _reduxActions: { [key: string]: any };
- _accountDataCache = new AccountDataCache(
+ private memoryHistory = createMemoryHistory();
+ private reduxStore = configureStore(this.memoryHistory);
+ private reduxActions: { [key: string]: any };
+ private accountDataCache = new AccountDataCache(
(accountToken) => {
return IpcRendererEventChannel.account.getData(accountToken);
},
(accountData) => {
const expiry = accountData ? accountData.expiry : null;
- this._reduxActions.account.updateAccountExpiry(expiry);
+ this.reduxActions.account.updateAccountExpiry(expiry);
},
);
- _tunnelState: TunnelStateTransition;
- _settings: Settings;
- _guiSettings: GuiSettingsState;
- _connectedToDaemon = false;
- _autoConnected = false;
- _doingLogin = false;
- _loginTimer?: NodeJS.Timeout;
+ private tunnelState: TunnelStateTransition;
+ private settings: ISettings;
+ private guiSettings: IGuiSettingsState;
+ private connectedToDaemon = false;
+ private autoConnected = false;
+ private doingLogin = false;
+ private loginTimer?: NodeJS.Timeout;
constructor() {
- const dispatch = this._reduxStore.dispatch;
- this._reduxActions = {
+ const dispatch = this.reduxStore.dispatch;
+ this.reduxActions = {
account: bindActionCreators(accountActions, dispatch),
connection: bindActionCreators(connectionActions, dispatch),
settings: bindActionCreators(settingsActions, dispatch),
@@ -78,105 +78,105 @@ export default class AppRenderer {
ipcRenderer.on(
'update-window-shape',
- (_event: Electron.Event, shapeParams: WindowShapeParameters) => {
+ (_event: Electron.Event, shapeParams: IWindowShapeParameters) => {
if (typeof shapeParams.arrowPosition === 'number') {
- this._reduxActions.userInterface.updateWindowArrowPosition(shapeParams.arrowPosition);
+ this.reduxActions.userInterface.updateWindowArrowPosition(shapeParams.arrowPosition);
}
},
);
ipcRenderer.on('window-shown', () => {
- if (this._connectedToDaemon) {
+ if (this.connectedToDaemon) {
this.updateAccountExpiry();
}
});
IpcRendererEventChannel.daemonConnected.listen(() => {
- this._onDaemonConnected();
+ this.onDaemonConnected();
});
IpcRendererEventChannel.daemonDisconnected.listen((errorMessage?: string) => {
- this._onDaemonDisconnected(errorMessage ? new Error(errorMessage) : undefined);
+ this.onDaemonDisconnected(errorMessage ? new Error(errorMessage) : undefined);
});
IpcRendererEventChannel.tunnel.listen((newState: TunnelStateTransition) => {
- this._setTunnelState(newState);
+ this.setTunnelState(newState);
});
- IpcRendererEventChannel.settings.listen((newSettings: Settings) => {
- const oldSettings = this._settings;
+ IpcRendererEventChannel.settings.listen((newSettings: ISettings) => {
+ const oldSettings = this.settings;
- this._setSettings(newSettings);
- this._handleAccountChange(oldSettings.accountToken, newSettings.accountToken);
+ this.setSettings(newSettings);
+ this.handleAccountChange(oldSettings.accountToken, newSettings.accountToken);
});
- IpcRendererEventChannel.location.listen((newLocation: Location) => {
- this._setLocation(newLocation);
+ IpcRendererEventChannel.location.listen((newLocation: ILocation) => {
+ this.setLocation(newLocation);
});
- IpcRendererEventChannel.relays.listen((newRelays: RelayList) => {
- this._setRelays(newRelays);
+ IpcRendererEventChannel.relays.listen((newRelays: IRelayList) => {
+ this.setRelays(newRelays);
});
- IpcRendererEventChannel.currentVersion.listen((currentVersion: CurrentAppVersionInfo) => {
- this._setCurrentVersion(currentVersion);
+ IpcRendererEventChannel.currentVersion.listen((currentVersion: ICurrentAppVersionInfo) => {
+ this.setCurrentVersion(currentVersion);
});
- IpcRendererEventChannel.upgradeVersion.listen((upgradeVersion: AppUpgradeInfo) => {
- this._setUpgradeVersion(upgradeVersion);
+ IpcRendererEventChannel.upgradeVersion.listen((upgradeVersion: IAppUpgradeInfo) => {
+ this.setUpgradeVersion(upgradeVersion);
});
- IpcRendererEventChannel.guiSettings.listen((guiSettings: GuiSettingsState) => {
- this._setGuiSettings(guiSettings);
+ IpcRendererEventChannel.guiSettings.listen((guiSettings: IGuiSettingsState) => {
+ this.setGuiSettings(guiSettings);
});
IpcRendererEventChannel.autoStart.listen((autoStart: boolean) => {
- this._setAutoStart(autoStart);
+ this.storeAutoStart(autoStart);
});
// Request the initial state from the main process
const initialState = IpcRendererEventChannel.state.get();
- this._tunnelState = initialState.tunnelState;
- this._settings = initialState.settings;
- this._guiSettings = initialState.guiSettings;
+ this.tunnelState = initialState.tunnelState;
+ this.settings = initialState.settings;
+ this.guiSettings = initialState.guiSettings;
- this._setTunnelState(initialState.tunnelState);
- this._setSettings(initialState.settings);
+ this.setTunnelState(initialState.tunnelState);
+ this.setSettings(initialState.settings);
if (initialState.location) {
- this._setLocation(initialState.location);
+ this.setLocation(initialState.location);
}
- this._setRelays(initialState.relays);
- this._setCurrentVersion(initialState.currentVersion);
- this._setUpgradeVersion(initialState.upgradeVersion);
- this._setGuiSettings(initialState.guiSettings);
- this._setAutoStart(initialState.autoStart);
+ this.setRelays(initialState.relays);
+ this.setCurrentVersion(initialState.currentVersion);
+ this.setUpgradeVersion(initialState.upgradeVersion);
+ this.setGuiSettings(initialState.guiSettings);
+ this.storeAutoStart(initialState.autoStart);
if (initialState.isConnected) {
- this._onDaemonConnected();
+ this.onDaemonConnected();
}
// disable pinch to zoom
webFrame.setVisualZoomLevelLimits(1, 1);
}
- renderView() {
+ public renderView() {
return (
- <Provider store={this._reduxStore}>
- <ConnectedRouter history={this._memoryHistory}>{makeRoutes({ app: this })}</ConnectedRouter>
+ <Provider store={this.reduxStore}>
+ <ConnectedRouter history={this.memoryHistory}>{makeRoutes({ app: this })}</ConnectedRouter>
</Provider>
);
}
- async login(accountToken: AccountToken) {
- const actions = this._reduxActions;
+ public async login(accountToken: AccountToken) {
+ const actions = this.reduxActions;
actions.account.startLogin(accountToken);
log.info('Logging in');
- this._doingLogin = true;
+ this.doingLogin = true;
try {
const verification = await this.verifyAccount(accountToken);
@@ -188,8 +188,8 @@ export default class AppRenderer {
await IpcRendererEventChannel.account.set(accountToken);
// Redirect the user after some time to allow for the 'Logged in' screen to be visible
- this._loginTimer = setTimeout(async () => {
- this._memoryHistory.replace('/connect');
+ this.loginTimer = setTimeout(async () => {
+ this.memoryHistory.replace('/connect');
try {
log.info('Auto-connecting the tunnel');
@@ -205,10 +205,10 @@ export default class AppRenderer {
}
}
- verifyAccount(accountToken: AccountToken): Promise<AccountVerification> {
+ public verifyAccount(accountToken: AccountToken): Promise<AccountVerification> {
return new Promise((resolve, reject) => {
- this._accountDataCache.invalidate();
- this._accountDataCache.fetch(accountToken, {
+ this.accountDataCache.invalidate();
+ this.accountDataCache.fetch(accountToken, {
onFinish: () => resolve({ status: 'verified' }),
onError: (error): AccountFetchRetryAction => {
if (error instanceof InvalidAccountError) {
@@ -223,7 +223,7 @@ export default class AppRenderer {
});
}
- async logout() {
+ public async logout() {
try {
await IpcRendererEventChannel.account.unset();
} catch (e) {
@@ -231,28 +231,81 @@ export default class AppRenderer {
}
}
- async connectTunnel(): Promise<void> {
- const state = this._tunnelState.state;
+ public async connectTunnel(): Promise<void> {
+ const state = this.tunnelState.state;
// connect only if tunnel is disconnected or blocked.
if (state === 'disconnecting' || state === 'disconnected' || state === 'blocked') {
// switch to the connecting state ahead of time to make the app look more responsive
- this._reduxActions.connection.connecting(null);
+ this.reduxActions.connection.connecting(null);
return IpcRendererEventChannel.tunnel.connect();
}
}
- disconnectTunnel(): Promise<void> {
+ public disconnectTunnel(): Promise<void> {
return IpcRendererEventChannel.tunnel.disconnect();
}
- updateRelaySettings(relaySettings: RelaySettingsUpdate) {
+ public updateRelaySettings(relaySettings: RelaySettingsUpdate) {
return IpcRendererEventChannel.settings.updateRelaySettings(relaySettings);
}
- _setRelaySettings(relaySettings: RelaySettings) {
- const actions = this._reduxActions;
+ public updateAccountExpiry() {
+ if (this.settings.accountToken) {
+ this.accountDataCache.fetch(this.settings.accountToken);
+ }
+ }
+
+ public async removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
+ await IpcRendererEventChannel.accountHistory.removeItem(accountToken);
+ await this.fetchAccountHistory();
+ }
+
+ public async setAllowLan(allowLan: boolean) {
+ const actions = this.reduxActions;
+ await IpcRendererEventChannel.settings.setAllowLan(allowLan);
+ actions.settings.updateAllowLan(allowLan);
+ }
+
+ public async setEnableIpv6(enableIpv6: boolean) {
+ const actions = this.reduxActions;
+ await IpcRendererEventChannel.settings.setEnableIpv6(enableIpv6);
+ actions.settings.updateEnableIpv6(enableIpv6);
+ }
+
+ public async setBlockWhenDisconnected(blockWhenDisconnected: boolean) {
+ const actions = this.reduxActions;
+ await IpcRendererEventChannel.settings.setBlockWhenDisconnected(blockWhenDisconnected);
+ actions.settings.updateBlockWhenDisconnected(blockWhenDisconnected);
+ }
+
+ public async setOpenVpnMssfix(mssfix?: number) {
+ const actions = this.reduxActions;
+ actions.settings.updateOpenVpnMssfix(mssfix);
+ await IpcRendererEventChannel.settings.setOpenVpnMssfix(mssfix);
+ }
+
+ public async setAutoConnect(autoConnect: boolean) {
+ return IpcRendererEventChannel.guiSettings.setAutoConnect(autoConnect);
+ }
+
+ public async setAutoStart(autoStart: boolean): Promise<void> {
+ this.storeAutoStart(autoStart);
+
+ return IpcRendererEventChannel.autoStart.set(autoStart);
+ }
+
+ public setStartMinimized(startMinimized: boolean) {
+ IpcRendererEventChannel.guiSettings.setStartMinimized(startMinimized);
+ }
+
+ public setMonochromaticIcon(monochromaticIcon: boolean) {
+ IpcRendererEventChannel.guiSettings.setMonochromaticIcon(monochromaticIcon);
+ }
+
+ private setRelaySettings(relaySettings: RelaySettings) {
+ const actions = this.reduxActions;
if ('normal' in relaySettings) {
const payload: { [key: string]: any } = {};
@@ -260,11 +313,7 @@ export default class AppRenderer {
const tunnel = normal.tunnel;
const location = normal.location;
- if (location === 'any') {
- payload.location = 'any';
- } else {
- payload.location = location.only;
- }
+ payload.location = location === 'any' ? 'any' : location.only;
if (tunnel === 'any') {
payload.port = 'any';
@@ -304,110 +353,57 @@ export default class AppRenderer {
}
}
- updateAccountExpiry() {
- if (this._settings.accountToken) {
- this._accountDataCache.fetch(this._settings.accountToken);
- }
- }
-
- async removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
- await IpcRendererEventChannel.accountHistory.removeItem(accountToken);
- await this._fetchAccountHistory();
- }
-
- async _fetchAccountHistory(): Promise<void> {
+ private async fetchAccountHistory(): Promise<void> {
const accountHistory = await IpcRendererEventChannel.accountHistory.get();
- this._reduxActions.account.updateAccountHistory(accountHistory);
- }
-
- async setAllowLan(allowLan: boolean) {
- const actions = this._reduxActions;
- await IpcRendererEventChannel.settings.setAllowLan(allowLan);
- actions.settings.updateAllowLan(allowLan);
- }
-
- async setEnableIpv6(enableIpv6: boolean) {
- const actions = this._reduxActions;
- await IpcRendererEventChannel.settings.setEnableIpv6(enableIpv6);
- actions.settings.updateEnableIpv6(enableIpv6);
- }
-
- async setBlockWhenDisconnected(blockWhenDisconnected: boolean) {
- const actions = this._reduxActions;
- await IpcRendererEventChannel.settings.setBlockWhenDisconnected(blockWhenDisconnected);
- actions.settings.updateBlockWhenDisconnected(blockWhenDisconnected);
- }
-
- async setOpenVpnMssfix(mssfix?: number) {
- const actions = this._reduxActions;
- actions.settings.updateOpenVpnMssfix(mssfix);
- await IpcRendererEventChannel.settings.setOpenVpnMssfix(mssfix);
- }
-
- async setAutoConnect(autoConnect: boolean) {
- return IpcRendererEventChannel.guiSettings.setAutoConnect(autoConnect);
- }
-
- async setAutoStart(autoStart: boolean): Promise<void> {
- this._setAutoStart(autoStart);
-
- return IpcRendererEventChannel.autoStart.set(autoStart);
+ this.reduxActions.account.updateAccountHistory(accountHistory);
}
- setStartMinimized(startMinimized: boolean) {
- IpcRendererEventChannel.guiSettings.setStartMinimized(startMinimized);
- }
-
- setMonochromaticIcon(monochromaticIcon: boolean) {
- IpcRendererEventChannel.guiSettings.setMonochromaticIcon(monochromaticIcon);
- }
-
- async _onDaemonConnected() {
- this._connectedToDaemon = true;
+ private async onDaemonConnected() {
+ this.connectedToDaemon = true;
try {
- await this._fetchAccountHistory();
+ await this.fetchAccountHistory();
} catch (error) {
log.error(`Cannot fetch the account history: ${error.message}`);
}
- if (this._settings.accountToken) {
- this._memoryHistory.replace('/connect');
+ if (this.settings.accountToken) {
+ this.memoryHistory.replace('/connect');
// try to autoconnect the tunnel
- await this._autoConnect();
+ await this.autoConnect();
} else {
- this._memoryHistory.replace('/login');
+ this.memoryHistory.replace('/login');
// show window when account is not set
ipcRenderer.send('show-window');
}
}
- _onDaemonDisconnected(error?: Error) {
- const wasConnected = this._connectedToDaemon;
+ private onDaemonDisconnected(error?: Error) {
+ const wasConnected = this.connectedToDaemon;
- this._connectedToDaemon = false;
+ this.connectedToDaemon = false;
if (error && wasConnected) {
- this._memoryHistory.replace('/');
+ this.memoryHistory.replace('/');
}
}
- async _autoConnect() {
+ private async autoConnect() {
if (process.env.NODE_ENV === 'development') {
log.info('Skip autoconnect in development');
- } else if (this._autoConnected) {
+ } else if (this.autoConnected) {
log.info('Skip autoconnect because it was done before');
- } else if (this._settings.accountToken) {
- if (this._guiSettings.autoConnect) {
+ } else if (this.settings.accountToken) {
+ if (this.guiSettings.autoConnect) {
try {
log.info('Autoconnect the tunnel');
await this.connectTunnel();
- this._autoConnected = true;
+ this.autoConnected = true;
} catch (error) {
log.error(`Failed to autoconnect the tunnel: ${error.message}`);
}
@@ -419,12 +415,12 @@ export default class AppRenderer {
}
}
- _setTunnelState(tunnelState: TunnelStateTransition) {
- const actions = this._reduxActions;
+ private setTunnelState(tunnelState: TunnelStateTransition) {
+ const actions = this.reduxActions;
log.debug(`Tunnel state: ${tunnelState.state}`);
- this._tunnelState = tunnelState;
+ this.tunnelState = tunnelState;
switch (tunnelState.state) {
case 'connecting':
@@ -449,18 +445,18 @@ export default class AppRenderer {
}
}
- _setSettings(newSettings: Settings) {
- this._settings = newSettings;
+ private setSettings(newSettings: ISettings) {
+ this.settings = newSettings;
- const reduxSettings = this._reduxActions.settings;
- const reduxAccount = this._reduxActions.account;
+ const reduxSettings = this.reduxActions.settings;
+ const reduxAccount = this.reduxActions.account;
reduxSettings.updateAllowLan(newSettings.allowLan);
reduxSettings.updateEnableIpv6(newSettings.tunnelOptions.generic.enableIpv6);
reduxSettings.updateBlockWhenDisconnected(newSettings.blockWhenDisconnected);
reduxSettings.updateOpenVpnMssfix(newSettings.tunnelOptions.openvpn.mssfix);
- this._setRelaySettings(newSettings.relaySettings);
+ this.setRelaySettings(newSettings.relaySettings);
if (newSettings.accountToken) {
reduxAccount.updateAccountToken(newSettings.accountToken);
@@ -470,33 +466,33 @@ export default class AppRenderer {
}
}
- _handleAccountChange(oldAccount?: string, newAccount?: string) {
+ private handleAccountChange(oldAccount?: string, newAccount?: string) {
if (oldAccount && !newAccount) {
- this._accountDataCache.invalidate();
+ this.accountDataCache.invalidate();
- if (this._loginTimer) {
- clearTimeout(this._loginTimer);
+ if (this.loginTimer) {
+ clearTimeout(this.loginTimer);
}
- this._memoryHistory.replace('/login');
+ this.memoryHistory.replace('/login');
} else if (!oldAccount && newAccount) {
- this._accountDataCache.fetch(newAccount);
+ this.accountDataCache.fetch(newAccount);
- if (!this._doingLogin) {
- this._memoryHistory.replace('/connect');
+ if (!this.doingLogin) {
+ this.memoryHistory.replace('/connect');
}
} else if (oldAccount && newAccount && oldAccount !== newAccount) {
- this._accountDataCache.fetch(newAccount);
+ this.accountDataCache.fetch(newAccount);
}
- this._doingLogin = false;
+ this.doingLogin = false;
}
- _setLocation(location: Location) {
- this._reduxActions.connection.newLocation(location);
+ private setLocation(location: ILocation) {
+ this.reduxActions.connection.newLocation(location);
}
- _setRelays(relayList: RelayList) {
+ private setRelays(relayList: IRelayList) {
const locations = relayList.countries.map((country) => ({
name: country.name,
code: country.code,
@@ -511,143 +507,137 @@ export default class AppRenderer {
})),
}));
- this._reduxActions.settings.updateRelayLocations(locations);
+ this.reduxActions.settings.updateRelayLocations(locations);
}
- _setCurrentVersion(versionInfo: CurrentAppVersionInfo) {
- this._reduxActions.version.updateVersion(versionInfo.gui, versionInfo.isConsistent);
+ private setCurrentVersion(versionInfo: ICurrentAppVersionInfo) {
+ this.reduxActions.version.updateVersion(versionInfo.gui, versionInfo.isConsistent);
}
- _setUpgradeVersion(upgradeVersion: AppUpgradeInfo) {
- this._reduxActions.version.updateLatest(upgradeVersion);
+ private setUpgradeVersion(upgradeVersion: IAppUpgradeInfo) {
+ this.reduxActions.version.updateLatest(upgradeVersion);
}
- _setGuiSettings(guiSettings: GuiSettingsState) {
- this._guiSettings = guiSettings;
- this._reduxActions.settings.updateGuiSettings(guiSettings);
+ private setGuiSettings(guiSettings: IGuiSettingsState) {
+ this.guiSettings = guiSettings;
+ this.reduxActions.settings.updateGuiSettings(guiSettings);
}
- _setAutoStart(autoStart: boolean) {
- this._reduxActions.settings.updateAutoStart(autoStart);
+ private storeAutoStart(autoStart: boolean) {
+ this.reduxActions.settings.updateAutoStart(autoStart);
}
}
type AccountVerification = { status: 'verified' } | { status: 'deferred'; error: Error };
type AccountFetchRetryAction = 'stop' | 'retry';
-type AccountFetchWatcher = {
+interface IAccountFetchWatcher {
onFinish: () => void;
onError: (error: Error) => AccountFetchRetryAction;
-};
+}
// An account data cache that helps to throttle RPC requests to get_account_data and retain the
// cached value for 1 minute.
export class AccountDataCache {
- _currentAccount?: AccountToken;
- _expiresAt?: Date;
- _fetchAttempt: number;
- _fetchRetryTimeout?: NodeJS.Timeout;
- _fetch: (token: AccountToken) => Promise<AccountData>;
- _update: (data?: AccountData) => void;
- _watchers: Array<AccountFetchWatcher>;
+ private currentAccount?: AccountToken;
+ private expiresAt?: Date;
+ private fetchAttempt = 0;
+ private fetchRetryTimeout?: NodeJS.Timeout;
+ private watchers: IAccountFetchWatcher[] = [];
constructor(
- fetch: (token: AccountToken) => Promise<AccountData>,
- update: (data?: AccountData) => void,
- ) {
- this._fetch = fetch;
- this._update = update;
- this._watchers = [];
- this._fetchAttempt = 0;
- }
+ private fetchHandler: (token: AccountToken) => Promise<IAccountData>,
+ private updateHandler: (data?: IAccountData) => void,
+ ) {}
- fetch(accountToken: AccountToken, watcher?: AccountFetchWatcher) {
+ public fetch(accountToken: AccountToken, watcher?: IAccountFetchWatcher) {
// invalidate cache if account token has changed
- if (accountToken !== this._currentAccount) {
+ if (accountToken !== this.currentAccount) {
this.invalidate();
- this._currentAccount = accountToken;
+ this.currentAccount = accountToken;
}
// Only fetch is value has expired
- if (this._isExpired()) {
+ if (this.isExpired()) {
if (watcher) {
- this._watchers.push(watcher);
+ this.watchers.push(watcher);
}
- this._performFetch(accountToken);
+ this.performFetch(accountToken);
} else if (watcher) {
watcher.onFinish();
}
}
- invalidate() {
- if (this._fetchRetryTimeout) {
- clearTimeout(this._fetchRetryTimeout);
- this._fetchRetryTimeout = undefined;
- this._fetchAttempt = 0;
+ public invalidate() {
+ if (this.fetchRetryTimeout) {
+ clearTimeout(this.fetchRetryTimeout);
+ this.fetchRetryTimeout = undefined;
+ this.fetchAttempt = 0;
}
- this._expiresAt = undefined;
- this._update();
- this._notifyWatchers((watcher) => {
+ this.expiresAt = undefined;
+ this.updateHandler();
+ this.notifyWatchers((watcher) => {
watcher.onError(new Error('Cancelled'));
});
}
- _setValue(value: AccountData) {
- this._expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration
- this._update(value);
- this._notifyWatchers((watcher) => watcher.onFinish());
+ private setValue(value: IAccountData) {
+ this.expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration
+ this.updateHandler(value);
+ this.notifyWatchers((watcher) => watcher.onFinish());
}
- _isExpired() {
- return !this._expiresAt || this._expiresAt < new Date();
+ private isExpired() {
+ return !this.expiresAt || this.expiresAt < new Date();
}
- async _performFetch(accountToken: AccountToken) {
+ private async performFetch(accountToken: AccountToken) {
try {
// it's possible for invalidate() to be called or for a fetch for a different account token
// to start before this fetch completes, so checking if the current account token is the one
// used is necessary below.
- const accountData = await this._fetch(accountToken);
+ const accountData = await this.fetchHandler(accountToken);
- if (this._currentAccount === accountToken) {
- this._setValue(accountData);
+ if (this.currentAccount === accountToken) {
+ this.setValue(accountData);
}
} catch (error) {
- if (this._currentAccount === accountToken) {
- this._handleFetchError(accountToken, error);
+ if (this.currentAccount === accountToken) {
+ this.handleFetchError(accountToken, error);
}
}
}
- _handleFetchError(accountToken: AccountToken, error: any) {
+ private handleFetchError(accountToken: AccountToken, error: any) {
let shouldRetry = true;
- this._notifyWatchers((watcher) => {
+ this.notifyWatchers((watcher) => {
if (watcher.onError(error) === 'stop') {
shouldRetry = false;
}
});
if (shouldRetry) {
- this._scheduleRetry(accountToken);
+ this.scheduleRetry(accountToken);
}
}
- _scheduleRetry(accountToken: AccountToken) {
- this._fetchAttempt += 1;
+ private scheduleRetry(accountToken: AccountToken) {
+ this.fetchAttempt += 1;
- const delay = Math.min(2048, 1 << (this._fetchAttempt + 2)) * 1000;
+ // tslint:disable-next-line
+ const delay = Math.min(2048, 1 << (this.fetchAttempt + 2)) * 1000;
log.warn(`Failed to fetch account data. Retrying in ${delay} ms`);
- this._fetchRetryTimeout = setTimeout(() => {
- this._fetchRetryTimeout = undefined;
- this._performFetch(accountToken);
+ this.fetchRetryTimeout = setTimeout(() => {
+ this.fetchRetryTimeout = undefined;
+ this.performFetch(accountToken);
}, delay);
}
- _notifyWatchers(notify: (watcher: AccountFetchWatcher) => void) {
- this._watchers.splice(0).forEach(notify);
+ private notifyWatchers(notify: (watcher: IAccountFetchWatcher) => void) {
+ this.watchers.splice(0).forEach(notify);
}
}
diff --git a/gui/packages/desktop/src/renderer/components/Account.tsx b/gui/packages/desktop/src/renderer/components/Account.tsx
index 8ff36aafbe..e49bfc0a61 100644
--- a/gui/packages/desktop/src/renderer/components/Account.tsx
+++ b/gui/packages/desktop/src/renderer/components/Account.tsx
@@ -1,15 +1,15 @@
+import { ClipboardLabel, HeaderTitle, SettingsHeader } from '@mullvad/components';
import moment from 'moment';
import * as React from 'react';
import { Component, Text, View } from 'reactxp';
-import { ClipboardLabel, SettingsHeader, HeaderTitle } from '@mullvad/components';
-import * as AppButton from './AppButton';
-import { Layout, Container } from './Layout';
-import { NavigationBar, BackBarItem } from './NavigationBar';
import styles from './AccountStyles';
+import * as AppButton from './AppButton';
+import { Container, Layout } from './Layout';
+import { BackBarItem, NavigationBar } from './NavigationBar';
import { AccountToken } from '../../shared/daemon-rpc-types';
-type Props = {
+interface IProps {
accountToken?: AccountToken;
accountExpiry?: string;
expiryLocale: string;
@@ -17,10 +17,10 @@ type Props = {
onLogout: () => void;
onClose: () => void;
onBuyMore: () => void;
-};
+}
-export default class Account extends Component<Props> {
- render() {
+export default class Account extends Component<IProps> {
+ public render() {
return (
<Layout>
<Container>
@@ -75,7 +75,7 @@ export default class Account extends Component<Props> {
}
}
-const FormattedAccountExpiry = (props: { expiry?: string; locale: string }) => {
+function FormattedAccountExpiry(props: { expiry?: string; locale: string }) {
if (!props.expiry) {
return <Text style={styles.account__row_value}>{'Currently unavailable'}</Text>;
}
@@ -99,4 +99,4 @@ const FormattedAccountExpiry = (props: { expiry?: string; locale: string }) => {
{expiry.toDate().toLocaleString(props.locale, formatOptions)}
</Text>
);
-};
+}
diff --git a/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx b/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx
index 99632a35f5..c8a2b67c93 100644
--- a/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx
+++ b/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx
@@ -1,23 +1,26 @@
+/* tslint:disable:jsx-no-lambda */
+// TODO: Refactor this file to fix the jsx-no-lambda warnings
+
+import { HeaderTitle, ImageView, SettingsHeader } from '@mullvad/components';
import * as React from 'react';
import { Button, Component, Text, View } from 'reactxp';
-import { ImageView, SettingsHeader, HeaderTitle } from '@mullvad/components';
+import { colors } from '../../config.json';
+import styles from './AdvancedSettingsStyles';
import * as Cell from './Cell';
-import { Layout, Container } from './Layout';
+import { Container, Layout } from './Layout';
import {
+ BackBarItem,
NavigationBar,
NavigationContainer,
NavigationScrollbars,
- BackBarItem,
TitleBarItem,
} from './NavigationBar';
import Switch from './Switch';
-import styles from './AdvancedSettingsStyles';
-import { colors } from '../../config.json';
const MIN_MSSFIX_VALUE = 1000;
const MAX_MSSFIX_VALUE = 1450;
-type Props = {
+interface IProps {
enableIpv6: boolean;
blockWhenDisconnected: boolean;
protocol: string;
@@ -28,16 +31,16 @@ type Props = {
setOpenVpnMssfix: (value: number | undefined) => void;
onUpdate: (protocol: string, port: string | number) => void;
onClose: () => void;
-};
+}
-type State = {
+interface IState {
persistedMssfix?: number;
editedMssfix?: number;
focusOnMssfix: boolean;
-};
+}
-export class AdvancedSettings extends Component<Props, State> {
- constructor(props: Props) {
+export class AdvancedSettings extends Component<IProps, IState> {
+ constructor(props: IProps) {
super(props);
this.state = {
@@ -47,7 +50,7 @@ export class AdvancedSettings extends Component<Props, State> {
};
}
- componentDidUpdate(_oldProps: Props, _oldState: State) {
+ public componentDidUpdate(_oldProps: IProps, _oldState: IState) {
if (this.props.mssfix !== this.state.persistedMssfix) {
this.setState((state, props) => ({
...state,
@@ -57,22 +60,19 @@ export class AdvancedSettings extends Component<Props, State> {
}
}
- render() {
+ public render() {
let portSelector = null;
let protocol = this.props.protocol.toUpperCase();
if (protocol === 'AUTOMATIC') {
protocol = 'Automatic';
} else {
- portSelector = this._createPortSelector();
+ portSelector = this.createPortSelector();
}
- let mssfixStyle;
- if (this._mssfixIsValid()) {
- mssfixStyle = styles.advanced_settings__mssfix_valid_value;
- } else {
- mssfixStyle = styles.advanced_settings__mssfix_invalid_value;
- }
+ const mssfixStyle = this.mssfixIsValid()
+ ? styles.advanced_settings__mssfix_valid_value
+ : styles.advanced_settings__mssfix_invalid_value;
const mssfixValue = this.state.editedMssfix;
@@ -118,8 +118,8 @@ export class AdvancedSettings extends Component<Props, State> {
title={'Network protocols'}
values={['Automatic', 'UDP', 'TCP']}
value={protocol}
- onSelect={(protocol) => {
- this.props.onUpdate(protocol, 'Automatic');
+ onSelect={(selectedProtocol) => {
+ this.props.onUpdate(selectedProtocol, 'Automatic');
}}
/>
@@ -137,9 +137,9 @@ export class AdvancedSettings extends Component<Props, State> {
placeholder={'Default'}
value={mssfixValue ? mssfixValue.toString() : ''}
style={mssfixStyle}
- onChangeText={this._onMssfixChange}
- onFocus={this._onMssfixFocus}
- onBlur={this._onMssfixBlur}
+ onChangeText={this.onMssfixChange}
+ onFocus={this.onMssfixFocus}
+ onBlur={this.onMssfixBlur}
/>
</Cell.InputFrame>
</Cell.Container>
@@ -155,7 +155,7 @@ export class AdvancedSettings extends Component<Props, State> {
);
}
- _createPortSelector() {
+ private createPortSelector() {
const protocol = this.props.protocol.toUpperCase();
const ports =
protocol === 'TCP'
@@ -174,7 +174,7 @@ export class AdvancedSettings extends Component<Props, State> {
);
}
- _onMssfixChange = (mssfixString: string) => {
+ private onMssfixChange = (mssfixString: string) => {
const mssfix = mssfixString.replace(/[^0-9]/g, '');
if (mssfix === '') {
@@ -184,64 +184,64 @@ export class AdvancedSettings extends Component<Props, State> {
}
};
- _onMssfixFocus = () => {
+ private onMssfixFocus = () => {
this.setState({ focusOnMssfix: true });
};
- _onMssfixBlur = () => {
+ private onMssfixBlur = () => {
this.setState({ focusOnMssfix: false });
- if (this._mssfixIsValid()) {
+ if (this.mssfixIsValid()) {
this.props.setOpenVpnMssfix(this.state.editedMssfix);
this.setState((state, _props) => ({ persistedMssfix: state.editedMssfix }));
}
};
- _mssfixIsValid(): boolean {
+ private mssfixIsValid(): boolean {
const mssfix = this.state.editedMssfix;
return mssfix === undefined || (mssfix >= MIN_MSSFIX_VALUE && mssfix <= MAX_MSSFIX_VALUE);
}
}
-type SelectorProps<T> = {
+interface ISelectorProps<T> {
title: string;
- values: Array<T>;
+ values: T[];
value: T;
onSelect: (value: T) => void;
-};
+}
-type SelectorState<T> = {
+interface ISelectorState<T> {
hoveredButtonValue?: T;
-};
+}
-class Selector<T> extends Component<SelectorProps<T>, SelectorState<T>> {
- state = { hoveredButtonValue: undefined };
+class Selector<T> extends Component<ISelectorProps<T>, ISelectorState<T>> {
+ public state: ISelectorState<T> = {};
- handleButtonHover = (value?: T) => {
- this.setState({ hoveredButtonValue: value });
- };
-
- render() {
+ public render() {
return (
<View>
<View style={styles.advanced_settings__section_title}>{this.props.title}</View>
- {this.props.values.map((value) => this._renderCell(value))}
+ {this.props.values.map((value) => this.renderCell(value))}
</View>
);
}
- _renderCell(value: T) {
+ private handleButtonHover = (value?: T) => {
+ this.setState({ hoveredButtonValue: value });
+ };
+
+ private renderCell(value: T) {
const selected = value === this.props.value;
if (selected) {
- return this._renderSelectedCell(value);
+ return this.renderSelectedCell(value);
} else {
- return this._renderUnselectedCell(value);
+ return this.renderUnselectedCell(value);
}
}
- _renderSelectedCell(value: T) {
+ private renderSelectedCell(value: T) {
return (
<Button
style={[
@@ -264,7 +264,7 @@ class Selector<T> extends Component<SelectorProps<T>, SelectorState<T>> {
);
}
- _renderUnselectedCell(value: T) {
+ private renderUnselectedCell(value: T) {
return (
<Button
style={[
diff --git a/gui/packages/desktop/src/renderer/components/AppButton.tsx b/gui/packages/desktop/src/renderer/components/AppButton.tsx
index 31fc9bd679..42e2205b77 100644
--- a/gui/packages/desktop/src/renderer/components/AppButton.tsx
+++ b/gui/packages/desktop/src/renderer/components/AppButton.tsx
@@ -1,27 +1,27 @@
+import { ImageView } from '@mullvad/components';
import * as React from 'react';
import { Button, Component, Text, Types } from 'reactxp';
-import { ImageView } from '@mullvad/components';
-import styles from './AppButtonStyles';
import { colors } from '../../config.json';
+import styles from './AppButtonStyles';
-type LabelProps = {
+interface ILabelProps {
children?: React.ReactText;
-};
+}
-export class Label extends Component<LabelProps> {
- render() {
+export class Label extends Component<ILabelProps> {
+ public render() {
return <Text style={styles.label}>{this.props.children}</Text>;
}
}
-type IconProps = {
+interface IIconProps {
source: string;
width?: number;
height?: number;
-};
+}
-export class Icon extends Component<IconProps> {
- render() {
+export class Icon extends Component<IIconProps> {
+ public render() {
return (
<ImageView
source={this.props.source}
@@ -34,27 +34,27 @@ export class Icon extends Component<IconProps> {
}
}
-type Props = {
+interface IProps {
children?: React.ReactNode;
style?: Types.ButtonStyleRuleSet;
disabled?: boolean;
onPress?: () => void;
-};
+}
-type State = {
+interface IState {
hovered: boolean;
-};
+}
-class BaseButton extends Component<Props, State> {
- state: State = { hovered: false };
+class BaseButton extends Component<IProps, IState> {
+ public state: IState = { hovered: false };
- backgroundStyle = (): Types.ButtonStyleRuleSet => {
+ public backgroundStyle = (): Types.ButtonStyleRuleSet => {
throw new Error('Implement backgroundStyle in subclasses.');
};
- onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null);
- onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null);
+ public onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null);
+ public onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null);
- render() {
+ public render() {
const { children, style, ...otherProps } = this.props;
return (
@@ -72,21 +72,23 @@ class BaseButton extends Component<Props, State> {
}
export class RedButton extends BaseButton {
- backgroundStyle = () => (this.state.hovered ? styles.redHover : styles.red);
+ public backgroundStyle = () => (this.state.hovered ? styles.redHover : styles.red);
}
export class GreenButton extends BaseButton {
- backgroundStyle = () => (this.state.hovered ? styles.greenHover : styles.green);
+ public backgroundStyle = () => (this.state.hovered ? styles.greenHover : styles.green);
}
export class BlueButton extends BaseButton {
- backgroundStyle = () => (this.state.hovered ? styles.blueHover : styles.blue);
+ public backgroundStyle = () => (this.state.hovered ? styles.blueHover : styles.blue);
}
export class TransparentButton extends BaseButton {
- backgroundStyle = () => (this.state.hovered ? styles.transparentHover : styles.transparent);
+ public backgroundStyle = () =>
+ this.state.hovered ? styles.transparentHover : styles.transparent;
}
export class RedTransparentButton extends BaseButton {
- backgroundStyle = () => (this.state.hovered ? styles.redTransparentHover : styles.redTransparent);
+ public backgroundStyle = () =>
+ this.state.hovered ? styles.redTransparentHover : styles.redTransparent;
}
diff --git a/gui/packages/desktop/src/renderer/components/Cell.tsx b/gui/packages/desktop/src/renderer/components/Cell.tsx
index 291b891e64..c2dedc9dfc 100644
--- a/gui/packages/desktop/src/renderer/components/Cell.tsx
+++ b/gui/packages/desktop/src/renderer/components/Cell.tsx
@@ -1,6 +1,6 @@
+import { ImageView } from '@mullvad/components';
import * as React from 'react';
import { Button, Component, Styles, Text, TextInput, Types, View } from 'reactxp';
-import { ImageView } from '@mullvad/components';
import { colors } from '../../config.json';
const styles = {
@@ -96,25 +96,27 @@ const styles = {
}),
};
-type CellButtonProps = {
+interface ICellButtonProps {
children?: React.ReactNode;
disabled?: boolean;
cellHoverStyle?: Types.StyleRuleSetRecursive<Types.ButtonStyleRuleSet>;
style?: Types.StyleRuleSetRecursive<Types.ButtonStyleRuleSet>;
onPress?: () => void;
-};
+}
-type State = { hovered: boolean };
+interface IState {
+ hovered: boolean;
+}
const CellHoverContext = React.createContext<boolean>(false);
-export class CellButton extends Component<CellButtonProps, State> {
- state = { hovered: false };
+export class CellButton extends Component<ICellButtonProps, IState> {
+ public state = { hovered: false };
- onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null);
- onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null);
+ public onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null);
+ public onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null);
- render() {
+ public render() {
const { children, style, cellHoverStyle, ...otherProps } = this.props;
const hoverStyle = cellHoverStyle || styles.cellHover;
return (
@@ -129,22 +131,24 @@ export class CellButton extends Component<CellButtonProps, State> {
}
}
-type ContainerProps = { children: React.ReactNode };
+interface IContainerProps {
+ children: React.ReactNode;
+}
-export function Container({ children }: ContainerProps) {
+export function Container({ children }: IContainerProps) {
return <View style={styles.cellContainer}>{children}</View>;
}
-type LabelProps = {
+interface ILabelProps {
containerStyle?: Types.ViewStyleRuleSet;
textStyle?: Types.TextStyleRuleSet;
cellHoverContainerStyle?: Types.ViewStyleRuleSet;
cellHoverTextStyle?: Types.TextStyleRuleSet;
onPress?: (event: Types.SyntheticEvent) => void;
children?: React.ReactNode;
-};
+}
-export function Label(props: LabelProps) {
+export function Label(props: ILabelProps) {
const {
children,
containerStyle,
@@ -173,10 +177,10 @@ export function Label(props: LabelProps) {
);
}
-type InputFrameProps = {
+interface InputFrameProps {
children?: React.ReactNode;
style?: Types.StyleRuleSetRecursive<Types.ViewStyleRuleSet>;
-};
+}
export function InputFrame(props: InputFrameProps) {
const { style, children } = props;
@@ -237,7 +241,7 @@ export function Icon(props: ImageView['props']) {
);
}
-export function Footer({ children }: ContainerProps) {
+export function Footer({ children }: IContainerProps) {
return (
<View style={styles.footer.container}>
<Text style={styles.footer.text}>{children}</Text>
diff --git a/gui/packages/desktop/src/renderer/components/ChevronButton.tsx b/gui/packages/desktop/src/renderer/components/ChevronButton.tsx
index efb7c03fdd..04aabf6a99 100644
--- a/gui/packages/desktop/src/renderer/components/ChevronButton.tsx
+++ b/gui/packages/desktop/src/renderer/components/ChevronButton.tsx
@@ -1,13 +1,13 @@
import * as React from 'react';
import { Component, Styles, Types } from 'reactxp';
-import * as Cell from './Cell';
import { colors } from '../../config.json';
+import * as Cell from './Cell';
-type Props = {
+interface IProps {
up: boolean;
onPress?: (event: Types.SyntheticEvent) => void;
style?: Types.StyleRuleSetRecursive<Types.ViewStyleRuleSet>;
-};
+}
const style = Styles.createViewStyle({
flex: 0,
@@ -17,8 +17,8 @@ const style = Styles.createViewStyle({
paddingLeft: 16,
});
-export default class ChevronButton extends Component<Props> {
- render() {
+export default class ChevronButton extends Component<IProps> {
+ public render() {
return (
<Cell.Icon
style={[style, this.props.style]}
diff --git a/gui/packages/desktop/src/renderer/components/CityRow.tsx b/gui/packages/desktop/src/renderer/components/CityRow.tsx
index 4137762b16..23efa17292 100644
--- a/gui/packages/desktop/src/renderer/components/CityRow.tsx
+++ b/gui/packages/desktop/src/renderer/components/CityRow.tsx
@@ -1,23 +1,25 @@
+import { Accordion } from '@mullvad/components';
import * as React from 'react';
import { Component, Styles, Types, View } from 'reactxp';
-import { Accordion } from '@mullvad/components';
+import { colors } from '../../config.json';
+import { compareRelayLocation, RelayLocation } from '../../shared/daemon-rpc-types';
import * as Cell from './Cell';
+import ChevronButton from './ChevronButton';
import RelayRow from './RelayRow';
import RelayStatusIndicator from './RelayStatusIndicator';
-import ChevronButton from './ChevronButton';
-import { colors } from '../../config.json';
type RelayRowElement = React.ReactElement<RelayRow['props']>;
-type Props = {
+interface IProps {
name: string;
hasActiveRelays: boolean;
+ location: RelayLocation;
selected: boolean;
expanded: boolean;
- onSelect?: () => void;
- onExpand?: (value: boolean) => void;
+ onSelect?: (location: RelayLocation) => void;
+ onExpand?: (location: RelayLocation, value: boolean) => void;
children?: RelayRowElement | RelayRowElement[];
-};
+}
const styles = {
base: Styles.createButtonStyle({
@@ -32,12 +34,8 @@ const styles = {
}),
};
-export default class CityRow extends Component<Props> {
- shouldComponentUpdate(nextProps: Props) {
- return !CityRow.compareProps(this.props, nextProps);
- }
-
- static compareProps(oldProps: Props, nextProps: Props): boolean {
+export default class CityRow extends Component<IProps> {
+ public static compareProps(oldProps: IProps, nextProps: IProps): boolean {
if (React.Children.count(oldProps.children) !== React.Children.count(nextProps.children)) {
return false;
}
@@ -46,7 +44,8 @@ export default class CityRow extends Component<Props> {
oldProps.name !== nextProps.name ||
oldProps.hasActiveRelays !== nextProps.hasActiveRelays ||
oldProps.selected !== nextProps.selected ||
- oldProps.expanded !== nextProps.expanded
+ oldProps.expanded !== nextProps.expanded ||
+ !compareRelayLocation(oldProps.location, nextProps.location)
) {
return false;
}
@@ -66,13 +65,17 @@ export default class CityRow extends Component<Props> {
return true;
}
- render() {
+ public shouldComponentUpdate(nextProps: IProps) {
+ return !CityRow.compareProps(this.props, nextProps);
+ }
+
+ public render() {
const hasChildren = React.Children.count(this.props.children) > 1;
return (
<View>
<Cell.CellButton
- onPress={this._handlePress}
+ onPress={this.handlePress}
disabled={!this.props.hasActiveRelays}
cellHoverStyle={this.props.selected ? styles.selected : undefined}
style={[styles.base, this.props.selected ? styles.selected : undefined]}>
@@ -82,7 +85,7 @@ export default class CityRow extends Component<Props> {
/>
<Cell.Label>{this.props.name}</Cell.Label>
- {hasChildren && <ChevronButton onPress={this._toggleCollapse} up={this.props.expanded} />}
+ {hasChildren && <ChevronButton onPress={this.toggleCollapse} up={this.props.expanded} />}
</Cell.CellButton>
{hasChildren && <Accordion expanded={this.props.expanded}>{this.props.children}</Accordion>}
@@ -90,16 +93,16 @@ export default class CityRow extends Component<Props> {
);
}
- _toggleCollapse = (event: Types.SyntheticEvent) => {
+ private toggleCollapse = (event: Types.SyntheticEvent) => {
if (this.props.onExpand) {
- this.props.onExpand(!this.props.expanded);
+ this.props.onExpand(this.props.location, !this.props.expanded);
}
event.stopPropagation();
};
- _handlePress = () => {
+ private handlePress = () => {
if (this.props.onSelect) {
- this.props.onSelect();
+ this.props.onSelect(this.props.location);
}
};
}
diff --git a/gui/packages/desktop/src/renderer/components/Connect.tsx b/gui/packages/desktop/src/renderer/components/Connect.tsx
index 5c4b883e57..6d9552cdc6 100644
--- a/gui/packages/desktop/src/renderer/components/Connect.tsx
+++ b/gui/packages/desktop/src/renderer/components/Connect.tsx
@@ -1,24 +1,23 @@
+import { Brand, HeaderBarStyle, ImageView, SettingsBarButton } from '@mullvad/components';
import * as React from 'react';
import { Component, View } from 'reactxp';
-import { SettingsBarButton, Brand, HeaderBarStyle, ImageView } from '@mullvad/components';
-import { Layout, Container, Header } from './Layout';
-import NotificationArea from './NotificationArea';
+import { links } from '../../config.json';
+import { NoCreditError, NoInternetError } from '../../main/errors';
+import { ITunnelEndpoint, parseSocketAddress } from '../../shared/daemon-rpc-types';
import * as AppButton from './AppButton';
-import TunnelControl from './TunnelControl';
-import Map, { MarkerStyle, ZoomLevel } from './Map';
import styles from './ConnectStyles';
-import { NoCreditError, NoInternetError } from '../../main/errors';
-import { TunnelEndpoint, parseSocketAddress } from '../../shared/daemon-rpc-types';
-import { links } from '../../config.json';
+import { Container, Header, Layout } from './Layout';
+import Map, { MarkerStyle, ZoomLevel } from './Map';
+import NotificationArea from './NotificationArea';
+import TunnelControl, { IRelayInAddress, IRelayOutAddress } from './TunnelControl';
-import { RelayOutAddress, RelayInAddress } from './TunnelControl';
import AccountExpiry from '../lib/account-expiry';
-import { ConnectionReduxState } from '../redux/connection/reducers';
-import { VersionReduxState } from '../redux/version/reducers';
+import { IConnectionReduxState } from '../redux/connection/reducers';
+import { IVersionReduxState } from '../redux/version/reducers';
-type Props = {
- connection: ConnectionReduxState;
- version: VersionReduxState;
+interface IProps {
+ connection: IConnectionReduxState;
+ version: IVersionReduxState;
accountExpiry?: AccountExpiry;
selectedRelayName: string;
connectionInfoOpen: boolean;
@@ -29,12 +28,12 @@ type Props = {
onDisconnect: () => void;
onExternalLink: (url: string) => void;
onToggleConnectionInfo: (value: boolean) => void;
-};
+}
type MarkerOrSpinner = 'marker' | 'spinner';
-export default class Connect extends Component<Props> {
- render() {
+export default class Connect extends Component<IProps> {
+ public render() {
const error = this.checkForErrors();
const child = error ? this.renderError(error) : this.renderMap();
@@ -49,7 +48,7 @@ export default class Connect extends Component<Props> {
);
}
- renderError(error: Error) {
+ public renderError(error: Error) {
let title = '';
let message = '';
@@ -75,9 +74,7 @@ export default class Connect extends Component<Props> {
<View style={styles.error_message}>{message}</View>
{error instanceof NoCreditError ? (
<View>
- <AppButton.GreenButton
- disabled={isBlocked}
- onPress={() => this.props.onExternalLink(links.purchase)}>
+ <AppButton.GreenButton disabled={isBlocked} onPress={this.handleBuyMorePress}>
<AppButton.Label>Buy more time</AppButton.Label>
<AppButton.Icon source="icon-extLink" height={16} width={16} />
</AppButton.GreenButton>
@@ -88,102 +85,23 @@ export default class Connect extends Component<Props> {
);
}
- _getMapProps(): Map['props'] {
- const {
- longitude,
- latitude,
- status: { state },
- } = this.props.connection;
-
- // when the user location is known
- if (typeof longitude === 'number' && typeof latitude === 'number') {
- return {
- center: [longitude, latitude],
- // do not show the marker when connecting or reconnecting
- showMarker: this._showMarkerOrSpinner() === 'marker',
- markerStyle: this._getMarkerStyle(),
- // zoom in when connected
- zoomLevel: state === 'connected' ? ZoomLevel.low : ZoomLevel.medium,
- // a magic offset to align marker with spinner
- offset: [0, 123],
- };
- } else {
- return {
- center: [0, 0],
- showMarker: false,
- markerStyle: MarkerStyle.unsecure,
- // show the world when user location is not known
- zoomLevel: ZoomLevel.high,
- // remove the offset since the marker is hidden
- offset: [0, 0],
- };
- }
- }
-
- _getMarkerStyle(): MarkerStyle {
- const { status } = this.props.connection;
-
- switch (status.state) {
- case 'connecting':
- case 'connected':
- return MarkerStyle.secure;
- case 'blocked':
- switch (status.details.reason) {
- case 'set_firewall_policy_error':
- return MarkerStyle.unsecure;
- default:
- return MarkerStyle.secure;
- }
- case 'disconnected':
- return MarkerStyle.unsecure;
- case 'disconnecting':
- switch (status.details) {
- case 'block':
- case 'reconnect':
- return MarkerStyle.secure;
- case 'nothing':
- return MarkerStyle.unsecure;
- default:
- throw new Error(`Invalid action after disconnection: ${status.details}`);
- }
- }
- }
-
- _showMarkerOrSpinner(): MarkerOrSpinner {
+ public renderMap() {
const status = this.props.connection.status;
- return status.state === 'connecting' ||
- (status.state === 'disconnecting' && status.details === 'reconnect')
- ? 'spinner'
- : 'marker';
- }
-
- _tunnelEndpointToRelayInAddress(tunnelEndpoint: TunnelEndpoint): RelayInAddress {
- const socketAddr = parseSocketAddress(tunnelEndpoint.address);
- return {
- ip: socketAddr.host,
- port: socketAddr.port,
- protocol: tunnelEndpoint.protocol,
- };
- }
-
- renderMap() {
- const status = this.props.connection.status;
-
- const relayOutAddress: RelayOutAddress = {
+ const relayOutAddress: IRelayOutAddress = {
ipv4: this.props.connection.ip,
};
- const relayInAddress: RelayInAddress | undefined =
+ const relayInAddress: IRelayInAddress | undefined =
(status.state === 'connecting' || status.state === 'connected') && status.details
- ? this._tunnelEndpointToRelayInAddress(status.details)
+ ? this.tunnelEndpointToRelayInAddress(status.details)
: undefined;
return (
<View style={styles.connect}>
- <Map style={styles.map} {...this._getMapProps()} />
+ <Map style={styles.map} {...this.getMapProps()} />
<View style={styles.container}>
{/* show spinner when connecting */}
- {this._showMarkerOrSpinner() === 'spinner' ? (
+ {this.showMarkerOrSpinner() === 'spinner' ? (
<View style={styles.status_icon}>
<ImageView source="icon-spinner" height={60} width={60} />
</View>
@@ -217,9 +135,11 @@ export default class Connect extends Component<Props> {
);
}
- // Private
+ private handleBuyMorePress = () => {
+ this.props.onExternalLink(links.purchase);
+ };
- headerBarStyle(): HeaderBarStyle {
+ private headerBarStyle(): HeaderBarStyle {
const { status } = this.props.connection;
switch (status.state) {
case 'disconnected':
@@ -247,7 +167,7 @@ export default class Connect extends Component<Props> {
}
}
- checkForErrors(): Error | undefined {
+ private checkForErrors(): Error | undefined {
// Offline?
if (!this.props.connection.isOnline) {
return new NoInternetError();
@@ -260,4 +180,83 @@ export default class Connect extends Component<Props> {
return undefined;
}
+
+ private getMapProps(): Map['props'] {
+ const {
+ longitude,
+ latitude,
+ status: { state },
+ } = this.props.connection;
+
+ // when the user location is known
+ if (typeof longitude === 'number' && typeof latitude === 'number') {
+ return {
+ center: [longitude, latitude],
+ // do not show the marker when connecting or reconnecting
+ showMarker: this.showMarkerOrSpinner() === 'marker',
+ markerStyle: this.getMarkerStyle(),
+ // zoom in when connected
+ zoomLevel: state === 'connected' ? ZoomLevel.low : ZoomLevel.medium,
+ // a magic offset to align marker with spinner
+ offset: [0, 123],
+ };
+ } else {
+ return {
+ center: [0, 0],
+ showMarker: false,
+ markerStyle: MarkerStyle.unsecure,
+ // show the world when user location is not known
+ zoomLevel: ZoomLevel.high,
+ // remove the offset since the marker is hidden
+ offset: [0, 0],
+ };
+ }
+ }
+
+ private getMarkerStyle(): MarkerStyle {
+ const { status } = this.props.connection;
+
+ switch (status.state) {
+ case 'connecting':
+ case 'connected':
+ return MarkerStyle.secure;
+ case 'blocked':
+ switch (status.details.reason) {
+ case 'set_firewall_policy_error':
+ return MarkerStyle.unsecure;
+ default:
+ return MarkerStyle.secure;
+ }
+ case 'disconnected':
+ return MarkerStyle.unsecure;
+ case 'disconnecting':
+ switch (status.details) {
+ case 'block':
+ case 'reconnect':
+ return MarkerStyle.secure;
+ case 'nothing':
+ return MarkerStyle.unsecure;
+ default:
+ throw new Error(`Invalid action after disconnection: ${status.details}`);
+ }
+ }
+ }
+
+ private showMarkerOrSpinner(): MarkerOrSpinner {
+ const status = this.props.connection.status;
+
+ return status.state === 'connecting' ||
+ (status.state === 'disconnecting' && status.details === 'reconnect')
+ ? 'spinner'
+ : 'marker';
+ }
+
+ private tunnelEndpointToRelayInAddress(tunnelEndpoint: ITunnelEndpoint): IRelayInAddress {
+ const socketAddr = parseSocketAddress(tunnelEndpoint.address);
+ return {
+ ip: socketAddr.host,
+ port: socketAddr.port,
+ protocol: tunnelEndpoint.protocol,
+ };
+ }
}
diff --git a/gui/packages/desktop/src/renderer/components/CountryRow.tsx b/gui/packages/desktop/src/renderer/components/CountryRow.tsx
index eac0c70c15..0a52cd02b4 100644
--- a/gui/packages/desktop/src/renderer/components/CountryRow.tsx
+++ b/gui/packages/desktop/src/renderer/components/CountryRow.tsx
@@ -1,23 +1,25 @@
+import { Accordion } from '@mullvad/components';
import * as React from 'react';
import { Component, Styles, Types, View } from 'reactxp';
-import { Accordion } from '@mullvad/components';
+import { colors } from '../../config.json';
+import { compareRelayLocation, RelayLocation } from '../../shared/daemon-rpc-types';
import * as Cell from './Cell';
+import ChevronButton from './ChevronButton';
import CityRow from './CityRow';
import RelayStatusIndicator from './RelayStatusIndicator';
-import ChevronButton from './ChevronButton';
-import { colors } from '../../config.json';
type CityRowElement = React.ReactElement<CityRow['props']>;
-type Props = {
+interface IProps {
name: string;
hasActiveRelays: boolean;
+ location: RelayLocation;
selected: boolean;
expanded: boolean;
- onSelect?: () => void;
- onExpand?: (value: boolean) => void;
+ onSelect?: (location: RelayLocation) => void;
+ onExpand?: (location: RelayLocation, value: boolean) => void;
children?: CityRowElement | CityRowElement[];
-};
+}
const styles = {
container: Styles.createViewStyle({
@@ -35,12 +37,8 @@ const styles = {
}),
};
-export default class CountryRow extends Component<Props> {
- shouldComponentUpdate(nextProps: Props) {
- return !CountryRow.compareProps(this.props, nextProps);
- }
-
- static compareProps(oldProps: Props, nextProps: Props) {
+export default class CountryRow extends Component<IProps> {
+ public static compareProps(oldProps: IProps, nextProps: IProps) {
if (React.Children.count(oldProps.children) !== React.Children.count(nextProps.children)) {
return false;
}
@@ -49,7 +47,8 @@ export default class CountryRow extends Component<Props> {
oldProps.name !== nextProps.name ||
oldProps.hasActiveRelays !== nextProps.hasActiveRelays ||
oldProps.selected !== nextProps.selected ||
- oldProps.expanded !== nextProps.expanded
+ oldProps.expanded !== nextProps.expanded ||
+ !compareRelayLocation(oldProps.location, nextProps.location)
) {
return false;
}
@@ -69,7 +68,11 @@ export default class CountryRow extends Component<Props> {
return true;
}
- render() {
+ public shouldComponentUpdate(nextProps: IProps) {
+ return !CountryRow.compareProps(this.props, nextProps);
+ }
+
+ public render() {
const childrenArray = React.Children.toArray(this.props.children || []) as CityRowElement[];
const numChildren = childrenArray.length;
const onlyChild = numChildren === 1 ? childrenArray[0] : undefined;
@@ -83,7 +86,7 @@ export default class CountryRow extends Component<Props> {
<Cell.CellButton
cellHoverStyle={this.props.selected ? styles.selected : undefined}
style={[styles.base, this.props.selected ? styles.selected : undefined]}
- onPress={this._handlePress}
+ onPress={this.handlePress}
disabled={!this.props.hasActiveRelays}>
<RelayStatusIndicator
isActive={this.props.hasActiveRelays}
@@ -91,7 +94,7 @@ export default class CountryRow extends Component<Props> {
/>
<Cell.Label>{this.props.name}</Cell.Label>
{hasChildren ? (
- <ChevronButton onPress={this._toggleCollapse} up={this.props.expanded} />
+ <ChevronButton onPress={this.toggleCollapse} up={this.props.expanded} />
) : null}
</Cell.CellButton>
@@ -100,16 +103,16 @@ export default class CountryRow extends Component<Props> {
);
}
- _toggleCollapse = (event: Types.SyntheticEvent) => {
+ private toggleCollapse = (event: Types.SyntheticEvent) => {
if (this.props.onExpand) {
- this.props.onExpand(!this.props.expanded);
+ this.props.onExpand(this.props.location, !this.props.expanded);
}
event.stopPropagation();
};
- _handlePress = () => {
+ private handlePress = () => {
if (this.props.onSelect) {
- this.props.onSelect();
+ this.props.onSelect(this.props.location);
}
};
}
diff --git a/gui/packages/desktop/src/renderer/components/CustomScrollbars.tsx b/gui/packages/desktop/src/renderer/components/CustomScrollbars.tsx
index 3e2ccef42f..c97c37341d 100644
--- a/gui/packages/desktop/src/renderer/components/CustomScrollbars.tsx
+++ b/gui/packages/desktop/src/renderer/components/CustomScrollbars.tsx
@@ -2,15 +2,15 @@ import * as React from 'react';
const AUTOHIDE_TIMEOUT = 1000;
-type Props = {
+interface IProps {
autoHide: boolean;
trackPadding: { x: number; y: number };
- onScroll?: (value: ScrollEvent) => void;
+ onScroll?: (value: IScrollEvent) => void;
style?: React.CSSProperties;
children?: React.ReactNode;
-};
+}
-type State = {
+interface IState {
canScroll: boolean;
showScrollIndicators: boolean;
showTrack: boolean;
@@ -21,24 +21,27 @@ type State = {
y: number;
};
isWide: boolean;
-};
+}
-export type ScrollEvent = { scrollLeft: number; scrollTop: number };
+export interface IScrollEvent {
+ scrollLeft: number;
+ scrollTop: number;
+}
export type ScrollPosition = 'top' | 'bottom' | 'middle';
-type ScrollbarUpdateContext = {
+interface IScrollbarUpdateContext {
size: boolean;
position: boolean;
-};
+}
-export default class CustomScrollbars extends React.Component<Props, State> {
- static defaultProps: Props = {
+export default class CustomScrollbars extends React.Component<IProps, IState> {
+ public static defaultProps: IProps = {
// auto-hide on macOS by default
autoHide: process.platform === 'darwin',
trackPadding: { x: 2, y: 2 },
};
- state = {
+ public state = {
canScroll: false,
showScrollIndicators: true,
showTrack: false,
@@ -48,21 +51,21 @@ export default class CustomScrollbars extends React.Component<Props, State> {
isWide: false,
};
- _scrollableRef = React.createRef<HTMLDivElement>();
- _trackRef = React.createRef<HTMLDivElement>();
- _thumbRef = React.createRef<HTMLDivElement>();
- _autoHideTimer?: NodeJS.Timeout;
+ private scrollableRef = React.createRef<HTMLDivElement>();
+ private trackRef = React.createRef<HTMLDivElement>();
+ private thumbRef = React.createRef<HTMLDivElement>();
+ private autoHideTimer?: NodeJS.Timeout;
- scrollTo(x: number, y: number) {
- const scrollable = this._scrollableRef.current;
+ public scrollTo(x: number, y: number) {
+ const scrollable = this.scrollableRef.current;
if (scrollable) {
scrollable.scrollLeft = x;
scrollable.scrollTop = y;
}
}
- scrollToElement(child: HTMLElement, scrollPosition: ScrollPosition) {
- const scrollable = this._scrollableRef.current;
+ public scrollToElement(child: HTMLElement, scrollPosition: ScrollPosition) {
+ const scrollable = this.scrollableRef.current;
if (scrollable) {
// throw if child is not a descendant of scroll view
if (!scrollable.contains(child)) {
@@ -71,13 +74,13 @@ export default class CustomScrollbars extends React.Component<Props, State> {
);
}
- const scrollTop = this._computeScrollTop(scrollable, child, scrollPosition);
+ const scrollTop = this.computeScrollTop(scrollable, child, scrollPosition);
this.scrollTo(0, scrollTop);
}
}
- componentDidMount() {
- this._updateScrollbarsHelper({
+ public componentDidMount() {
+ this.updateScrollbarsHelper({
position: true,
size: true,
});
@@ -88,11 +91,11 @@ export default class CustomScrollbars extends React.Component<Props, State> {
// show scroll indicators briefly when mounted
if (this.props.autoHide) {
- this._startAutoHide();
+ this.startAutoHide();
}
}
- shouldComponentUpdate(nextProps: Props, nextState: State) {
+ public shouldComponentUpdate(nextProps: IProps, nextState: IState) {
const prevProps = this.props;
const prevState = this.state;
@@ -110,23 +113,84 @@ export default class CustomScrollbars extends React.Component<Props, State> {
);
}
- componentWillUnmount() {
- this._stopAutoHide();
+ public componentWillUnmount() {
+ this.stopAutoHide();
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
document.removeEventListener('mousedown', this.handleMouseDown);
}
- componentDidUpdate() {
- this._updateScrollbarsHelper({
+ public componentDidUpdate() {
+ this.updateScrollbarsHelper({
position: true,
size: true,
});
}
- handleEnterTrack = () => {
- this._stopAutoHide();
+ public render() {
+ const {
+ autoHide: _autoHide,
+ trackPadding: _trackPadding,
+ onScroll: _onScroll,
+ children,
+ ...otherProps
+ } = this.props;
+ const showScrollbars = this.state.canScroll && this.state.showScrollIndicators;
+ const thumbAnimationClass = showScrollbars ? ' custom-scrollbars__thumb--visible' : '';
+ const thumbActiveClass =
+ this.state.isTrackHovered || this.state.isDragging ? ' custom-scrollbars__thumb--active' : '';
+ const thumbWideClass = this.state.isWide ? ' custom-scrollbars__thumb--wide' : '';
+ const trackClass =
+ showScrollbars && this.state.showTrack ? ' custom-scrollbars__track--visible' : '';
+
+ return (
+ <div {...otherProps} className="custom-scrollbars">
+ <div className={`custom-scrollbars__track ${trackClass}`} ref={this.trackRef} />
+ <div
+ className={`custom-scrollbars__thumb ${thumbWideClass} ${thumbActiveClass} ${thumbAnimationClass}`}
+ style={{ position: 'absolute', top: 0, right: 0 }}
+ ref={this.thumbRef}
+ />
+ <div
+ className="custom-scrollbars__scrollable"
+ style={{ overflow: 'auto' }}
+ onScroll={this.onScroll}
+ ref={this.scrollableRef}>
+ {children}
+ </div>
+ </div>
+ );
+ }
+
+ private onScroll = () => {
+ this.updateScrollbarsHelper({ position: true });
+
+ if (this.props.autoHide) {
+ this.ensureScrollbarsVisible();
+
+ // only auto-hide when scrolling with mousewheel
+ if (!this.state.isDragging) {
+ this.startAutoHide();
+ }
+ } else {
+ // only auto-shrink when scrolling with mousewheel
+ if (!this.state.isDragging) {
+ this.startAutoShrink();
+ }
+ }
+
+ const scrollView = this.scrollableRef.current;
+ if (scrollView && this.props.onScroll) {
+ this.props.onScroll({
+ scrollLeft: scrollView.scrollLeft,
+ scrollTop: scrollView.scrollTop,
+ });
+ }
+ };
+
+ private handleEnterTrack = () => {
+ this.stopAutoHide();
this.setState({
isTrackHovered: true,
showScrollIndicators: true,
@@ -135,7 +199,7 @@ export default class CustomScrollbars extends React.Component<Props, State> {
});
};
- handleLeaveTrack = () => {
+ private handleLeaveTrack = () => {
this.setState({
isTrackHovered: false,
});
@@ -143,30 +207,30 @@ export default class CustomScrollbars extends React.Component<Props, State> {
// do not hide the scrollbar if user is dragging a thumb but left the track area.
if (!this.state.isDragging) {
if (this.props.autoHide) {
- this._startAutoHide();
+ this.startAutoHide();
} else {
- this._startAutoShrink();
+ this.startAutoShrink();
}
}
};
- handleMouseDown = (event: MouseEvent) => {
- const thumb = this._thumbRef.current;
+ private handleMouseDown = (event: MouseEvent) => {
+ const thumb = this.thumbRef.current;
const cursorPosition = {
x: event.clientX,
y: event.clientY,
};
// initiate dragging when user clicked inside of thumb
- if (thumb && this._isPointInsideOfElement(thumb, cursorPosition)) {
+ if (thumb && this.isPointInsideOfElement(thumb, cursorPosition)) {
this.setState({
isDragging: true,
- dragStart: this._getPointRelativeToElement(thumb, cursorPosition),
+ dragStart: this.getPointRelativeToElement(thumb, cursorPosition),
});
}
};
- handleMouseUp = (event: MouseEvent) => {
+ private handleMouseUp = (event: MouseEvent) => {
if (!this.state.isDragging) {
return;
}
@@ -175,7 +239,7 @@ export default class CustomScrollbars extends React.Component<Props, State> {
isDragging: false,
});
- const track = this._trackRef.current;
+ const track = this.trackRef.current;
if (track) {
// Make sure to auto-hide the scrollbar if cursor ended up outside of scroll track
const cursorPosition = {
@@ -183,20 +247,20 @@ export default class CustomScrollbars extends React.Component<Props, State> {
y: event.clientY,
};
- if (!this._isPointInsideOfElement(track, cursorPosition)) {
+ if (!this.isPointInsideOfElement(track, cursorPosition)) {
if (this.props.autoHide) {
- this._startAutoHide();
+ this.startAutoHide();
} else {
- this._startAutoShrink();
+ this.startAutoShrink();
}
}
}
};
- handleMouseMove = (event: MouseEvent) => {
- const scrollable = this._scrollableRef.current;
- const thumb = this._thumbRef.current;
- const track = this._trackRef.current;
+ private handleMouseMove = (event: MouseEvent) => {
+ const scrollable = this.scrollableRef.current;
+ const thumb = this.thumbRef.current;
+ const track = this.trackRef.current;
const cursorPosition = {
x: event.clientX,
@@ -214,11 +278,11 @@ export default class CustomScrollbars extends React.Component<Props, State> {
const maxScrollTop = scrollHeight - visibleHeight;
// Map absolute cursor coordinate to point in scroll container
- const pointInScrollContainer = this._getPointRelativeToElement(scrollable, cursorPosition);
+ const pointInScrollContainer = this.getPointRelativeToElement(scrollable, cursorPosition);
// calculate the thumb boundary to make sure that the visual appearance of
// a thumb at the lowest point matches the bottom of scrollable view
- const thumbBoundary = this._computeTrackLength(scrollable) - thumb.clientHeight;
+ const thumbBoundary = this.computeTrackLength(scrollable) - thumb.clientHeight;
const thumbTop =
pointInScrollContainer.y - this.state.dragStart.y - this.props.trackPadding.y;
const newScrollTop = (thumbTop / thumbBoundary) * maxScrollTop;
@@ -227,7 +291,7 @@ export default class CustomScrollbars extends React.Component<Props, State> {
}
if (scrollable && track) {
- const intersectsTrack = this._isPointInsideOfElement(track, cursorPosition);
+ const intersectsTrack = this.isPointInsideOfElement(track, cursorPosition);
if (!this.state.isTrackHovered && intersectsTrack) {
this.handleEnterTrack();
@@ -237,68 +301,7 @@ export default class CustomScrollbars extends React.Component<Props, State> {
}
};
- render() {
- const {
- autoHide: _autoHide,
- trackPadding: _trackPadding,
- onScroll: _onScroll,
- children,
- ...otherProps
- } = this.props;
- const showScrollbars = this.state.canScroll && this.state.showScrollIndicators;
- const thumbAnimationClass = showScrollbars ? ' custom-scrollbars__thumb--visible' : '';
- const thumbActiveClass =
- this.state.isTrackHovered || this.state.isDragging ? ' custom-scrollbars__thumb--active' : '';
- const thumbWideClass = this.state.isWide ? ' custom-scrollbars__thumb--wide' : '';
- const trackClass =
- showScrollbars && this.state.showTrack ? ' custom-scrollbars__track--visible' : '';
-
- return (
- <div {...otherProps} className="custom-scrollbars">
- <div className={`custom-scrollbars__track ${trackClass}`} ref={this._trackRef} />
- <div
- className={`custom-scrollbars__thumb ${thumbWideClass} ${thumbActiveClass} ${thumbAnimationClass}`}
- style={{ position: 'absolute', top: 0, right: 0 }}
- ref={this._thumbRef}
- />
- <div
- className="custom-scrollbars__scrollable"
- style={{ overflow: 'auto' }}
- onScroll={this._onScroll}
- ref={this._scrollableRef}>
- {children}
- </div>
- </div>
- );
- }
-
- _onScroll = () => {
- this._updateScrollbarsHelper({ position: true });
-
- if (this.props.autoHide) {
- this._ensureScrollbarsVisible();
-
- // only auto-hide when scrolling with mousewheel
- if (!this.state.isDragging) {
- this._startAutoHide();
- }
- } else {
- // only auto-shrink when scrolling with mousewheel
- if (!this.state.isDragging) {
- this._startAutoShrink();
- }
- }
-
- const scrollView = this._scrollableRef.current;
- if (scrollView && this.props.onScroll) {
- this.props.onScroll({
- scrollLeft: scrollView.scrollLeft,
- scrollTop: scrollView.scrollTop,
- });
- }
- };
-
- _ensureScrollbarsVisible() {
+ private ensureScrollbarsVisible() {
if (!this.state.showScrollIndicators) {
this.setState({
showScrollIndicators: true,
@@ -306,12 +309,12 @@ export default class CustomScrollbars extends React.Component<Props, State> {
}
}
- _startAutoHide() {
- if (this._autoHideTimer) {
- clearTimeout(this._autoHideTimer);
+ private startAutoHide() {
+ if (this.autoHideTimer) {
+ clearTimeout(this.autoHideTimer);
}
- this._autoHideTimer = setTimeout(() => {
+ this.autoHideTimer = setTimeout(() => {
this.setState({
showScrollIndicators: false,
showTrack: false,
@@ -320,12 +323,12 @@ export default class CustomScrollbars extends React.Component<Props, State> {
}, AUTOHIDE_TIMEOUT);
}
- _startAutoShrink() {
- if (this._autoHideTimer) {
- clearTimeout(this._autoHideTimer);
+ private startAutoShrink() {
+ if (this.autoHideTimer) {
+ clearTimeout(this.autoHideTimer);
}
- this._autoHideTimer = setTimeout(() => {
+ this.autoHideTimer = setTimeout(() => {
this.setState({
showTrack: false,
isWide: false,
@@ -333,21 +336,21 @@ export default class CustomScrollbars extends React.Component<Props, State> {
}, AUTOHIDE_TIMEOUT);
}
- _stopAutoHide() {
- if (this._autoHideTimer) {
- clearTimeout(this._autoHideTimer);
- this._autoHideTimer = undefined;
+ private stopAutoHide() {
+ if (this.autoHideTimer) {
+ clearTimeout(this.autoHideTimer);
+ this.autoHideTimer = undefined;
}
}
- _isPointInsideOfElement(element: HTMLElement, point: { x: number; y: number }) {
+ private isPointInsideOfElement(element: HTMLElement, point: { x: number; y: number }) {
const rect = element.getBoundingClientRect();
return (
point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom
);
}
- _getPointRelativeToElement(element: HTMLElement, point: { x: number; y: number }) {
+ private getPointRelativeToElement(element: HTMLElement, point: { x: number; y: number }) {
const rect = element.getBoundingClientRect();
return {
x: point.x - rect.left,
@@ -355,12 +358,12 @@ export default class CustomScrollbars extends React.Component<Props, State> {
};
}
- _computeTrackLength(scrollable: HTMLElement) {
+ private computeTrackLength(scrollable: HTMLElement) {
return scrollable.offsetHeight - this.props.trackPadding.y * 2;
}
// Computes the position of child element within scrollable container
- _computeOffsetTop(scrollable: HTMLElement, child: HTMLElement) {
+ private computeOffsetTop(scrollable: HTMLElement, child: HTMLElement) {
let offsetTop = 0;
let node = child;
@@ -376,8 +379,12 @@ export default class CustomScrollbars extends React.Component<Props, State> {
return offsetTop;
}
- _computeScrollTop(scrollable: HTMLElement, child: HTMLElement, scrollPosition: ScrollPosition) {
- const offsetTop = this._computeOffsetTop(scrollable, child);
+ private computeScrollTop(
+ scrollable: HTMLElement,
+ child: HTMLElement,
+ scrollPosition: ScrollPosition,
+ ) {
+ const offsetTop = this.computeOffsetTop(scrollable, child);
switch (scrollPosition) {
case 'top':
@@ -391,7 +398,7 @@ export default class CustomScrollbars extends React.Component<Props, State> {
}
}
- _computeThumbPosition(scrollable: HTMLElement, thumb: HTMLElement) {
+ private computeThumbPosition(scrollable: HTMLElement, thumb: HTMLElement) {
// the content height of the scroll view
const scrollHeight = scrollable.scrollHeight;
@@ -409,7 +416,7 @@ export default class CustomScrollbars extends React.Component<Props, State> {
// calculate the thumb boundary to make sure that the visual appearance of
// a thumb at the lowest point matches the bottom of scrollable view
- const thumbBoundary = this._computeTrackLength(scrollable) - thumb.clientHeight;
+ const thumbBoundary = this.computeTrackLength(scrollable) - thumb.clientHeight;
// calculate thumb position based on scroll progress and thumb boundary
// adding vertical inset to adjust the thumb's appearance
@@ -421,7 +428,7 @@ export default class CustomScrollbars extends React.Component<Props, State> {
};
}
- _computeThumbHeight(scrollable: HTMLElement) {
+ private computeThumbHeight(scrollable: HTMLElement) {
const scrollHeight = scrollable.scrollHeight;
const visibleHeight = scrollable.offsetHeight;
@@ -431,21 +438,21 @@ export default class CustomScrollbars extends React.Component<Props, State> {
return Math.max(thumbHeight, 8);
}
- _updateScrollbarsHelper(updateFlags: Partial<ScrollbarUpdateContext>) {
- const scrollable = this._scrollableRef.current;
- const thumb = this._thumbRef.current;
+ private updateScrollbarsHelper(updateFlags: Partial<IScrollbarUpdateContext>) {
+ const scrollable = this.scrollableRef.current;
+ const thumb = this.thumbRef.current;
if (scrollable && thumb) {
- this._updateScrollbars(scrollable, thumb, updateFlags);
+ this.updateScrollbars(scrollable, thumb, updateFlags);
}
}
- _updateScrollbars(
+ private updateScrollbars(
scrollable: HTMLElement,
thumb: HTMLElement,
- context: Partial<ScrollbarUpdateContext>,
+ context: Partial<IScrollbarUpdateContext>,
) {
if (context.size) {
- const thumbHeight = this._computeThumbHeight(scrollable);
+ const thumbHeight = this.computeThumbHeight(scrollable);
thumb.style.setProperty('height', thumbHeight + 'px');
// hide thumb when there is nothing to scroll
@@ -455,14 +462,14 @@ export default class CustomScrollbars extends React.Component<Props, State> {
// flash the scroll indicators when the view becomes scrollable
if (this.props.autoHide && canScroll) {
- this._startAutoHide();
- this._ensureScrollbarsVisible();
+ this.startAutoHide();
+ this.ensureScrollbarsVisible();
}
}
}
if (context.position) {
- const { x, y } = this._computeThumbPosition(scrollable, thumb);
+ const { x, y } = this.computeThumbPosition(scrollable, thumb);
thumb.style.setProperty('transform', `translate(${x}px, ${y}px)`);
}
}
diff --git a/gui/packages/desktop/src/renderer/components/Launch.tsx b/gui/packages/desktop/src/renderer/components/Launch.tsx
index 5841113fce..facf5942e5 100644
--- a/gui/packages/desktop/src/renderer/components/Launch.tsx
+++ b/gui/packages/desktop/src/renderer/components/Launch.tsx
@@ -1,8 +1,8 @@
-import * as React from 'react';
-import { Component, Styles, View, Text } from 'reactxp';
import { ImageView, SettingsBarButton } from '@mullvad/components';
-import { Layout, Container, Header } from './Layout';
+import * as React from 'react';
+import { Component, Styles, Text, View } from 'reactxp';
import { colors } from '../../config.json';
+import { Container, Header, Layout } from './Layout';
const styles = {
container: Styles.createViewStyle({
@@ -32,12 +32,12 @@ const styles = {
}),
};
-type Props = {
+interface IProps {
openSettings: () => void;
-};
+}
-export default class Launch extends Component<Props> {
- render() {
+export default class Launch extends Component<IProps> {
+ public render() {
return (
<Layout>
<Header>
diff --git a/gui/packages/desktop/src/renderer/components/Layout.tsx b/gui/packages/desktop/src/renderer/components/Layout.tsx
index efe3b3ff11..e1e07bd072 100644
--- a/gui/packages/desktop/src/renderer/components/Layout.tsx
+++ b/gui/packages/desktop/src/renderer/components/Layout.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
-import { View, Component } from 'reactxp';
import { HeaderBar } from '@mullvad/components';
+import * as React from 'react';
+import { Component, View } from 'reactxp';
import styles from './LayoutStyles';
export class Header extends Component<HeaderBar['props']> {
- static defaultProps = HeaderBar.defaultProps;
+ public static defaultProps = HeaderBar.defaultProps;
- render() {
+ public render() {
return (
<View style={[styles.header, this.props.style]}>
<HeaderBar barStyle={this.props.barStyle}>{this.props.children}</HeaderBar>
@@ -15,20 +15,20 @@ export class Header extends Component<HeaderBar['props']> {
}
}
-type ContainerProps = {
+interface IContainerProps {
children: React.ReactNode;
-};
-export class Container extends Component<ContainerProps> {
- render() {
+}
+export class Container extends Component<IContainerProps> {
+ public render() {
return <View style={styles.container}>{this.props.children}</View>;
}
}
-type LayoutProps = {
- children: Array<React.ReactNode> | React.ReactNode;
-};
-export class Layout extends Component<LayoutProps> {
- render() {
+interface ILayoutProps {
+ children: React.ReactNode;
+}
+export class Layout extends Component<ILayoutProps> {
+ public render() {
return <View style={styles.layout}>{this.props.children}</View>;
}
}
diff --git a/gui/packages/desktop/src/renderer/components/Login.tsx b/gui/packages/desktop/src/renderer/components/Login.tsx
index 3af814a347..4327bc1f60 100644
--- a/gui/packages/desktop/src/renderer/components/Login.tsx
+++ b/gui/packages/desktop/src/renderer/components/Login.tsx
@@ -1,18 +1,18 @@
+import { Accordion, Brand, ImageView, SettingsBarButton } from '@mullvad/components';
import * as React from 'react';
-import { Component, Text, TextInput, View, Animated, Styles, UserInterface, Types } from 'reactxp';
-import { Layout, Container, Header } from './Layout';
-import { Accordion, ImageView, SettingsBarButton, Brand } from '@mullvad/components';
-import * as Cell from './Cell';
+import { Animated, Component, Styles, Text, TextInput, Types, UserInterface, View } from 'reactxp';
+import { colors, links } from '../../config.json';
import * as AppButton from './AppButton';
+import * as Cell from './Cell';
+import { Container, Header, Layout } from './Layout';
import styles from './LoginStyles';
-import { colors, links } from '../../config.json';
-import { LoginState } from '../redux/account/reducers';
import { AccountToken } from '../../shared/daemon-rpc-types';
+import { LoginState } from '../redux/account/reducers';
-type Props = {
+interface IProps {
accountToken?: AccountToken;
- accountHistory: Array<AccountToken>;
+ accountHistory: AccountToken[];
loginError?: Error;
loginState: LoginState;
openSettings?: () => void;
@@ -21,77 +21,77 @@ type Props = {
resetLoginError: () => void;
updateAccountToken: (accountToken: AccountToken) => void;
removeAccountTokenFromHistory: (accountToken: AccountToken) => Promise<void>;
-};
+}
-type State = {
+interface IState {
isActive: boolean;
-};
+}
const MIN_ACCOUNT_TOKEN_LENGTH = 10;
-export default class Login extends Component<Props, State> {
- state = {
+export default class Login extends Component<IProps, IState> {
+ public state: IState = {
isActive: true,
};
- _accountInput = React.createRef<TextInput>();
- _shouldResetLoginError = false;
+ private accountInput = React.createRef<TextInput>();
+ private shouldResetLoginError = false;
- _showsFooter = true;
- _footerAnimatedValue = Animated.createValue(0);
- _footerAnimation?: Types.Animated.CompositeAnimation;
- _footerAnimationStyle: Types.AnimatedViewStyleRuleSet;
- _footerRef = React.createRef<Animated.View>();
+ private showsFooter = true;
+ private footerAnimatedValue = Animated.createValue(0);
+ private footerAnimation?: Types.Animated.CompositeAnimation;
+ private footerAnimationStyle: Types.AnimatedViewStyleRuleSet;
+ private footerRef = React.createRef<Animated.View>();
- _isLoginButtonActive = false;
- _loginButtonAnimatedValue = Animated.createValue(0);
- _loginButtonAnimation?: Types.Animated.CompositeAnimation;
- _loginButtonAnimationStyle: Types.AnimatedViewStyleRuleSet;
+ private isLoginButtonActive = false;
+ private loginButtonAnimatedValue = Animated.createValue(0);
+ private loginButtonAnimation?: Types.Animated.CompositeAnimation;
+ private loginButtonAnimationStyle: Types.AnimatedViewStyleRuleSet;
- constructor(props: Props) {
+ constructor(props: IProps) {
super(props);
if (props.loginState === 'failed') {
- this._shouldResetLoginError = true;
+ this.shouldResetLoginError = true;
}
- this._footerAnimationStyle = Styles.createAnimatedViewStyle({
- transform: [{ translateY: this._footerAnimatedValue }],
+ this.footerAnimationStyle = Styles.createAnimatedViewStyle({
+ transform: [{ translateY: this.footerAnimatedValue }],
});
- this._loginButtonAnimationStyle = Styles.createAnimatedViewStyle({
+ this.loginButtonAnimationStyle = Styles.createAnimatedViewStyle({
backgroundColor: Animated.interpolate(
- this._loginButtonAnimatedValue,
+ this.loginButtonAnimatedValue,
[0.0, 1.0],
[colors.white, colors.green],
),
});
}
- componentDidMount() {
- this._setFooterVisibility(this._shouldShowFooter());
+ public componentDidMount() {
+ this.setFooterVisibility(this.shouldShowFooter());
}
- componentDidUpdate(prevProps: Props, _prevState: State) {
+ public componentDidUpdate(prevProps: IProps, _prevState: IState) {
if (
this.props.loginState !== prevProps.loginState &&
this.props.loginState === 'failed' &&
- !this._shouldResetLoginError
+ !this.shouldResetLoginError
) {
- this._shouldResetLoginError = true;
+ this.shouldResetLoginError = true;
// focus on login field when failed to log in
- const accountInput = this._accountInput.current;
+ const accountInput = this.accountInput.current;
if (accountInput) {
accountInput.focus();
}
}
- this._setLoginButtonActive(this._shouldActivateLoginButton());
- this._setFooterVisibility(this._shouldShowFooter());
+ this.setLoginButtonActive(this.shouldActivateLoginButton());
+ this.setFooterVisibility(this.shouldShowFooter());
}
- render() {
+ public render() {
return (
<Layout>
<Header>
@@ -100,29 +100,29 @@ export default class Login extends Component<Props, State> {
</Header>
<Container>
<View style={styles.login_form}>
- {this._getStatusIcon()}
- <Text style={styles.title}>{this._formTitle()}</Text>
+ {this.getStatusIcon()}
+ <Text style={styles.title}>{this.formTitle()}</Text>
- {this._createLoginForm()}
+ {this.createLoginForm()}
</View>
<Animated.View
- ref={this._footerRef}
- style={[styles.login_footer, this._footerAnimationStyle]}>
- {this._createFooter()}
+ ref={this.footerRef}
+ style={[styles.login_footer, this.footerAnimationStyle]}>
+ {this.createFooter()}
</Animated.View>
</Container>
</Layout>
);
}
- _onCreateAccount = () => this.props.openExternalLink(links.createAccount);
+ private onCreateAccount = () => this.props.openExternalLink(links.createAccount);
- _onFocus = () => {
+ private onFocus = () => {
this.setState({ isActive: true });
};
- _onBlur = (e: Types.SyntheticEvent) => {
+ private onBlur = (e: Types.SyntheticEvent) => {
// TOOD: relatedTarget is not exposed by ReactXP and may not work on non-web platforms.
// Find a workaround.
// @ts-ignore
@@ -130,8 +130,8 @@ export default class Login extends Component<Props, State> {
// restore focus if click happened within dropdown
if (relatedTarget) {
- if (this._accountInput.current) {
- this._accountInput.current.focus();
+ if (this.accountInput.current) {
+ this.accountInput.current.focus();
}
return;
}
@@ -139,65 +139,65 @@ export default class Login extends Component<Props, State> {
this.setState({ isActive: false });
};
- async _setLoginButtonActive(isActive: boolean) {
- if (this._isLoginButtonActive === isActive) {
+ private async setLoginButtonActive(isActive: boolean) {
+ if (this.isLoginButtonActive === isActive) {
return;
}
- const animation = Animated.timing(this._loginButtonAnimatedValue, {
+ const animation = Animated.timing(this.loginButtonAnimatedValue, {
toValue: isActive ? 1 : 0,
easing: Animated.Easing.Linear(),
duration: 250,
});
- const oldAnimation = this._loginButtonAnimation;
+ const oldAnimation = this.loginButtonAnimation;
if (oldAnimation) {
oldAnimation.stop();
}
animation.start();
- this._loginButtonAnimation = animation;
- this._isLoginButtonActive = isActive;
+ this.loginButtonAnimation = animation;
+ this.isLoginButtonActive = isActive;
}
- async _setFooterVisibility(show: boolean) {
- if (this._showsFooter === show || !this._footerRef.current) {
+ private async setFooterVisibility(show: boolean) {
+ if (this.showsFooter === show || !this.footerRef.current) {
return;
}
- this._showsFooter = show;
+ this.showsFooter = show;
- const layout = await UserInterface.measureLayoutRelativeToWindow(this._footerRef.current);
+ const layout = await UserInterface.measureLayoutRelativeToWindow(this.footerRef.current);
const value = show ? 0 : layout.height;
- const animation = Animated.timing(this._footerAnimatedValue, {
+ const animation = Animated.timing(this.footerAnimatedValue, {
toValue: value,
easing: Animated.Easing.InOut(),
duration: 250,
});
- const oldAnimation = this._footerAnimation;
+ const oldAnimation = this.footerAnimation;
if (oldAnimation) {
oldAnimation.stop();
}
animation.start();
- this._footerAnimation = animation;
+ this.footerAnimation = animation;
}
- _onSubmit = () => {
+ private onSubmit = () => {
const accountToken = this.props.accountToken;
if (accountToken && accountToken.length >= MIN_ACCOUNT_TOKEN_LENGTH) {
this.props.login(accountToken);
}
};
- _onInputChange = (value: string) => {
+ private onInputChange = (value: string) => {
// reset error when user types in the new account number
- if (this._shouldResetLoginError) {
- this._shouldResetLoginError = false;
+ if (this.shouldResetLoginError) {
+ this.shouldResetLoginError = false;
this.props.resetLoginError();
}
@@ -206,7 +206,7 @@ export default class Login extends Component<Props, State> {
this.props.updateAccountToken(accountToken);
};
- _formTitle() {
+ private formTitle() {
switch (this.props.loginState) {
case 'logging in':
return 'Logging in...';
@@ -219,7 +219,7 @@ export default class Login extends Component<Props, State> {
}
}
- _formSubtitle() {
+ private formSubtitle() {
const { loginState, loginError } = this.props;
switch (loginState) {
case 'failed':
@@ -233,8 +233,8 @@ export default class Login extends Component<Props, State> {
}
}
- _getStatusIcon() {
- const statusIconPath = this._getStatusIconPath();
+ private getStatusIcon() {
+ const statusIconPath = this.getStatusIconPath();
return (
<View style={styles.status_icon}>
{statusIconPath ? <ImageView source={statusIconPath} height={48} width={48} /> : null}
@@ -242,7 +242,7 @@ export default class Login extends Component<Props, State> {
);
}
- _getStatusIconPath(): string | undefined {
+ private getStatusIconPath(): string | undefined {
switch (this.props.loginState) {
case 'logging in':
return 'icon-spinner';
@@ -255,7 +255,7 @@ export default class Login extends Component<Props, State> {
}
}
- _accountInputGroupStyles(): Types.ViewStyleRuleSet[] {
+ private accountInputGroupStyles(): Types.ViewStyleRuleSet[] {
const classes = [styles.account_input_group];
if (this.state.isActive) {
classes.push(styles.account_input_group__active);
@@ -274,7 +274,7 @@ export default class Login extends Component<Props, State> {
return classes;
}
- _accountInputButtonStyles() {
+ private accountInputButtonStyles() {
const classes: Array<
Types.StyleRuleSet<Types.AnimatedViewStyle> | Types.StyleRuleSet<Types.ViewStyle>
> = [styles.input_button];
@@ -283,12 +283,12 @@ export default class Login extends Component<Props, State> {
classes.push(styles.input_button__invisible);
}
- classes.push(this._loginButtonAnimationStyle);
+ classes.push(this.loginButtonAnimationStyle);
return classes;
}
- _accountInputArrowStyles(): Types.ViewStyleRuleSet[] {
+ private accountInputArrowStyles(): Types.ViewStyleRuleSet[] {
const { loginState } = this.props;
const classes = [styles.input_arrow];
@@ -299,7 +299,7 @@ export default class Login extends Component<Props, State> {
return classes;
}
- _shouldActivateLoginButton(): boolean {
+ private shouldActivateLoginButton(): boolean {
const { accountToken } = this.props;
if (accountToken && accountToken.length >= MIN_ACCOUNT_TOKEN_LENGTH) {
return true;
@@ -307,36 +307,34 @@ export default class Login extends Component<Props, State> {
return false;
}
- _shouldEnableAccountInput() {
+ private shouldEnableAccountInput() {
// enable account input always except when "logging in" or "logged in"
return this.props.loginState !== 'logging in' && this.props.loginState !== 'ok';
}
- _shouldShowAccountHistory() {
+ private shouldShowAccountHistory() {
return (
- this._shouldEnableAccountInput() &&
- this.state.isActive &&
- this.props.accountHistory.length > 0
+ this.shouldEnableAccountInput() && this.state.isActive && this.props.accountHistory.length > 0
);
}
- _shouldShowFooter() {
+ private shouldShowFooter() {
return (
(this.props.loginState === 'none' || this.props.loginState === 'failed') &&
- !this._shouldShowAccountHistory()
+ !this.shouldShowAccountHistory()
);
}
- _onSelectAccountFromHistory = (accountToken: string) => {
+ private onSelectAccountFromHistory = (accountToken: string) => {
this.props.updateAccountToken(accountToken);
this.props.login(accountToken);
};
- _onRemoveAccountFromHistory = (accountToken: string) => {
- this._removeAccountFromHistory(accountToken);
+ private onRemoveAccountFromHistory = (accountToken: string) => {
+ this.removeAccountFromHistory(accountToken);
};
- async _removeAccountFromHistory(accountToken: AccountToken) {
+ private async removeAccountFromHistory(accountToken: AccountToken) {
try {
await this.props.removeAccountTokenFromHistory(accountToken);
@@ -346,11 +344,11 @@ export default class Login extends Component<Props, State> {
}
}
- _createLoginForm() {
+ private createLoginForm() {
return (
<View>
- <Text style={styles.subtitle}>{this._formSubtitle()}</Text>
- <View style={this._accountInputGroupStyles()}>
+ <Text style={styles.subtitle}>{this.formSubtitle()}</Text>
+ <View style={this.accountInputGroupStyles()}>
<View style={styles.account_input_backdrop}>
<TextInput
style={styles.account_input_textfield}
@@ -358,19 +356,19 @@ export default class Login extends Component<Props, State> {
placeholderTextColor={colors.blue40}
value={this.props.accountToken || ''}
autoCorrect={false}
- editable={this._shouldEnableAccountInput()}
- onFocus={this._onFocus}
- onBlur={this._onBlur}
- onChangeText={this._onInputChange}
- onSubmitEditing={this._onSubmit}
+ editable={this.shouldEnableAccountInput()}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
+ onChangeText={this.onInputChange}
+ onSubmitEditing={this.onSubmit}
returnKeyType="done"
keyboardType="numeric"
autoFocus={true}
- ref={this._accountInput}
+ ref={this.accountInput}
/>
- <Animated.View style={this._accountInputButtonStyles()} onPress={this._onSubmit}>
+ <Animated.View style={this.accountInputButtonStyles()} onPress={this.onSubmit}>
<ImageView
- style={this._accountInputArrowStyles()}
+ style={this.accountInputArrowStyles()}
source="icon-arrow"
height={16}
width={24}
@@ -378,12 +376,12 @@ export default class Login extends Component<Props, State> {
/>
</Animated.View>
</View>
- <Accordion expanded={this._shouldShowAccountHistory()}>
+ <Accordion expanded={this.shouldShowAccountHistory()}>
{
<AccountDropdown
items={this.props.accountHistory.slice().reverse()}
- onSelect={this._onSelectAccountFromHistory}
- onRemove={this._onRemoveAccountFromHistory}
+ onSelect={this.onSelectAccountFromHistory}
+ onRemove={this.onRemoveAccountFromHistory}
/>
}
</Accordion>
@@ -392,11 +390,11 @@ export default class Login extends Component<Props, State> {
);
}
- _createFooter() {
+ private createFooter() {
return (
<View>
<Text style={styles.login_footer__prompt}>{"Don't have an account number?"}</Text>
- <AppButton.BlueButton onPress={this._onCreateAccount}>
+ <AppButton.BlueButton onPress={this.onCreateAccount}>
<AppButton.Label>Create account</AppButton.Label>
<AppButton.Icon source="icon-extLink" height={16} width={16} />
</AppButton.BlueButton>
@@ -405,14 +403,14 @@ export default class Login extends Component<Props, State> {
}
}
-type AccountDropdownProps = {
- items: Array<AccountToken>;
+interface IAccountDropdownProps {
+ items: AccountToken[];
onSelect: (value: AccountToken) => void;
onRemove: (value: AccountToken) => void;
-};
+}
-class AccountDropdown extends React.Component<AccountDropdownProps> {
- render() {
+class AccountDropdown extends Component<IAccountDropdownProps> {
+ public render() {
const uniqueItems = [...new Set(this.props.items)];
return (
<View>
@@ -430,15 +428,15 @@ class AccountDropdown extends React.Component<AccountDropdownProps> {
}
}
-type AccountDropdownItemProps = {
+interface IAccountDropdownItemProps {
label: string;
value: AccountToken;
onRemove: (value: AccountToken) => void;
onSelect: (value: AccountToken) => void;
-};
+}
-class AccountDropdownItem extends React.Component<AccountDropdownItemProps> {
- render() {
+class AccountDropdownItem extends Component<IAccountDropdownItemProps> {
+ public render() {
return (
<View>
<View style={styles.account_dropdown__spacer} />
@@ -449,7 +447,7 @@ class AccountDropdownItem extends React.Component<AccountDropdownItemProps> {
textStyle={styles.account_dropdown__label}
containerStyle={styles.account_dropdown__label_container}
cellHoverTextStyle={styles.account_dropdown__label_hover}
- onPress={() => this.props.onSelect(this.props.value)}>
+ onPress={this.handleSelect}>
{this.props.label}
</Cell.Label>
<ImageView
@@ -459,10 +457,18 @@ class AccountDropdownItem extends React.Component<AccountDropdownItemProps> {
source="icon-close-sml"
height={16}
width={16}
- onPress={() => this.props.onRemove(this.props.value)}
+ onPress={this.handleRemove}
/>
</Cell.CellButton>
</View>
);
}
+
+ private handleSelect = () => {
+ this.props.onSelect(this.props.value);
+ };
+
+ private handleRemove = () => {
+ this.props.onRemove(this.props.value);
+ };
}
diff --git a/gui/packages/desktop/src/renderer/components/Map.tsx b/gui/packages/desktop/src/renderer/components/Map.tsx
index 5640c00676..4fc77cb739 100644
--- a/gui/packages/desktop/src/renderer/components/Map.tsx
+++ b/gui/packages/desktop/src/renderer/components/Map.tsx
@@ -31,34 +31,34 @@ interface IState {
}
export default class Map extends Component<IProps, IState> {
- state: IState = {
+ public state: IState = {
bounds: {
width: 0,
height: 0,
},
};
- render() {
+ public render() {
const { width, height } = this.state.bounds;
const readyToRenderTheMap = width > 0 && height > 0;
return (
- <View style={this.props.style} onLayout={this._onLayout}>
+ <View style={this.props.style} onLayout={this.onLayout}>
{readyToRenderTheMap && (
<SvgMap
width={width}
height={height}
center={this.props.center}
offset={this.props.offset}
- zoomLevel={this._zoomLevel(this.props.zoomLevel)}
+ zoomLevel={this.zoomLevel(this.props.zoomLevel)}
showMarker={this.props.showMarker}
- markerImagePath={this._markerImage(this.props.markerStyle)}
+ markerImagePath={this.markerImage(this.props.markerStyle)}
/>
)}
</View>
);
}
- shouldComponentUpdate(nextProps: IProps, nextState: IState) {
+ public shouldComponentUpdate(nextProps: IProps, nextState: IState) {
const oldProps = this.props;
const oldState = this.state;
return (
@@ -74,7 +74,7 @@ export default class Map extends Component<IProps, IState> {
);
}
- _onLayout = (layoutInfo: Types.ViewOnLayoutEvent) => {
+ private onLayout = (layoutInfo: Types.ViewOnLayoutEvent) => {
this.setState({
bounds: {
width: layoutInfo.width,
@@ -84,7 +84,7 @@ export default class Map extends Component<IProps, IState> {
};
// TODO: Remove zoom level in favor of center + coordinate span
- _zoomLevel(variant: ZoomLevel) {
+ private zoomLevel(variant: ZoomLevel) {
switch (variant) {
case ZoomLevel.high:
return 1;
@@ -95,7 +95,7 @@ export default class Map extends Component<IProps, IState> {
}
}
- _markerImage(style: MarkerStyle): string {
+ private markerImage(style: MarkerStyle): string {
switch (style) {
case MarkerStyle.secure:
return '../../assets/images/location-marker-secure.svg';
diff --git a/gui/packages/desktop/src/renderer/components/NavigationBar.tsx b/gui/packages/desktop/src/renderer/components/NavigationBar.tsx
index 6708e345f7..98e2a367c7 100644
--- a/gui/packages/desktop/src/renderer/components/NavigationBar.tsx
+++ b/gui/packages/desktop/src/renderer/components/NavigationBar.tsx
@@ -1,8 +1,8 @@
-import * as React from 'react';
-import { Animated, Button, Component, Text, Types, View, Styles, UserInterface } from 'reactxp';
import { ImageView } from '@mullvad/components';
-import CustomScrollbars, { ScrollEvent } from './CustomScrollbars';
+import * as React from 'react';
+import { Animated, Button, Component, Styles, Text, Types, UserInterface, View } from 'reactxp';
import { colors } from '../../config.json';
+import CustomScrollbars, { IScrollEvent } from './CustomScrollbars';
const styles = {
navigationBar: {
@@ -78,51 +78,53 @@ const styles = {
},
};
-type NavigationScrollContextValue = {
+interface INavigationScrollContextValue {
scrollTop: number;
- onScroll: (event: ScrollEvent) => void;
-};
+ onScroll: (event: IScrollEvent) => void;
+}
-const NavigationScrollContext = React.createContext<NavigationScrollContextValue>({
+const NavigationScrollContext = React.createContext<INavigationScrollContextValue>({
scrollTop: 0,
- onScroll: (_event: ScrollEvent) => {},
+ onScroll: (_event: IScrollEvent) => {
+ // no-op
+ },
});
export class NavigationContainer extends Component {
- state = {
+ public state = {
scrollTop: 0,
};
- _onScroll = (event: ScrollEvent) => {
- this.setState({
- scrollTop: event.scrollTop,
- });
- };
-
- render() {
+ public render() {
return (
<NavigationScrollContext.Provider
- value={{ scrollTop: this.state.scrollTop, onScroll: this._onScroll }}>
+ value={{ scrollTop: this.state.scrollTop, onScroll: this.onScroll }}>
{this.props.children}
</NavigationScrollContext.Provider>
);
}
+
+ private onScroll = (event: IScrollEvent) => {
+ this.setState({
+ scrollTop: event.scrollTop,
+ });
+ };
}
-type NavigationScrollbarsProps = {
- onScroll?: (value: ScrollEvent) => void;
+interface INavigationScrollbarsProps {
+ onScroll?: (value: IScrollEvent) => void;
style?: React.CSSProperties;
children?: React.ReactNode;
-};
+}
export const NavigationScrollbars = React.forwardRef(function NavigationScrollbarsT(
- props: NavigationScrollbarsProps,
+ props: INavigationScrollbarsProps,
ref?: React.Ref<CustomScrollbars>,
) {
return (
<NavigationScrollContext.Consumer>
{(context) => {
const { style, children, ...otherProps } = props;
- const wrappedOnScroll = (scroll: ScrollEvent) => {
+ const wrappedOnScroll = (scroll: IScrollEvent) => {
context.onScroll(scroll);
if (otherProps.onScroll) {
@@ -140,14 +142,14 @@ export const NavigationScrollbars = React.forwardRef(function NavigationScrollba
);
});
-type PrivateTitleBarItemProps = {
+interface IPrivateTitleBarItemProps {
visible: boolean;
titleAdjustment: number;
children?: React.ReactText;
-};
+}
-class PrivateTitleBarItem extends Component<PrivateTitleBarItemProps> {
- shouldComponentUpdate(nextProps: PrivateTitleBarItemProps) {
+class PrivateTitleBarItem extends Component<IPrivateTitleBarItemProps> {
+ public shouldComponentUpdate(nextProps: IPrivateTitleBarItemProps) {
return (
this.props.visible !== nextProps.visible ||
this.props.titleAdjustment !== nextProps.titleAdjustment ||
@@ -155,7 +157,7 @@ class PrivateTitleBarItem extends Component<PrivateTitleBarItemProps> {
);
}
- render() {
+ public render() {
const titleAdjustment = this.props.titleAdjustment;
const titleAdjustmentStyle = Styles.createViewStyle(
{
@@ -175,50 +177,50 @@ class PrivateTitleBarItem extends Component<PrivateTitleBarItemProps> {
}
}
-type PrivateBarItemAnimationContainerProps = {
+interface IPrivateBarItemAnimationContainerProps {
visible: boolean;
children?: React.ReactNode;
-};
+}
-class PrivateBarItemAnimationContainer extends Component<PrivateBarItemAnimationContainerProps> {
- _opacityValue: Animated.Value;
- _opacityStyle: Types.AnimatedViewStyleRuleSet;
- _animation?: Types.Animated.CompositeAnimation;
+class PrivateBarItemAnimationContainer extends Component<IPrivateBarItemAnimationContainerProps> {
+ private opacityValue: Animated.Value;
+ private opacityStyle: Types.AnimatedViewStyleRuleSet;
+ private animation?: Types.Animated.CompositeAnimation;
- constructor(props: PrivateBarItemAnimationContainerProps) {
+ constructor(props: IPrivateBarItemAnimationContainerProps) {
super(props);
- this._opacityValue = Animated.createValue(props.visible ? 1 : 0);
- this._opacityStyle = Styles.createAnimatedViewStyle({
- opacity: this._opacityValue,
+ this.opacityValue = Animated.createValue(props.visible ? 1 : 0);
+ this.opacityStyle = Styles.createAnimatedViewStyle({
+ opacity: this.opacityValue,
});
}
- shouldComponentUpdate(nextProps: PrivateBarItemAnimationContainerProps) {
+ public shouldComponentUpdate(nextProps: IPrivateBarItemAnimationContainerProps) {
return this.props.visible !== nextProps.visible || this.props.children !== nextProps.children;
}
- componentDidUpdate() {
- this._animateOpacity(this.props.visible);
+ public componentDidUpdate() {
+ this.animateOpacity(this.props.visible);
}
- componentWillUnmount() {
- if (this._animation) {
- this._animation.stop();
+ public componentWillUnmount() {
+ if (this.animation) {
+ this.animation.stop();
}
}
- render() {
- return <Animated.View style={this._opacityStyle}>{this.props.children}</Animated.View>;
+ public render() {
+ return <Animated.View style={this.opacityStyle}>{this.props.children}</Animated.View>;
}
- _animateOpacity(visible: boolean) {
- const oldAnimation = this._animation;
+ private animateOpacity(visible: boolean) {
+ const oldAnimation = this.animation;
if (oldAnimation) {
oldAnimation.stop();
}
- const animation = Animated.timing(this._opacityValue, {
+ const animation = Animated.timing(this.opacityValue, {
toValue: visible ? 1 : 0,
easing: Animated.Easing.InOut(),
duration: 250,
@@ -226,16 +228,16 @@ class PrivateBarItemAnimationContainer extends Component<PrivateBarItemAnimation
animation.start();
- this._animation = animation;
+ this.animation = animation;
}
}
-type NavigationBarProps = {
+interface INavigationBarProps {
children?: React.ReactNode;
-};
+}
export const NavigationBar = React.forwardRef(function NavigationBarT(
- props: NavigationBarProps,
+ props: INavigationBarProps,
ref?: React.Ref<PrivateNavigationBar>,
) {
return (
@@ -249,16 +251,16 @@ export const NavigationBar = React.forwardRef(function NavigationBarT(
);
});
-type PrivateNavigationBarProps = {
+interface IPrivateNavigationBarProps {
scrollTop: number;
children?: React.ReactNode;
-};
+}
-type PrivateNavigationBarState = {
+interface IPrivateNavigationBarState {
titleAdjustment: number;
showsBarSeparator: boolean;
showsBarTitle: boolean;
-};
+}
const PrivateTitleBarItemContext = React.createContext({
titleAdjustment: 0,
@@ -266,22 +268,17 @@ const PrivateTitleBarItemContext = React.createContext({
titleRef: React.createRef<PrivateTitleBarItem>(),
});
-class PrivateNavigationBar extends Component<PrivateNavigationBarProps, PrivateNavigationBarState> {
- static defaultProps: Partial<PrivateNavigationBarProps> = {
+class PrivateNavigationBar extends Component<
+ IPrivateNavigationBarProps,
+ IPrivateNavigationBarState
+> {
+ public static defaultProps: Partial<IPrivateNavigationBarProps> = {
scrollTop: 0,
};
- state: PrivateNavigationBarState = {
- titleAdjustment: 0,
- showsBarSeparator: false,
- showsBarTitle: false,
- };
-
- _titleViewRef = React.createRef<PrivateTitleBarItem>();
-
- static getDerivedStateFromProps(
- props: PrivateNavigationBarProps,
- state: PrivateNavigationBarState,
+ public static getDerivedStateFromProps(
+ props: IPrivateNavigationBarProps,
+ state: IPrivateNavigationBarState,
) {
// that's where SettingsHeader.HeaderTitle intersects the navigation bar
const showsBarSeparator = props.scrollTop > 11;
@@ -296,9 +293,17 @@ class PrivateNavigationBar extends Component<PrivateNavigationBarProps, PrivateN
};
}
- shouldComponentUpdate(
- nextProps: PrivateNavigationBarProps,
- nextState: PrivateNavigationBarState,
+ public state: IPrivateNavigationBarState = {
+ titleAdjustment: 0,
+ showsBarSeparator: false,
+ showsBarTitle: false,
+ };
+
+ private titleViewRef = React.createRef<PrivateTitleBarItem>();
+
+ public shouldComponentUpdate(
+ nextProps: IPrivateNavigationBarProps,
+ nextState: IPrivateNavigationBarState,
) {
return (
this.props.children !== nextProps.children ||
@@ -308,20 +313,20 @@ class PrivateNavigationBar extends Component<PrivateNavigationBarProps, PrivateN
);
}
- render() {
+ public render() {
return (
<View
style={[
styles.navigationBar.default,
this.state.showsBarSeparator ? styles.navigationBar.separator : undefined,
- this._getPlatformStyle(),
+ this.getPlatformStyle(),
]}
- onLayout={this._onLayout}>
+ onLayout={this.onLayout}>
<PrivateTitleBarItemContext.Provider
value={{
titleAdjustment: this.state.titleAdjustment,
visible: this.state.showsBarTitle,
- titleRef: this._titleViewRef,
+ titleRef: this.titleViewRef,
}}>
{this.props.children}
</PrivateTitleBarItemContext.Provider>
@@ -329,7 +334,7 @@ class PrivateNavigationBar extends Component<PrivateNavigationBarProps, PrivateN
);
}
- _getPlatformStyle(): Types.ViewStyleRuleSet | undefined {
+ private getPlatformStyle(): Types.ViewStyleRuleSet | undefined {
switch (process.platform) {
case 'darwin':
return styles.navigationBar.darwin;
@@ -342,8 +347,8 @@ class PrivateNavigationBar extends Component<PrivateNavigationBarProps, PrivateN
}
}
- _onLayout = async (containerLayout: Types.ViewOnLayoutEvent) => {
- const titleView = this._titleViewRef.current;
+ private onLayout = async (containerLayout: Types.ViewOnLayoutEvent) => {
+ const titleView = this.titleViewRef.current;
if (titleView) {
// calculate the title layout frame
const titleLayout = await UserInterface.measureLayoutRelativeToAncestor(titleView, this);
@@ -358,10 +363,10 @@ class PrivateNavigationBar extends Component<PrivateNavigationBarProps, PrivateN
};
}
-type TitleBarItemProps = {
+interface ITitleBarItemProps {
children?: React.ReactText;
-};
-export function TitleBarItem(props: TitleBarItemProps) {
+}
+export function TitleBarItem(props: ITitleBarItemProps) {
return (
<PrivateTitleBarItemContext.Consumer>
{(context) => (
@@ -376,10 +381,12 @@ export function TitleBarItem(props: TitleBarItemProps) {
);
}
-export class CloseBarItem extends Component<{
+interface ICloseBarItemProps {
action: () => void;
-}> {
- render() {
+}
+
+export class CloseBarItem extends Component<ICloseBarItemProps> {
+ public render() {
return (
<Button style={[styles.closeBarItem.default]} onPress={this.props.action}>
<ImageView height={24} width={24} style={[styles.closeBarItem.icon]} source="icon-close" />
@@ -388,11 +395,13 @@ export class CloseBarItem extends Component<{
}
}
-export class BackBarItem extends Component<{
+interface IBackBarItemProps {
children?: React.ReactText;
action: () => void;
-}> {
- render() {
+}
+
+export class BackBarItem extends Component<IBackBarItemProps> {
+ public render() {
return (
<Button style={styles.backBarButton.default} onPress={this.props.action}>
<View style={styles.backBarButton.content}>
diff --git a/gui/packages/desktop/src/renderer/components/NotificationArea.tsx b/gui/packages/desktop/src/renderer/components/NotificationArea.tsx
index c5fedd97e0..0f339c2979 100644
--- a/gui/packages/desktop/src/renderer/components/NotificationArea.tsx
+++ b/gui/packages/desktop/src/renderer/components/NotificationArea.tsx
@@ -1,30 +1,30 @@
import moment from 'moment';
import * as React from 'react';
import { Component, Types } from 'reactxp';
+import { links } from '../../config.json';
import {
+ NotificationActions,
NotificationBanner,
- NotificationIndicator,
NotificationContent,
- NotificationActions,
- NotificationTitle,
- NotificationSubtitle,
+ NotificationIndicator,
NotificationOpenLinkAction,
+ NotificationSubtitle,
+ NotificationTitle,
} from './NotificationBanner';
-import { links } from '../../config.json';
-import { AuthFailure } from '../lib/auth-failure';
-import AccountExpiry from '../lib/account-expiry';
import { BlockReason, TunnelStateTransition } from '../../shared/daemon-rpc-types';
-import { VersionReduxState } from '../redux/version/reducers';
+import AccountExpiry from '../lib/account-expiry';
+import { AuthFailure } from '../lib/auth-failure';
+import { IVersionReduxState } from '../redux/version/reducers';
-type Props = {
+interface IProps {
style?: Types.ViewStyleRuleSet;
accountExpiry?: AccountExpiry;
tunnelState: TunnelStateTransition;
- version: VersionReduxState;
+ version: IVersionReduxState;
openExternalLink: (url: string) => void;
blockWhenDisconnected: boolean;
-};
+}
type NotificationAreaPresentation =
| { type: 'failure-unsecured'; reason: string }
@@ -60,14 +60,12 @@ function getBlockReasonMessage(blockReason: BlockReason): string {
}
}
-export default class NotificationArea extends Component<Props, State> {
- state: State = {
- type: 'blocking',
- reason: '',
- visible: false,
- };
+function capitalizeFirstLetter(inputString: string): string {
+ return inputString.charAt(0).toUpperCase() + inputString.slice(1);
+}
- static getDerivedStateFromProps(props: Props, state: State) {
+export default class NotificationArea extends Component<IProps, State> {
+ public static getDerivedStateFromProps(props: IProps, state: State) {
const { accountExpiry, blockWhenDisconnected, tunnelState, version } = props;
switch (tunnelState.state) {
@@ -142,7 +140,7 @@ export default class NotificationArea extends Component<Props, State> {
return {
visible: true,
type: 'expires-soon',
- timeLeft: NotificationArea._capitalizeFirstLetter(accountExpiry.remainingTime()),
+ timeLeft: capitalizeFirstLetter(accountExpiry.remainingTime()),
};
}
@@ -153,13 +151,13 @@ export default class NotificationArea extends Component<Props, State> {
}
}
- static _capitalizeFirstLetter(initialString: string): string {
- return initialString.length > 0
- ? initialString.charAt(0).toUpperCase() + initialString.slice(1)
- : '';
- }
+ public state: State = {
+ type: 'blocking',
+ reason: '',
+ visible: false,
+ };
- render() {
+ public render() {
return (
<NotificationBanner style={this.props.style} visible={this.state.visible}>
{this.state.type === 'failure-unsecured' && (
@@ -204,11 +202,7 @@ export default class NotificationArea extends Component<Props, State> {
} now to ensure your security`}</NotificationSubtitle>
</NotificationContent>
<NotificationActions>
- <NotificationOpenLinkAction
- onPress={() => {
- this.props.openExternalLink(links.download);
- }}
- />
+ <NotificationOpenLinkAction onPress={this.handleOpenDownloadLink} />
</NotificationActions>
</React.Fragment>
)}
@@ -223,11 +217,7 @@ export default class NotificationArea extends Component<Props, State> {
}) to stay up to date`}</NotificationSubtitle>
</NotificationContent>
<NotificationActions>
- <NotificationOpenLinkAction
- onPress={() => {
- this.props.openExternalLink(links.download);
- }}
- />
+ <NotificationOpenLinkAction onPress={this.handleOpenDownloadLink} />
</NotificationActions>
</React.Fragment>
)}
@@ -240,15 +230,19 @@ export default class NotificationArea extends Component<Props, State> {
<NotificationSubtitle>{this.state.timeLeft}</NotificationSubtitle>
</NotificationContent>
<NotificationActions>
- <NotificationOpenLinkAction
- onPress={() => {
- this.props.openExternalLink(links.purchase);
- }}
- />
+ <NotificationOpenLinkAction onPress={this.handleOpenBuyMoreLink} />
</NotificationActions>
</React.Fragment>
)}
</NotificationBanner>
);
}
+
+ private handleOpenDownloadLink = () => {
+ this.props.openExternalLink(links.download);
+ };
+
+ private handleOpenBuyMoreLink = () => {
+ this.props.openExternalLink(links.purchase);
+ };
}
diff --git a/gui/packages/desktop/src/renderer/components/NotificationBanner.tsx b/gui/packages/desktop/src/renderer/components/NotificationBanner.tsx
index 3b31a1a16a..7048cb0aa6 100644
--- a/gui/packages/desktop/src/renderer/components/NotificationBanner.tsx
+++ b/gui/packages/desktop/src/renderer/components/NotificationBanner.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
-import { Animated, View, Button, Text, Component, UserInterface, Styles, Types } from 'reactxp';
import { ImageView } from '@mullvad/components';
+import * as React from 'react';
+import { Animated, Button, Component, Styles, Text, Types, UserInterface, View } from 'reactxp';
import { colors } from '../../config.json';
const styles = {
@@ -70,13 +70,13 @@ const styles = {
};
export class NotificationTitle extends Component {
- render() {
+ public render() {
return <Text style={styles.title}>{this.props.children}</Text>;
}
}
export class NotificationSubtitle extends Component {
- render() {
+ public render() {
return React.Children.count(this.props.children) > 0 ? (
<Text style={styles.subtitle}>{this.props.children}</Text>
) : null;
@@ -84,17 +84,17 @@ export class NotificationSubtitle extends Component {
}
export class NotificationOpenLinkAction extends Component<{ onPress: () => void }> {
- state = {
+ public state = {
hovered: false,
};
- render() {
+ public render() {
return (
<Button
style={styles.actionButton}
onPress={this.props.onPress}
- onHoverStart={this._onHoverStart}
- onHoverEnd={this._onHoverEnd}>
+ onHoverStart={this.onHoverStart}
+ onHoverEnd={this.onHoverEnd}>
<ImageView
height={12}
width={12}
@@ -105,72 +105,75 @@ export class NotificationOpenLinkAction extends Component<{ onPress: () => void
);
}
- _onHoverStart = () => {
+ private onHoverStart = () => {
this.setState({ hovered: true });
};
- _onHoverEnd = () => {
+ private onHoverEnd = () => {
this.setState({ hovered: false });
};
}
export class NotificationContent extends Component {
- render() {
+ public render() {
return <View style={styles.textContainer}>{this.props.children}</View>;
}
}
export class NotificationActions extends Component {
- render() {
+ public render() {
return <View style={styles.actionContainer}>{this.props.children}</View>;
}
}
export class NotificationIndicator extends Component<{ type: 'success' | 'warning' | 'error' }> {
- render() {
+ public render() {
return <View style={[styles.indicator.base, styles.indicator[this.props.type]]} />;
}
}
-type NotificationBannerProps = {
+interface INotificationBannerProps {
children: React.ReactNode; // Array<NotificationContent | NotificationActions>,
style?: Types.ViewStyleRuleSet;
visible: boolean;
animationDuration: number;
-};
+}
-type NotificationBannerState = {
+interface INotificationBannerState {
contentPinnedToBottom: boolean;
-};
+}
export class NotificationBanner extends Component<
- NotificationBannerProps,
- NotificationBannerState
+ INotificationBannerProps,
+ INotificationBannerState
> {
- static defaultProps = {
+ public static defaultProps = {
animationDuration: 350,
};
- _containerRef = React.createRef<Animated.View>();
- _contentHeight = 0;
- _heightValue = Animated.createValue(0);
- _animationStyle: Types.AnimatedViewStyleRuleSet;
- _animation?: Types.Animated.CompositeAnimation;
- _didFinishFirstLayoutPass = false;
-
- state = {
+ public state = {
contentPinnedToBottom: false,
};
- constructor(props: NotificationBannerProps) {
+ private containerRef = React.createRef<Animated.View>();
+ private contentHeight = 0;
+ private heightValue = Animated.createValue(0);
+ private animationStyle: Types.AnimatedViewStyleRuleSet;
+ private animation?: Types.Animated.CompositeAnimation;
+ private didFinishFirstLayoutPass = false;
+
+ constructor(props: INotificationBannerProps) {
super(props);
- this._animationStyle = Styles.createAnimatedViewStyle({
- height: this._heightValue,
+ this.animationStyle = Styles.createAnimatedViewStyle({
+ height: this.heightValue,
});
}
- shouldComponentUpdate(nextProps: NotificationBannerProps, nextState: NotificationBannerState) {
+ public shouldComponentUpdate(
+ nextProps: INotificationBannerProps,
+ nextState: INotificationBannerState,
+ ) {
return (
this.props.children !== nextProps.children ||
this.props.visible !== nextProps.visible ||
@@ -178,79 +181,79 @@ export class NotificationBanner extends Component<
);
}
- componentDidUpdate(prevProps: NotificationBannerProps) {
+ public componentDidUpdate(prevProps: INotificationBannerProps) {
if (prevProps.visible !== this.props.visible) {
// enable drawer-like animation when changing banner's visibility
this.setState({ contentPinnedToBottom: true }, () => {
- this._animateHeightChanges();
+ this.animateHeightChanges();
});
}
}
- componentWillUnmount() {
- if (this._animation) {
- this._animation.stop();
+ public componentWillUnmount() {
+ if (this.animation) {
+ this.animation.stop();
}
}
- render() {
+ public render() {
return (
<Animated.View
style={[
styles.collapsible,
this.state.contentPinnedToBottom ? styles.drawer : undefined,
- this._animationStyle,
+ this.animationStyle,
this.props.style,
]}
- ref={this._containerRef}>
- <View onLayout={this._onLayout}>
+ ref={this.containerRef}>
+ <View onLayout={this.onLayout}>
<View style={styles.container}>{this.props.children}</View>
</View>
</Animated.View>
);
}
- _onLayout = ({ height }: Types.ViewOnLayoutEvent) => {
- const oldHeight = this._contentHeight;
- this._contentHeight = height;
+ private onLayout = ({ height }: Types.ViewOnLayoutEvent) => {
+ const oldHeight = this.contentHeight;
+ this.contentHeight = height;
// The first layout pass should not be animated because this would cause the initially visible
// notification banner to slide down each time the component is mounted.
- if (this._didFinishFirstLayoutPass) {
+ if (this.didFinishFirstLayoutPass) {
if (oldHeight !== height) {
- this._animateHeightChanges();
+ this.animateHeightChanges();
}
} else {
- this._didFinishFirstLayoutPass = true;
+ this.didFinishFirstLayoutPass = true;
if (this.props.visible) {
- this._stopAnimation();
- this._heightValue.setValue(height);
+ this.stopAnimation();
+ this.heightValue.setValue(height);
}
}
};
- async _animateHeightChanges() {
- const containerView = this._containerRef.current;
+ private async animateHeightChanges() {
+ const containerView = this.containerRef.current;
if (!containerView) {
return;
}
- this._stopAnimation();
+ this.stopAnimation();
// calculate the animation duration based on travel distance
const layout = await UserInterface.measureLayoutRelativeToWindow(containerView);
- const toValue = this.props.visible ? this._contentHeight : 0;
- const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this._contentHeight);
+ const toValue = this.props.visible ? this.contentHeight : 0;
+ const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this.contentHeight);
const duration = Math.ceil(this.props.animationDuration * multiplier);
- const animation = Animated.timing(this._heightValue, {
+ const animation = Animated.timing(this.heightValue, {
toValue,
easing: Animated.Easing.InOut(),
duration,
useNativeDriver: true,
});
- this._animation = animation;
+ this.animation = animation;
animation.start(({ finished }) => {
if (finished) {
@@ -260,10 +263,10 @@ export class NotificationBanner extends Component<
});
}
- _stopAnimation() {
- if (this._animation) {
- this._animation.stop();
- this._animation = undefined;
+ private stopAnimation() {
+ if (this.animation) {
+ this.animation.stop();
+ this.animation = undefined;
}
}
}
diff --git a/gui/packages/desktop/src/renderer/components/PlatformWindow.tsx b/gui/packages/desktop/src/renderer/components/PlatformWindow.tsx
index e8d4330ef1..baf446fd5b 100644
--- a/gui/packages/desktop/src/renderer/components/PlatformWindow.tsx
+++ b/gui/packages/desktop/src/renderer/components/PlatformWindow.tsx
@@ -1,13 +1,13 @@
import * as React from 'react';
-import { Component, View, Styles } from 'reactxp';
+import { Component, Styles, View } from 'reactxp';
-type Props = {
+interface IProps {
arrowPosition?: number;
-};
+}
-export default class PlatformWindow extends Component<Props> {
- render() {
- let style = undefined;
+export default class PlatformWindow extends Component<IProps> {
+ public render() {
+ let style;
if (process.platform === 'darwin') {
const arrowPosition = this.props.arrowPosition;
diff --git a/gui/packages/desktop/src/renderer/components/Preferences.tsx b/gui/packages/desktop/src/renderer/components/Preferences.tsx
index 256390b899..91c2c6b050 100644
--- a/gui/packages/desktop/src/renderer/components/Preferences.tsx
+++ b/gui/packages/desktop/src/renderer/components/Preferences.tsx
@@ -1,19 +1,19 @@
+import { HeaderTitle, SettingsHeader } from '@mullvad/components';
import * as React from 'react';
import { Component, View } from 'reactxp';
-import { SettingsHeader, HeaderTitle } from '@mullvad/components';
import * as Cell from './Cell';
-import { Layout, Container } from './Layout';
+import { Container, Layout } from './Layout';
import {
+ BackBarItem,
NavigationBar,
NavigationContainer,
NavigationScrollbars,
- BackBarItem,
TitleBarItem,
} from './NavigationBar';
-import Switch from './Switch';
import styles from './PreferencesStyles';
+import Switch from './Switch';
-export type PreferencesProps = {
+export interface IPreferencesProps {
autoStart: boolean;
autoConnect: boolean;
allowLan: boolean;
@@ -27,10 +27,10 @@ export type PreferencesProps = {
setStartMinimized: (startMinimized: boolean) => void;
setMonochromaticIcon: (monochromaticIcon: boolean) => void;
onClose: () => void;
-};
+}
-export default class Preferences extends Component<PreferencesProps> {
- render() {
+export default class Preferences extends Component<IPreferencesProps> {
+ public render() {
return (
<Layout>
<Container>
@@ -50,7 +50,7 @@ export default class Preferences extends Component<PreferencesProps> {
<View style={styles.preferences__content}>
<Cell.Container>
<Cell.Label>Launch app on start-up</Cell.Label>
- <Switch isOn={this.props.autoStart} onChange={this._onChangeAutoStart} />
+ <Switch isOn={this.props.autoStart} onChange={this.onChangeAutoStart} />
</Cell.Container>
<View style={styles.preferences__separator} />
@@ -91,19 +91,19 @@ export default class Preferences extends Component<PreferencesProps> {
);
}
- _onChangeAutoStart = (autoStart: boolean) => {
+ private onChangeAutoStart = (autoStart: boolean) => {
this.props.setAutoStart(autoStart);
};
}
-type MonochromaticIconProps = {
+interface IMonochromaticIconProps {
enable: boolean;
monochromaticIcon: boolean;
onChange: (value: boolean) => void;
-};
+}
-class MonochromaticIconToggle extends Component<MonochromaticIconProps> {
- render() {
+class MonochromaticIconToggle extends Component<IMonochromaticIconProps> {
+ public render() {
if (this.props.enable) {
return (
<View>
@@ -120,14 +120,14 @@ class MonochromaticIconToggle extends Component<MonochromaticIconProps> {
}
}
-type StartMinimizedProps = {
+interface IStartMinimizedProps {
enable: boolean;
startMinimized: boolean;
onChange: (value: boolean) => void;
-};
+}
-class StartMinimizedToggle extends Component<StartMinimizedProps> {
- render() {
+class StartMinimizedToggle extends Component<IStartMinimizedProps> {
+ public render() {
if (this.props.enable) {
return (
<View>
diff --git a/gui/packages/desktop/src/renderer/components/RelayRow.tsx b/gui/packages/desktop/src/renderer/components/RelayRow.tsx
index 1170961458..69f632e04e 100644
--- a/gui/packages/desktop/src/renderer/components/RelayRow.tsx
+++ b/gui/packages/desktop/src/renderer/components/RelayRow.tsx
@@ -1,14 +1,16 @@
import * as React from 'react';
import { Component, Styles } from 'reactxp';
+import { colors } from '../../config.json';
+import { compareRelayLocation, RelayLocation } from '../../shared/daemon-rpc-types';
import * as Cell from './Cell';
import RelayStatusIndicator from './RelayStatusIndicator';
-import { colors } from '../../config.json';
-type Props = {
+interface IProps {
+ location: RelayLocation;
hostname: string;
selected: boolean;
- onSelect?: () => void;
-};
+ onSelect?: (location: RelayLocation) => void;
+}
const styles = {
base: Styles.createViewStyle({
@@ -23,19 +25,23 @@ const styles = {
}),
};
-export default class RelayRow extends Component<Props> {
- shouldComponentUpdate(nextProps: Props) {
- return !RelayRow.compareProps(this.props, nextProps);
+export default class RelayRow extends Component<IProps> {
+ public static compareProps(oldProps: IProps, nextProps: IProps) {
+ return (
+ oldProps.hostname === nextProps.hostname &&
+ oldProps.selected === nextProps.selected &&
+ compareRelayLocation(oldProps.location, nextProps.location)
+ );
}
- static compareProps(oldProps: Props, nextProps: Props) {
- return oldProps.hostname === nextProps.hostname && oldProps.selected === nextProps.selected;
+ public shouldComponentUpdate(nextProps: IProps) {
+ return !RelayRow.compareProps(this.props, nextProps);
}
- render() {
+ public render() {
return (
<Cell.CellButton
- onPress={this._handlePress}
+ onPress={this.handlePress}
cellHoverStyle={this.props.selected ? styles.selected : undefined}
style={[styles.base, this.props.selected ? styles.selected : undefined]}>
<RelayStatusIndicator isActive={true} isSelected={this.props.selected} />
@@ -45,9 +51,9 @@ export default class RelayRow extends Component<Props> {
);
}
- _handlePress = () => {
+ private handlePress = () => {
if (this.props.onSelect) {
- this.props.onSelect();
+ this.props.onSelect(this.props.location);
}
};
}
diff --git a/gui/packages/desktop/src/renderer/components/RelayStatusIndicator.tsx b/gui/packages/desktop/src/renderer/components/RelayStatusIndicator.tsx
index d256352bf1..3409d78331 100644
--- a/gui/packages/desktop/src/renderer/components/RelayStatusIndicator.tsx
+++ b/gui/packages/desktop/src/renderer/components/RelayStatusIndicator.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Component, Styles, View } from 'reactxp';
-import * as Cell from './Cell';
import { colors } from '../../config.json';
+import * as Cell from './Cell';
const styles = {
relay_status: Styles.createViewStyle({
@@ -23,13 +23,13 @@ const styles = {
}),
};
-type Props = {
+interface IProps {
isActive: boolean;
isSelected: boolean;
-};
+}
-export default class RelayStatusIndicator extends Component<Props> {
- render() {
+export default class RelayStatusIndicator extends Component<IProps> {
+ public render() {
return this.props.isSelected ? (
<Cell.Icon
style={styles.tick_icon}
diff --git a/gui/packages/desktop/src/renderer/components/SelectLocation.tsx b/gui/packages/desktop/src/renderer/components/SelectLocation.tsx
index 620ef504b0..ba7a24a79e 100644
--- a/gui/packages/desktop/src/renderer/components/SelectLocation.tsx
+++ b/gui/packages/desktop/src/renderer/components/SelectLocation.tsx
@@ -1,46 +1,49 @@
+import { HeaderSubTitle, HeaderTitle, SettingsHeader } from '@mullvad/components';
import * as React from 'react';
import ReactDOM from 'react-dom';
-import { View, Component } from 'reactxp';
-import { SettingsHeader, HeaderTitle, HeaderSubTitle } from '@mullvad/components';
-import { Layout, Container } from './Layout';
+import { Component, View } from 'reactxp';
import CustomScrollbars from './CustomScrollbars';
+import { Container, Layout } from './Layout';
import {
+ CloseBarItem,
+ NavigationBar,
NavigationContainer,
NavigationScrollbars,
- NavigationBar,
- CloseBarItem,
TitleBarItem,
} from './NavigationBar';
import styles from './SelectLocationStyles';
-import CountryRow from './CountryRow';
import CityRow from './CityRow';
+import CountryRow from './CountryRow';
import RelayRow from './RelayRow';
-import { RelaySettingsRedux, RelayLocationRedux } from '../redux/settings/reducers';
-import { RelayLocation } from '../../shared/daemon-rpc-types';
+import {
+ compareRelayLocation,
+ compareRelayLocationLoose,
+ RelayLocation,
+} from '../../shared/daemon-rpc-types';
+import { IRelayLocationRedux, RelaySettingsRedux } from '../redux/settings/reducers';
-type Props = {
+interface IProps {
relaySettings: RelaySettingsRedux;
- relayLocations: RelayLocationRedux[];
+ relayLocations: IRelayLocationRedux[];
onClose: () => void;
onSelect: (location: RelayLocation) => void;
-};
+}
-type State = {
+interface IState {
selectedLocation?: RelayLocation;
expandedItems: RelayLocation[];
-};
-
-export default class SelectLocation extends Component<Props, State> {
- _selectedCellRef = React.createRef<React.ReactNode>();
- _scrollViewRef = React.createRef<CustomScrollbars>();
+}
- state: State = {
+export default class SelectLocation extends Component<IProps, IState> {
+ public state: IState = {
expandedItems: [],
};
+ private selectedCellRef = React.createRef<React.ReactNode>();
+ private scrollViewRef = React.createRef<CustomScrollbars>();
- constructor(props: Props) {
+ constructor(props: IProps) {
super(props);
if ('normal' in this.props.relaySettings) {
@@ -64,7 +67,7 @@ export default class SelectLocation extends Component<Props, State> {
}
}
- componentDidUpdate(oldProps: Props) {
+ public componentDidUpdate(oldProps: IProps) {
const currentLocation = this.state.selectedLocation;
let newLocation =
'normal' in this.props.relaySettings ? this.props.relaySettings.normal.location : undefined;
@@ -81,17 +84,17 @@ export default class SelectLocation extends Component<Props, State> {
}
if (
- !compareLocationLoose(oldLocation, newLocation) &&
- !compareLocationLoose(currentLocation, newLocation)
+ !compareRelayLocationLoose(oldLocation, newLocation) &&
+ !compareRelayLocationLoose(currentLocation, newLocation)
) {
this.setState({ selectedLocation: newLocation });
}
}
- componentDidMount() {
+ public componentDidMount() {
// restore scroll to the selected cell
- const cell = this._selectedCellRef.current;
- const scrollView = this._scrollViewRef.current;
+ const cell = this.selectedCellRef.current;
+ const scrollView = this.scrollViewRef.current;
if (scrollView && cell) {
// TODO: Fix the browser specific code
const cellDOMNode = ReactDOM.findDOMNode(cell as Element);
@@ -101,7 +104,7 @@ export default class SelectLocation extends Component<Props, State> {
}
}
- render() {
+ public render() {
return (
<Layout>
<Container>
@@ -112,7 +115,7 @@ export default class SelectLocation extends Component<Props, State> {
<TitleBarItem>{'Select location'}</TitleBarItem>
</NavigationBar>
<View style={styles.container}>
- <NavigationScrollbars ref={this._scrollViewRef}>
+ <NavigationScrollbars ref={this.scrollViewRef}>
<View style={styles.content}>
<SettingsHeader style={styles.subtitle_header}>
<HeaderTitle>Select location</HeaderTitle>
@@ -123,42 +126,42 @@ export default class SelectLocation extends Component<Props, State> {
</SettingsHeader>
{this.props.relayLocations.map((relayCountry) => {
- const location: RelayLocation = { country: relayCountry.code };
+ const countryLocation: RelayLocation = { country: relayCountry.code };
return (
<CountryRow
- key={getLocationKey(location)}
+ key={getLocationKey(countryLocation)}
name={relayCountry.name}
hasActiveRelays={relayCountry.hasActiveRelays}
- expanded={this._isExpanded(location)}
- onSelect={() => this._handleSelection(location)}
- onExpand={(expand) => this._handleExpand(location, expand)}
- {...this._getCommonCellProps(location)}>
+ expanded={this.isExpanded(countryLocation)}
+ onSelect={this.handleSelection}
+ onExpand={this.handleExpand}
+ {...this.getCommonCellProps(countryLocation)}>
{relayCountry.cities.map((relayCity) => {
- const location: RelayLocation = {
+ const cityLocation: RelayLocation = {
city: [relayCountry.code, relayCity.code],
};
return (
<CityRow
- key={getLocationKey(location)}
+ key={getLocationKey(cityLocation)}
name={relayCity.name}
hasActiveRelays={relayCity.hasActiveRelays}
- expanded={this._isExpanded(location)}
- onSelect={() => this._handleSelection(location)}
- onExpand={(expand) => this._handleExpand(location, expand)}
- {...this._getCommonCellProps(location)}>
+ expanded={this.isExpanded(cityLocation)}
+ onSelect={this.handleSelection}
+ onExpand={this.handleExpand}
+ {...this.getCommonCellProps(cityLocation)}>
{relayCity.relays.map((relay) => {
- const location: RelayLocation = {
+ const relayLocation: RelayLocation = {
hostname: [relayCountry.code, relayCity.code, relay.hostname],
};
return (
<RelayRow
- key={getLocationKey(location)}
+ key={getLocationKey(relayLocation)}
hostname={relay.hostname}
- onSelect={() => this._handleSelection(location)}
- {...this._getCommonCellProps(location)}
+ onSelect={this.handleSelection}
+ {...this.getCommonCellProps(relayLocation)}
/>
);
})}
@@ -178,25 +181,29 @@ export default class SelectLocation extends Component<Props, State> {
);
}
- _isExpanded(relayLocation: RelayLocation) {
- return this.state.expandedItems.some((location) => compareLocation(location, relayLocation));
+ private isExpanded(relayLocation: RelayLocation) {
+ return this.state.expandedItems.some((location) =>
+ compareRelayLocation(location, relayLocation),
+ );
}
- _isSelected(relayLocation: RelayLocation) {
- return compareLocationLoose(this.state.selectedLocation, relayLocation);
+ private isSelected(relayLocation: RelayLocation) {
+ return compareRelayLocationLoose(this.state.selectedLocation, relayLocation);
}
- _handleSelection = (location: RelayLocation) => {
- if (!compareLocationLoose(this.state.selectedLocation, location)) {
+ private handleSelection = (location: RelayLocation) => {
+ if (!compareRelayLocationLoose(this.state.selectedLocation, location)) {
this.setState({ selectedLocation: location }, () => {
this.props.onSelect(location);
});
}
};
- _handleExpand = (location: RelayLocation, expand: boolean) => {
+ private handleExpand = (location: RelayLocation, expand: boolean) => {
this.setState((state) => {
- const expandedItems = state.expandedItems.filter((item) => !compareLocation(item, location));
+ const expandedItems = state.expandedItems.filter(
+ (item) => !compareRelayLocation(item, location),
+ );
if (expand) {
expandedItems.push(location);
@@ -209,11 +216,13 @@ export default class SelectLocation extends Component<Props, State> {
});
};
- _getCommonCellProps<T>(location: RelayLocation): { selected: boolean; ref?: React.RefObject<T> } {
- const selected = this._isSelected(location);
- const ref = selected ? (this._selectedCellRef as React.RefObject<T>) : undefined;
+ private getCommonCellProps<T>(
+ location: RelayLocation,
+ ): { location: RelayLocation; selected: boolean; ref?: React.RefObject<T> } {
+ const selected = this.isSelected(location);
+ const ref = selected ? (this.selectedCellRef as React.RefObject<T>) : undefined;
- return { ref, selected };
+ return { ref, selected, location };
}
}
@@ -230,27 +239,3 @@ function getLocationKey(location: RelayLocation): string {
return ([] as string[]).concat(components).join('-');
}
-
-function compareLocation(lhs: RelayLocation, rhs: RelayLocation) {
- if ('country' in lhs && 'country' in rhs && lhs.country && rhs.country) {
- return lhs.country === rhs.country;
- } else if ('city' in lhs && 'city' in rhs && lhs.city && rhs.city) {
- return lhs.city[0] === rhs.city[0] && lhs.city[1] === rhs.city[1];
- } else if ('hostname' in lhs && 'hostname' in rhs && lhs.hostname && rhs.hostname) {
- return (
- lhs.hostname[0] === rhs.hostname[0] &&
- lhs.hostname[1] === rhs.hostname[1] &&
- lhs.hostname[2] === rhs.hostname[2]
- );
- } else {
- return false;
- }
-}
-
-function compareLocationLoose(lhs?: RelayLocation, rhs?: RelayLocation) {
- if (lhs && rhs) {
- return compareLocation(lhs, rhs);
- } else {
- return lhs === rhs;
- }
-}
diff --git a/gui/packages/desktop/src/renderer/components/Settings.tsx b/gui/packages/desktop/src/renderer/components/Settings.tsx
index 555ad94954..913911c410 100644
--- a/gui/packages/desktop/src/renderer/components/Settings.tsx
+++ b/gui/packages/desktop/src/renderer/components/Settings.tsx
@@ -1,19 +1,19 @@
+import { HeaderTitle, ImageView, SettingsHeader } from '@mullvad/components';
import * as React from 'react';
import { Component, Text, View } from 'reactxp';
-import { ImageView, SettingsHeader, HeaderTitle } from '@mullvad/components';
+import { colors, links } from '../../config.json';
+import AccountExpiry from '../lib/account-expiry';
import * as AppButton from './AppButton';
import * as Cell from './Cell';
-import { Layout, Container } from './Layout';
+import { Container, Layout } from './Layout';
import {
+ CloseBarItem,
NavigationBar,
NavigationContainer,
NavigationScrollbars,
- CloseBarItem,
TitleBarItem,
} from './NavigationBar';
import styles from './SettingsStyles';
-import AccountExpiry from '../lib/account-expiry';
-import { colors, links } from '../../config.json';
import { LoginState } from '../redux/account/reducers';
@@ -52,11 +52,11 @@ export default class Settings extends Component<IProps> {
<HeaderTitle>Settings</HeaderTitle>
</SettingsHeader>
<View>
- {this._renderTopButtons()}
- {this._renderMiddleButtons()}
- {this._renderBottomButtons()}
+ {this.renderTopButtons()}
+ {this.renderMiddleButtons()}
+ {this.renderBottomButtons()}
</View>
- {this._renderQuitButton()}
+ {this.renderQuitButton()}
</View>
</NavigationScrollbars>
</View>
@@ -67,7 +67,15 @@ export default class Settings extends Component<IProps> {
);
}
- private _renderTopButtons() {
+ private renderQuitButton() {
+ return (
+ <View style={styles.settings__footer}>
+ <AppButton.RedButton onPress={this.props.onQuit}>{'Quit app'}</AppButton.RedButton>
+ </View>
+ );
+ }
+
+ private renderTopButtons() {
const isLoggedIn = this.props.loginState === 'ok';
if (!isLoggedIn) {
return null;
@@ -111,7 +119,7 @@ export default class Settings extends Component<IProps> {
);
}
- private _renderMiddleButtons() {
+ private renderMiddleButtons() {
let icon;
let footer;
if (!this.props.consistentVersion || !this.props.upToDateVersion) {
@@ -137,7 +145,7 @@ export default class Settings extends Component<IProps> {
return (
<View>
- <Cell.CellButton disabled={this.props.isOffline} onPress={this._openDownloadLink}>
+ <Cell.CellButton disabled={this.props.isOffline} onPress={this.openDownloadLink}>
{icon}
<Cell.Label>App version</Cell.Label>
<Cell.SubText>{this.props.appVersion}</Cell.SubText>
@@ -148,10 +156,10 @@ export default class Settings extends Component<IProps> {
);
}
- private _openDownloadLink = () => this.props.onExternalLink(links.download);
- private _openFaqLink = () => this.props.onExternalLink(links.faq);
+ private openDownloadLink = () => this.props.onExternalLink(links.download);
+ private openFaqLink = () => this.props.onExternalLink(links.faq);
- private _renderBottomButtons() {
+ private renderBottomButtons() {
return (
<View>
<Cell.CellButton onPress={this.props.onViewSupport}>
@@ -159,19 +167,11 @@ export default class Settings extends Component<IProps> {
<Cell.Icon height={12} width={7} source="icon-chevron" />
</Cell.CellButton>
- <Cell.CellButton disabled={this.props.isOffline} onPress={this._openFaqLink}>
+ <Cell.CellButton disabled={this.props.isOffline} onPress={this.openFaqLink}>
<Cell.Label>{'FAQs & Guides'}</Cell.Label>
<Cell.Icon height={16} width={16} source="icon-extLink" />
</Cell.CellButton>
</View>
);
}
-
- _renderQuitButton() {
- return (
- <View style={styles.settings__footer}>
- <AppButton.RedButton onPress={this.props.onQuit}>{'Quit app'}</AppButton.RedButton>
- </View>
- );
- }
}
diff --git a/gui/packages/desktop/src/renderer/components/Support.tsx b/gui/packages/desktop/src/renderer/components/Support.tsx
index cb432839ca..95e96bebe8 100644
--- a/gui/packages/desktop/src/renderer/components/Support.tsx
+++ b/gui/packages/desktop/src/renderer/components/Support.tsx
@@ -1,21 +1,21 @@
-import * as React from 'react';
-import { Component, Text, View, TextInput } from 'reactxp';
import {
- ImageView,
- SettingsHeader,
- HeaderTitle,
HeaderSubTitle,
+ HeaderTitle,
+ ImageView,
+ ModalAlert,
ModalContainer,
ModalContent,
- ModalAlert,
+ SettingsHeader,
} from '@mullvad/components';
+import * as React from 'react';
+import { Component, Text, TextInput, View } from 'reactxp';
import * as AppButton from './AppButton';
-import { Layout, Container } from './Layout';
-import { NavigationBar, BackBarItem } from './NavigationBar';
+import { Container, Layout } from './Layout';
+import { BackBarItem, NavigationBar } from './NavigationBar';
import styles from './SupportStyles';
import { AccountToken } from '../../shared/daemon-rpc-types';
-import { SupportReportForm } from '../redux/support/actions';
+import { ISupportReportForm } from '../redux/support/actions';
enum SendState {
Initial,
@@ -25,37 +25,37 @@ enum SendState {
Failed,
}
-type SupportState = {
+interface ISupportState {
email: string;
message: string;
savedReport?: string;
sendState: SendState;
-};
+}
-type SupportProps = {
+interface ISupportProps {
defaultEmail: string;
defaultMessage: string;
- accountHistory: Array<AccountToken>;
+ accountHistory: AccountToken[];
isOffline: boolean;
onClose: () => void;
viewLog: (path: string) => void;
- saveReportForm: (form: SupportReportForm) => void;
+ saveReportForm: (form: ISupportReportForm) => void;
clearReportForm: () => void;
- collectProblemReport: (accountsToRedact: Array<string>) => Promise<string>;
+ collectProblemReport: (accountsToRedact: string[]) => Promise<string>;
sendProblemReport: (email: string, message: string, savedReport: string) => Promise<void>;
-};
+}
-export default class Support extends Component<SupportProps, SupportState> {
- state = {
+export default class Support extends Component<ISupportProps, ISupportState> {
+ public state = {
email: '',
message: '',
savedReport: undefined,
sendState: SendState.Initial,
};
- _collectLogPromise?: Promise<string>;
+ private collectLogPromise?: Promise<string>;
- constructor(props: SupportProps) {
+ constructor(props: ISupportProps) {
super(props);
// seed initial data from props
@@ -63,68 +63,39 @@ export default class Support extends Component<SupportProps, SupportState> {
this.state.message = props.defaultMessage;
}
- validate() {
+ public validate() {
return this.state.message.trim().length > 0;
}
- onChangeEmail = (email: string) => {
- this.setState({ email: email }, () => {
- this._saveFormData();
+ public onChangeEmail = (email: string) => {
+ this.setState({ email }, () => {
+ this.saveFormData();
});
};
- onChangeDescription = (description: string) => {
+ public onChangeDescription = (description: string) => {
this.setState({ message: description }, () => {
- this._saveFormData();
+ this.saveFormData();
});
};
- onViewLog = async (): Promise<void> => {
+ public onViewLog = async (): Promise<void> => {
try {
- const reportPath = await this._collectLog();
+ const reportPath = await this.collectLog();
this.props.viewLog(reportPath);
} catch (error) {
// TODO: handle error
}
};
- _saveFormData() {
- this.props.saveReportForm({
- email: this.state.email,
- message: this.state.message,
- });
- }
-
- async _collectLog(): Promise<string> {
- if (this._collectLogPromise) {
- return this._collectLogPromise;
- } else {
- const collectPromise = this.props.collectProblemReport(this.props.accountHistory);
-
- // save promise to prevent subsequent requests
- this._collectLogPromise = collectPromise;
-
- try {
- const reportPath = await collectPromise;
- return new Promise((resolve) => {
- this.setState({ savedReport: reportPath }, () => resolve(reportPath));
- });
- } catch (error) {
- this._collectLogPromise = undefined;
-
- throw error;
- }
- }
- }
-
- onSend = async (): Promise<void> => {
+ public onSend = async (): Promise<void> => {
switch (this.state.sendState) {
case SendState.Initial:
if (this.state.email.length === 0) {
this.setState({ sendState: SendState.Confirm });
} else {
try {
- await this._sendReport();
+ await this.sendReport();
} catch (error) {
// No-op
}
@@ -133,7 +104,7 @@ export default class Support extends Component<SupportProps, SupportState> {
case SendState.Confirm:
try {
- await this._sendReport();
+ await this.sendReport();
} catch (error) {
// No-op
}
@@ -146,31 +117,11 @@ export default class Support extends Component<SupportProps, SupportState> {
return Promise.resolve();
};
- onCancelConfirmation = () => {
+ public onCancelConfirmation = () => {
this.setState({ sendState: SendState.Initial });
};
- _sendReport(): Promise<void> {
- return new Promise((resolve, reject) => {
- this.setState({ sendState: SendState.Loading }, async () => {
- try {
- const { email, message } = this.state;
- const reportPath = await this._collectLog();
- await this.props.sendProblemReport(email, message, reportPath);
- this.props.clearReportForm();
- this.setState({ sendState: SendState.Success }, () => {
- resolve();
- });
- } catch (error) {
- this.setState({ sendState: SendState.Failed }, () => {
- reject(error);
- });
- }
- });
- });
- }
-
- render() {
+ public render() {
const { sendState } = this.state;
const header = (
<SettingsHeader>
@@ -185,7 +136,7 @@ export default class Support extends Component<SupportProps, SupportState> {
</SettingsHeader>
);
- const content = this._renderContent();
+ const content = this.renderContent();
return (
<Layout>
@@ -203,7 +154,7 @@ export default class Support extends Component<SupportProps, SupportState> {
</View>
</ModalContent>
{sendState === SendState.Confirm ? (
- <ModalAlert>{this._renderConfirm()}</ModalAlert>
+ <ModalAlert>{this.renderConfirm()}</ModalAlert>
) : (
undefined
)}
@@ -213,27 +164,76 @@ export default class Support extends Component<SupportProps, SupportState> {
);
}
- _renderContent() {
+ private saveFormData() {
+ this.props.saveReportForm({
+ email: this.state.email,
+ message: this.state.message,
+ });
+ }
+
+ private async collectLog(): Promise<string> {
+ if (this.collectLogPromise) {
+ return this.collectLogPromise;
+ } else {
+ const collectPromise = this.props.collectProblemReport(this.props.accountHistory);
+
+ // save promise to prevent subsequent requests
+ this.collectLogPromise = collectPromise;
+
+ try {
+ const reportPath = await collectPromise;
+ return new Promise((resolve) => {
+ this.setState({ savedReport: reportPath }, () => resolve(reportPath));
+ });
+ } catch (error) {
+ this.collectLogPromise = undefined;
+
+ throw error;
+ }
+ }
+ }
+
+ private sendReport(): Promise<void> {
+ return new Promise((resolve, reject) => {
+ this.setState({ sendState: SendState.Loading }, async () => {
+ try {
+ const { email, message } = this.state;
+ const reportPath = await this.collectLog();
+ await this.props.sendProblemReport(email, message, reportPath);
+ this.props.clearReportForm();
+ this.setState({ sendState: SendState.Success }, () => {
+ resolve();
+ });
+ } catch (error) {
+ this.setState({ sendState: SendState.Failed }, () => {
+ reject(error);
+ });
+ }
+ });
+ });
+ }
+
+ private renderContent() {
switch (this.state.sendState) {
case SendState.Initial:
case SendState.Confirm:
- return this._renderForm();
+ return this.renderForm();
case SendState.Loading:
- return this._renderLoading();
+ return this.renderLoading();
case SendState.Success:
- return this._renderSent();
+ return this.renderSent();
case SendState.Failed:
- return this._renderFailed();
+ return this.renderFailed();
default:
return null;
}
}
- _renderConfirm() {
+ private renderConfirm() {
return <ConfirmNoEmailDialog onConfirm={this.onSend} onDismiss={this.onCancelConfirmation} />;
}
- _renderForm() {
+ private renderForm() {
return (
<View style={styles.support__content}>
<View style={styles.support__form}>
@@ -271,7 +271,7 @@ export default class Support extends Component<SupportProps, SupportState> {
);
}
- _renderLoading() {
+ private renderLoading() {
return (
<View style={styles.support__content}>
<View style={styles.support__form}>
@@ -287,7 +287,7 @@ export default class Support extends Component<SupportProps, SupportState> {
);
}
- _renderSent() {
+ private renderSent() {
return (
<View style={styles.support__content}>
<View style={styles.support__form}>
@@ -311,7 +311,7 @@ export default class Support extends Component<SupportProps, SupportState> {
);
}
- _renderFailed() {
+ private renderFailed() {
return (
<View style={styles.support__content}>
<View style={styles.support__form}>
@@ -329,9 +329,7 @@ export default class Support extends Component<SupportProps, SupportState> {
</View>
</View>
<View style={styles.support__footer}>
- <AppButton.BlueButton
- style={styles.edit_message_button}
- onPress={() => this.setState({ sendState: SendState.Initial })}>
+ <AppButton.BlueButton style={styles.edit_message_button} onPress={this.handleEditMessage}>
{'Edit message'}
</AppButton.BlueButton>
<AppButton.GreenButton onPress={this.onSend}>Try again</AppButton.GreenButton>
@@ -339,15 +337,19 @@ export default class Support extends Component<SupportProps, SupportState> {
</View>
);
}
+
+ private handleEditMessage = () => {
+ this.setState({ sendState: SendState.Initial });
+ };
}
-type ConfirmNoEmailDialogProps = {
+interface IConfirmNoEmailDialogProps {
onConfirm: () => void;
onDismiss: () => void;
-};
+}
-class ConfirmNoEmailDialog extends Component<ConfirmNoEmailDialogProps> {
- render() {
+class ConfirmNoEmailDialog extends Component<IConfirmNoEmailDialogProps> {
+ public render() {
return (
<View style={styles.confirm_no_email_background}>
<View style={styles.confirm_no_email_dialog}>
@@ -355,10 +357,8 @@ class ConfirmNoEmailDialog extends Component<ConfirmNoEmailDialogProps> {
You are about to send the problem report without a way for us to get back to you. If you
want an answer to your report you will have to enter an email address.
</Text>
- <AppButton.GreenButton onPress={this.props.onConfirm}>
- {'Send anyway'}
- </AppButton.GreenButton>
- <AppButton.RedButton onPress={this._dismiss} style={styles.confirm_no_email_back_button}>
+ <AppButton.GreenButton onPress={this.confirm}>{'Send anyway'}</AppButton.GreenButton>
+ <AppButton.RedButton onPress={this.dismiss} style={styles.confirm_no_email_back_button}>
{'Back'}
</AppButton.RedButton>
</View>
@@ -366,11 +366,11 @@ class ConfirmNoEmailDialog extends Component<ConfirmNoEmailDialogProps> {
);
}
- _confirm = () => {
+ private confirm = () => {
this.props.onConfirm();
};
- _dismiss = () => {
+ private dismiss = () => {
this.props.onDismiss();
};
}
diff --git a/gui/packages/desktop/src/renderer/components/SvgMap.tsx b/gui/packages/desktop/src/renderer/components/SvgMap.tsx
index eb07d74677..25c77de7f2 100644
--- a/gui/packages/desktop/src/renderer/components/SvgMap.tsx
+++ b/gui/packages/desktop/src/renderer/components/SvgMap.tsx
@@ -1,27 +1,27 @@
+import { geoTimes } from 'd3-geo-projection';
+import rbush from 'rbush';
import * as React from 'react';
import {
ComposableMap,
- ZoomableGroup,
Geographies,
Geography,
- Markers,
Marker,
+ Markers,
+ ZoomableGroup,
} from 'react-simple-maps';
-import rbush from 'rbush';
-import { geoTimes } from 'd3-geo-projection';
import geographyData from '../../../assets/geo/geometry.json';
import statesProvincesLinesData from '../../../assets/geo/states-provinces-lines.json';
-import countryTreeData from '../../../assets/geo/countries.rbush.json';
import cityTreeData from '../../../assets/geo/cities.rbush.json';
+import countryTreeData from '../../../assets/geo/countries.rbush.json';
import geometryTreeData from '../../../assets/geo/geometry.rbush.json';
import statesProvincesLinesTreeData from '../../../assets/geo/states-provinces-lines.rbush.json';
// Infer the GeoProjection type from the `geoTimes()` return value
type GeoProjection = ReturnType<typeof geoTimes>;
-interface CountryLeaf extends rbush.BBox {
+interface ICountryLeaf extends rbush.BBox {
id: string;
properties: {
name: string;
@@ -32,7 +32,7 @@ interface CountryLeaf extends rbush.BBox {
};
}
-interface CityLeaf extends rbush.BBox {
+interface ICityLeaf extends rbush.BBox {
id: string;
properties: {
scalerank: number;
@@ -46,24 +46,24 @@ interface CityLeaf extends rbush.BBox {
};
}
-interface GeometryLeaf extends rbush.BBox {
+interface IGeometryLeaf extends rbush.BBox {
id: string;
}
-interface ProvinceAndStateLineLeaf extends rbush.BBox {
+interface IProvinceAndStateLineLeaf extends rbush.BBox {
id: string;
}
-const countryTree = rbush<CountryLeaf>().fromJSON(countryTreeData);
-const cityTree = rbush<CityLeaf>().fromJSON(cityTreeData);
-const geometryTree = rbush<GeometryLeaf>().fromJSON(geometryTreeData);
-const provincesStatesLinesTree = rbush<ProvinceAndStateLineLeaf>().fromJSON(
+const countryTree = rbush<ICountryLeaf>().fromJSON(countryTreeData);
+const cityTree = rbush<ICityLeaf>().fromJSON(cityTreeData);
+const geometryTree = rbush<IGeometryLeaf>().fromJSON(geometryTreeData);
+const provincesStatesLinesTree = rbush<IProvinceAndStateLineLeaf>().fromJSON(
statesProvincesLinesTreeData,
);
type BBox = [number, number, number, number];
-export type Props = {
+export interface IProps {
width: number;
height: number;
center: [number, number]; // longitude, latitude
@@ -71,23 +71,23 @@ export type Props = {
zoomLevel: number;
showMarker: boolean;
markerImagePath: string;
-};
+}
-type State = {
+interface IState {
zoomCenter: [number, number];
zoomLevel: number;
- visibleCities: CityLeaf[];
- visibleCountries: CountryLeaf[];
- visibleGeometry: GeometryLeaf[];
- visibleStatesProvincesLines: ProvinceAndStateLineLeaf[];
+ visibleCities: ICityLeaf[];
+ visibleCountries: ICountryLeaf[];
+ visibleGeometry: IGeometryLeaf[];
+ visibleStatesProvincesLines: IProvinceAndStateLineLeaf[];
viewportBbox: BBox;
-};
+}
const MOVE_SPEED = 2000;
// @TODO: Calculate zoom level based on (center + span) (aka MKCoordinateSpan)
-export default class SvgMap extends React.Component<Props, State> {
- state: State = {
+export default class SvgMap extends React.Component<IProps, IState> {
+ public state: IState = {
zoomCenter: [0, 0],
zoomLevel: 1,
visibleCities: [],
@@ -97,23 +97,23 @@ export default class SvgMap extends React.Component<Props, State> {
viewportBbox: [0, 0, 0, 0],
};
- _projectionConfig = {
+ private projectionConfig = {
scale: 160,
};
- constructor(props: Props) {
+ constructor(props: IProps) {
super(props);
- this.state = this._getNextState(null, props);
+ this.state = this.getNextState(null, props);
}
- UNSAFE_componentWillReceiveProps(nextProps: Props) {
- if (this._shouldInvalidateState(nextProps)) {
- this.setState((prevState) => this._getNextState(prevState, nextProps));
+ public UNSAFE_componentWillReceiveProps(nextProps: IProps) {
+ if (this.shouldInvalidateState(nextProps)) {
+ this.setState((prevState) => this.getNextState(prevState, nextProps));
}
}
- shouldComponentUpdate(nextProps: Props, nextState: State) {
+ public shouldComponentUpdate(nextProps: IProps, nextState: IState) {
return (
this.props.width !== nextProps.width ||
this.props.height !== nextProps.height ||
@@ -129,7 +129,7 @@ export default class SvgMap extends React.Component<Props, State> {
);
}
- render() {
+ public render() {
const mapStyle = {
width: '100%',
height: '100%',
@@ -140,7 +140,7 @@ export default class SvgMap extends React.Component<Props, State> {
transition: `transform ${MOVE_SPEED}ms ease-in-out`,
};
- const geographyStyle = this._mergeRsmStyle({
+ const geographyStyle = this.mergeRsmStyle({
default: {
fill: '#294d73',
stroke: '#192e45',
@@ -148,7 +148,7 @@ export default class SvgMap extends React.Component<Props, State> {
},
});
- const stateProvinceLineStyle = this._mergeRsmStyle({
+ const stateProvinceLineStyle = this.mergeRsmStyle({
default: {
fill: 'transparent',
stroke: '#192e45',
@@ -156,7 +156,7 @@ export default class SvgMap extends React.Component<Props, State> {
},
});
- const markerStyle = this._mergeRsmStyle({
+ const markerStyle = this.mergeRsmStyle({
default: {
transition: `transform ${MOVE_SPEED}ms ease-in-out`,
},
@@ -201,8 +201,8 @@ export default class SvgMap extends React.Component<Props, State> {
width={this.props.width}
height={this.props.height}
style={mapStyle}
- projection={this._getProjection}
- projectionConfig={this._projectionConfig}>
+ projection={this.getProjection}
+ projectionConfig={this.projectionConfig}>
<ZoomableGroup
center={this.state.zoomCenter}
zoom={this.state.zoomLevel}
@@ -213,7 +213,7 @@ export default class SvgMap extends React.Component<Props, State> {
return this.state.visibleGeometry.map(({ id }) => (
<Geography
key={id}
- geography={geographies[parseInt(id)]}
+ geography={geographies[parseInt(id, 10)]}
projection={projection}
style={geographyStyle}
/>
@@ -225,7 +225,7 @@ export default class SvgMap extends React.Component<Props, State> {
return this.state.visibleStatesProvincesLines.map(({ id }) => (
<Geography
key={id}
- geography={geographies[parseInt(id)]}
+ geography={geographies[parseInt(id, 10)]}
projection={projection}
style={stateProvinceLineStyle}
/>
@@ -238,7 +238,7 @@ export default class SvgMap extends React.Component<Props, State> {
);
}
- _mergeRsmStyle(style: { [key: string]: any }) {
+ private mergeRsmStyle(style: { [key: string]: any }) {
const defaultStyle = style.default || {};
return {
default: defaultStyle,
@@ -247,7 +247,7 @@ export default class SvgMap extends React.Component<Props, State> {
};
}
- _getProjection(
+ private getProjection(
width: number,
height: number,
config: {
@@ -271,7 +271,7 @@ export default class SvgMap extends React.Component<Props, State> {
.precision(precision);
}
- _getZoomCenter(
+ private getZoomCenter(
center: [number, number],
offset: [number, number],
projection: GeoProjection,
@@ -281,7 +281,7 @@ export default class SvgMap extends React.Component<Props, State> {
return projection.invert!([pos[0] + offset[0] / zoom, pos[1] + offset[1] / zoom])!;
}
- _getViewportGeoBoundingBox(
+ private getViewportGeoBoundingBox(
centerCoordinate: [number, number],
width: number,
height: number,
@@ -304,7 +304,7 @@ export default class SvgMap extends React.Component<Props, State> {
];
}
- _shouldInvalidateState(nextProps: Props) {
+ private shouldInvalidateState(nextProps: IProps) {
const oldProps = this.props;
return (
oldProps.width !== nextProps.width ||
@@ -317,12 +317,12 @@ export default class SvgMap extends React.Component<Props, State> {
);
}
- _getNextState(prevState: State | null, nextProps: Props): State {
+ private getNextState(prevState: IState | null, nextProps: IProps): IState {
const { width, height, center, offset, zoomLevel } = nextProps;
- const projection = this._getProjection(width, height, this._projectionConfig);
- const zoomCenter = this._getZoomCenter(center, offset, projection, zoomLevel);
- const viewportBbox = this._getViewportGeoBoundingBox(
+ const projection = this.getProjection(width, height, this.projectionConfig);
+ const zoomCenter = this.getZoomCenter(center, offset, projection, zoomLevel);
+ const viewportBbox = this.getViewportGeoBoundingBox(
zoomCenter,
width,
height,
diff --git a/gui/packages/desktop/src/renderer/components/Switch.tsx b/gui/packages/desktop/src/renderer/components/Switch.tsx
index d74efd97a6..eef588f44b 100644
--- a/gui/packages/desktop/src/renderer/components/Switch.tsx
+++ b/gui/packages/desktop/src/renderer/components/Switch.tsx
@@ -3,34 +3,57 @@ import * as React from 'react';
const CLICK_TIMEOUT = 1000;
const MOVE_THRESHOLD = 10;
-export type SwitchProps = {
+interface IProps {
className?: string;
isOn: boolean;
onChange?: (isOn: boolean) => void;
-};
+}
-type State = {
+interface IState {
ignoreChange: boolean;
initialPos: { x: number; y: number };
startTime?: number;
-};
+}
-export default class Switch extends React.Component<SwitchProps, State> {
- static defaultProps: SwitchProps = {
+export default class Switch extends React.Component<IProps, IState> {
+ public static defaultProps: Partial<IProps> = {
isOn: false,
onChange: undefined,
};
- state = {
+ public state: IState = {
ignoreChange: false,
initialPos: { x: 0, y: 0 },
startTime: undefined,
};
- isCapturingMouseEvents = false;
- ref = React.createRef<HTMLInputElement>();
+ public isCapturingMouseEvents = false;
+ public ref = React.createRef<HTMLInputElement>();
+
+ public componentWillUnmount() {
+ // guard from abrupt programmatic unmount
+ if (this.isCapturingMouseEvents) {
+ this.stopCapturingMouseEvents();
+ }
+ }
+
+ public render() {
+ const { isOn, onChange, ...otherProps } = this.props;
+ const className = ('switch ' + (otherProps.className || '')).trim();
+ return (
+ <input
+ {...otherProps}
+ type="checkbox"
+ ref={this.ref}
+ className={className}
+ checked={isOn}
+ onMouseDown={this.handleMouseDown}
+ onChange={this.handleChange}
+ />
+ );
+ }
- handleMouseDown = (e: React.MouseEvent<HTMLInputElement>) => {
+ private handleMouseDown = (e: React.MouseEvent<HTMLInputElement>) => {
const { clientX: x, clientY: y } = e;
this.startCapturingMouseEvents();
this.setState({
@@ -39,7 +62,7 @@ export default class Switch extends React.Component<SwitchProps, State> {
});
};
- handleMouseMove = (e: MouseEvent) => {
+ private handleMouseMove = (e: MouseEvent) => {
const inputElement = this.ref.current;
const { x: x0 } = this.state.initialPos;
const { clientX: x, clientY: y } = e;
@@ -72,11 +95,11 @@ export default class Switch extends React.Component<SwitchProps, State> {
}
};
- handleMouseUp = () => {
+ private handleMouseUp = () => {
this.stopCapturingMouseEvents();
};
- handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ private handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const startTime = this.state.startTime;
const eventTarget = e.target;
@@ -96,14 +119,14 @@ export default class Switch extends React.Component<SwitchProps, State> {
}
};
- notify(isOn: boolean) {
+ private notify(isOn: boolean) {
const onChange = this.props.onChange;
if (onChange) {
onChange(isOn);
}
}
- startCapturingMouseEvents() {
+ private startCapturingMouseEvents() {
if (this.isCapturingMouseEvents) {
throw new Error('startCapturingMouseEvents() is called out of order.');
}
@@ -112,7 +135,7 @@ export default class Switch extends React.Component<SwitchProps, State> {
this.isCapturingMouseEvents = true;
}
- stopCapturingMouseEvents() {
+ private stopCapturingMouseEvents() {
if (!this.isCapturingMouseEvents) {
throw new Error('stopCapturingMouseEvents() is called out of order.');
}
@@ -120,27 +143,4 @@ export default class Switch extends React.Component<SwitchProps, State> {
document.removeEventListener('mouseup', this.handleMouseUp);
this.isCapturingMouseEvents = false;
}
-
- componentWillUnmount() {
- // guard from abrupt programmatic unmount
- if (this.isCapturingMouseEvents) {
- this.stopCapturingMouseEvents();
- }
- }
-
- render() {
- const { isOn, onChange, ...otherProps } = this.props;
- const className = ('switch ' + (otherProps.className || '')).trim();
- return (
- <input
- {...otherProps}
- type="checkbox"
- ref={this.ref}
- className={className}
- checked={isOn}
- onMouseDown={this.handleMouseDown}
- onChange={this.handleChange}
- />
- );
- }
}
diff --git a/gui/packages/desktop/src/renderer/components/TransitionContainer.tsx b/gui/packages/desktop/src/renderer/components/TransitionContainer.tsx
index 112b4c8dff..e7db17b880 100644
--- a/gui/packages/desktop/src/renderer/components/TransitionContainer.tsx
+++ b/gui/packages/desktop/src/renderer/components/TransitionContainer.tsx
@@ -1,17 +1,17 @@
import * as React from 'react';
-import { Styles, Component, Animated, View, Types, UserInterface } from 'reactxp';
-import { TransitionGroupProps } from '../transitions';
+import { Animated, Component, Styles, Types, UserInterface, View } from 'reactxp';
+import { ITransitionGroupProps } from '../transitions';
-type Props = {
+interface IProps extends ITransitionGroupProps {
children: React.ReactNode;
-} & TransitionGroupProps;
+}
-type State = {
+interface IState {
previousChildren?: React.ReactNode;
childrenAnimation?: Types.AnimatedViewStyleRuleSet;
previousChildrenAnimation?: Types.AnimatedViewStyleRuleSet;
dimensions: Types.Dimensions;
-};
+}
const dimensions = UserInterface.measureWindow();
const styles = {
@@ -31,16 +31,12 @@ const styles = {
}),
};
-export default class TransitionContainer extends Component<Props, State> {
- constructor(props: Props) {
- super(props);
-
- this.state = {
- dimensions: UserInterface.measureWindow(),
- };
- }
+export default class TransitionContainer extends Component<IProps, IState> {
+ public state: IState = {
+ dimensions: UserInterface.measureWindow(),
+ };
- UNSAFE_componentWillReceiveProps(nextProps: Props) {
+ public UNSAFE_componentWillReceiveProps(nextProps: IProps) {
switch (nextProps.name) {
case 'slide-up':
this.slideUpTransition(nextProps);
@@ -59,14 +55,14 @@ export default class TransitionContainer extends Component<Props, State> {
}
}
- onFinishedAnimation() {
+ public onFinishedAnimation() {
this.setState({
childrenAnimation: styles.allowPointerEventsStyle,
previousChildren: null,
});
}
- slideUpTransition(nextProps: Props) {
+ public slideUpTransition(nextProps: IProps) {
const currentTranslationValue = Animated.createValue(this.state.dimensions.height);
this.setState(
{
@@ -94,7 +90,7 @@ export default class TransitionContainer extends Component<Props, State> {
);
}
- slideDownTransition(nextProps: Props) {
+ public slideDownTransition(nextProps: IProps) {
const previousTranslationValue = Animated.createValue(0);
this.setState(
{
@@ -122,7 +118,7 @@ export default class TransitionContainer extends Component<Props, State> {
);
}
- pushTransition(nextProps: Props) {
+ public pushTransition(nextProps: IProps) {
const currentTranslationValue = Animated.createValue(this.state.dimensions.width);
const previousTranslationValue = Animated.createValue(0);
this.setState(
@@ -159,7 +155,7 @@ export default class TransitionContainer extends Component<Props, State> {
);
}
- popTransition(nextProps: Props) {
+ public popTransition(nextProps: IProps) {
const currentTranslationValue = Animated.createValue(-this.state.dimensions.width / 2);
const previousTranslationValue = Animated.createValue(0);
this.setState(
@@ -196,7 +192,7 @@ export default class TransitionContainer extends Component<Props, State> {
);
}
- render() {
+ public render() {
const { children } = this.props;
const { previousChildren, childrenAnimation, previousChildrenAnimation } = this.state;
diff --git a/gui/packages/desktop/src/renderer/components/TunnelControl.tsx b/gui/packages/desktop/src/renderer/components/TunnelControl.tsx
index 8418f64086..aa21556fae 100644
--- a/gui/packages/desktop/src/renderer/components/TunnelControl.tsx
+++ b/gui/packages/desktop/src/renderer/components/TunnelControl.tsx
@@ -1,36 +1,36 @@
+import { ConnectionInfo, SecuredDisplayStyle, SecuredLabel } from '@mullvad/components';
import * as React from 'react';
-import { Component, Text, View, Styles, Types } from 'reactxp';
-import { ConnectionInfo, SecuredLabel, SecuredDisplayStyle } from '@mullvad/components';
-import * as AppButton from './AppButton';
+import { Component, Styles, Text, Types, View } from 'reactxp';
import { colors } from '../../config.json';
+import * as AppButton from './AppButton';
-import { TunnelStateTransition, RelayProtocol } from '../../shared/daemon-rpc-types';
+import { RelayProtocol, TunnelStateTransition } from '../../shared/daemon-rpc-types';
-export type RelayInAddress = {
+export interface IRelayInAddress {
ip: string;
port: number;
protocol: RelayProtocol;
-};
+}
-export type RelayOutAddress = {
+export interface IRelayOutAddress {
ipv4?: string;
ipv6?: string;
-};
+}
-type TunnelControlProps = {
+interface ITunnelControlProps {
tunnelState: TunnelStateTransition;
selectedRelayName: string;
city?: string;
country?: string;
hostname?: string;
defaultConnectionInfoOpen?: boolean;
- relayInAddress?: RelayInAddress;
- relayOutAddress?: RelayOutAddress;
+ relayInAddress?: IRelayInAddress;
+ relayOutAddress?: IRelayOutAddress;
onConnect: () => void;
onDisconnect: () => void;
onSelectLocation: () => void;
onToggleConnectionInfo: (value: boolean) => void;
-};
+}
const styles = {
body: Styles.createViewStyle({
@@ -75,8 +75,8 @@ const styles = {
}),
};
-export default class TunnelControl extends Component<TunnelControlProps> {
- render() {
+export default class TunnelControl extends Component<ITunnelControlProps> {
+ public render() {
const Location = ({ children }: { children?: React.ReactNode }) => (
<View style={styles.status_location}>{children}</View>
);
@@ -253,18 +253,18 @@ export default class TunnelControl extends Component<TunnelControlProps> {
}
}
-type ContainerProps = {
+interface IContainerProps {
children?: Types.ReactNode;
-};
+}
-class Wrapper extends Component<ContainerProps> {
- render() {
+class Wrapper extends Component<IContainerProps> {
+ public render() {
return <View style={styles.wrapper}>{this.props.children}</View>;
}
}
-class Body extends Component<ContainerProps> {
- render() {
+class Body extends Component<IContainerProps> {
+ public render() {
return <View style={styles.body}>{this.props.children}</View>;
}
}
diff --git a/gui/packages/desktop/src/renderer/containers/AccountPage.tsx b/gui/packages/desktop/src/renderer/containers/AccountPage.tsx
index c9725147b5..33dad1e1cb 100644
--- a/gui/packages/desktop/src/renderer/containers/AccountPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/AccountPage.tsx
@@ -1,20 +1,20 @@
+import { goBack } from 'connected-react-router';
import { remote, shell } from 'electron';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { goBack } from 'connected-react-router';
-import Account from '../components/Account';
import { links } from '../../config.json';
+import Account from '../components/Account';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: ReduxState) => ({
+const mapStateToProps = (state: IReduxState) => ({
accountToken: state.account.accountToken,
accountExpiry: state.account.expiry,
expiryLocale: remote.app.getLocale(),
isOffline: state.connection.isBlocked,
});
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
const history = bindActionCreators({ goBack }, dispatch);
return {
onLogout: () => {
@@ -23,7 +23,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) =>
onClose: () => {
history.goBack();
},
- onBuyMore: () => shell.openExternal(links['purchase']),
+ onBuyMore: () => shell.openExternal(links.purchase),
};
};
diff --git a/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx b/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx
index cc32d6556d..d70c0a99ee 100644
--- a/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx
@@ -1,16 +1,16 @@
+import { goBack } from 'connected-react-router';
import log from 'electron-log';
import { connect } from 'react-redux';
-import { goBack } from 'connected-react-router';
import { bindActionCreators } from 'redux';
+import { RelayProtocol } from '../../shared/daemon-rpc-types';
import { AdvancedSettings } from '../components/AdvancedSettings';
import RelaySettingsBuilder from '../lib/relay-settings-builder';
-import { RelayProtocol } from '../../shared/daemon-rpc-types';
-import { ReduxState, ReduxDispatch } from '../redux/store';
import { RelaySettingsRedux } from '../redux/settings/reducers';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: ReduxState) => {
+const mapStateToProps = (state: IReduxState) => {
const protocolAndPort = mapRelaySettingsToProtocolAndPort(state.settings.relaySettings);
return {
@@ -36,7 +36,7 @@ const mapRelaySettingsToProtocolAndPort = (relaySettings: RelaySettingsRedux) =>
}
};
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
const history = bindActionCreators({ goBack }, dispatch);
return {
onClose: () => {
diff --git a/gui/packages/desktop/src/renderer/containers/ConnectPage.tsx b/gui/packages/desktop/src/renderer/containers/ConnectPage.tsx
index f20927ab29..75071e3a1f 100644
--- a/gui/packages/desktop/src/renderer/containers/ConnectPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/ConnectPage.tsx
@@ -1,20 +1,20 @@
+import { push } from 'connected-react-router';
import { shell } from 'electron';
import log from 'electron-log';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { push } from 'connected-react-router';
import Connect from '../components/Connect';
import AccountExpiry from '../lib/account-expiry';
import userInterfaceActions from '../redux/userinterface/actions';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-import { RelaySettingsRedux, RelayLocationRedux } from '../redux/settings/reducers';
+import { IRelayLocationRedux, RelaySettingsRedux } from '../redux/settings/reducers';
function getRelayName(
relaySettings: RelaySettingsRedux,
- relayLocations: Array<RelayLocationRedux>,
+ relayLocations: IRelayLocationRedux[],
): string {
if ('normal' in relaySettings) {
const location = relaySettings.normal.location;
@@ -54,7 +54,7 @@ function getRelayName(
}
}
-const mapStateToProps = (state: ReduxState) => {
+const mapStateToProps = (state: IReduxState) => {
return {
accountExpiry: state.account.expiry ? new AccountExpiry(state.account.expiry) : undefined,
selectedRelayName: getRelayName(state.settings.relaySettings, state.settings.relayLocations),
@@ -65,7 +65,7 @@ const mapStateToProps = (state: ReduxState) => {
};
};
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
const userInterface = bindActionCreators(userInterfaceActions, dispatch);
const history = bindActionCreators({ push }, dispatch);
diff --git a/gui/packages/desktop/src/renderer/containers/LaunchPage.tsx b/gui/packages/desktop/src/renderer/containers/LaunchPage.tsx
index 00d42acdb6..8619b33d87 100644
--- a/gui/packages/desktop/src/renderer/containers/LaunchPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/LaunchPage.tsx
@@ -1,13 +1,13 @@
-import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
import { push } from 'connected-react-router';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
import Launch from '../components/Launch';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (_state: ReduxState) => ({});
-const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) => {
+const mapStateToProps = (_state: IReduxState) => ({});
+const mapDispatchToProps = (dispatch: ReduxDispatch, _props: ISharedRouteProps) => {
const history = bindActionCreators({ push }, dispatch);
return {
openSettings: () => {
diff --git a/gui/packages/desktop/src/renderer/containers/LoginPage.tsx b/gui/packages/desktop/src/renderer/containers/LoginPage.tsx
index b73883c7fc..e1f70fbedb 100644
--- a/gui/packages/desktop/src/renderer/containers/LoginPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/LoginPage.tsx
@@ -1,14 +1,14 @@
+import { push } from 'connected-react-router';
import { shell } from 'electron';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { push } from 'connected-react-router';
import Login from '../components/Login';
import accountActions from '../redux/account/actions';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: ReduxState) => {
+const mapStateToProps = (state: IReduxState) => {
const { accountToken, accountHistory, error, status } = state.account;
return {
accountToken,
@@ -17,7 +17,7 @@ const mapStateToProps = (state: ReduxState) => {
loginState: status,
};
};
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
const history = bindActionCreators({ push }, dispatch);
const { resetLoginError, updateAccountToken } = bindActionCreators(accountActions, dispatch);
return {
@@ -31,7 +31,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) =>
resetLoginError();
},
openExternalLink: (url: string) => shell.openExternal(url),
- updateAccountToken: updateAccountToken,
+ updateAccountToken,
removeAccountTokenFromHistory: (token: string) => props.app.removeAccountFromHistory(token),
};
};
diff --git a/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.tsx b/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.tsx
index 844dd74253..f39cd82600 100644
--- a/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.tsx
+++ b/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.tsx
@@ -1,9 +1,9 @@
import { connect } from 'react-redux';
import PlatformWindow from '../components/PlatformWindow';
-import { ReduxState } from '../redux/store';
+import { IReduxState } from '../redux/store';
-const mapStateToProps = (state: ReduxState) => ({
+const mapStateToProps = (state: IReduxState) => ({
arrowPosition: state.userInterface.arrowPosition,
});
diff --git a/gui/packages/desktop/src/renderer/containers/PreferencesPage.tsx b/gui/packages/desktop/src/renderer/containers/PreferencesPage.tsx
index 9701a2ff0f..4147b42cfc 100644
--- a/gui/packages/desktop/src/renderer/containers/PreferencesPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/PreferencesPage.tsx
@@ -1,13 +1,13 @@
+import { goBack } from 'connected-react-router';
import log from 'electron-log';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { goBack } from 'connected-react-router';
import Preferences from '../components/Preferences';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: ReduxState) => ({
+const mapStateToProps = (state: IReduxState) => ({
autoStart: state.settings.autoStart,
autoConnect: state.settings.guiSettings.autoConnect,
allowLan: state.settings.allowLan,
@@ -15,7 +15,7 @@ const mapStateToProps = (state: ReduxState) => ({
startMinimized: state.settings.guiSettings.startMinimized,
});
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
const history = bindActionCreators({ goBack }, dispatch);
return {
onClose: () => {
diff --git a/gui/packages/desktop/src/renderer/containers/SelectLocationPage.tsx b/gui/packages/desktop/src/renderer/containers/SelectLocationPage.tsx
index c15feea88a..a517c34b3f 100644
--- a/gui/packages/desktop/src/renderer/containers/SelectLocationPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/SelectLocationPage.tsx
@@ -1,19 +1,19 @@
+import { goBack } from 'connected-react-router';
import log from 'electron-log';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { goBack } from 'connected-react-router';
+import { RelayLocation } from '../../shared/daemon-rpc-types';
import SelectLocation from '../components/SelectLocation';
import RelaySettingsBuilder from '../lib/relay-settings-builder';
-import { RelayLocation } from '../../shared/daemon-rpc-types';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: ReduxState) => ({
+const mapStateToProps = (state: IReduxState) => ({
relaySettings: state.settings.relaySettings,
relayLocations: state.settings.relayLocations,
});
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
const history = bindActionCreators({ goBack }, dispatch);
return {
onClose: () => history.goBack(),
diff --git a/gui/packages/desktop/src/renderer/containers/SettingsPage.tsx b/gui/packages/desktop/src/renderer/containers/SettingsPage.tsx
index abe6fa0e93..da4a10cdb2 100644
--- a/gui/packages/desktop/src/renderer/containers/SettingsPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/SettingsPage.tsx
@@ -1,13 +1,13 @@
+import { goBack, push } from 'connected-react-router';
import { remote, shell } from 'electron';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { push, goBack } from 'connected-react-router';
import Settings from '../components/Settings';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: ReduxState) => ({
+const mapStateToProps = (state: IReduxState) => ({
loginState: state.account.status,
accountExpiry: state.account.expiry,
appVersion: state.version.current,
@@ -15,7 +15,7 @@ const mapStateToProps = (state: ReduxState) => ({
upToDateVersion: state.version.upToDate,
isOffline: state.connection.isBlocked,
});
-const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, _props: ISharedRouteProps) => {
const history = bindActionCreators({ push, goBack }, dispatch);
return {
onQuit: () => remote.app.quit(),
diff --git a/gui/packages/desktop/src/renderer/containers/SupportPage.tsx b/gui/packages/desktop/src/renderer/containers/SupportPage.tsx
index 2746734aee..00be7fee44 100644
--- a/gui/packages/desktop/src/renderer/containers/SupportPage.tsx
+++ b/gui/packages/desktop/src/renderer/containers/SupportPage.tsx
@@ -1,22 +1,22 @@
+import { goBack } from 'connected-react-router';
import { shell } from 'electron';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { goBack } from 'connected-react-router';
import Support from '../components/Support';
import { collectProblemReport, sendProblemReport } from '../lib/problem-report';
-import { ReduxState, ReduxDispatch } from '../redux/store';
-import { SharedRouteProps } from '../routes';
+import { IReduxState, ReduxDispatch } from '../redux/store';
import supportActions from '../redux/support/actions';
+import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: ReduxState) => ({
+const mapStateToProps = (state: IReduxState) => ({
defaultEmail: state.support.email,
defaultMessage: state.support.message,
accountHistory: state.account.accountHistory,
isOffline: state.connection.isBlocked,
});
-const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) => {
+const mapDispatchToProps = (dispatch: ReduxDispatch, _props: ISharedRouteProps) => {
const { saveReportForm, clearReportForm } = bindActionCreators(supportActions, dispatch);
const history = bindActionCreators({ goBack }, dispatch);
diff --git a/gui/packages/desktop/src/renderer/lib/account-expiry.ts b/gui/packages/desktop/src/renderer/lib/account-expiry.ts
index 4f4f51ce05..f7f85afbda 100644
--- a/gui/packages/desktop/src/renderer/lib/account-expiry.ts
+++ b/gui/packages/desktop/src/renderer/lib/account-expiry.ts
@@ -1,21 +1,21 @@
import moment from 'moment';
export default class AccountExpiry {
- _expiry: moment.Moment;
+ private expiry: moment.Moment;
constructor(expiry: string) {
- this._expiry = moment(expiry);
+ this.expiry = moment(expiry);
}
- hasExpired(): boolean {
+ public hasExpired(): boolean {
return this.willHaveExpiredIn(moment());
}
- willHaveExpiredIn(time: moment.Moment): boolean {
- return this._expiry.isSameOrBefore(time);
+ public willHaveExpiredIn(time: moment.Moment): boolean {
+ return this.expiry.isSameOrBefore(time);
}
- remainingTime(): string {
- return this._expiry.fromNow(true) + ' left';
+ public remainingTime(): string {
+ return this.expiry.fromNow(true) + ' left';
}
}
diff --git a/gui/packages/desktop/src/renderer/lib/auth-failure.ts b/gui/packages/desktop/src/renderer/lib/auth-failure.ts
index efee82b08c..3f6f8b4609 100644
--- a/gui/packages/desktop/src/renderer/lib/auth-failure.ts
+++ b/gui/packages/desktop/src/renderer/lib/auth-failure.ts
@@ -16,14 +16,14 @@ const TOO_MANY_CONNECTIONS_MSG =
'This account has too many simultaneous connections. Disconnect another device or try connecting again shortly.';
export class AuthFailure {
- _reasonId: AuthFailureKind;
- _message: string;
+ private reasonId: AuthFailureKind;
+ private message: string;
constructor(reason?: string) {
if (!reason) {
log.error('Received invalid auth_failed reason: ', reason);
- this._reasonId = 'UNKNOWN';
- this._message = GENERIC_FAILURE_MSG;
+ this.reasonId = 'UNKNOWN';
+ this.message = GENERIC_FAILURE_MSG;
return;
}
@@ -31,18 +31,18 @@ export class AuthFailure {
if (!results || results.length < 3) {
log.error(`Received invalid auth_failed message - "${reason}"`);
- this._reasonId = 'UNKNOWN';
- this._message = reason;
+ this.reasonId = 'UNKNOWN';
+ this.message = reason;
return;
}
- const id_string = results[1];
- this._reasonId = strToFailureKind(id_string);
- this._message = results[2] || GENERIC_FAILURE_MSG;
+ const idString = results[1];
+ this.reasonId = strToFailureKind(idString);
+ this.message = results[2] || GENERIC_FAILURE_MSG;
}
- show(): string {
- switch (this._reasonId) {
+ public show(): string {
+ switch (this.reasonId) {
case 'INVALID_ACCOUNT':
return INVALID_ACCOUNT_MSG;
case 'EXPIRED_ACCOUNT':
@@ -50,7 +50,7 @@ export class AuthFailure {
case 'TOO_MANY_CONNECTIONS':
return TOO_MANY_CONNECTIONS_MSG;
case 'UNKNOWN':
- return this._message;
+ return this.message;
}
}
}
diff --git a/gui/packages/desktop/src/renderer/lib/problem-report.ts b/gui/packages/desktop/src/renderer/lib/problem-report.ts
index 0afcf30091..7213ed8d07 100644
--- a/gui/packages/desktop/src/renderer/lib/problem-report.ts
+++ b/gui/packages/desktop/src/renderer/lib/problem-report.ts
@@ -1,11 +1,14 @@
import { ipcRenderer } from 'electron';
import * as uuid from 'uuid';
-type ErrorResult = { success: false; error: string };
-type CollectResult = { success: true; reportPath: string } | ErrorResult;
-type SendResult = { success: true } | ErrorResult;
+interface IErrorResult {
+ success: false;
+ error: string;
+}
+type CollectResult = { success: true; reportPath: string } | IErrorResult;
+type SendResult = { success: true } | IErrorResult;
-const collectProblemReport = (toRedact: Array<string>): Promise<string> => {
+const collectProblemReport = (toRedact: string[]): Promise<string> => {
return new Promise((resolve, reject) => {
const requestId = uuid.v4();
const responseListener = (
diff --git a/gui/packages/desktop/src/renderer/lib/relay-settings-builder.ts b/gui/packages/desktop/src/renderer/lib/relay-settings-builder.ts
index 949b02d35f..90cec5d3c6 100644
--- a/gui/packages/desktop/src/renderer/lib/relay-settings-builder.ts
+++ b/gui/packages/desktop/src/renderer/lib/relay-settings-builder.ts
@@ -1,64 +1,64 @@
import {
+ IOpenVpnConstraints,
RelayLocation,
RelayProtocol,
- RelaySettingsUpdate,
RelaySettingsNormalUpdate,
- OpenVpnConstraints,
+ RelaySettingsUpdate,
} from '../../shared/daemon-rpc-types';
-type LocationBuilder<Self> = {
+interface ILocationBuilder<Self> {
country: (country: string) => Self;
city: (country: string, city: string) => Self;
hostname: (country: string, city: string, hostname: string) => Self;
any: () => Self;
fromRaw: (location: 'any' | RelayLocation) => Self;
-};
+}
-interface ExactOrAny<T, Self> {
+interface IExactOrAny<T, Self> {
exact(value: T): Self;
any(): Self;
}
-interface OpenVPNConfigurator {
- port: ExactOrAny<number, OpenVPNConfigurator>;
- protocol: ExactOrAny<RelayProtocol, OpenVPNConfigurator>;
+interface IOpenVPNConfigurator {
+ port: IExactOrAny<number, IOpenVPNConfigurator>;
+ protocol: IExactOrAny<RelayProtocol, IOpenVPNConfigurator>;
}
-interface TunnelBuilder {
+interface ITunnelBuilder {
openvpn(
- configurator: (openVpnConfigurator: OpenVPNConfigurator) => void,
+ configurator: (openVpnConfigurator: IOpenVPNConfigurator) => void,
): NormalRelaySettingsBuilder;
any(): NormalRelaySettingsBuilder;
}
class NormalRelaySettingsBuilder {
- _payload: RelaySettingsNormalUpdate = {};
+ private payload: RelaySettingsNormalUpdate = {};
- build(): RelaySettingsUpdate {
+ public build(): RelaySettingsUpdate {
return {
- normal: this._payload,
+ normal: this.payload,
};
}
- get location(): LocationBuilder<NormalRelaySettingsBuilder> {
+ get location(): ILocationBuilder<NormalRelaySettingsBuilder> {
return {
country: (country: string) => {
- this._payload.location = { only: { country } };
+ this.payload.location = { only: { country } };
return this;
},
city: (country: string, city: string) => {
- this._payload.location = { only: { city: [country, city] } };
+ this.payload.location = { only: { city: [country, city] } };
return this;
},
hostname: (country: string, city: string, hostname: string) => {
- this._payload.location = { only: { hostname: [country, city, hostname] } };
+ this.payload.location = { only: { hostname: [country, city, hostname] } };
return this;
},
any: () => {
- this._payload.location = 'any';
+ this.payload.location = 'any';
return this;
},
- fromRaw: function(location: 'any' | RelayLocation) {
+ fromRaw(location: 'any' | RelayLocation) {
if (location === 'any') {
return this.any();
} else if ('hostname' in location) {
@@ -78,18 +78,18 @@ class NormalRelaySettingsBuilder {
};
}
- get tunnel(): TunnelBuilder {
- const updateOpenvpn = (next: Partial<OpenVpnConstraints>) => {
- const tunnel = this._payload.tunnel;
+ get tunnel(): ITunnelBuilder {
+ const updateOpenvpn = (next: Partial<IOpenVpnConstraints>) => {
+ const tunnel = this.payload.tunnel;
if (typeof tunnel === 'string' || typeof tunnel === 'undefined') {
- this._payload.tunnel = {
+ this.payload.tunnel = {
only: {
openvpn: next,
},
};
} else if (typeof tunnel === 'object') {
const prev = (tunnel.only && tunnel.only.openvpn) || {};
- this._payload.tunnel = {
+ this.payload.tunnel = {
only: {
openvpn: { ...prev, ...next },
},
@@ -98,8 +98,8 @@ class NormalRelaySettingsBuilder {
};
return {
- openvpn: (configurator: (configurator: OpenVPNConfigurator) => void) => {
- const openvpnBuilder: OpenVPNConfigurator = {
+ openvpn: (configurator: (configurator: IOpenVPNConfigurator) => void) => {
+ const openvpnBuilder: IOpenVPNConfigurator = {
get port() {
const apply = (port: 'any' | { only: number }) => {
updateOpenvpn({ port });
@@ -127,7 +127,7 @@ class NormalRelaySettingsBuilder {
return this;
},
any: () => {
- this._payload.tunnel = 'any';
+ this.payload.tunnel = 'any';
return this;
},
};
diff --git a/gui/packages/desktop/src/renderer/lib/transition-rule.ts b/gui/packages/desktop/src/renderer/lib/transition-rule.ts
index 5e3a9138c4..ae0e2ad5b7 100644
--- a/gui/packages/desktop/src/renderer/lib/transition-rule.ts
+++ b/gui/packages/desktop/src/renderer/lib/transition-rule.ts
@@ -1,41 +1,33 @@
-export type TransitionDescriptor = {
+export interface ITransitionDescriptor {
name: string;
duration: number;
-};
+}
-export type TransitionFork = {
- forward: TransitionDescriptor;
- backward: TransitionDescriptor;
-};
+export interface ITransitionFork {
+ forward: ITransitionDescriptor;
+ backward: ITransitionDescriptor;
+}
-export type TransitionMatch = {
+export interface ITransitionMatch {
direction: 'forward' | 'backward';
- descriptor: TransitionDescriptor;
-};
+ descriptor: ITransitionDescriptor;
+}
export default class TransitionRule {
- _from: string | null;
- _to: string;
- _fork: TransitionFork;
-
- constructor(from: string | null, to: string, fork: TransitionFork) {
- this._from = from;
- this._to = to;
- this._fork = fork;
- }
+ constructor(private from: string | null, private to: string, private fork: ITransitionFork) {}
- match(fromRoute: string | null, toRoute: string): TransitionMatch | null {
- if ((!this._from || this._from === fromRoute) && this._to === toRoute) {
+ public match(fromRoute: string | null, toRoute: string): ITransitionMatch | null {
+ if ((!this.from || this.from === fromRoute) && this.to === toRoute) {
return {
direction: 'forward',
- descriptor: this._fork['forward'],
+ descriptor: this.fork.forward,
};
}
- if ((!this._from || this._from === toRoute) && this._to === fromRoute) {
+ if ((!this.from || this.from === toRoute) && this.to === fromRoute) {
return {
direction: 'backward',
- descriptor: this._fork['backward'],
+ descriptor: this.fork.backward,
};
}
diff --git a/gui/packages/desktop/src/renderer/redux/account/actions.ts b/gui/packages/desktop/src/renderer/redux/account/actions.ts
index 0084b5a9d7..8dba590737 100644
--- a/gui/packages/desktop/src/renderer/redux/account/actions.ts
+++ b/gui/packages/desktop/src/renderer/redux/account/actions.ts
@@ -1,99 +1,99 @@
import { AccountToken } from '../../../shared/daemon-rpc-types';
-type StartLoginAction = {
+interface IStartLoginAction {
type: 'START_LOGIN';
accountToken: AccountToken;
-};
+}
-type LoggedInAction = {
+interface ILoggedInAction {
type: 'LOGGED_IN';
-};
+}
-type LoginFailedAction = {
+interface ILoginFailedAction {
type: 'LOGIN_FAILED';
error: Error;
-};
+}
-type LoggedOutAction = {
+interface ILoggedOutAction {
type: 'LOGGED_OUT';
-};
+}
-type ResetLoginErrorAction = {
+interface IResetLoginErrorAction {
type: 'RESET_LOGIN_ERROR';
-};
+}
-type UpdateAccountTokenAction = {
+interface IUpdateAccountTokenAction {
type: 'UPDATE_ACCOUNT_TOKEN';
token: AccountToken;
-};
+}
-type UpdateAccountHistoryAction = {
+interface IUpdateAccountHistoryAction {
type: 'UPDATE_ACCOUNT_HISTORY';
- accountHistory: Array<AccountToken>;
-};
+ accountHistory: AccountToken[];
+}
-type UpdateAccountExpiryAction = {
+interface IUpdateAccountExpiryAction {
type: 'UPDATE_ACCOUNT_EXPIRY';
expiry: string;
-};
+}
export type AccountAction =
- | StartLoginAction
- | LoggedInAction
- | LoginFailedAction
- | LoggedOutAction
- | ResetLoginErrorAction
- | UpdateAccountTokenAction
- | UpdateAccountHistoryAction
- | UpdateAccountExpiryAction;
+ | IStartLoginAction
+ | ILoggedInAction
+ | ILoginFailedAction
+ | ILoggedOutAction
+ | IResetLoginErrorAction
+ | IUpdateAccountTokenAction
+ | IUpdateAccountHistoryAction
+ | IUpdateAccountExpiryAction;
-function startLogin(accountToken: AccountToken): StartLoginAction {
+function startLogin(accountToken: AccountToken): IStartLoginAction {
return {
type: 'START_LOGIN',
- accountToken: accountToken,
+ accountToken,
};
}
-function loggedIn(): LoggedInAction {
+function loggedIn(): ILoggedInAction {
return {
type: 'LOGGED_IN',
};
}
-function loginFailed(error: Error): LoginFailedAction {
+function loginFailed(error: Error): ILoginFailedAction {
return {
type: 'LOGIN_FAILED',
error,
};
}
-function loggedOut(): LoggedOutAction {
+function loggedOut(): ILoggedOutAction {
return {
type: 'LOGGED_OUT',
};
}
-function resetLoginError(): ResetLoginErrorAction {
+function resetLoginError(): IResetLoginErrorAction {
return {
type: 'RESET_LOGIN_ERROR',
};
}
-function updateAccountToken(token: AccountToken): UpdateAccountTokenAction {
+function updateAccountToken(token: AccountToken): IUpdateAccountTokenAction {
return {
type: 'UPDATE_ACCOUNT_TOKEN',
token,
};
}
-function updateAccountHistory(accountHistory: Array<AccountToken>): UpdateAccountHistoryAction {
+function updateAccountHistory(accountHistory: AccountToken[]): IUpdateAccountHistoryAction {
return {
type: 'UPDATE_ACCOUNT_HISTORY',
accountHistory,
};
}
-function updateAccountExpiry(expiry: string): UpdateAccountExpiryAction {
+function updateAccountExpiry(expiry: string): IUpdateAccountExpiryAction {
return {
type: 'UPDATE_ACCOUNT_EXPIRY',
expiry,
diff --git a/gui/packages/desktop/src/renderer/redux/account/reducers.ts b/gui/packages/desktop/src/renderer/redux/account/reducers.ts
index 75f4fcd997..754ea09d13 100644
--- a/gui/packages/desktop/src/renderer/redux/account/reducers.ts
+++ b/gui/packages/desktop/src/renderer/redux/account/reducers.ts
@@ -1,16 +1,16 @@
-import { ReduxAction } from '../store';
import { AccountToken } from '../../../shared/daemon-rpc-types';
+import { ReduxAction } from '../store';
export type LoginState = 'none' | 'logging in' | 'failed' | 'ok';
-export type AccountReduxState = {
+export interface IAccountReduxState {
accountToken?: AccountToken;
- accountHistory: Array<AccountToken>;
+ accountHistory: AccountToken[];
expiry?: string; // ISO8601
status: LoginState;
error?: Error;
-};
+}
-const initialState: AccountReduxState = {
+const initialState: IAccountReduxState = {
accountToken: undefined,
accountHistory: [],
expiry: undefined,
@@ -19,9 +19,9 @@ const initialState: AccountReduxState = {
};
export default function(
- state: AccountReduxState = initialState,
+ state: IAccountReduxState = initialState,
action: ReduxAction,
-): AccountReduxState {
+): IAccountReduxState {
switch (action.type) {
case 'START_LOGIN':
return {
diff --git a/gui/packages/desktop/src/renderer/redux/connection/actions.ts b/gui/packages/desktop/src/renderer/redux/connection/actions.ts
index e700c8918c..f24d080a57 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/actions.ts
+++ b/gui/packages/desktop/src/renderer/redux/connection/actions.ts
@@ -1,30 +1,30 @@
-import { AfterDisconnect, BlockReason, TunnelEndpoint } from '../../../shared/daemon-rpc-types';
+import { AfterDisconnect, BlockReason, ITunnelEndpoint } from '../../../shared/daemon-rpc-types';
-type ConnectingAction = {
+interface IConnectingAction {
type: 'CONNECTING';
- tunnelEndpoint?: TunnelEndpoint;
-};
+ tunnelEndpoint?: ITunnelEndpoint;
+}
-type ConnectedAction = {
+interface IConnectedAction {
type: 'CONNECTED';
- tunnelEndpoint: TunnelEndpoint;
-};
+ tunnelEndpoint: ITunnelEndpoint;
+}
-type DisconnectedAction = {
+interface IDisconnectedAction {
type: 'DISCONNECTED';
-};
+}
-type DisconnectingAction = {
+interface IDisconnectingAction {
type: 'DISCONNECTING';
afterDisconnect: AfterDisconnect;
-};
+}
-type BlockedAction = {
+interface IBlockedAction {
type: 'BLOCKED';
reason: BlockReason;
-};
+}
-type NewLocationAction = {
+interface INewLocationAction {
type: 'NEW_LOCATION';
newLocation: {
country: string;
@@ -34,74 +34,74 @@ type NewLocationAction = {
mullvadExitIp: boolean;
hostname?: string;
};
-};
+}
-type OnlineAction = {
+interface IOnlineAction {
type: 'ONLINE';
-};
+}
-type OfflineAction = {
+interface IOfflineAction {
type: 'OFFLINE';
-};
+}
export type ConnectionAction =
- | NewLocationAction
- | ConnectingAction
- | ConnectedAction
- | DisconnectedAction
- | DisconnectingAction
- | BlockedAction
- | OnlineAction
- | OfflineAction;
+ | INewLocationAction
+ | IConnectingAction
+ | IConnectedAction
+ | IDisconnectedAction
+ | IDisconnectingAction
+ | IBlockedAction
+ | IOnlineAction
+ | IOfflineAction;
-function connecting(tunnelEndpoint?: TunnelEndpoint): ConnectingAction {
+function connecting(tunnelEndpoint?: ITunnelEndpoint): IConnectingAction {
return {
type: 'CONNECTING',
tunnelEndpoint,
};
}
-function connected(tunnelEndpoint: TunnelEndpoint): ConnectedAction {
+function connected(tunnelEndpoint: ITunnelEndpoint): IConnectedAction {
return {
type: 'CONNECTED',
tunnelEndpoint,
};
}
-function disconnected(): DisconnectedAction {
+function disconnected(): IDisconnectedAction {
return {
type: 'DISCONNECTED',
};
}
-function disconnecting(afterDisconnect: AfterDisconnect): DisconnectingAction {
+function disconnecting(afterDisconnect: AfterDisconnect): IDisconnectingAction {
return {
type: 'DISCONNECTING',
afterDisconnect,
};
}
-function blocked(reason: BlockReason): BlockedAction {
+function blocked(reason: BlockReason): IBlockedAction {
return {
type: 'BLOCKED',
reason,
};
}
-function newLocation(newLocation: NewLocationAction['newLocation']): NewLocationAction {
+function newLocation(location: INewLocationAction['newLocation']): INewLocationAction {
return {
type: 'NEW_LOCATION',
- newLocation,
+ newLocation: location,
};
}
-function online(): OnlineAction {
+function online(): IOnlineAction {
return {
type: 'ONLINE',
};
}
-function offline(): OfflineAction {
+function offline(): IOfflineAction {
return {
type: 'OFFLINE',
};
diff --git a/gui/packages/desktop/src/renderer/redux/connection/reducers.ts b/gui/packages/desktop/src/renderer/redux/connection/reducers.ts
index 4bca04fa7c..bd22a6f110 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/reducers.ts
+++ b/gui/packages/desktop/src/renderer/redux/connection/reducers.ts
@@ -1,7 +1,7 @@
+import { Ip, TunnelStateTransition } from '../../../shared/daemon-rpc-types';
import { ReduxAction } from '../store';
-import { TunnelStateTransition, Ip } from '../../../shared/daemon-rpc-types';
-export type ConnectionReduxState = {
+export interface IConnectionReduxState {
status: TunnelStateTransition;
isOnline: boolean;
isBlocked: boolean;
@@ -11,9 +11,9 @@ export type ConnectionReduxState = {
longitude?: number;
country?: string;
city?: string;
-};
+}
-const initialState: ConnectionReduxState = {
+const initialState: IConnectionReduxState = {
status: { state: 'disconnected' },
isOnline: true,
isBlocked: false,
@@ -26,9 +26,9 @@ const initialState: ConnectionReduxState = {
};
export default function(
- state: ConnectionReduxState = initialState,
+ state: IConnectionReduxState = initialState,
action: ReduxAction,
-): ConnectionReduxState {
+): IConnectionReduxState {
switch (action.type) {
case 'NEW_LOCATION':
const { hostname, latitude, longitude, city, country } = action.newLocation;
diff --git a/gui/packages/desktop/src/renderer/redux/settings/actions.ts b/gui/packages/desktop/src/renderer/redux/settings/actions.ts
index e08f61fce5..4587a1c513 100644
--- a/gui/packages/desktop/src/renderer/redux/settings/actions.ts
+++ b/gui/packages/desktop/src/renderer/redux/settings/actions.ts
@@ -1,87 +1,85 @@
-import { RelaySettingsRedux, RelayLocationRedux } from './reducers';
-import { GuiSettingsState } from '../../../shared/gui-settings-state';
+import { IGuiSettingsState } from '../../../shared/gui-settings-state';
+import { IRelayLocationRedux, RelaySettingsRedux } from './reducers';
-export type UpdateGuiSettingsAction = {
+export interface IUpdateGuiSettingsAction {
type: 'UPDATE_GUI_SETTINGS';
- guiSettings: GuiSettingsState;
-};
+ guiSettings: IGuiSettingsState;
+}
-export type UpdateRelayAction = {
+export interface IUpdateRelayAction {
type: 'UPDATE_RELAY';
relay: RelaySettingsRedux;
-};
+}
-export type UpdateRelayLocationsAction = {
+export interface IUpdateRelayLocationsAction {
type: 'UPDATE_RELAY_LOCATIONS';
- relayLocations: Array<RelayLocationRedux>;
-};
+ relayLocations: IRelayLocationRedux[];
+}
-export type UpdateAllowLanAction = {
+export interface IUpdateAllowLanAction {
type: 'UPDATE_ALLOW_LAN';
allowLan: boolean;
-};
+}
-export type UpdateEnableIpv6Action = {
+export interface IUpdateEnableIpv6Action {
type: 'UPDATE_ENABLE_IPV6';
enableIpv6: boolean;
-};
+}
-export type UpdateBlockWhenDisconnectedAction = {
+export interface IUpdateBlockWhenDisconnectedAction {
type: 'UPDATE_BLOCK_WHEN_DISCONNECTED';
blockWhenDisconnected: boolean;
-};
+}
-export type UpdateOpenVpnMssfixAction = {
+export interface IUpdateOpenVpnMssfixAction {
type: 'UPDATE_OPENVPN_MSSFIX';
mssfix?: number;
-};
+}
-export type UpdateAutoStartAction = {
+export interface IUpdateAutoStartAction {
type: 'UPDATE_AUTO_START';
autoStart: boolean;
-};
+}
export type SettingsAction =
- | UpdateGuiSettingsAction
- | UpdateRelayAction
- | UpdateRelayLocationsAction
- | UpdateAllowLanAction
- | UpdateEnableIpv6Action
- | UpdateBlockWhenDisconnectedAction
- | UpdateOpenVpnMssfixAction
- | UpdateAutoStartAction;
+ | IUpdateGuiSettingsAction
+ | IUpdateRelayAction
+ | IUpdateRelayLocationsAction
+ | IUpdateAllowLanAction
+ | IUpdateEnableIpv6Action
+ | IUpdateBlockWhenDisconnectedAction
+ | IUpdateOpenVpnMssfixAction
+ | IUpdateAutoStartAction;
-function updateGuiSettings(guiSettings: GuiSettingsState): UpdateGuiSettingsAction {
+function updateGuiSettings(guiSettings: IGuiSettingsState): IUpdateGuiSettingsAction {
return {
type: 'UPDATE_GUI_SETTINGS',
guiSettings,
};
}
-function updateRelay(relay: RelaySettingsRedux): UpdateRelayAction {
+function updateRelay(relay: RelaySettingsRedux): IUpdateRelayAction {
return {
type: 'UPDATE_RELAY',
- relay: relay,
+ relay,
};
}
-function updateRelayLocations(
- relayLocations: Array<RelayLocationRedux>,
-): UpdateRelayLocationsAction {
+function updateRelayLocations(relayLocations: IRelayLocationRedux[]): IUpdateRelayLocationsAction {
return {
type: 'UPDATE_RELAY_LOCATIONS',
- relayLocations: relayLocations,
+ relayLocations,
};
}
-function updateAllowLan(allowLan: boolean): UpdateAllowLanAction {
+function updateAllowLan(allowLan: boolean): IUpdateAllowLanAction {
return {
type: 'UPDATE_ALLOW_LAN',
allowLan,
};
}
-function updateEnableIpv6(enableIpv6: boolean): UpdateEnableIpv6Action {
+function updateEnableIpv6(enableIpv6: boolean): IUpdateEnableIpv6Action {
return {
type: 'UPDATE_ENABLE_IPV6',
enableIpv6,
@@ -90,21 +88,21 @@ function updateEnableIpv6(enableIpv6: boolean): UpdateEnableIpv6Action {
function updateBlockWhenDisconnected(
blockWhenDisconnected: boolean,
-): UpdateBlockWhenDisconnectedAction {
+): IUpdateBlockWhenDisconnectedAction {
return {
type: 'UPDATE_BLOCK_WHEN_DISCONNECTED',
blockWhenDisconnected,
};
}
-function updateOpenVpnMssfix(mssfix?: number): UpdateOpenVpnMssfixAction {
+function updateOpenVpnMssfix(mssfix?: number): IUpdateOpenVpnMssfixAction {
return {
type: 'UPDATE_OPENVPN_MSSFIX',
mssfix,
};
}
-function updateAutoStart(autoStart: boolean): UpdateAutoStartAction {
+function updateAutoStart(autoStart: boolean): IUpdateAutoStartAction {
return {
type: 'UPDATE_AUTO_START',
autoStart,
diff --git a/gui/packages/desktop/src/renderer/redux/settings/reducers.ts b/gui/packages/desktop/src/renderer/redux/settings/reducers.ts
index a4e269626a..60a610ece0 100644
--- a/gui/packages/desktop/src/renderer/redux/settings/reducers.ts
+++ b/gui/packages/desktop/src/renderer/redux/settings/reducers.ts
@@ -1,6 +1,6 @@
+import { RelayLocation, RelayProtocol } from '../../../shared/daemon-rpc-types';
+import { IGuiSettingsState } from '../../../shared/gui-settings-state';
import { ReduxAction } from '../store';
-import { RelayProtocol, RelayLocation } from '../../../shared/daemon-rpc-types';
-import { GuiSettingsState } from '../../../shared/gui-settings-state';
export type RelaySettingsRedux =
| {
@@ -18,44 +18,44 @@ export type RelaySettingsRedux =
};
};
-export type RelayLocationRelayRedux = {
+export interface IRelayLocationRelayRedux {
hostname: string;
ipv4AddrIn: string;
ipv4AddrExit: string;
includeInCountry: boolean;
weight: number;
-};
+}
-export type RelayLocationCityRedux = {
+export interface IRelayLocationCityRedux {
name: string;
code: string;
latitude: number;
longitude: number;
hasActiveRelays: boolean;
- relays: RelayLocationRelayRedux[];
-};
+ relays: IRelayLocationRelayRedux[];
+}
-export type RelayLocationRedux = {
+export interface IRelayLocationRedux {
name: string;
code: string;
hasActiveRelays: boolean;
- cities: RelayLocationCityRedux[];
-};
+ cities: IRelayLocationCityRedux[];
+}
-export type SettingsReduxState = {
+export interface ISettingsReduxState {
autoStart: boolean;
- guiSettings: GuiSettingsState;
+ guiSettings: IGuiSettingsState;
relaySettings: RelaySettingsRedux;
- relayLocations: RelayLocationRedux[];
+ relayLocations: IRelayLocationRedux[];
allowLan: boolean;
enableIpv6: boolean;
blockWhenDisconnected: boolean;
openVpn: {
mssfix?: number;
};
-};
+}
-const initialState: SettingsReduxState = {
+const initialState: ISettingsReduxState = {
autoStart: false,
guiSettings: {
autoConnect: true,
@@ -77,9 +77,9 @@ const initialState: SettingsReduxState = {
};
export default function(
- state: SettingsReduxState = initialState,
+ state: ISettingsReduxState = initialState,
action: ReduxAction,
-): SettingsReduxState {
+): ISettingsReduxState {
switch (action.type) {
case 'UPDATE_GUI_SETTINGS':
return {
diff --git a/gui/packages/desktop/src/renderer/redux/store.ts b/gui/packages/desktop/src/renderer/redux/store.ts
index 6bd38f45ea..e1324607dc 100644
--- a/gui/packages/desktop/src/renderer/redux/store.ts
+++ b/gui/packages/desktop/src/renderer/redux/store.ts
@@ -1,42 +1,29 @@
-import { createStore, applyMiddleware, combineReducers, compose, Dispatch, Store } from 'redux';
-import { routerMiddleware, connectRouter, push, replace } from 'connected-react-router';
+import { connectRouter, push, replace, routerMiddleware } from 'connected-react-router';
+import { applyMiddleware, combineReducers, compose, createStore, Dispatch, Store } from 'redux';
-import accountReducer from './account/reducers';
-import accountActions from './account/actions';
-import connectionReducer from './connection/reducers';
-import connectionActions from './connection/actions';
-import settingsReducer from './settings/reducers';
-import settingsActions from './settings/actions';
-import supportReducer from './support/reducers';
-import supportActions from './support/actions';
-import versionReducer from './version/reducers';
-import versionActions from './version/actions';
-import userInterfaceReducer from './userinterface/reducers';
-import userInterfaceActions from './userinterface/actions';
+import accountActions, { AccountAction } from './account/actions';
+import accountReducer, { IAccountReduxState } from './account/reducers';
+import connectionActions, { ConnectionAction } from './connection/actions';
+import connectionReducer, { IConnectionReduxState } from './connection/reducers';
+import settingsActions, { SettingsAction } from './settings/actions';
+import settingsReducer, { ISettingsReduxState } from './settings/reducers';
+import supportActions, { SupportAction } from './support/actions';
+import supportReducer, { ISupportReduxState } from './support/reducers';
+import userInterfaceActions, { UserInterfaceAction } from './userinterface/actions';
+import userInterfaceReducer, { IUserInterfaceReduxState } from './userinterface/reducers';
+import versionActions, { VersionAction } from './version/actions';
+import versionReducer, { IVersionReduxState } from './version/reducers';
import { History } from 'history';
-import { AccountReduxState } from './account/reducers';
-import { ConnectionReduxState } from './connection/reducers';
-import { SettingsReduxState } from './settings/reducers';
-import { SupportReduxState } from './support/reducers';
-import { VersionReduxState } from './version/reducers';
-import { UserInterfaceReduxState } from './userinterface/reducers';
-import { AccountAction } from './account/actions';
-import { ConnectionAction } from './connection/actions';
-import { SettingsAction } from './settings/actions';
-import { SupportAction } from './support/actions';
-import { VersionAction } from './version/actions';
-import { UserInterfaceAction } from './userinterface/actions';
-
-export type ReduxState = {
- account: AccountReduxState;
- connection: ConnectionReduxState;
- settings: SettingsReduxState;
- support: SupportReduxState;
- version: VersionReduxState;
- userInterface: UserInterfaceReduxState;
-};
+export interface IReduxState {
+ account: IAccountReduxState;
+ connection: IConnectionReduxState;
+ settings: ISettingsReduxState;
+ support: ISupportReduxState;
+ version: IVersionReduxState;
+ userInterface: IUserInterfaceReduxState;
+}
export type ReduxAction =
| AccountAction
@@ -45,14 +32,14 @@ export type ReduxAction =
| SupportAction
| VersionAction
| UserInterfaceAction;
-export type ReduxStore = Store<ReduxState, ReduxAction>;
+export type ReduxStore = Store<IReduxState, ReduxAction>;
export type ReduxDispatch = Dispatch<ReduxAction>;
export default function configureStore(
- initialState: ReduxState | null,
routerHistory: History,
+ initialState?: IReduxState,
): ReduxStore {
- const actionCreators: { [key: string]: Function } = {
+ const actionCreators = {
...accountActions,
...connectionActions,
...settingsActions,
diff --git a/gui/packages/desktop/src/renderer/redux/support/actions.ts b/gui/packages/desktop/src/renderer/redux/support/actions.ts
index 1ab40a775a..23cb57daf3 100644
--- a/gui/packages/desktop/src/renderer/redux/support/actions.ts
+++ b/gui/packages/desktop/src/renderer/redux/support/actions.ts
@@ -1,27 +1,27 @@
-export type SupportReportForm = {
+export interface ISupportReportForm {
email: string;
message: string;
-};
+}
-export type KeepReportFormAction = {
+export interface IKeepReportFormAction {
type: 'SAVE_REPORT_FORM';
- form: SupportReportForm;
-};
+ form: ISupportReportForm;
+}
-export type ClearReportFormAction = {
+export interface IClearReportFormAction {
type: 'CLEAR_REPORT_FORM';
-};
+}
-export type SupportAction = KeepReportFormAction | ClearReportFormAction;
+export type SupportAction = IKeepReportFormAction | IClearReportFormAction;
-function saveReportForm(form: SupportReportForm): KeepReportFormAction {
+function saveReportForm(form: ISupportReportForm): IKeepReportFormAction {
return {
type: 'SAVE_REPORT_FORM',
form,
};
}
-function clearReportForm(): ClearReportFormAction {
+function clearReportForm(): IClearReportFormAction {
return {
type: 'CLEAR_REPORT_FORM',
};
diff --git a/gui/packages/desktop/src/renderer/redux/support/reducers.ts b/gui/packages/desktop/src/renderer/redux/support/reducers.ts
index 19bb395782..7a300cf2ca 100644
--- a/gui/packages/desktop/src/renderer/redux/support/reducers.ts
+++ b/gui/packages/desktop/src/renderer/redux/support/reducers.ts
@@ -1,19 +1,19 @@
import { ReduxAction } from '../store';
-export type SupportReduxState = {
+export interface ISupportReduxState {
email: string;
message: string;
-};
+}
-const initialState: SupportReduxState = {
+const initialState: ISupportReduxState = {
email: '',
message: '',
};
export default function(
- state: SupportReduxState = initialState,
+ state: ISupportReduxState = initialState,
action: ReduxAction,
-): SupportReduxState {
+): ISupportReduxState {
switch (action.type) {
case 'SAVE_REPORT_FORM':
return {
diff --git a/gui/packages/desktop/src/renderer/redux/userinterface/actions.ts b/gui/packages/desktop/src/renderer/redux/userinterface/actions.ts
index 701639f884..724f53883d 100644
--- a/gui/packages/desktop/src/renderer/redux/userinterface/actions.ts
+++ b/gui/packages/desktop/src/renderer/redux/userinterface/actions.ts
@@ -1,23 +1,25 @@
-export type UpdateWindowArrowPositionAction = {
+export interface IUpdateWindowArrowPositionAction {
type: 'UPDATE_WINDOW_ARROW_POSITION';
arrowPosition: number;
-};
+}
-export type UpdateConnectionInfoOpenAction = {
+export interface IUpdateConnectionInfoOpenAction {
type: 'UPDATE_CONNECTION_INFO_OPEN';
isOpen: boolean;
-};
+}
-export type UserInterfaceAction = UpdateWindowArrowPositionAction | UpdateConnectionInfoOpenAction;
+export type UserInterfaceAction =
+ | IUpdateWindowArrowPositionAction
+ | IUpdateConnectionInfoOpenAction;
-function updateWindowArrowPosition(arrowPosition: number): UpdateWindowArrowPositionAction {
+function updateWindowArrowPosition(arrowPosition: number): IUpdateWindowArrowPositionAction {
return {
type: 'UPDATE_WINDOW_ARROW_POSITION',
arrowPosition,
};
}
-function updateConnectionInfoOpen(isOpen: boolean): UpdateConnectionInfoOpenAction {
+function updateConnectionInfoOpen(isOpen: boolean): IUpdateConnectionInfoOpenAction {
return {
type: 'UPDATE_CONNECTION_INFO_OPEN',
isOpen,
diff --git a/gui/packages/desktop/src/renderer/redux/userinterface/reducers.ts b/gui/packages/desktop/src/renderer/redux/userinterface/reducers.ts
index d46ea0a2e2..75005fd423 100644
--- a/gui/packages/desktop/src/renderer/redux/userinterface/reducers.ts
+++ b/gui/packages/desktop/src/renderer/redux/userinterface/reducers.ts
@@ -1,18 +1,18 @@
import { ReduxAction } from '../store';
-export type UserInterfaceReduxState = {
+export interface IUserInterfaceReduxState {
arrowPosition?: number;
connectionInfoOpen: boolean;
-};
+}
-const initialState: UserInterfaceReduxState = {
+const initialState: IUserInterfaceReduxState = {
connectionInfoOpen: false,
};
export default function(
- state: UserInterfaceReduxState = initialState,
+ state: IUserInterfaceReduxState = initialState,
action: ReduxAction,
-): UserInterfaceReduxState {
+): IUserInterfaceReduxState {
switch (action.type) {
case 'UPDATE_WINDOW_ARROW_POSITION':
return { ...state, arrowPosition: action.arrowPosition };
diff --git a/gui/packages/desktop/src/renderer/redux/version/actions.ts b/gui/packages/desktop/src/renderer/redux/version/actions.ts
index 9faadf963e..1eefb7e62c 100644
--- a/gui/packages/desktop/src/renderer/redux/version/actions.ts
+++ b/gui/packages/desktop/src/renderer/redux/version/actions.ts
@@ -1,31 +1,31 @@
-import { AppVersionInfo } from '../../../shared/daemon-rpc-types';
+import { IAppVersionInfo } from '../../../shared/daemon-rpc-types';
-type UpdateLatestActionPayload = {
+interface IUpdateLatestActionPayload extends IAppVersionInfo {
upToDate: boolean;
nextUpgrade?: string;
-} & AppVersionInfo;
+}
-export type UpdateLatestAction = {
+export interface IUpdateLatestAction {
type: 'UPDATE_LATEST';
- latestInfo: UpdateLatestActionPayload;
-};
+ latestInfo: IUpdateLatestActionPayload;
+}
-export type UpdateVersionAction = {
+export interface IUpdateVersionAction {
type: 'UPDATE_VERSION';
version: string;
consistent: boolean;
-};
+}
-export type VersionAction = UpdateLatestAction | UpdateVersionAction;
+export type VersionAction = IUpdateLatestAction | IUpdateVersionAction;
-function updateLatest(latestInfo: UpdateLatestActionPayload): UpdateLatestAction {
+function updateLatest(latestInfo: IUpdateLatestActionPayload): IUpdateLatestAction {
return {
type: 'UPDATE_LATEST',
latestInfo,
};
}
-function updateVersion(version: string, consistent: boolean): UpdateVersionAction {
+function updateVersion(version: string, consistent: boolean): IUpdateVersionAction {
return {
type: 'UPDATE_VERSION',
version,
diff --git a/gui/packages/desktop/src/renderer/redux/version/reducers.ts b/gui/packages/desktop/src/renderer/redux/version/reducers.ts
index e496b61563..775b605ded 100644
--- a/gui/packages/desktop/src/renderer/redux/version/reducers.ts
+++ b/gui/packages/desktop/src/renderer/redux/version/reducers.ts
@@ -1,6 +1,6 @@
import { ReduxAction } from '../store';
-export type VersionReduxState = {
+export interface IVersionReduxState {
current: string;
currentIsSupported: boolean;
latest?: string;
@@ -8,9 +8,9 @@ export type VersionReduxState = {
nextUpgrade?: string;
upToDate: boolean;
consistent: boolean;
-};
+}
-const initialState: VersionReduxState = {
+const initialState: IVersionReduxState = {
current: '',
currentIsSupported: true,
latest: undefined,
@@ -21,9 +21,9 @@ const initialState: VersionReduxState = {
};
export default function(
- state: VersionReduxState = initialState,
+ state: IVersionReduxState = initialState,
action: ReduxAction,
-): VersionReduxState {
+): IVersionReduxState {
switch (action.type) {
case 'UPDATE_LATEST': {
const { latest, ...other } = action.latestInfo;
diff --git a/gui/packages/desktop/src/renderer/routes.tsx b/gui/packages/desktop/src/renderer/routes.tsx
index 74c9be5bee..7aaf15d1e7 100644
--- a/gui/packages/desktop/src/renderer/routes.tsx
+++ b/gui/packages/desktop/src/renderer/routes.tsx
@@ -1,61 +1,61 @@
import * as React from 'react';
-import { Switch, Route } from 'react-router';
+import { Route, RouteComponentProps, Switch } from 'react-router';
+import App from './app';
import TransitionContainer from './components/TransitionContainer';
-import PlatformWindowContainer from './containers/PlatformWindowContainer';
+import AccountPage from './containers/AccountPage';
+import AdvancedSettingsPage from './containers/AdvancedSettingsPage';
+import ConnectPage from './containers/ConnectPage';
import LaunchPage from './containers/LaunchPage';
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 PlatformWindowContainer from './containers/PlatformWindowContainer';
import PreferencesPage from './containers/PreferencesPage';
-import SupportPage from './containers/SupportPage';
import SelectLocationPage from './containers/SelectLocationPage';
+import SettingsPage from './containers/SettingsPage';
+import SupportPage from './containers/SupportPage';
import { getTransitionProps } from './transitions';
-import App from './app';
-export type SharedRouteProps = {
+export interface ISharedRouteProps {
app: App;
-};
+}
type CustomRouteProps = {
- component: React.ComponentClass<SharedRouteProps>;
+ component: React.ComponentClass<ISharedRouteProps>;
} & Route['props'];
-export default function makeRoutes(componentProps: SharedRouteProps) {
+export default function makeRoutes(componentProps: ISharedRouteProps) {
// Renders a route extended with shared props
- const CustomRoute = ({ component: ComponentClass, ...routeProps }: CustomRouteProps) => (
- <Route {...routeProps} render={() => <ComponentClass {...componentProps} />} />
- );
+ function CustomRoute({ component: ComponentClass, ...routeProps }: CustomRouteProps) {
+ const renderOverride = () => <ComponentClass {...componentProps} />;
+
+ return <Route {...routeProps} render={renderOverride} />;
+ }
// store previous route
let sourceRoute: string | null = null;
- return (
- <Route
- render={({ location }) => {
- const destinationRoute = location.pathname;
- const transitionProps = getTransitionProps(sourceRoute, destinationRoute);
- sourceRoute = destinationRoute;
+ function renderRoute({ location }: RouteComponentProps) {
+ const destinationRoute = location.pathname;
+ const transitionProps = getTransitionProps(sourceRoute, destinationRoute);
+ sourceRoute = destinationRoute;
+
+ return (
+ <PlatformWindowContainer>
+ <TransitionContainer {...transitionProps}>
+ <Switch key={location.key} location={location}>
+ <CustomRoute exact={true} path="/" component={LaunchPage} />
+ <CustomRoute exact={true} path="/login" component={LoginPage} />
+ <CustomRoute exact={true} path="/connect" component={ConnectPage} />
+ <CustomRoute exact={true} path="/settings" component={SettingsPage} />
+ <CustomRoute exact={true} path="/settings/account" component={AccountPage} />
+ <CustomRoute exact={true} path="/settings/preferences" component={PreferencesPage} />
+ <CustomRoute exact={true} path="/settings/advanced" component={AdvancedSettingsPage} />
+ <CustomRoute exact={true} path="/settings/support" component={SupportPage} />
+ <CustomRoute exact={true} path="/select-location" component={SelectLocationPage} />
+ </Switch>
+ </TransitionContainer>
+ </PlatformWindowContainer>
+ );
+ }
- return (
- <PlatformWindowContainer>
- <TransitionContainer {...transitionProps}>
- <Switch key={location.key} location={location}>
- <CustomRoute exact path="/" component={LaunchPage} />
- <CustomRoute exact path="/login" component={LoginPage} />
- <CustomRoute exact path="/connect" component={ConnectPage} />
- <CustomRoute exact path="/settings" component={SettingsPage} />
- <CustomRoute exact path="/settings/account" component={AccountPage} />
- <CustomRoute exact path="/settings/preferences" component={PreferencesPage} />
- <CustomRoute exact path="/settings/advanced" component={AdvancedSettingsPage} />
- <CustomRoute exact path="/settings/support" component={SupportPage} />
- <CustomRoute exact path="/select-location" component={SelectLocationPage} />
- </Switch>
- </TransitionContainer>
- </PlatformWindowContainer>
- );
- }}
- />
- );
+ return <Route render={renderRoute} />;
}
diff --git a/gui/packages/desktop/src/renderer/transitions.ts b/gui/packages/desktop/src/renderer/transitions.ts
index 71585f9c24..7a8ac825c2 100644
--- a/gui/packages/desktop/src/renderer/transitions.ts
+++ b/gui/packages/desktop/src/renderer/transitions.ts
@@ -1,19 +1,18 @@
-import TransitionRule from './lib/transition-rule';
-import { TransitionFork, TransitionDescriptor } from './lib/transition-rule';
+import TransitionRule, { ITransitionDescriptor, ITransitionFork } from './lib/transition-rule';
-export type TransitionGroupProps = {
+export interface ITransitionGroupProps {
name: string;
duration: number;
-};
+}
-type TransitionMap = {
- [name: string]: TransitionFork;
-};
+interface ITransitionMap {
+ [name: string]: ITransitionFork;
+}
/**
* Transition descriptors
*/
-const transitions: TransitionMap = {
+const transitions: ITransitionMap = {
slide: {
forward: {
name: 'slide-up',
@@ -58,7 +57,7 @@ const transitionRules = [
export function getTransitionProps(
fromRoute: string | null,
toRoute: string,
-): TransitionGroupProps {
+): ITransitionGroupProps {
// ignore initial transition and transition between the same routes
if (!fromRoute || fromRoute === toRoute) {
return noTransitionProps();
@@ -75,21 +74,21 @@ export function getTransitionProps(
}
/**
- * Integrate TransitionDescriptor into TransitionGroupProps
- * @param {TransitionDescriptor} descriptor
+ * Integrate ITransitionDescriptor into ITransitionGroupProps
+ * @param {ITransitionDescriptor} descriptor
*/
-function toTransitionGroupProps(descriptor: TransitionDescriptor): TransitionGroupProps {
+function toTransitionGroupProps(descriptor: ITransitionDescriptor): ITransitionGroupProps {
const { name, duration } = descriptor;
return {
- name: name,
- duration: duration,
+ name,
+ duration,
};
}
/**
* Returns default props with no animation
*/
-function noTransitionProps(): TransitionGroupProps {
+function noTransitionProps(): ITransitionGroupProps {
return {
name: '',
duration: 0,
@@ -99,6 +98,6 @@ function noTransitionProps(): TransitionGroupProps {
/**
* Shortcut to create TransitionRule
*/
-function r(from: string | null, to: string, fork: TransitionFork): TransitionRule {
+function r(from: string | null, to: string, fork: ITransitionFork): TransitionRule {
return new TransitionRule(from, to, fork);
}
diff --git a/gui/packages/desktop/src/shared/daemon-rpc-types.ts b/gui/packages/desktop/src/shared/daemon-rpc-types.ts
index 2cfc30f884..894978cbca 100644
--- a/gui/packages/desktop/src/shared/daemon-rpc-types.ts
+++ b/gui/packages/desktop/src/shared/daemon-rpc-types.ts
@@ -1,7 +1,9 @@
-export type AccountData = { expiry: string };
+export interface IAccountData {
+ expiry: string;
+}
export type AccountToken = string;
export type Ip = string;
-export type Location = {
+export interface ILocation {
ip?: string;
country: string;
city?: string;
@@ -9,7 +11,7 @@ export type Location = {
longitude: number;
mullvadExitIp: boolean;
hostname?: string;
-};
+}
export type BlockReason =
| {
@@ -32,16 +34,16 @@ export type TunnelType = 'wireguard' | 'openvpn';
export type RelayProtocol = 'tcp' | 'udp';
-export type TunnelEndpoint = {
+export interface ITunnelEndpoint {
address: string;
protocol: RelayProtocol;
tunnel: TunnelType;
-};
+}
export type TunnelStateTransition =
| { state: 'disconnected' }
- | { state: 'connecting'; details?: TunnelEndpoint }
- | { state: 'connected'; details: TunnelEndpoint }
+ | { state: 'connecting'; details?: ITunnelEndpoint }
+ | { state: 'connected'; details: ITunnelEndpoint }
| { state: 'disconnecting'; details: AfterDisconnect }
| { state: 'blocked'; details: BlockReason };
@@ -50,16 +52,16 @@ export type RelayLocation =
| { city: [string, string] }
| { country: string };
-export type OpenVpnConstraints = {
+export interface IOpenVpnConstraints {
port: 'any' | { only: number };
protocol: 'any' | { only: RelayProtocol };
-};
+}
-type TunnelConstraints<TOpenVpnConstraints> = {
+interface ITunnelConstraints<TOpenVpnConstraints> {
openvpn: TOpenVpnConstraints;
-};
+}
-type RelaySettingsNormal<TTunnelConstraints> = {
+interface IRelaySettingsNormal<TTunnelConstraints> {
location:
| 'any'
| {
@@ -70,7 +72,7 @@ type RelaySettingsNormal<TTunnelConstraints> = {
| {
only: TTunnelConstraints;
};
-};
+}
export type ConnectionConfig =
| {
@@ -87,11 +89,11 @@ export type ConnectionConfig =
wireguard: {
tunnel: {
private_key: string;
- addresses: Array<string>;
+ addresses: string[];
};
peer: {
public_key: string;
- addresses: Array<string>;
+ addresses: string[];
endpoint: string;
};
gateway: string;
@@ -99,56 +101,56 @@ export type ConnectionConfig =
};
// types describing the structure of RelaySettings
-export type RelaySettingsCustom = {
+export interface IRelaySettingsCustom {
host: string;
config: ConnectionConfig;
-};
+}
export type RelaySettings =
| {
- normal: RelaySettingsNormal<TunnelConstraints<OpenVpnConstraints>>;
+ normal: IRelaySettingsNormal<ITunnelConstraints<IOpenVpnConstraints>>;
}
| {
- customTunnelEndpoint: RelaySettingsCustom;
+ customTunnelEndpoint: IRelaySettingsCustom;
};
// types describing the partial update of RelaySettings
export type RelaySettingsNormalUpdate = Partial<
- RelaySettingsNormal<TunnelConstraints<Partial<OpenVpnConstraints>>>
+ IRelaySettingsNormal<ITunnelConstraints<Partial<IOpenVpnConstraints>>>
>;
export type RelaySettingsUpdate =
| {
normal: RelaySettingsNormalUpdate;
}
| {
- customTunnelEndpoint: RelaySettingsCustom;
+ customTunnelEndpoint: IRelaySettingsCustom;
};
-export type RelayList = {
- countries: Array<RelayListCountry>;
-};
+export interface IRelayList {
+ countries: IRelayListCountry[];
+}
-export type RelayListCountry = {
+export interface IRelayListCountry {
name: string;
code: string;
- cities: Array<RelayListCity>;
-};
+ cities: IRelayListCity[];
+}
-export type RelayListCity = {
+export interface IRelayListCity {
name: string;
code: string;
latitude: number;
longitude: number;
- relays: Array<RelayListHostname>;
-};
+ relays: IRelayListHostname[];
+}
-export type RelayListHostname = {
+export interface IRelayListHostname {
hostname: string;
ipv4AddrIn: string;
includeInCountry: boolean;
weight: number;
-};
+}
-export type TunnelOptions = {
+export interface ITunnelOptions {
openvpn: {
mssfix?: number;
proxy?: ProxySettings;
@@ -161,54 +163,81 @@ export type TunnelOptions = {
generic: {
enableIpv6: boolean;
};
-};
+}
-export type ProxySettings = LocalProxySettings | RemoteProxySettings;
+export type ProxySettings = ILocalProxySettings | IRemoteProxySettings;
-export type LocalProxySettings = {
+export interface ILocalProxySettings {
port: number;
peer: string;
-};
+}
-export type RemoteProxySettings = {
+export interface IRemoteProxySettings {
address: string;
- auth?: RemoteProxyAuth;
-};
+ auth?: IRemoteProxyAuth;
+}
-export type RemoteProxyAuth = {
+export interface IRemoteProxyAuth {
username: string;
password: string;
-};
+}
-export type AppVersionInfo = {
+export interface IAppVersionInfo {
currentIsSupported: boolean;
latest: {
latestStable: string;
latest: string;
};
-};
+}
-export type Settings = {
+export interface ISettings {
accountToken?: AccountToken;
allowLan: boolean;
autoConnect: boolean;
blockWhenDisconnected: boolean;
relaySettings: RelaySettings;
- tunnelOptions: TunnelOptions;
-};
+ tunnelOptions: ITunnelOptions;
+}
-export type SocketAddress = { host: string; port: number };
+export interface ISocketAddress {
+ host: string;
+ port: number;
+}
-export function parseSocketAddress(socketAddrStr: string): SocketAddress {
+export function parseSocketAddress(socketAddrStr: string): ISocketAddress {
const re = new RegExp(/(.+):(\d+)$/);
const matches = socketAddrStr.match(re);
if (!matches || matches.length < 3) {
throw new Error(`Failed to parse socket address from address string '${socketAddrStr}'`);
}
- const socketAddress: SocketAddress = {
+ const socketAddress: ISocketAddress = {
host: matches[1],
port: Number(matches[2]),
};
return socketAddress;
}
+
+export function compareRelayLocation(lhs: RelayLocation, rhs: RelayLocation) {
+ if ('country' in lhs && 'country' in rhs && lhs.country && rhs.country) {
+ return lhs.country === rhs.country;
+ } else if ('city' in lhs && 'city' in rhs && lhs.city && rhs.city) {
+ return lhs.city[0] === rhs.city[0] && lhs.city[1] === rhs.city[1];
+ } else if ('hostname' in lhs && 'hostname' in rhs && lhs.hostname && rhs.hostname) {
+ return (
+ lhs.hostname[0] === rhs.hostname[0] &&
+ lhs.hostname[1] === rhs.hostname[1] &&
+ lhs.hostname[2] === rhs.hostname[2]
+ );
+ } else {
+ return false;
+ }
+}
+
+export function compareRelayLocationLoose(lhs?: RelayLocation, rhs?: RelayLocation) {
+ if (lhs && rhs) {
+ return compareRelayLocation(lhs, rhs);
+ } else {
+ return lhs === rhs;
+ }
+}
diff --git a/gui/packages/desktop/src/shared/gui-settings-state.ts b/gui/packages/desktop/src/shared/gui-settings-state.ts
index a94176c650..5bfb6e79c8 100644
--- a/gui/packages/desktop/src/shared/gui-settings-state.ts
+++ b/gui/packages/desktop/src/shared/gui-settings-state.ts
@@ -1,5 +1,5 @@
-export type GuiSettingsState = {
+export interface IGuiSettingsState {
autoConnect: boolean;
monochromaticIcon: boolean;
startMinimized: boolean;
-};
+}
diff --git a/gui/packages/desktop/src/shared/ipc-event-channel.ts b/gui/packages/desktop/src/shared/ipc-event-channel.ts
index 3ddde3fc13..c8cb1bf4c8 100644
--- a/gui/packages/desktop/src/shared/ipc-event-channel.ts
+++ b/gui/packages/desktop/src/shared/ipc-event-channel.ts
@@ -1,54 +1,54 @@
import { ipcMain, ipcRenderer, WebContents } from 'electron';
import * as uuid from 'uuid';
-import { GuiSettingsState } from './gui-settings-state';
+import { IGuiSettingsState } from './gui-settings-state';
-import { AppUpgradeInfo, CurrentAppVersionInfo } from '../main/index';
+import { IAppUpgradeInfo, ICurrentAppVersionInfo } from '../main/index';
import {
AccountToken,
- AccountData,
- Location,
- RelayList,
+ IAccountData,
+ ILocation,
+ IRelayList,
+ ISettings,
RelaySettingsUpdate,
- Settings,
TunnelStateTransition,
} from './daemon-rpc-types';
-export type AppStateSnapshot = {
+export interface IAppStateSnapshot {
isConnected: boolean;
autoStart: boolean;
tunnelState: TunnelStateTransition;
- settings: Settings;
- location?: Location;
- relays: RelayList;
- currentVersion: CurrentAppVersionInfo;
- upgradeVersion: AppUpgradeInfo;
- guiSettings: GuiSettingsState;
-};
+ settings: ISettings;
+ location?: ILocation;
+ relays: IRelayList;
+ currentVersion: ICurrentAppVersionInfo;
+ upgradeVersion: IAppUpgradeInfo;
+ guiSettings: IGuiSettingsState;
+}
-interface Sender<T> {
+interface ISender<T> {
notify(webContents: WebContents, value: T): void;
}
-interface SenderVoid {
+interface ISenderVoid {
notify(webContents: WebContents): void;
}
-interface Receiver<T> {
+interface IReceiver<T> {
listen(fn: (value: T) => void): void;
}
-interface TunnelMethods {
+interface ITunnelMethods {
connect(): Promise<void>;
disconnect(): Promise<void>;
}
-interface TunnelHandlers {
+interface ITunnelHandlers {
handleConnect(fn: () => Promise<void>): void;
handleDisconnect(fn: () => Promise<void>): void;
}
-interface SettingsMethods {
+interface ISettingsMethods {
setAllowLan(allowLan: boolean): Promise<void>;
setEnableIpv6(enableIpv6: boolean): Promise<void>;
setBlockWhenDisconnected(block: boolean): Promise<void>;
@@ -56,7 +56,7 @@ interface SettingsMethods {
updateRelaySettings(update: RelaySettingsUpdate): Promise<void>;
}
-interface SettingsHandlers {
+interface ISettingsHandlers {
handleAllowLan(fn: (allowLan: boolean) => Promise<void>): void;
handleEnableIpv6(fn: (enableIpv6: boolean) => Promise<void>): void;
handleBlockWhenDisconnected(fn: (block: boolean) => Promise<void>): void;
@@ -64,45 +64,45 @@ interface SettingsHandlers {
handleUpdateRelaySettings(fn: (update: RelaySettingsUpdate) => Promise<void>): void;
}
-interface GuiSettingsMethods {
+interface IGuiSettingsMethods {
setAutoConnect(autoConnect: boolean): void;
setStartMinimized(startMinimized: boolean): void;
setMonochromaticIcon(monochromaticIcon: boolean): void;
}
-interface GuiSettingsHandlers {
+interface IGuiSettingsHandlers {
handleAutoConnect(fn: (autoConnect: boolean) => void): void;
handleStartMinimized(fn: (startMinimized: boolean) => void): void;
handleMonochromaticIcon(fn: (monochromaticIcon: boolean) => void): void;
}
-interface AccountHandlers {
+interface IAccountHandlers {
handleSet(fn: (token: AccountToken) => Promise<void>): void;
handleUnset(fn: () => Promise<void>): void;
- handleGetData(fn: (token: AccountToken) => Promise<AccountData>): void;
+ handleGetData(fn: (token: AccountToken) => Promise<IAccountData>): void;
}
-interface AccountMethods {
+interface IAccountMethods {
set(token: AccountToken): Promise<void>;
unset(): Promise<void>;
- getData(token: AccountToken): Promise<AccountData>;
+ getData(token: AccountToken): Promise<IAccountData>;
}
-interface AccountHistoryHandlers {
- handleGet(fn: () => Promise<Array<AccountToken>>): void;
+interface IAccountHistoryHandlers {
+ handleGet(fn: () => Promise<AccountToken[]>): void;
handleRemoveItem(fn: (token: AccountToken) => Promise<void>): void;
}
-interface AccountHistoryMethods {
- get(): Promise<Array<AccountToken>>;
+interface IAccountHistoryMethods {
+ get(): Promise<AccountToken[]>;
removeItem(token: AccountToken): Promise<void>;
}
-interface AutoStartMethods {
+interface IAutoStartMethods {
set(autoStart: boolean): Promise<void>;
}
-interface AutoStartHandlers {
+interface IAutoStartHandlers {
handleSet(fn: (value: boolean) => Promise<void>): void;
}
@@ -150,28 +150,28 @@ const SET_AUTO_START = 'set-auto-start';
/// instance methods are meant to be used from a main process.
///
export class IpcRendererEventChannel {
- static state = {
+ public static state = {
/// Synchronously sends the IPC request and returns the app state snapshot
- get(): AppStateSnapshot {
+ get(): IAppStateSnapshot {
return ipcRenderer.sendSync(GET_APP_STATE);
},
};
- static daemonConnected: Receiver<void> = {
+ public static daemonConnected: IReceiver<void> = {
listen: listen(DAEMON_CONNECTED),
};
- static daemonDisconnected: Receiver<string | undefined> = {
+ public static daemonDisconnected: IReceiver<string | undefined> = {
listen: listen(DAEMON_DISCONNECTED),
};
- static tunnel: Receiver<TunnelStateTransition> & TunnelMethods = {
+ public static tunnel: IReceiver<TunnelStateTransition> & ITunnelMethods = {
listen: listen(TUNNEL_STATE_CHANGED),
connect: requestSender(CONNECT_TUNNEL),
disconnect: requestSender(DISCONNECT_TUNNEL),
};
- static settings: Receiver<Settings> & SettingsMethods = {
+ public static settings: IReceiver<ISettings> & ISettingsMethods = {
listen: listen(SETTINGS_CHANGED),
setAllowLan: requestSender(SET_ALLOW_LAN),
setEnableIpv6: requestSender(SET_ENABLE_IPV6),
@@ -180,74 +180,74 @@ export class IpcRendererEventChannel {
updateRelaySettings: requestSender(UPDATE_RELAY_SETTINGS),
};
- static location: Receiver<Location> = {
- listen: listen<Location>(LOCATION_CHANGED),
+ public static location: IReceiver<ILocation> = {
+ listen: listen(LOCATION_CHANGED),
};
- static relays: Receiver<RelayList> = {
+ public static relays: IReceiver<IRelayList> = {
listen: listen(RELAYS_CHANGED),
};
- static currentVersion: Receiver<CurrentAppVersionInfo> = {
+ public static currentVersion: IReceiver<ICurrentAppVersionInfo> = {
listen: listen(CURRENT_VERSION_CHANGED),
};
- static upgradeVersion: Receiver<AppUpgradeInfo> = {
+ public static upgradeVersion: IReceiver<IAppUpgradeInfo> = {
listen: listen(UPGRADE_VERSION_CHANGED),
};
- static guiSettings: Receiver<GuiSettingsState> & GuiSettingsMethods = {
+ public static guiSettings: IReceiver<IGuiSettingsState> & IGuiSettingsMethods = {
listen: listen(GUI_SETTINGS_CHANGED),
setAutoConnect: set(SET_AUTO_CONNECT),
setMonochromaticIcon: set(SET_MONOCHROMATIC_ICON),
setStartMinimized: set(SET_START_MINIMIZED),
};
- static autoStart: Receiver<boolean> & AutoStartMethods = {
+ public static autoStart: IReceiver<boolean> & IAutoStartMethods = {
listen: listen(AUTO_START_CHANGED),
set: requestSender(SET_AUTO_START),
};
- static account: AccountMethods = {
+ public static account: IAccountMethods = {
set: requestSender(SET_ACCOUNT),
unset: requestSender(UNSET_ACCOUNT),
getData: requestSender(GET_ACCOUNT_DATA),
};
- static accountHistory: AccountHistoryMethods = {
+ public static accountHistory: IAccountHistoryMethods = {
get: requestSender(GET_ACCOUNT_HISTORY),
removeItem: requestSender(REMOVE_ACCOUNT_HISTORY_ITEM),
};
}
export class IpcMainEventChannel {
- static state = {
- handleGet(fn: () => AppStateSnapshot) {
- ipcMain.on(GET_APP_STATE, (event: any) => {
+ public static state = {
+ handleGet(fn: () => IAppStateSnapshot) {
+ ipcMain.on(GET_APP_STATE, (event: Electron.Event) => {
event.returnValue = fn();
});
},
};
- static daemonConnected: SenderVoid = {
+ public static daemonConnected: ISenderVoid = {
notify: senderVoid(DAEMON_CONNECTED),
};
- static daemonDisconnected: Sender<string | undefined> = {
+ public static daemonDisconnected: ISender<string | undefined> = {
notify: sender(DAEMON_DISCONNECTED),
};
- static tunnel: Sender<TunnelStateTransition> & TunnelHandlers = {
+ public static tunnel: ISender<TunnelStateTransition> & ITunnelHandlers = {
notify: sender(TUNNEL_STATE_CHANGED),
handleConnect: requestHandler(CONNECT_TUNNEL),
handleDisconnect: requestHandler(DISCONNECT_TUNNEL),
};
- static location: Sender<Location> = {
+ public static location: ISender<ILocation> = {
notify: sender(LOCATION_CHANGED),
};
- static settings: Sender<Settings> & SettingsHandlers = {
+ public static settings: ISender<ISettings> & ISettingsHandlers = {
notify: sender(SETTINGS_CHANGED),
handleAllowLan: requestHandler(SET_ALLOW_LAN),
handleEnableIpv6: requestHandler(SET_ENABLE_IPV6),
@@ -256,50 +256,50 @@ export class IpcMainEventChannel {
handleUpdateRelaySettings: requestHandler(UPDATE_RELAY_SETTINGS),
};
- static relays: Sender<RelayList> = {
+ public static relays: ISender<IRelayList> = {
notify: sender(RELAYS_CHANGED),
};
- static currentVersion: Sender<CurrentAppVersionInfo> = {
+ public static currentVersion: ISender<ICurrentAppVersionInfo> = {
notify: sender(CURRENT_VERSION_CHANGED),
};
- static upgradeVersion: Sender<AppUpgradeInfo> = {
+ public static upgradeVersion: ISender<IAppUpgradeInfo> = {
notify: sender(UPGRADE_VERSION_CHANGED),
};
- static guiSettings: Sender<GuiSettingsState> & GuiSettingsHandlers = {
+ public static guiSettings: ISender<IGuiSettingsState> & IGuiSettingsHandlers = {
notify: sender(GUI_SETTINGS_CHANGED),
handleAutoConnect: handler(SET_AUTO_CONNECT),
handleMonochromaticIcon: handler(SET_MONOCHROMATIC_ICON),
handleStartMinimized: handler(SET_START_MINIMIZED),
};
- static autoStart: Sender<boolean> & AutoStartHandlers = {
+ public static autoStart: ISender<boolean> & IAutoStartHandlers = {
notify: sender<boolean>(AUTO_START_CHANGED),
handleSet: requestHandler(SET_AUTO_START),
};
- static account: AccountHandlers = {
+ public static account: IAccountHandlers = {
handleSet: requestHandler(SET_ACCOUNT),
handleUnset: requestHandler(UNSET_ACCOUNT),
handleGetData: requestHandler(GET_ACCOUNT_DATA),
};
- static accountHistory: AccountHistoryHandlers = {
+ public static accountHistory: IAccountHistoryHandlers = {
handleGet: requestHandler(GET_ACCOUNT_HISTORY),
handleRemoveItem: requestHandler(REMOVE_ACCOUNT_HISTORY_ITEM),
};
}
function listen<T>(event: string): (fn: (value: T) => void) => void {
- return function(fn: (value: T) => void) {
- ipcRenderer.on(event, (_event: any, newState: T) => fn(newState));
+ return (fn: (value: T) => void) => {
+ ipcRenderer.on(event, (_event: Electron.Event, newState: T) => fn(newState));
};
}
function set<T>(event: string): (value: T) => void {
- return function(newValue: T) {
+ return (newValue: T) => {
ipcRenderer.send(event, newValue);
};
}
@@ -311,14 +311,14 @@ function sender<T>(event: string): (webContents: WebContents, value: T) => void
}
function senderVoid(event: string): (webContents: WebContents) => void {
- return function(webContents: WebContents) {
+ return (webContents: WebContents) => {
webContents.send(event);
};
}
function handler<T>(event: string): (handlerFn: (value: T) => void) => void {
- return function(handlerFn: (value: T) => void) {
- ipcMain.on(event, (_: any, newValue: T) => {
+ return (handlerFn: (value: T) => void) => {
+ ipcMain.on(event, (_event: Electron.Event, newValue: T) => {
handlerFn(newValue);
});
};
@@ -326,31 +326,30 @@ function handler<T>(event: string): (handlerFn: (value: T) => void) => void {
type RequestResult<T> = { type: 'success'; value: T } | { type: 'error'; message: string };
-function requestHandler<T>(event: string): (fn: (...args: Array<any>) => Promise<T>) => void {
- return function(fn: (...args: Array<any>) => Promise<T>) {
- ipcMain.on(event, async (ipcEvent: any, requestId: string, ...args: Array<any>) => {
- const sender = ipcEvent.sender;
+function requestHandler<T>(event: string): (fn: (...args: any[]) => Promise<T>) => void {
+ return (fn: (...args: any[]) => Promise<T>) => {
+ ipcMain.on(event, async (ipcEvent: Electron.Event, requestId: string, ...args: any[]) => {
const responseEvent = `${event}-${requestId}`;
try {
const result: RequestResult<T> = { type: 'success', value: await fn(...args) };
- sender.send(responseEvent, result);
+ ipcEvent.sender.send(responseEvent, result);
} catch (error) {
const result: RequestResult<T> = { type: 'error', message: error.message || '' };
- sender.send(responseEvent, result);
+ ipcEvent.sender.send(responseEvent, result);
}
});
};
}
-function requestSender<T>(event: string): (...args: Array<any>) => Promise<T> {
- return function(...args: Array<any>): Promise<T> {
+function requestSender<T>(event: string): (...args: any[]) => Promise<T> {
+ return (...args: any[]): Promise<T> => {
return new Promise((resolve: (result: T) => void, reject: (error: Error) => void) => {
const requestId = uuid.v4();
const responseEvent = `${event}-${requestId}`;
- ipcRenderer.once(responseEvent, (_ipcEvent: any, result: RequestResult<T>) => {
+ ipcRenderer.once(responseEvent, (_ipcEvent: Electron.Event, result: RequestResult<T>) => {
switch (result.type) {
case 'error':
reject(new Error(result.message));
diff --git a/gui/packages/desktop/tslint.json b/gui/packages/desktop/tslint.json
index 6b68bf43d6..b6c5e4e05b 100644
--- a/gui/packages/desktop/tslint.json
+++ b/gui/packages/desktop/tslint.json
@@ -1,3 +1,14 @@
{
- "extends": "@mullvad/config/tslint.json"
+ "extends": "@mullvad/config/tslint.json",
+ "rules": {
+ "no-implicit-dependencies": [
+ true,
+ "optional",
+ [
+ "electron",
+ "electron-devtools-installer"
+ ]
+ ],
+ "no-submodule-imports": [true, "validated"]
+ }
}