diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift index d43831d00..202882eb1 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift @@ -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) } } } diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift index 8d75847d2..170417a8f 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift @@ -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) } } } diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/SettingsView.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/SettingsView.swift index 04c70f0b8..9ea65c8d2 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/SettingsView.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/SettingsView.swift @@ -534,7 +534,7 @@ public struct SettingsView: View { Log.error(error) } - await macOSAlert.show(for: error) + macOSAlert.show(for: error) } self.isExportingLogs = false diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/macOSAlert.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/macOSAlert.swift index 53ecb483d..53b29b8a5 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/macOSAlert.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/macOSAlert.swift @@ -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 diff --git a/website/src/components/Changelog/Apple.tsx b/website/src/components/Changelog/Apple.tsx index 4453313fd..9afbb6835 100644 --- a/website/src/components/Changelog/Apple.tsx +++ b/website/src/components/Changelog/Apple.tsx @@ -20,6 +20,10 @@ export default function Apple() { {/* 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. */} + + Shows friendlier and more-human alert messages when something goes + wrong. + Fixes a bug that prevented certain Resource fields from being updated when they were updated in the admin portal.