diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2024-05-30 09:20:48 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2024-05-31 10:17:06 +0200 |
| commit | 4650984b64d0aba0ff4c63aece6844dce3825e4b (patch) | |
| tree | d700d047037829fb5abddb38b1570f963e7b84a7 /gui/src/shared | |
| parent | 71d3cfa59f80dbe51b987f23b84a4a13a9eb01f5 (diff) | |
| download | mullvadvpn-4650984b64d0aba0ff4c63aece6844dce3825e4b.tar.xz mullvadvpn-4650984b64d0aba0ff4c63aece6844dce3825e4b.zip | |
Show error and troubleshoot dialog when lacking full disk access
Diffstat (limited to 'gui/src/shared')
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 4 | ||||
| -rw-r--r-- | gui/src/shared/ipc-schema.ts | 1 | ||||
| -rw-r--r-- | gui/src/shared/notifications/error.ts | 421 | ||||
| -rw-r--r-- | gui/src/shared/notifications/notification.ts | 6 |
4 files changed, 235 insertions, 197 deletions
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index 761dd01a4c..ce0f3a8968 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -48,6 +48,7 @@ export enum ErrorStateCause { tunnelParameterError, isOffline, splitTunnelError, + needFullDiskPermissions, } export enum AuthFailedError { @@ -71,7 +72,8 @@ export type ErrorState = | ErrorStateCause.setDnsError | ErrorStateCause.startTunnelError | ErrorStateCause.isOffline - | ErrorStateCause.splitTunnelError; + | ErrorStateCause.splitTunnelError + | ErrorStateCause.needFullDiskPermissions; blockingError?: FirewallPolicyError; } | { diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts index e33736bcf8..acbb6366d2 100644 --- a/gui/src/shared/ipc-schema.ts +++ b/gui/src/shared/ipc-schema.ts @@ -162,6 +162,7 @@ export const ipcSchema = { openUrl: invoke<string, void>(), showOpenDialog: invoke<Electron.OpenDialogOptions, Electron.OpenDialogReturnValue>(), showLaunchDaemonSettings: invoke<void, void>(), + showFullDiskAccessSettings: invoke<void, void>(), getPathBaseName: invoke<string, string>(), }, tunnel: { diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts index 1d36b7e1de..387ed146a9 100644 --- a/gui/src/shared/notifications/error.ts +++ b/gui/src/shared/notifications/error.ts @@ -22,6 +22,7 @@ import { interface ErrorNotificationContext { tunnelState: TunnelState; hasExcludedApps: boolean; + showFullDiskAccessSettings?: () => void; } export class ErrorNotificationProvider @@ -32,7 +33,7 @@ export class ErrorNotificationProvider public getSystemNotification(): SystemNotification | undefined { if (this.context.tunnelState.state === 'error') { - let message = getMessage(this.context.tunnelState.details); + let message = this.getMessage(this.context.tunnelState.details); if (!this.context.tunnelState.details.blockingError && this.context.hasExcludedApps) { message = `${message} ${sprintf( messages.pgettext( @@ -58,7 +59,7 @@ export class ErrorNotificationProvider public getInAppNotification(): InAppNotification | undefined { if (this.context.tunnelState.state === 'error') { - let subtitle = getMessage(this.context.tunnelState.details); + let subtitle = this.getMessage(this.context.tunnelState.details); if (!this.context.tunnelState.details.blockingError && this.context.hasExcludedApps) { subtitle = `${subtitle} ${sprintf( messages.pgettext( @@ -78,231 +79,259 @@ export class ErrorNotificationProvider ? messages.pgettext('in-app-notifications', 'NETWORK TRAFFIC MIGHT BE LEAKING') : messages.pgettext('in-app-notifications', 'BLOCKING INTERNET'), subtitle, - action: getActions(this.context.tunnelState.details) ?? undefined, + action: this.getActions(this.context.tunnelState.details) ?? undefined, }; } else { return undefined; } } -} - -function getMessage(errorState: ErrorState): string { - if (errorState.blockingError) { - if (errorState.cause === ErrorStateCause.setFirewallPolicyError) { - switch (process.platform ?? window.env.platform) { - case 'win32': - return messages.pgettext( - 'notifications', - 'Unable to block all network traffic. Try temporarily disabling any third-party antivirus or security software or send a problem report.', - ); - case 'linux': - return messages.pgettext( - 'notifications', - 'Unable to block all network traffic. Try updating your kernel or send a problem report.', - ); - } - } - - return messages.pgettext( - 'notifications', - 'Unable to block all network traffic. Please troubleshoot or send a problem report.', - ); - } else { - 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 send a problem report.', - ); - } - case ErrorStateCause.ipv6Unavailable: - return messages.pgettext( - 'notifications', - 'Could not configure IPv6. Disable it in the app or enable it on your device.', - ); - case ErrorStateCause.setFirewallPolicyError: + private getMessage(errorState: ErrorState): string { + if (errorState.blockingError) { + if (errorState.cause === ErrorStateCause.setFirewallPolicyError) { switch (process.platform ?? window.env.platform) { case 'win32': return messages.pgettext( 'notifications', - 'Unable to apply firewall rules. Try temporarily disabling any third-party antivirus or security software.', + 'Unable to block all network traffic. Try temporarily disabling any third-party antivirus or security software or send a problem report.', ); case 'linux': return messages.pgettext( 'notifications', - 'Unable to apply firewall rules. Try updating your kernel.', + 'Unable to block all network traffic. Try updating your kernel or send a problem report.', ); - default: - return messages.pgettext('notifications', 'Unable to apply firewall rules.'); } - case ErrorStateCause.setDnsError: - return messages.pgettext( - 'notifications', - 'Unable to set system DNS server. Please send a problem report.', - ); - case ErrorStateCause.startTunnelError: - return messages.pgettext( - 'notifications', - 'Unable to start tunnel connection. Please send a problem report.', - ); - case ErrorStateCause.createTunnelDeviceError: - if (errorState.osError === 4319) { + } + + return messages.pgettext( + 'notifications', + 'Unable to block all network traffic. Please troubleshoot or send a problem report.', + ); + } else { + 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 send a problem report.', + ); + } + case ErrorStateCause.ipv6Unavailable: return messages.pgettext( 'notifications', - 'Unable to start tunnel connection. This could be because of conflicts with VMware, please troubleshoot.', + 'Could not configure IPv6. Disable it in the app or enable it on your device.', ); - } + case ErrorStateCause.setFirewallPolicyError: + switch (process.platform ?? window.env.platform) { + case 'win32': + return messages.pgettext( + 'notifications', + 'Unable to apply firewall rules. Try temporarily disabling any third-party antivirus or security software.', + ); + case 'linux': + return messages.pgettext( + 'notifications', + 'Unable to apply firewall rules. Try updating your kernel.', + ); + default: + return messages.pgettext('notifications', 'Unable to apply firewall rules.'); + } + case ErrorStateCause.setDnsError: + return messages.pgettext( + 'notifications', + 'Unable to set system DNS server. Please send a problem report.', + ); + case ErrorStateCause.startTunnelError: + return messages.pgettext( + 'notifications', + 'Unable to start tunnel connection. Please send a problem report.', + ); + case ErrorStateCause.createTunnelDeviceError: + if (errorState.osError === 4319) { + return messages.pgettext( + 'notifications', + 'Unable to start tunnel connection. This could be because of conflicts with VMware, please troubleshoot.', + ); + } + return messages.pgettext( + 'notifications', + 'Unable to start tunnel connection. Please send a problem report.', + ); + case ErrorStateCause.tunnelParameterError: + return this.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 ErrorStateCause.needFullDiskPermissions: + return messages.pgettext('notifications', 'Failed to enable split tunneling.'); + case ErrorStateCause.splitTunnelError: + switch (process.platform ?? window.env.platform) { + case 'darwin': + return messages.pgettext( + 'notifications', + 'Failed to enable split tunneling. Please try reconnecting or disable split tunneling.', + ); + default: + return messages.pgettext( + 'notifications', + 'Unable to communicate with Mullvad kernel driver. Try reconnecting or send a problem report.', + ); + } + } + } + } + + private getTunnelParameterMessage(error: TunnelParameterError): string { + switch (error) { + /// TODO: once bridge constraints can be set, add a more descriptive error message + case TunnelParameterError.noMatchingBridgeRelay: + case TunnelParameterError.noMatchingRelay: return messages.pgettext( 'notifications', - 'Unable to start tunnel connection. Please send a problem report.', + 'No servers match your settings, try changing server or other settings.', + ); + case TunnelParameterError.noWireguardKey: + return sprintf( + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(wireguard)s - will be replaced with "WireGuard" + messages.pgettext( + 'notifications', + 'Valid %(wireguard)s key is missing. Manage keys under Advanced settings.', + ), + { wireguard: strings.wireguard }, ); - case ErrorStateCause.tunnelParameterError: - return getTunnelParameterMessage(errorState.parameterError); - case ErrorStateCause.isOffline: + case TunnelParameterError.customTunnelHostResolutionError: return messages.pgettext( 'notifications', - 'Your device is offline. The tunnel will automatically connect once your device is back online.', + 'Unable to resolve host of custom tunnel. Try changing your settings.', ); - case ErrorStateCause.splitTunnelError: - switch (process.platform ?? window.env.platform) { - case 'darwin': - return messages.pgettext( - 'notifications', - 'Failed to enable split tunneling. Please try again or disable it.', - ); - default: - return messages.pgettext( - 'notifications', - 'Unable to communicate with Mullvad kernel driver. Try reconnecting or send a problem report.', - ); - } } } -} -function getTunnelParameterMessage(error: TunnelParameterError): string { - switch (error) { - /// TODO: once bridge constraints can be set, add a more descriptive error message - case TunnelParameterError.noMatchingBridgeRelay: - case TunnelParameterError.noMatchingRelay: - return messages.pgettext( - 'notifications', - 'No servers match your settings, try changing server or other settings.', - ); - case TunnelParameterError.noWireguardKey: - return sprintf( - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(wireguard)s - will be replaced with "WireGuard" - messages.pgettext( - 'notifications', - 'Valid %(wireguard)s key is missing. Manage keys under Advanced settings.', - ), - { wireguard: strings.wireguard }, - ); - case TunnelParameterError.customTunnelHostResolutionError: - return messages.pgettext( - 'notifications', - 'Unable to resolve host of custom tunnel. Try changing your settings.', - ); - } -} + private getActions(errorState: ErrorState): InAppNotificationAction | void { + const platform = process.platform ?? window.env.platform; + + if (errorState.cause === ErrorStateCause.setFirewallPolicyError && platform === 'linux') { + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext('troubleshoot', 'This might be caused by an outdated kernel.'), + steps: [ + messages.pgettext('troubleshoot', 'Update your kernel.'), + messages.pgettext('troubleshoot', 'Make sure you have NF tables support.'), + ], + }, + }; + } else if (errorState.cause === ErrorStateCause.setDnsError) { + const troubleshootSteps = []; + if (platform === 'darwin') { + troubleshootSteps.push( + messages.pgettext( + 'troubleshoot', + 'Try to turn Wi-Fi Calling off in the FaceTime app settings and restart the Mac.', + ), + messages.pgettext( + 'troubleshoot', + 'Uninstall or disable other DNS, networking and ads/website blocking apps.', + ), + ); + } else if (platform === 'win32') { + troubleshootSteps.push( + messages.pgettext( + 'troubleshoot', + 'Uninstall or disable other DNS, networking and ads/website blocking apps.', + ), + ); + } -function getActions(errorState: ErrorState): InAppNotificationAction | void { - const platform = process.platform ?? window.env.platform; + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext( + 'troubleshoot', + 'This error can happen when something other than Mullvad is actively updating the DNS.', + ), + steps: troubleshootSteps, + }, + }; + } else if (errorState.cause === ErrorStateCause.needFullDiskPermissions) { + let troubleshootButtons = undefined; + if (this.context.showFullDiskAccessSettings) { + troubleshootButtons = [ + { + label: messages.pgettext('troubleshoot', 'Open system settings'), + action: () => this.context.showFullDiskAccessSettings?.(), + }, + ]; + } - if (errorState.cause === ErrorStateCause.setFirewallPolicyError && platform === 'linux') { - return { - type: 'troubleshoot-dialog', - troubleshoot: { - details: messages.pgettext('troubleshoot', 'This might be caused by an outdated kernel.'), - steps: [ - messages.pgettext('troubleshoot', 'Update your kernel.'), - messages.pgettext('troubleshoot', 'Make sure you have NF tables support.'), - ], - }, - }; - } else if (errorState.cause === ErrorStateCause.setDnsError) { - const troubleshootSteps = []; - if (platform === 'darwin') { - troubleshootSteps.push( - messages.pgettext( - 'troubleshoot', - 'Try to turn Wi-Fi Calling off in the FaceTime app settings and restart the Mac.', - ), - messages.pgettext( - 'troubleshoot', - 'Uninstall or disable other DNS, networking and ads/website blocking apps.', - ), - ); - } else if (platform === 'win32') { - troubleshootSteps.push( - messages.pgettext( - 'troubleshoot', - 'Uninstall or disable other DNS, networking and ads/website blocking apps.', - ), - ); + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext( + 'troubleshoot', + 'Failed to enable split tunneling. This is because the app is missing system permissions. What you can do:', + ), + steps: [ + messages.pgettext( + 'troubleshoot', + 'Enable “Full Disk Access” for “Mullvad VPN” in the macOS system settings.', + ), + ], + buttons: troubleshootButtons, + }, + }; + } else if (platform === 'win32' && errorState.cause === ErrorStateCause.splitTunnelError) { + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext( + 'troubleshoot', + 'Unable to communicate with Mullvad kernel driver.', + ), + steps: [ + messages.pgettext('troubleshoot', 'Try reconnecting.'), + messages.pgettext('troubleshoot', 'Try restarting your device.'), + ], + }, + }; + } else if ( + errorState.cause === ErrorStateCause.createTunnelDeviceError && + errorState.osError === 4319 + ) { + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext( + 'troubleshoot', + 'Unable to start tunnel connection because of a failure when creating the tunnel device. This is often caused by conflicts with the VMware Bridge Protocol.', + ), + steps: [ + messages.pgettext('troubleshoot', 'Try to reinstall VMware.'), + messages.pgettext('troubleshoot', 'Try to uninstall VMware.'), + ], + }, + }; } - - return { - type: 'troubleshoot-dialog', - troubleshoot: { - details: messages.pgettext( - 'troubleshoot', - 'This error can happen when something other than Mullvad is actively updating the DNS.', - ), - steps: troubleshootSteps, - }, - }; - } else if (errorState.cause === ErrorStateCause.splitTunnelError) { - // TODO: macos: handle this and full disk access error - return { - type: 'troubleshoot-dialog', - troubleshoot: { - details: messages.pgettext( - 'troubleshoot', - 'Unable to communicate with Mullvad kernel driver.', - ), - steps: [ - messages.pgettext('troubleshoot', 'Try reconnecting.'), - messages.pgettext('troubleshoot', 'Try restarting your device.'), - ], - }, - }; - } else if ( - errorState.cause === ErrorStateCause.createTunnelDeviceError && - errorState.osError === 4319 - ) { - return { - type: 'troubleshoot-dialog', - troubleshoot: { - details: messages.pgettext( - 'troubleshoot', - 'Unable to start tunnel connection because of a failure when creating the tunnel device. This is often caused by conflicts with the VMware Bridge Protocol.', - ), - steps: [ - messages.pgettext('troubleshoot', 'Try to reinstall VMware.'), - messages.pgettext('troubleshoot', 'Try to uninstall VMware.'), - ], - }, - }; } } diff --git a/gui/src/shared/notifications/notification.ts b/gui/src/shared/notifications/notification.ts index 1957cc07fb..87166aab4d 100644 --- a/gui/src/shared/notifications/notification.ts +++ b/gui/src/shared/notifications/notification.ts @@ -8,6 +8,12 @@ export type NotificationAction = { export interface InAppNotificationTroubleshootInfo { details: string; steps: string[]; + buttons?: Array<InAppNotificationTroubleshootButton>; +} + +export interface InAppNotificationTroubleshootButton { + label: string; + action: () => void; } export type InAppNotificationAction = |
