summaryrefslogtreecommitdiffhomepage
path: root/gui/src/shared
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-11-30 10:37:14 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-11-30 10:37:14 +0100
commit4bf5b1c5df0776d18e7a31f40c0d840e508b4b22 (patch)
tree86b0897032d465ec70b878ee336443171455cd4f /gui/src/shared
parentd424bdbedeb1c8e44b693eac02d169e11b7ac92a (diff)
parent4fe3a01bc392ee1392e23f424987a01733a9a57e (diff)
downloadmullvadvpn-4bf5b1c5df0776d18e7a31f40c0d840e508b4b22.tar.xz
mullvadvpn-4bf5b1c5df0776d18e7a31f40c0d840e508b4b22.zip
Merge branch 'improve-tunnel-state-error-handling'
Diffstat (limited to 'gui/src/shared')
-rw-r--r--gui/src/shared/auth-failure.ts81
-rw-r--r--gui/src/shared/daemon-rpc-types.ts84
-rw-r--r--gui/src/shared/notifications/error.ts104
3 files changed, 116 insertions, 153 deletions
diff --git a/gui/src/shared/auth-failure.ts b/gui/src/shared/auth-failure.ts
deleted file mode 100644
index f8db50d82e..0000000000
--- a/gui/src/shared/auth-failure.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { messages } from './gettext';
-
-export enum AuthFailureKind {
- invalidAccount,
- expiredAccount,
- tooManyConnections,
- unknown,
-}
-
-interface IAuthFailure {
- kind: AuthFailureKind;
- message: string;
-}
-
-export function parseAuthFailure(rawFailureMessage?: string): IAuthFailure {
- if (rawFailureMessage) {
- const results = /^\[(\w+)\]\s*(.*)$/.exec(rawFailureMessage);
-
- if (results && results.length === 3) {
- const kind = parseRawFailureKind(results[1]);
- const message = kind === AuthFailureKind.unknown ? results[2] : messageForFailureKind(kind);
-
- return {
- kind,
- message,
- };
- } else {
- return {
- kind: AuthFailureKind.unknown,
- message: rawFailureMessage,
- };
- }
- } else {
- return {
- kind: AuthFailureKind.unknown,
- message: messageForFailureKind(AuthFailureKind.unknown),
- };
- }
-}
-
-function parseRawFailureKind(failureId: string): AuthFailureKind {
- // These strings should match up with mullvad-types/src/auth_failed.rs
- switch (failureId) {
- case 'INVALID_ACCOUNT':
- return AuthFailureKind.invalidAccount;
-
- case 'EXPIRED_ACCOUNT':
- return AuthFailureKind.expiredAccount;
-
- case 'TOO_MANY_CONNECTIONS':
- return AuthFailureKind.tooManyConnections;
-
- default:
- return AuthFailureKind.unknown;
- }
-}
-
-function messageForFailureKind(kind: AuthFailureKind): string {
- switch (kind) {
- case AuthFailureKind.invalidAccount:
- return messages.pgettext(
- 'auth-failure',
- 'You are logged in with an invalid account number. Please log out and try another one.',
- );
-
- case AuthFailureKind.expiredAccount:
- return messages.pgettext('auth-failure', 'Blocking internet: account is out of time');
-
- case AuthFailureKind.tooManyConnections:
- return messages.pgettext(
- 'auth-failure',
- 'Too many simultaneous connections on this account. Disconnect another device or try connecting again shortly.',
- );
-
- case AuthFailureKind.unknown:
- return messages.pgettext(
- 'auth-failure',
- 'Unable to authenticate account. Please contact support.',
- );
- }
-}
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 735bb91224..4a5f27d98e 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -17,34 +17,69 @@ export interface ILocation {
provider?: string;
}
+export enum FirewallPolicyErrorType {
+ generic,
+ locked,
+}
+
export type FirewallPolicyError =
- | { reason: 'generic' }
+ | { type: FirewallPolicyErrorType.generic }
| {
- reason: 'locked';
- details?: {
- name: string;
- pid: number;
- };
+ type: FirewallPolicyErrorType.locked;
+ name: string;
+ pid: number;
};
-export type TunnelParameterError =
- | 'no_matching_relay'
- | 'no_matching_bridge_relay'
- | 'no_wireguard_key'
- | 'custom_tunnel_host_resultion_error';
+export enum ErrorStateCause {
+ authFailed,
+ ipv6Unavailable,
+ setFirewallPolicyError,
+ setDnsError,
+ startTunnelError,
+ tunnelParameterError,
+ isOffline,
+ splitTunnelError,
+}
+
+export enum AuthFailedError {
+ unknown,
+ invalidAccount,
+ expiredAccount,
+ tooManyConnections,
+}
+
+export enum TunnelParameterError {
+ noMatchingRelay,
+ noMatchingBridgeRelay,
+ noWireguardKey,
+ customTunnelHostResolutionError,
+}
-export type ErrorStateCause =
+export type ErrorState =
| {
- reason:
- | 'ipv6_unavailable'
- | 'set_dns_error'
- | 'start_tunnel_error'
- | 'is_offline'
- | 'split_tunnel_error';
+ cause:
+ | ErrorStateCause.ipv6Unavailable
+ | ErrorStateCause.setDnsError
+ | ErrorStateCause.startTunnelError
+ | ErrorStateCause.isOffline
+ | ErrorStateCause.splitTunnelError;
+ blockingError?: FirewallPolicyError;
}
- | { reason: 'set_firewall_policy_error'; details: FirewallPolicyError }
- | { reason: 'tunnel_parameter_error'; details: TunnelParameterError }
- | { reason: 'auth_failed'; details?: string };
+ | {
+ cause: ErrorStateCause.authFailed;
+ blockingError?: FirewallPolicyError;
+ authFailedError: AuthFailedError;
+ }
+ | {
+ cause: ErrorStateCause.tunnelParameterError;
+ blockingError?: FirewallPolicyError;
+ parameterError: TunnelParameterError;
+ }
+ | {
+ cause: ErrorStateCause.setFirewallPolicyError;
+ blockingError?: FirewallPolicyError;
+ policyError: FirewallPolicyError;
+ };
export type AfterDisconnect = 'nothing' | 'block' | 'reconnect';
@@ -134,12 +169,7 @@ export type TunnelState =
| { state: 'connecting'; details?: ITunnelStateRelayInfo }
| { state: 'connected'; details: ITunnelStateRelayInfo }
| { state: 'disconnecting'; details: AfterDisconnect }
- | { state: 'error'; details: IErrorState };
-
-export interface IErrorState {
- blockFailure?: FirewallPolicyError;
- cause: ErrorStateCause;
-}
+ | { state: 'error'; details: ErrorState };
export type RelayLocation =
| { hostname: [string, string, string] }
diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts
index c901c5de67..08462fa6db 100644
--- a/gui/src/shared/notifications/error.ts
+++ b/gui/src/shared/notifications/error.ts
@@ -1,9 +1,13 @@
import { sprintf } from 'sprintf-js';
import { strings } from '../../config.json';
-import { hasExpired } from '../account-expiry';
-import { AuthFailureKind, parseAuthFailure } from '../auth-failure';
-import { IErrorState, TunnelParameterError, TunnelState } from '../daemon-rpc-types';
+import {
+ AuthFailedError,
+ ErrorState,
+ ErrorStateCause,
+ TunnelParameterError,
+ TunnelState,
+} from '../daemon-rpc-types';
import { messages } from '../gettext';
import {
InAppNotification,
@@ -13,7 +17,6 @@ import {
interface ErrorNotificationContext {
tunnelState: TunnelState;
- accountExpiry?: string;
hasExcludedApps: boolean;
}
@@ -25,8 +28,8 @@ export class ErrorNotificationProvider
public getSystemNotification() {
if (this.context.tunnelState.state === 'error') {
- let message = getMessage(this.context.tunnelState.details, this.context.accountExpiry);
- if (!this.context.tunnelState.details.blockFailure && this.context.hasExcludedApps) {
+ let message = getMessage(this.context.tunnelState.details);
+ if (!this.context.tunnelState.details.blockingError && this.context.hasExcludedApps) {
message = `${message} ${sprintf(
messages.pgettext(
'notifications',
@@ -38,7 +41,7 @@ export class ErrorNotificationProvider
return {
message,
- critical: !!this.context.tunnelState.details.blockFailure,
+ critical: !!this.context.tunnelState.details.blockingError,
};
} else {
return undefined;
@@ -47,8 +50,8 @@ export class ErrorNotificationProvider
public getInAppNotification(): InAppNotification | undefined {
if (this.context.tunnelState.state === 'error') {
- let subtitle = getMessage(this.context.tunnelState.details, this.context.accountExpiry);
- if (!this.context.tunnelState.details.blockFailure && this.context.hasExcludedApps) {
+ let subtitle = getMessage(this.context.tunnelState.details);
+ if (!this.context.tunnelState.details.blockingError && this.context.hasExcludedApps) {
subtitle = `${subtitle} ${sprintf(
messages.pgettext(
'notifications',
@@ -60,10 +63,12 @@ export class ErrorNotificationProvider
return {
indicator:
- this.context.tunnelState.details.cause.reason === 'is_offline' ? 'warning' : 'error',
- title: !this.context.tunnelState.details.blockFailure
- ? messages.pgettext('in-app-notifications', 'BLOCKING INTERNET')
- : messages.pgettext('in-app-notifications', 'NETWORK TRAFFIC MIGHT BE LEAKING'),
+ this.context.tunnelState.details.cause === ErrorStateCause.isOffline
+ ? 'warning'
+ : 'error',
+ title: this.context.tunnelState.details.blockingError
+ ? messages.pgettext('in-app-notifications', 'NETWORK TRAFFIC MIGHT BE LEAKING')
+ : messages.pgettext('in-app-notifications', 'BLOCKING INTERNET'),
subtitle,
};
} else {
@@ -72,9 +77,9 @@ export class ErrorNotificationProvider
}
}
-function getMessage(errorDetails: IErrorState, accountExpiry?: string): string {
- if (errorDetails.blockFailure) {
- if (errorDetails.cause.reason === 'set_firewall_policy_error') {
+function getMessage(errorState: ErrorState): string {
+ if (errorState.blockingError) {
+ if (errorState.cause === ErrorStateCause.setFirewallPolicyError) {
switch (process.platform ?? window.env.platform) {
case 'win32':
return messages.pgettext(
@@ -94,28 +99,37 @@ function getMessage(errorDetails: IErrorState, accountExpiry?: string): string {
'Unable to block all network traffic. Please troubleshoot or contact support.',
);
} else {
- switch (errorDetails.cause.reason) {
- case 'auth_failed': {
- const authFailure = parseAuthFailure(errorDetails.cause.details);
- if (
- authFailure.kind === AuthFailureKind.unknown &&
- accountExpiry &&
- hasExpired(accountExpiry)
- ) {
- return messages.pgettext(
- 'auth-failure',
- 'You are logged in with an invalid account number. Please log out and try another one.',
- );
- } else {
- return authFailure.message;
+ switch (errorState.cause) {
+ case ErrorStateCause.authFailed:
+ switch (errorState.authFailedError) {
+ case AuthFailedError.invalidAccount:
+ return messages.pgettext(
+ 'auth-failure',
+ 'You are logged in with an invalid account number. Please log out and try another one.',
+ );
+
+ case AuthFailedError.expiredAccount:
+ return messages.pgettext('auth-failure', 'Blocking internet: account is out of time');
+
+ case AuthFailedError.tooManyConnections:
+ return messages.pgettext(
+ 'auth-failure',
+ 'Too many simultaneous connections on this account. Disconnect another device or try connecting again shortly.',
+ );
+
+ case AuthFailedError.unknown:
+ default:
+ return messages.pgettext(
+ 'auth-failure',
+ 'Unable to authenticate account. Please contact support.',
+ );
}
- }
- case 'ipv6_unavailable':
+ case ErrorStateCause.ipv6Unavailable:
return messages.pgettext(
'notifications',
'Could not configure IPv6. Disable it in the app or enable it on your device.',
);
- case 'set_firewall_policy_error':
+ case ErrorStateCause.setFirewallPolicyError:
switch (process.platform ?? window.env.platform) {
case 'win32':
return messages.pgettext(
@@ -130,24 +144,24 @@ function getMessage(errorDetails: IErrorState, accountExpiry?: string): string {
default:
return messages.pgettext('notifications', 'Unable to apply firewall rules.');
}
- case 'set_dns_error':
+ case ErrorStateCause.setDnsError:
return messages.pgettext(
'notifications',
'Unable to set system DNS server. Please contact support.',
);
- case 'start_tunnel_error':
+ case ErrorStateCause.startTunnelError:
return messages.pgettext(
'notifications',
'Unable to start tunnel connection. Please contact support.',
);
- case 'tunnel_parameter_error':
- return getTunnelParameterMessage(errorDetails.cause.details);
- case 'is_offline':
+ case ErrorStateCause.tunnelParameterError:
+ return getTunnelParameterMessage(errorState.parameterError);
+ case ErrorStateCause.isOffline:
return messages.pgettext(
'notifications',
'Your device is offline. The tunnel will automatically connect once your device is back online.',
);
- case 'split_tunnel_error':
+ case ErrorStateCause.splitTunnelError:
return messages.pgettext(
'notifications',
'Unable to communicate with Mullvad kernel driver. Try reconnecting or contact support.',
@@ -156,16 +170,16 @@ function getMessage(errorDetails: IErrorState, accountExpiry?: string): string {
}
}
-function getTunnelParameterMessage(err: TunnelParameterError): string {
- switch (err) {
+function getTunnelParameterMessage(error: TunnelParameterError): string {
+ switch (error) {
/// TODO: once bridge constraints can be set, add a more descriptive error message
- case 'no_matching_bridge_relay':
- case 'no_matching_relay':
+ case TunnelParameterError.noMatchingBridgeRelay:
+ case TunnelParameterError.noMatchingRelay:
return messages.pgettext(
'notifications',
'No servers in your selected location match your settings.',
);
- case 'no_wireguard_key':
+ case TunnelParameterError.noWireguardKey:
return sprintf(
// TRANSLATORS: Available placeholders:
// TRANSLATORS: %(wireguard)s - will be replaced with "WireGuard"
@@ -175,7 +189,7 @@ function getTunnelParameterMessage(err: TunnelParameterError): string {
),
{ wireguard: strings.wireguard },
);
- case 'custom_tunnel_host_resultion_error':
+ case TunnelParameterError.customTunnelHostResolutionError:
return messages.pgettext(
'notifications',
'Unable to resolve host of custom tunnel. Try changing your settings.',