mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 18:18:55 +00:00
fix(apple): actually show user-friendly alert messages (#8282)
Before, we would receive an `NSError` object and the type-matching wouldn't take effect at all, causing the default alert to show every time. This solves that by introducing a `UserFriendlyError` protocol which is more robust against the two main `Error` and `NSError` variants.
This commit is contained in:
@@ -143,7 +143,7 @@ struct GrantVPNView: View {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
} catch {
|
||||
Log.error(error)
|
||||
await macOSAlert.show(for: error)
|
||||
macOSAlert.show(for: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ struct GrantVPNView: View {
|
||||
}
|
||||
} catch {
|
||||
Log.error(error)
|
||||
await macOSAlert.show(for: error)
|
||||
macOSAlert.show(for: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +683,7 @@ public final class MenuBar: NSObject, ObservableObject {
|
||||
try await WebAuthSession.signIn(store: store)
|
||||
} catch {
|
||||
Log.error(error)
|
||||
await macOSAlert.show(for: error)
|
||||
macOSAlert.show(for: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,7 +713,7 @@ public final class MenuBar: NSObject, ObservableObject {
|
||||
}
|
||||
} catch {
|
||||
Log.error(error)
|
||||
await macOSAlert.show(for: error)
|
||||
macOSAlert.show(for: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +534,7 @@ public struct SettingsView: View {
|
||||
Log.error(error)
|
||||
}
|
||||
|
||||
await macOSAlert.show(for: error)
|
||||
macOSAlert.show(for: error)
|
||||
}
|
||||
|
||||
self.isExportingLogs = false
|
||||
|
||||
@@ -10,148 +10,136 @@ import SystemExtensions
|
||||
import AppKit
|
||||
import NetworkExtension
|
||||
|
||||
@MainActor
|
||||
struct macOSAlert { // swiftlint:disable:this type_name
|
||||
static func show(for error: Error) async {
|
||||
guard let message = userMessage(for: error)
|
||||
else { return }
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
// swiftlint:disable function_body_length
|
||||
protocol UserFriendlyError {
|
||||
func userMessage() -> String?
|
||||
}
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.messageText = message
|
||||
alert.alertStyle = .critical
|
||||
_ = await withCheckedContinuation { continuation in
|
||||
continuation.resume(returning: alert.runModal())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NEVPNError
|
||||
private static func userMessage(for error: NEVPNError) -> String? {
|
||||
return {
|
||||
switch error.code {
|
||||
extension NEVPNError: UserFriendlyError {
|
||||
func userMessage() -> String? {
|
||||
switch code {
|
||||
|
||||
// Code 1
|
||||
case .configurationDisabled:
|
||||
return """
|
||||
case .configurationDisabled:
|
||||
return """
|
||||
The VPN configuration appears to be disabled. Please remove the Firezone
|
||||
VPN configuration in System Settings and try again.
|
||||
"""
|
||||
|
||||
// Code 2
|
||||
case .configurationInvalid:
|
||||
return """
|
||||
case .configurationInvalid:
|
||||
return """
|
||||
The VPN configuration appears to be invalid. Please remove the Firezone
|
||||
VPN configuration in System Settings and try again.
|
||||
"""
|
||||
|
||||
// Code 3
|
||||
case .connectionFailed:
|
||||
return """
|
||||
case .connectionFailed:
|
||||
return """
|
||||
The VPN connection failed. Try signing in again.
|
||||
"""
|
||||
|
||||
// Code 4
|
||||
case .configurationStale:
|
||||
return """
|
||||
case .configurationStale:
|
||||
return """
|
||||
The VPN configuration appears to be stale. Please remove the Firezone
|
||||
VPN configuration in System Settings and try again.
|
||||
"""
|
||||
|
||||
// Code 5
|
||||
case .configurationReadWriteFailed:
|
||||
return """
|
||||
case .configurationReadWriteFailed:
|
||||
return """
|
||||
Could not read or write the VPN configuration. Try removing the Firezone
|
||||
VPN configuration from System Settings if this issue persists.
|
||||
"""
|
||||
|
||||
// Code 6
|
||||
case .configurationUnknown:
|
||||
return """
|
||||
case .configurationUnknown:
|
||||
return """
|
||||
An unknown VPN configuration error occurred. Try removing the Firezone
|
||||
VPN configuration from System Settings if this issue persists.
|
||||
"""
|
||||
|
||||
@unknown default:
|
||||
return "\(error)"
|
||||
}
|
||||
}()
|
||||
@unknown default:
|
||||
return "\(self)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OSSystemExtensionError
|
||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||
private static func userMessage(for error: OSSystemExtensionError) -> String? {
|
||||
return {
|
||||
switch error.code {
|
||||
extension OSSystemExtensionError: UserFriendlyError {
|
||||
func userMessage() -> String? {
|
||||
switch code {
|
||||
|
||||
// Code 1
|
||||
case .unknown:
|
||||
return """
|
||||
// Code 1
|
||||
case .unknown:
|
||||
return """
|
||||
An unknown error occurred. Please try enabling the system extension again.
|
||||
If the issue persists, contact your administrator.
|
||||
"""
|
||||
|
||||
// Code 2
|
||||
case .missingEntitlement:
|
||||
return """
|
||||
// Code 2
|
||||
case .missingEntitlement:
|
||||
return """
|
||||
The system extension appears to be missing an entitlement. Please try
|
||||
downloading and installing Firezone again.
|
||||
"""
|
||||
|
||||
// Code 3
|
||||
case .unsupportedParentBundleLocation:
|
||||
return """
|
||||
// Code 3
|
||||
case .unsupportedParentBundleLocation:
|
||||
return """
|
||||
Please ensure Firezone.app is launched from the /Applications folder
|
||||
and try again.
|
||||
"""
|
||||
|
||||
// Code 4
|
||||
case .extensionNotFound:
|
||||
return """
|
||||
// Code 4
|
||||
case .extensionNotFound:
|
||||
return """
|
||||
The Firezone.app bundle seems corrupt. Please try downloading and
|
||||
installing Firezone again.
|
||||
"""
|
||||
|
||||
// Code 5
|
||||
case .extensionMissingIdentifier:
|
||||
return """
|
||||
// Code 5
|
||||
case .extensionMissingIdentifier:
|
||||
return """
|
||||
The system extension is missing its bundle identifier. Please try
|
||||
downloading and installing Firezone again.
|
||||
"""
|
||||
|
||||
// Code 6
|
||||
case .duplicateExtensionIdentifer:
|
||||
return """
|
||||
// Code 6
|
||||
case .duplicateExtensionIdentifer:
|
||||
return """
|
||||
The system extension appears to have been installed already. Please try
|
||||
completely removing Firezone and all Firezone-related system extensions
|
||||
and try again.
|
||||
"""
|
||||
|
||||
// Code 7
|
||||
case .unknownExtensionCategory:
|
||||
return """
|
||||
// Code 7
|
||||
case .unknownExtensionCategory:
|
||||
return """
|
||||
The system extension doesn't belong to any recognizable category.
|
||||
Please contact your administrator for assistance.
|
||||
"""
|
||||
|
||||
// Code 8
|
||||
case .codeSignatureInvalid:
|
||||
return """
|
||||
// Code 8
|
||||
case .codeSignatureInvalid:
|
||||
return """
|
||||
The system extension contains an invalid code signature. Please ensure
|
||||
your macOS version is up to date and system integrity protection (SIP)
|
||||
is enabled and functioning properly.
|
||||
"""
|
||||
|
||||
// Code 9
|
||||
case .validationFailed:
|
||||
return """
|
||||
// Code 9
|
||||
case .validationFailed:
|
||||
return """
|
||||
The system extension unexpectedly failed validation. Please try updating
|
||||
to the latest version and contact your administrator if this issue
|
||||
persists.
|
||||
"""
|
||||
|
||||
// Code 10
|
||||
case .forbiddenBySystemPolicy:
|
||||
return """
|
||||
// Code 10
|
||||
case .forbiddenBySystemPolicy:
|
||||
return """
|
||||
The FirezoneNetworkExtension was blocked from loading by a system policy.
|
||||
This will prevent Firezone from functioning. Please contact your
|
||||
administrator for assistance.
|
||||
@@ -160,37 +148,67 @@ struct macOSAlert { // swiftlint:disable:this type_name
|
||||
Extension Identifier: dev.firezone.firezone.network-extension
|
||||
"""
|
||||
|
||||
// Code 11
|
||||
case .requestCanceled:
|
||||
// This will happen if the user cancels
|
||||
return nil
|
||||
// Code 11
|
||||
case .requestCanceled:
|
||||
// This will happen if the user cancels
|
||||
return """
|
||||
You must enable the FirezoneNetworkExtension System Extension in System Settings to continue. Until you do,
|
||||
all functionality will be disabled.
|
||||
"""
|
||||
|
||||
// Code 12
|
||||
case .requestSuperseded:
|
||||
// This will happen if the user repeatedly clicks "Enable ..."
|
||||
return """
|
||||
// Code 12
|
||||
case .requestSuperseded:
|
||||
// This will happen if the user repeatedly clicks "Enable ..."
|
||||
return """
|
||||
You must enable the FirezoneNetworkExtension System Extension in System Settings to continue. Until you do,
|
||||
all functionality will be disabled.
|
||||
|
||||
For more information and troubleshooting, please contact your administrator.
|
||||
"""
|
||||
|
||||
// Code 13
|
||||
case .authorizationRequired:
|
||||
// This happens the first time we try to install the system extension.
|
||||
// The user is prompted but we still get this.
|
||||
return nil
|
||||
// Code 13
|
||||
case .authorizationRequired:
|
||||
// This happens the first time we try to install the system extension.
|
||||
// The user is prompted but we still get this.
|
||||
return nil
|
||||
|
||||
@unknown default:
|
||||
return "\(error)"
|
||||
}
|
||||
}()
|
||||
@unknown default:
|
||||
return "\(self)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error (fallback case)
|
||||
private static func userMessage(for error: Error) -> String? {
|
||||
return "\(error)"
|
||||
// Sometimes errors are passed to us as NSError objects, the Objective-C variant
|
||||
extension NSError: UserFriendlyError {
|
||||
func userMessage() -> String? {
|
||||
switch domain {
|
||||
case NEVPNErrorDomain:
|
||||
let code = NEVPNError.Code(rawValue: self.code)! // SAFETY: These are the same error type
|
||||
let err = NEVPNError(code)
|
||||
return err.userMessage()
|
||||
case OSSystemExtensionErrorDomain:
|
||||
let code = OSSystemExtensionError.Code(rawValue: self.code)! // SAFETY: These are the same error type
|
||||
let err = OSSystemExtensionError(code)
|
||||
return err.userMessage()
|
||||
default:
|
||||
return "\(self)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct macOSAlert { // swiftlint:disable:this type_name
|
||||
static func show(for error: Error) {
|
||||
let message = (error as UserFriendlyError).userMessage() ?? "\(error)"
|
||||
let alert = NSAlert()
|
||||
|
||||
alert.messageText = "An error occurred."
|
||||
alert.informativeText = message
|
||||
alert.alertStyle = .critical
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
// swiftlint:enable cyclomatic_complexity
|
||||
// swiftlint:enable function_body_length
|
||||
|
||||
@@ -20,6 +20,10 @@ export default function Apple() {
|
||||
<Entries downloadLinks={downloadLinks} title="macOS / iOS">
|
||||
{/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */}
|
||||
<Unreleased>
|
||||
<ChangeItem pull="8282">
|
||||
Shows friendlier and more-human alert messages when something goes
|
||||
wrong.
|
||||
</ChangeItem>
|
||||
<ChangeItem pull="8286">
|
||||
Fixes a bug that prevented certain Resource fields from being updated
|
||||
when they were updated in the admin portal.
|
||||
|
||||
Reference in New Issue
Block a user