diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2022-11-30 10:37:14 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-11-30 10:37:14 +0100 |
| commit | 4bf5b1c5df0776d18e7a31f40c0d840e508b4b22 (patch) | |
| tree | 86b0897032d465ec70b878ee336443171455cd4f /gui/src/shared | |
| parent | d424bdbedeb1c8e44b693eac02d169e11b7ac92a (diff) | |
| parent | 4fe3a01bc392ee1392e23f424987a01733a9a57e (diff) | |
| download | mullvadvpn-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.ts | 81 | ||||
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 84 | ||||
| -rw-r--r-- | gui/src/shared/notifications/error.ts | 104 |
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.', |
