From 278efaf3474e3a2e7905e48148b4c57942ca9106 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sun, 2 Jul 2023 12:51:50 +0200 Subject: [PATCH] Refactor domain errors (#310) --- CHANGELOG.md | 4 + Passepartout.xcodeproj/project.pbxproj | 12 ++ .../xcshareddata/swiftpm/Package.resolved | 4 +- Passepartout/App/Context/AppContext.swift | 8 +- Passepartout/App/Context/AppError.swift | 49 ++++++++ Passepartout/App/InApp/ProductManager.swift | 6 - .../Intents/IntentDispatcher+Activities.swift | 13 +- .../Models/DefaultLightProviderManager.swift | 10 +- Passepartout/App/PassepartoutApp.swift | 1 + Passepartout/App/Reusable/ErrorHandler.swift | 69 +++++++++++ .../App/Reusable/GenericCreditsView.swift | 2 +- .../App/SceneDelegate+Shortcuts.swift | 6 +- Passepartout/App/SceneDelegate.swift | 1 + Passepartout/App/Views/AddHostViewModel.swift | 18 +-- .../App/Views/AddProviderViewModel.swift | 18 +-- Passepartout/App/Views/DonateView.swift | 16 +-- Passepartout/App/Views/OrganizerView.swift | 25 +--- .../App/Views/PaywallView+Purchase.swift | 53 ++------- Passepartout/App/Views/VPNToggle.swift | 2 + .../AppShared/Context/CoreContext.swift | 2 +- Passepartout/AppShared/L10n/Core+L10n.swift | 37 +----- Passepartout/AppShared/L10n/Errors+L10n.swift | 93 +++++++++++++++ .../AppShared/L10n/OpenVPN+L10n.swift | 75 ++++++++++++ .../AppShared/L10n/TunnelKit+L10n.swift | 112 ------------------ .../AppShared/L10n/WireGuard+L10n.swift | 18 ++- PassepartoutLibrary/Package.swift | 8 +- .../PassepartoutCore/Passepartout.swift | 12 -- .../PassepartoutProviders/Domain/Errors.swift | 33 ++++++ .../Managers/ProviderManager.swift | 12 +- .../PassepartoutVPN/Domain/Errors.swift | 29 +++-- .../Managers/ProfileManager.swift | 4 +- .../Managers/UpgradeManager.swift | 4 +- .../Managers/VPNManager+Extensions.swift | 10 +- .../PassepartoutVPN/Managers/VPNManager.swift | 30 +++-- ...egy.swift => UpgradeManagerStrategy.swift} | 4 +- .../Strategies/VPNManagerStrategy.swift | 2 +- .../OpenVPNSettings+TunnelKit.swift | 2 +- .../PassepartoutProviders+TunnelKit.swift | 2 +- .../Extensions/Profile+ProviderManager.swift | 6 +- .../Extensions/ProfileManager+TunnelKit.swift | 11 +- .../ProviderManager+Extensions.swift | 2 +- .../WireGuardSettings+TunnelKit.swift | 2 +- ...ft => DefaultUpgradeManagerStrategy.swift} | 50 ++++---- .../TunnelKitVPNManagerStrategy.swift | 10 +- 44 files changed, 530 insertions(+), 357 deletions(-) create mode 100644 Passepartout/App/Context/AppError.swift create mode 100644 Passepartout/App/Reusable/ErrorHandler.swift create mode 100644 Passepartout/AppShared/L10n/Errors+L10n.swift create mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/Domain/Errors.swift rename PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/{UpgradeStrategy.swift => UpgradeManagerStrategy.swift} (93%) rename PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/{DefaultUpgradeStrategy.swift => DefaultUpgradeManagerStrategy.swift} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3966afa..212d218f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Internal error handling. [#310](https://github.com/passepartoutvpn/passepartout-apple/pull/310) + ### Fixed - Allow wildcards in proxy bypass domains. [#296](https://github.com/passepartoutvpn/passepartout-apple/issues/296) diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index edc04d83..87774b33 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -30,6 +30,8 @@ 0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; }; 0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; }; 0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; }; + 0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */; }; + 0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */; }; 0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; }; 0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; }; 0E1F562B287F0EF100F8ADD7 /* ProviderProfileItem+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */; }; @@ -50,6 +52,7 @@ 0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; }; 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; }; 0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */; }; + 0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */; }; 0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */; }; 0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; }; 0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */; }; @@ -317,6 +320,8 @@ 0E0F4C6329C84B5A0022E884 /* LockableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockableView.swift; sourceTree = ""; }; 0E0F4C6529C84CF60022E884 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = ""; }; 0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = ""; }; + 0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = ""; }; + 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Errors+L10n.swift"; sourceTree = ""; }; 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = ""; }; 0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileItem.swift; sourceTree = ""; }; @@ -339,6 +344,7 @@ 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = ""; }; 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = ""; }; 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = ""; }; + 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = ""; }; 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = ""; }; 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Provider.swift"; sourceTree = ""; }; @@ -594,6 +600,7 @@ children = ( 0E021D9B284E68580077EF5D /* AppContext.swift */, 0E293856285A73BC002A6E0E /* AppContext+Shared.swift */, + 0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */, 0E293850285A70AC002A6E0E /* AppPreference.swift */, ); path = Context; @@ -608,6 +615,7 @@ 0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */, 0E7577D62816A3B200081CBE /* DestructiveButton.swift */, 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */, + 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */, 0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */, 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */, 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */, @@ -629,6 +637,7 @@ isa = PBXGroup; children = ( 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */, + 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */, 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */, 0E71ACE227C0F2E300F85C4B /* Providers+L10n.swift */, 0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */, @@ -1445,6 +1454,7 @@ 0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */, 0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */, 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */, + 0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */, 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */, 0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */, 0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */, @@ -1539,6 +1549,7 @@ 0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */, 0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */, 0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */, + 0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */, 0E53249927D26B51002565C3 /* ProductManager.swift in Sources */, 0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */, 0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */, @@ -1556,6 +1567,7 @@ 0E021D9D284E68580077EF5D /* AppContext.swift in Sources */, 0E2DE72527DCDF550067B9E1 /* WireGuard+L10n.swift in Sources */, 0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */, + 0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */, 0ED1D6DC27DBA41700983466 /* DiagnosticsView+OpenVPN.swift in Sources */, 0ED30DCC27EA197D0057D8A3 /* RevealingSecureField.swift in Sources */, 0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */, diff --git a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e2900a2c..486d64f4 100644 --- a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/passepartoutvpn/tunnelkit", "state": { "branch": null, - "revision": "8f066a9e4821f041693c8262ba01ef49ab0084ae", - "version": "6.0.0" + "revision": "729e8973cfbb40330e046439417650e6bf993105", + "version": null } }, { diff --git a/Passepartout/App/Context/AppContext.swift b/Passepartout/App/Context/AppContext.swift index e8d36595..a47820ba 100644 --- a/Passepartout/App/Context/AppContext.swift +++ b/Passepartout/App/Context/AppContext.swift @@ -48,8 +48,10 @@ final class AppContext { configureObjects(coreContext: coreContext) } +} - private func configureObjects(coreContext: CoreContext) { +private extension AppContext { + func configureObjects(coreContext: CoreContext) { coreContext.vpnManager.isOnDemandRulesSupported = { self.isEligibleForOnDemandRules() } @@ -75,7 +77,7 @@ final class AppContext { } // eligibility: ignore network settings if ineligible - private func isEligibleForNetworkSettings() -> Bool { + func isEligibleForNetworkSettings() -> Bool { guard productManager.isEligible(forFeature: .networkSettings) else { pp_log.warning("Ignore network settings, not eligible") return false @@ -84,7 +86,7 @@ final class AppContext { } // eligibility: reset on-demand rules if no trusted networks - private func isEligibleForOnDemandRules() -> Bool { + func isEligibleForOnDemandRules() -> Bool { guard productManager.isEligible(forFeature: .trustedNetworks) else { pp_log.warning("Ignore on-demand rules, not eligible for trusted networks") return false diff --git a/Passepartout/App/Context/AppError.swift b/Passepartout/App/Context/AppError.swift new file mode 100644 index 00000000..809561d3 --- /dev/null +++ b/Passepartout/App/Context/AppError.swift @@ -0,0 +1,49 @@ +// +// AppError.swift +// Passepartout +// +// Created by Davide De Rosa on 5/30/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutLibrary + +enum AppError: Error { + case profile(Passepartout.ProfileError) + + case provider(Passepartout.ProviderError) + + case vpn(Passepartout.VPNError) + + case generic(Error) + + init(_ error: Error) { + if let profileError = error as? Passepartout.ProfileError { + self = .profile(profileError) + } else if let providerError = error as? Passepartout.ProviderError { + self = .provider(providerError) + } else if let vpnError = error as? Passepartout.VPNError { + self = .vpn(vpnError) + } else { + self = .generic(error) + } + } +} diff --git a/Passepartout/App/InApp/ProductManager.swift b/Passepartout/App/InApp/ProductManager.swift index 182a8e99..4f3b979b 100644 --- a/Passepartout/App/InApp/ProductManager.swift +++ b/Passepartout/App/InApp/ProductManager.swift @@ -29,12 +29,6 @@ import Kvitto import PassepartoutLibrary import StoreKit -enum ProductError: Error { - case uneligible - - case beta -} - final class ProductManager: NSObject, ObservableObject { enum AppType: Int { case freemium = 0 diff --git a/Passepartout/App/Intents/IntentDispatcher+Activities.swift b/Passepartout/App/Intents/IntentDispatcher+Activities.swift index 6f4ebc14..c04f80d2 100644 --- a/Passepartout/App/Intents/IntentDispatcher+Activities.swift +++ b/Passepartout/App/Intents/IntentDispatcher+Activities.swift @@ -29,14 +29,6 @@ import PassepartoutLibrary @MainActor extension IntentDispatcher { - private enum IntentError: Error { - case notProvider(UUID) - - case serverNotFound(UUID) - - case activeAndConnected(UUID) - } - typealias VPNIntentActivity = IntentActivity static let enableVPN = VPNIntentActivity(name: Constants.Activities.enableVPN) { _, vpnManager in @@ -47,6 +39,7 @@ extension IntentDispatcher { try await vpnManager.connectWithActiveProfile(toServer: nil) } catch { pp_log.error("Unable to connect with active profile: \(error)") + ErrorHandler.shared.handle(error) } } } @@ -78,6 +71,7 @@ extension IntentDispatcher { _ = try await vpnManager.connect(with: profileId) } catch { pp_log.error("Unable to connect with profile \(profileId): \(error)") + ErrorHandler.shared.handle(error) } } } @@ -107,6 +101,7 @@ extension IntentDispatcher { _ = try await vpnManager.connect(with: profileId, toServer: newServerId) } catch { pp_log.error("Unable to connect with profile \(profileId): \(error)") + ErrorHandler.shared.handle(error) } } } @@ -139,6 +134,7 @@ extension IntentDispatcher { } } catch { pp_log.error("Unable to modify cellular trust: \(error)") + ErrorHandler.shared.handle(error) } } } @@ -156,6 +152,7 @@ extension IntentDispatcher { } } catch { pp_log.error("Unable to modify Wi-Fi trust: \(error)") + ErrorHandler.shared.handle(error) } } } diff --git a/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift b/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift index 96f18c26..419ccc25 100644 --- a/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift +++ b/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift @@ -111,7 +111,15 @@ final class DefaultLightProviderManager: LightProviderManager { return } Task { - try await providerManager.fetchProviderPublisher(withName: name, vpnProtocol: vpnProtocolType, priority: .remoteThenBundle).async() + do { + try await providerManager.fetchProviderPublisher( + withName: name, + vpnProtocol: vpnProtocolType, + priority: .remoteThenBundle + ).async() + } catch { + ErrorHandler.shared.handle(error) + } } } } diff --git a/Passepartout/App/PassepartoutApp.swift b/Passepartout/App/PassepartoutApp.swift index 3ed1197a..3d9c882f 100644 --- a/Passepartout/App/PassepartoutApp.swift +++ b/Passepartout/App/PassepartoutApp.swift @@ -34,6 +34,7 @@ struct PassepartoutApp: App { WindowGroup { MainView() .withoutTitleBar() + .withErrorHandler() .onIntentActivity(IntentDispatcher.connectVPN) .onIntentActivity(IntentDispatcher.disableVPN) .onIntentActivity(IntentDispatcher.enableVPN) diff --git a/Passepartout/App/Reusable/ErrorHandler.swift b/Passepartout/App/Reusable/ErrorHandler.swift new file mode 100644 index 00000000..8c333642 --- /dev/null +++ b/Passepartout/App/Reusable/ErrorHandler.swift @@ -0,0 +1,69 @@ +import SwiftUI + +// https://www.ralfebert.com/swiftui/generic-error-handling/ + +private struct ErrorAlert: Identifiable { + let id = UUID() + + let title: String? + + let message: String + + let dismissAction: (() -> Void)? +} + +@MainActor +final class ErrorHandler: ObservableObject { + static let shared = ErrorHandler() + + @Published fileprivate var currentAlert: ErrorAlert? + + func handle(_ error: Error, title: String? = nil, onDismiss: (() -> Void)? = nil) { + currentAlert = ErrorAlert( + title: title, + message: AppError(error).localizedDescription, + dismissAction: onDismiss + ) + } + + func handle(title: String, message: String, onDismiss: (() -> Void)? = nil) { + currentAlert = ErrorAlert( + title: title, + message: message, + dismissAction: onDismiss + ) + } +} + +struct HandleErrorsByShowingAlertViewModifier: ViewModifier { + @ObservedObject private var errorHandler: ErrorHandler + + init() { + errorHandler = .shared + } + + func body(content: Content) -> some View { + content + // Applying the alert for error handling using a background element + // is a workaround, if the alert would be applied directly, + // other .alert modifiers inside of content would not work anymore + .background( + EmptyView() + .alert(item: $errorHandler.currentAlert) { currentAlert in + Alert( + title: Text(currentAlert.title ?? Unlocalized.appName), + message: Text(currentAlert.message.withTrailingDot), + dismissButton: .cancel(Text(L10n.Global.Strings.ok)) { + currentAlert.dismissAction?() + } + ) + } + ) + } +} + +extension View { + func withErrorHandler() -> some View { + modifier(HandleErrorsByShowingAlertViewModifier()) + } +} diff --git a/Passepartout/App/Reusable/GenericCreditsView.swift b/Passepartout/App/Reusable/GenericCreditsView.swift index edcfe41e..4d35aad0 100644 --- a/Passepartout/App/Reusable/GenericCreditsView.swift +++ b/Passepartout/App/Reusable/GenericCreditsView.swift @@ -185,7 +185,7 @@ extension GenericCreditsView { do { content = try String(contentsOf: url) } catch { - content = error.localizedDescription + content = AppError(error).localizedDescription } } } diff --git a/Passepartout/App/SceneDelegate+Shortcuts.swift b/Passepartout/App/SceneDelegate+Shortcuts.swift index 9f72156b..40424266 100644 --- a/Passepartout/App/SceneDelegate+Shortcuts.swift +++ b/Passepartout/App/SceneDelegate+Shortcuts.swift @@ -56,7 +56,11 @@ extension SceneDelegate { switch shortcutItem.type { case ShortcutType.enableVPN.rawValue: Task { - try await VPNManager.shared.connectWithActiveProfile(toServer: nil) + do { + try await VPNManager.shared.connectWithActiveProfile(toServer: nil) + } catch { + ErrorHandler.shared.handle(error) + } } case ShortcutType.disableVPN.rawValue: diff --git a/Passepartout/App/SceneDelegate.swift b/Passepartout/App/SceneDelegate.swift index dcf41094..29777527 100644 --- a/Passepartout/App/SceneDelegate.swift +++ b/Passepartout/App/SceneDelegate.swift @@ -26,6 +26,7 @@ import PassepartoutLibrary import SwiftUI +@MainActor final class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneDidEnterBackground(_ scene: UIScene) { ProfileManager.shared.persist() diff --git a/Passepartout/App/Views/AddHostViewModel.swift b/Passepartout/App/Views/AddHostViewModel.swift index 726734c0..50c67380 100644 --- a/Passepartout/App/Views/AddHostViewModel.swift +++ b/Passepartout/App/Views/AddHostViewModel.swift @@ -84,16 +84,12 @@ extension AddHostView { try? FileManager.default.removeItem(at: url) } } catch { - switch error { - case OpenVPN.ConfigurationError.encryptionPassphrase, - OpenVPN.ConfigurationError.unableToDecrypt: - + if case Passepartout.ProfileError.decryptionFailure = error { requiresPassphrase = true - - default: + } else { requiresPassphrase = false } - setMessage(forParsingError: error) + setErrorMessage(for: error) } } @@ -108,8 +104,12 @@ extension AddHostView { return true } - private mutating func setMessage(forParsingError error: Error) { - errorMessage = error.localizedVPNParsingDescription + private mutating func setErrorMessage(for error: Error) { + setErrorMessage(AppError(error).localizedDescription) + } + + private mutating func setErrorMessage(_ message: String) { + errorMessage = message.withTrailingDot } } } diff --git a/Passepartout/App/Views/AddProviderViewModel.swift b/Passepartout/App/Views/AddProviderViewModel.swift index 44390849..2f45636c 100644 --- a/Passepartout/App/Views/AddProviderViewModel.swift +++ b/Passepartout/App/Views/AddProviderViewModel.swift @@ -96,10 +96,10 @@ extension AddProviderView { ) { doSelectProvider(metadata, server) } else { - errorMessage = L10n.AddProfile.Provider.Errors.noDefaultServer + setErrorMessage(L10n.AddProfile.Provider.Errors.noDefaultServer) } } catch { - errorMessage = error.localizedDescription + setErrorMessage(for: error) } pendingOperation = nil } @@ -119,7 +119,7 @@ extension AddProviderView { priority: .remoteThenBundle ).async() } catch { - errorMessage = error.localizedDescription + setErrorMessage(for: error) } pendingOperation = nil } @@ -128,6 +128,14 @@ extension AddProviderView { func presentPaywall() { isPaywallPresented = true } + + private func setErrorMessage(for error: Error) { + setErrorMessage(AppError(error).localizedDescription) + } + + private func setErrorMessage(_ message: String) { + errorMessage = message.withTrailingDot + } } } @@ -173,10 +181,6 @@ extension AddProviderView.NameView { profileManager.saveProfile(finalProfile, isActive: nil) return finalProfile } - - private mutating func setMessage(forError error: Error) { - errorMessage = error.localizedDescription - } } } diff --git a/Passepartout/App/Views/DonateView.swift b/Passepartout/App/Views/DonateView.swift index acff2933..cf06a32d 100644 --- a/Passepartout/App/Views/DonateView.swift +++ b/Passepartout/App/Views/DonateView.swift @@ -31,14 +31,10 @@ struct DonateView: View { enum AlertType: Identifiable { case thankYou - case purchaseFailed(Error) - // XXX: alert ids var id: Int { switch self { case .thankYou: return 1 - - case .purchaseFailed: return 2 } } } @@ -81,13 +77,6 @@ struct DonateView: View { message: Text(L10n.Donate.Alerts.Purchase.Success.message), dismissButton: .cancel(Text(L10n.Global.Strings.ok)) ) - - case .purchaseFailed(let error): - return Alert( - title: Text(L10n.Donate.title), - message: Text(L10n.Donate.Alerts.Purchase.Failure.message(error.localizedDescription)), - dismissButton: .cancel(Text(L10n.Global.Strings.ok)) - ) } } @@ -140,7 +129,10 @@ extension DonateView { } case .failure(let error): - alertType = .purchaseFailed(error) + ErrorHandler.shared.handle( + title: L10n.Donate.title, + message: L10n.Donate.Alerts.Purchase.Failure.message(AppError(error).localizedDescription) + ) } pendingDonationIdentifier = nil } diff --git a/Passepartout/App/Views/OrganizerView.swift b/Passepartout/App/Views/OrganizerView.swift index b61b5ec7..6032d34b 100644 --- a/Passepartout/App/Views/OrganizerView.swift +++ b/Passepartout/App/Views/OrganizerView.swift @@ -41,14 +41,10 @@ struct OrganizerView: View { enum AlertType: Identifiable { case subscribeReddit - case error(String, String) - // XXX: alert ids var id: Int { switch self { case .subscribeReddit: return 1 - - case .error: return 2 } } } @@ -93,11 +89,6 @@ struct OrganizerView: View { onCompletion: onHostFileImporterResult ).onOpenURL(perform: onOpenURL) .themePrimaryView() - - // VPN configuration error publisher (no need to observe VPNManager) - .onReceive(VPNManager.shared.configurationError) { - alertType = .error($0.profile.header.name, $0.error.localizedAppDescription) - } } private var hiddenSceneView: some View { @@ -109,6 +100,8 @@ struct OrganizerView: View { } extension OrganizerView { + + @MainActor private func onHostFileImporterResult(_ result: Result<[URL], Error>) { switch result { case .success(let urls): @@ -116,16 +109,13 @@ extension OrganizerView { assertionFailure("Empty URLs from file importer?") return } - Task { @MainActor in + Task { await Task.maybeWait(forMilliseconds: Constants.Delays.xxxPresentFileImporter) addProfileModalType = .addHost(url, false) } case .failure(let error): - alertType = .error( - L10n.Menu.Contextual.AddProfile.fromFiles, - error.localizedDescription - ) + ErrorHandler.shared.handle(error, title: L10n.Menu.Contextual.AddProfile.fromFiles) } } @@ -157,13 +147,6 @@ extension OrganizerView { didHandleSubreddit = true } ) - - case .error(let title, let errorDescription): - return Alert( - title: Text(title), - message: Text(errorDescription), - dismissButton: .cancel(Text(L10n.Global.Strings.ok)) - ) } } } diff --git a/Passepartout/App/Views/PaywallView+Purchase.swift b/Passepartout/App/Views/PaywallView+Purchase.swift index e13f4984..b2ebdc61 100644 --- a/Passepartout/App/Views/PaywallView+Purchase.swift +++ b/Passepartout/App/Views/PaywallView+Purchase.swift @@ -29,20 +29,6 @@ import SwiftUI extension PaywallView { struct PurchaseView: View { - private enum AlertType: Identifiable { - case purchaseFailed(SKProduct, Error) - - case restoreFailed(Error) - - var id: Int { - switch self { - case .purchaseFailed: return 1 - - case .restoreFailed: return 2 - } - } - } - fileprivate enum PurchaseState { case purchasing(SKProduct) @@ -59,8 +45,6 @@ extension PaywallView { private let feature: LocalProduct? - @State private var alertType: AlertType? - @State private var purchaseState: PurchaseState? init(isPresented: Binding, feature: LocalProduct? = nil) { @@ -74,7 +58,6 @@ extension PaywallView { productsSection .disabled(purchaseState != nil) }.navigationTitle(Unlocalized.appName) - .alert(item: $alertType, content: presentedAlert) // reloading .onAppear { @@ -86,28 +69,6 @@ extension PaywallView { }.themeAnimation(on: productManager.isRefreshingProducts) } - private func presentedAlert(_ alertType: AlertType) -> Alert { - switch alertType { - case .purchaseFailed(let product, let error): - return Alert( - title: Text(product.localizedTitle), - message: Text(error.localizedDescription), - dismissButton: .default(Text(L10n.Global.Strings.ok)) { - purchaseState = nil - } - ) - - case .restoreFailed(let error): - return Alert( - title: Text(L10n.Paywall.Items.Restore.title), - message: Text(error.localizedDescription), - dismissButton: .default(Text(L10n.Global.Strings.ok)) { - purchaseState = nil - } - ) - } - } - private var productsSection: some View { Section { if !productManager.isRefreshingProducts { @@ -164,7 +125,12 @@ extension PaywallView.PurchaseView { case .failure(let error): pp_log.error("Unable to purchase: \(error)") - alertType = .purchaseFailed(product, error) + ErrorHandler.shared.handle( + title: product.localizedTitle, + message: AppError(error).localizedDescription + ) { + purchaseState = nil + } } } } @@ -175,7 +141,12 @@ extension PaywallView.PurchaseView { productManager.restorePurchases { if let error = $0 { pp_log.error("Unable to restore purchases: \(error)") - alertType = .restoreFailed(error) + ErrorHandler.shared.handle( + title: L10n.Paywall.Items.Restore.title, + message: AppError(error).localizedDescription + ) { + purchaseState = nil + } return } isPresented = false diff --git a/Passepartout/App/Views/VPNToggle.swift b/Passepartout/App/Views/VPNToggle.swift index 3d8cb08e..879496a9 100644 --- a/Passepartout/App/Views/VPNToggle.swift +++ b/Passepartout/App/Views/VPNToggle.swift @@ -98,6 +98,8 @@ struct VPNToggle: View { } catch { pp_log.warning("Unable to connect to profile \(profile.id): \(error)") canToggle = true + + ErrorHandler.shared.handle(error, title: profile.header.name) } } } diff --git a/Passepartout/AppShared/Context/CoreContext.swift b/Passepartout/AppShared/Context/CoreContext.swift index 5ba9d5ad..7556f935 100644 --- a/Passepartout/AppShared/Context/CoreContext.swift +++ b/Passepartout/AppShared/Context/CoreContext.swift @@ -76,7 +76,7 @@ final class CoreContext { upgradeManager = UpgradeManager( store: store, - strategy: DefaultUpgradeStrategy() + strategy: DefaultUpgradeManagerStrategy() ) let remoteProvidersStrategy = APIRemoteProvidersStrategy( diff --git a/Passepartout/AppShared/L10n/Core+L10n.swift b/Passepartout/AppShared/L10n/Core+L10n.swift index f6951476..b6aca679 100644 --- a/Passepartout/AppShared/L10n/Core+L10n.swift +++ b/Passepartout/AppShared/L10n/Core+L10n.swift @@ -26,46 +26,13 @@ import Foundation import PassepartoutLibrary -extension Error { - var localizedAppDescription: String { - if let errorDescription = (self as? PassepartoutError)?.localizedAppDescription, !errorDescription.isEmpty { - return "\(errorDescription)." - } - return localizedDescription - } -} - -extension PassepartoutError { - var localizedAppDescription: String? { - let V = L10n.Global.Errors.self - switch self { - case .missingProfile: - return V.missingProfile - - case .missingAccount: - return V.missingAccount - - case .missingProviderServer: - return V.missingProviderServer - - case .missingProviderPreset: - return V.missingProviderPreset - - default: - return nil - } - } -} - extension ObservableVPNState { func localizedStatusDescription(isActiveProfile: Bool, withErrors: Bool, dataCountIfAvailable: Bool) -> String { guard isActiveProfile && isEnabled else { return L10n.Tunnelkit.Vpn.disabled } - if withErrors { - if let errorDescription = lastError?.localizedVPNDescription, !errorDescription.isEmpty { - return errorDescription - } + if withErrors, let lastError { + return AppError(lastError).localizedDescription } if dataCountIfAvailable, vpnStatus == .connected, let dataCount = dataCount { return dataCount.localizedDescription diff --git a/Passepartout/AppShared/L10n/Errors+L10n.swift b/Passepartout/AppShared/L10n/Errors+L10n.swift new file mode 100644 index 00000000..64c8722c --- /dev/null +++ b/Passepartout/AppShared/L10n/Errors+L10n.swift @@ -0,0 +1,93 @@ +// +// Errors+L10n.swift +// Passepartout +// +// Created by Davide De Rosa on 5/30/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension AppError: LocalizedError { + var errorDescription: String? { + guard let errorDescriptionImpl, !errorDescriptionImpl.isEmpty else { + return localizedDescription + } + return errorDescriptionImpl + } + + private var errorDescriptionImpl: String? { + let V = L10n.Global.Errors.self + switch self { + case .profile(let domainError): + switch domainError { + case .importFailure(let error): + return error.localizedDescription + + case .decryptionFailure(let error): + return error.localizedDescription + + case .notFound: + return V.missingProfile + + case .failedToFetchProvider(_, let error): + return error.localizedDescription + } + + case .provider(let domainError): + switch domainError { + case .fetchFailure(let error): + return error.localizedDescription + } + + case .vpn(let domainError): + switch domainError { + case .notProvider: + assertionFailure() + return nil + + case .providerServerNotFound: + return V.missingProviderServer + + case .providerPresetNotFound: + return V.missingProviderPreset + + case .missingAccount: + return V.missingAccount + + case .emptyEndpoints: + assertionFailure() + return nil + } + + case .generic(let error): + return error.localizedDescription + } + } +} + +extension String { + var withTrailingDot: String { + guard !hasSuffix(".") else { + return self + } + return "\(self)." + } +} diff --git a/Passepartout/AppShared/L10n/OpenVPN+L10n.swift b/Passepartout/AppShared/L10n/OpenVPN+L10n.swift index 17f8428c..a48e407f 100644 --- a/Passepartout/AppShared/L10n/OpenVPN+L10n.swift +++ b/Passepartout/AppShared/L10n/OpenVPN+L10n.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutLibrary import TunnelKitOpenVPN extension OpenVPN.Cipher { @@ -160,3 +161,77 @@ extension OpenVPN.PullMask { } } } + +extension TunnelKitOpenVPNError: LocalizedError { + public var errorDescription: String? { + let V = L10n.Tunnelkit.Errors.Vpn.self + switch self { + case .socketActivity, .timeout: + return V.timeout + + case .dnsFailure: + return V.dns + + case .tlsInitialization, .tlsServerVerification, .tlsHandshake: + return V.tls + + case .authentication: + return V.auth + + case .encryptionInitialization, .encryptionData: + return V.encryption + + case .serverCompression, .lzo: + return V.compression + + case .networkChanged: + return V.network + + case .routing: + return V.routing + + case .gatewayUnattainable: + return V.gateway + + case .serverShutdown: + return V.shutdown + + default: + return nil + } + } +} + +extension OpenVPN.ConfigurationError: LocalizedError { + public var errorDescription: String? { + let V = L10n.Tunnelkit.Errors.Openvpn.self + switch self { + case .encryptionPassphrase: + pp_log.error("Could not parse configuration URL: unable to decrypt, passphrase required") + return V.passphraseRequired + + case .unableToDecrypt(let error): + pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)") + return V.decryption + + case .malformed(let option): + pp_log.error("Could not parse configuration URL: malformed option, \(option)") + return V.malformed(option) + + case .missingConfiguration(let option): + pp_log.error("Could not parse configuration URL: missing configuration, \(option)") + return V.requiredOption(option) + + case .unsupportedConfiguration(var option): + if option.contains("external") { + option.append(" (see FAQ)") + } + pp_log.error("Could not parse configuration URL: unsupported configuration, \(option)") + return V.unsupportedOption(option) + + case .continuationPushReply: + assertionFailure("This is a server-side configuration parsing error") + return nil + } + } +} diff --git a/Passepartout/AppShared/L10n/TunnelKit+L10n.swift b/Passepartout/AppShared/L10n/TunnelKit+L10n.swift index 59f261f2..70d98860 100644 --- a/Passepartout/AppShared/L10n/TunnelKit+L10n.swift +++ b/Passepartout/AppShared/L10n/TunnelKit+L10n.swift @@ -107,115 +107,3 @@ extension IPv6Settings.Route { "\(destination)/\(prefixLength) -> \(gateway ?? "*")" } } - -extension Error { - var localizedVPNDescription: String? { - if let ovpnError = self as? OpenVPNProviderError { - return ovpnErrorDescription(ovpnError) - } - if let wgError = self as? WireGuardProviderError { - return wgErrorDescription(wgError) - } - if let neError = self as? NEVPNError { - return neErrorDescription(neError) - } - return localizedDescription - } - - private func ovpnErrorDescription(_ error: OpenVPNProviderError) -> String? { - let V = L10n.Tunnelkit.Errors.Vpn.self - switch error { - case .socketActivity, .timeout: - return V.timeout - - case .dnsFailure: - return V.dns - - case .tlsInitialization, .tlsServerVerification, .tlsHandshake: - return V.tls - - case .authentication: - return V.auth - - case .encryptionInitialization, .encryptionData: - return V.encryption - - case .serverCompression, .lzo: - return V.compression - - case .networkChanged: - return V.network - - case .routing: - return V.routing - - case .gatewayUnattainable: - return V.gateway - - case .serverShutdown: - return V.shutdown - - default: - return nil - } - } - - private func wgErrorDescription(_ error: WireGuardProviderError) -> String? { - let V = L10n.Tunnelkit.Errors.Vpn.self - switch error { - case .dnsResolutionFailure: - return V.dns - - default: - return nil - } - } - - private func neErrorDescription(_ error: NEVPNError) -> String? { - error.localizedDescription.capitalized - } -} - -extension Error { - var localizedVPNParsingDescription: String? { - if let ovpnError = self as? OpenVPN.ConfigurationError { - return ovpnConfigurationErrorDescription(ovpnError) - } else if let wgError = self as? WireGuard.ConfigurationError { - return wgConfigurationErrorDescription(wgError) - } - pp_log.error("Could not parse configuration URL: \(localizedDescription)") - return L10n.Tunnelkit.Errors.parsing(localizedDescription) - } - - private func ovpnConfigurationErrorDescription(_ error: OpenVPN.ConfigurationError) -> String { - let V = L10n.Tunnelkit.Errors.Openvpn.self - switch error { - case .encryptionPassphrase: - pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)") - return V.passphraseRequired - - case .unableToDecrypt(let error): - pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)") - return V.decryption - - case .malformed(let option): - pp_log.error("Could not parse configuration URL: malformed option, \(option)") - return V.malformed(option) - - case .missingConfiguration(let option): - pp_log.error("Could not parse configuration URL: missing configuration, \(option)") - return V.requiredOption(option) - - case .unsupportedConfiguration(var option): - if option.contains("external") { - option.append(" (see FAQ)") - } - pp_log.error("Could not parse configuration URL: unsupported configuration, \(option)") - return V.unsupportedOption(option) - } - } - - private func wgConfigurationErrorDescription(_ error: WireGuard.ConfigurationError) -> String { - error.localizedDescription - } -} diff --git a/Passepartout/AppShared/L10n/WireGuard+L10n.swift b/Passepartout/AppShared/L10n/WireGuard+L10n.swift index b5f00c43..69156633 100644 --- a/Passepartout/AppShared/L10n/WireGuard+L10n.swift +++ b/Passepartout/AppShared/L10n/WireGuard+L10n.swift @@ -24,4 +24,20 @@ // import Foundation -import TunnelKitWireGuardCore +import TunnelKitWireGuard + +extension TunnelKitWireGuardError: LocalizedError { + public var errorDescription: String? { + let V = L10n.Tunnelkit.Errors.Vpn.self + switch self { + case .dnsResolutionFailure: + return V.dns + + default: + return nil + } + } +} + +extension WireGuard.ConfigurationError: LocalizedError { +} diff --git a/PassepartoutLibrary/Package.swift b/PassepartoutLibrary/Package.swift index e8987693..03816496 100644 --- a/PassepartoutLibrary/Package.swift +++ b/PassepartoutLibrary/Package.swift @@ -23,8 +23,8 @@ let package = Package( dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), - .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", from: "6.0.0"), -// .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", .revision("ac362f90ef1c8b64fca113be8521312d85248b48")), +// .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", from: "6.0.0"), + .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", .revision("729e8973cfbb40330e046439417650e6bf993105")), // .package(name: "TunnelKit", path: "../../tunnelkit"), .package(url: "https://github.com/zoul/generic-json-swift", from: "2.0.0"), .package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver", from: "1.9.0") @@ -69,8 +69,8 @@ let package = Package( dependencies: [ "PassepartoutProviders", .product(name: "TunnelKit", package: "TunnelKit"), - .product(name: "TunnelKitOpenVPN", package: "TunnelKit"), // FIXME: arch, drop this - .product(name: "TunnelKitWireGuard", package: "TunnelKit"), // FIXME: arch, drop this + .product(name: "TunnelKitOpenVPN", package: "TunnelKit"), + .product(name: "TunnelKitWireGuard", package: "TunnelKit"), ]), .target( name: "PassepartoutProviders", diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Passepartout.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Passepartout.swift index 111b662a..34a0cae9 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Passepartout.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Passepartout.swift @@ -34,15 +34,3 @@ public class Passepartout { public var logger: Logger = DefaultLogger() } - -public struct PassepartoutError: Error, Equatable { - private let string: String - - public init(_ string: String) { - self.string = string - } - - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.string == rhs.string - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/Errors.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/Errors.swift new file mode 100644 index 00000000..35e10f8f --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/Errors.swift @@ -0,0 +1,33 @@ +// +// Errors.swift +// Passepartout +// +// Created by Davide De Rosa on 5/30/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +extension Passepartout { + public enum ProviderError: Error { + case fetchFailure(error: Error) + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift index 5b4e8e89..6db4710c 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift @@ -88,10 +88,10 @@ public final class ProviderManager: ObservableObject, RateLimited { // MARK: Modification - public func fetchProvidersIndexPublisher(priority: RemoteProvidersPriority) -> AnyPublisher { + public func fetchProvidersIndexPublisher(priority: RemoteProvidersPriority) -> AnyPublisher { guard !isRateLimited(indexActionName) else { return Just(()) - .setFailureType(to: Error.self) + .setFailureType(to: Passepartout.ProviderError.self) .eraseToAnyPublisher() } @@ -101,13 +101,15 @@ public final class ProviderManager: ObservableObject, RateLimited { return savePublisher .map { self.didUpdateProviders.send() + }.mapError { + .fetchFailure(error: $0) }.eraseToAnyPublisher() } - public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: RemoteProvidersPriority) -> AnyPublisher { + public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: RemoteProvidersPriority) -> AnyPublisher { guard !isRateLimited(providerName) else { return Just(()) - .setFailureType(to: Error.self) + .setFailureType(to: Passepartout.ProviderError.self) .eraseToAnyPublisher() } @@ -123,6 +125,8 @@ public final class ProviderManager: ObservableObject, RateLimited { return savePublisher .map { self.didUpdateProviders.send() + }.mapError { + .fetchFailure(error: $0) }.eraseToAnyPublisher() } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Errors.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Errors.swift index d721ee1c..5addc292 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Errors.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Errors.swift @@ -2,7 +2,7 @@ // Errors.swift // Passepartout // -// Created by Davide De Rosa on 6/21/22. +// Created by Davide De Rosa on 5/30/23. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -25,15 +25,28 @@ import Foundation import PassepartoutCore +import PassepartoutProviders -extension PassepartoutError { - public static let missingProfile = Self("missingProfile") +extension Passepartout { + public enum ProfileError: Error { + case importFailure(error: Error) - public static let missingAccount = Self("missingAccount") + case decryptionFailure(error: Error) - public static let missingProviderServer = Self("missingProviderServer") + case notFound(profileId: UUID) - public static let missingProviderPreset = Self("missingProviderPreset") + case failedToFetchProvider(profileId: UUID, error: Error) + } + + public enum VPNError: Error { + case notProvider(profile: Profile) + + case providerServerNotFound(profile: Profile) + + case providerPresetNotFound(profile: Profile) + + case missingAccount(profile: Profile) + + case emptyEndpoints(server: ProviderServer) + } } - -public typealias VPNConfigurationError = (profile: Profile, error: Error) diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager.swift index 95a44825..25327e28 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager.swift @@ -142,7 +142,7 @@ extension ProfileManager { public func liveProfileEx(withId id: UUID) throws -> ProfileEx { guard let profile = liveProfile(withId: id) else { pp_log.error("Profile not found: \(id)") - throw PassepartoutError.missingProfile + throw Passepartout.ProfileError.notFound(profileId: id) } pp_log.info("Found profile: \(profile.logDescription)") return (profile, isProfileReady(profile)) @@ -483,7 +483,7 @@ extension ProfileManager { pp_log.info("Finished!") } catch { pp_log.error("Unable to import missing provider: \(error)") - throw PassepartoutError.missingProfile + throw Passepartout.ProfileError.failedToFetchProvider(profileId: profile.id, error: error) } } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/UpgradeManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/UpgradeManager.swift index da2cf643..f89b491d 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/UpgradeManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/UpgradeManager.swift @@ -33,7 +33,7 @@ public final class UpgradeManager: ObservableObject { private let store: KeyValueStore - private let strategy: UpgradeStrategy + private let strategy: UpgradeManagerStrategy // MARK: State @@ -41,7 +41,7 @@ public final class UpgradeManager: ObservableObject { public init( store: KeyValueStore, - strategy: UpgradeStrategy + strategy: UpgradeManagerStrategy ) { self.store = store self.strategy = strategy diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift index 87e0573d..26d95aad 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift @@ -67,7 +67,7 @@ extension VPNManager { } profileManager.activateProfile(profile) - await reconnect(profile) + try await reconnect(profile) return profile } @@ -77,7 +77,7 @@ extension VPNManager { var profile = result.profile guard profile.isProvider else { assertionFailure("Profile \(profile.logDescription) is not a provider") - throw PassepartoutError.missingProfile + throw Passepartout.VPNError.notProvider(profile: profile) } if !result.isReady { try await profileManager.makeProfileReady(profile) @@ -86,7 +86,7 @@ extension VPNManager { let oldServerId = profile.providerServerId guard let newServer = providerManager.server(withId: newServerId) else { pp_log.warning("Server \(newServerId) not found") - throw PassepartoutError.missingProviderServer + throw Passepartout.VPNError.providerServerNotFound(profile: profile) } guard !profileManager.isActiveProfile(profileId) || currentState.vpnStatus != .connected || @@ -104,7 +104,7 @@ extension VPNManager { pp_log.debug("Active profile is current, will reconnect via observation") return profile } - await reconnect(profile) + try await reconnect(profile) return profile } @@ -122,6 +122,6 @@ extension VPNManager { pp_log.debug("Active profile is current, will reinstate via observation") return } - await reinstate(profile) + try await reinstate(profile) } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift index 4754c0e8..67c219a5 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift @@ -60,8 +60,6 @@ public final class VPNManager: ObservableObject { } } - public let configurationError = PassthroughSubject() - // MARK: Internals private var lastProfile: Profile = .placeholder @@ -84,7 +82,7 @@ public final class VPNManager: ObservableObject { currentState = ObservableVPNState() } - func reinstate(_ profile: Profile) async { + func reinstate(_ profile: Profile) async throws { pp_log.info("Reinstating VPN") clearLastError() do { @@ -92,11 +90,11 @@ public final class VPNManager: ObservableObject { await strategy.reinstate(parameters) } catch { pp_log.error("Unable to build configuration: \(error)") - configurationError.send((profile, error)) + throw error } } - func reconnect(_ profile: Profile) async { + func reconnect(_ profile: Profile) async throws { pp_log.info("Reconnecting VPN (with new configuration)") clearLastError() do { @@ -104,7 +102,7 @@ public final class VPNManager: ObservableObject { await strategy.connect(parameters) } catch { pp_log.error("Unable to build configuration: \(error)") - configurationError.send((profile, error)) + throw error } } @@ -151,11 +149,7 @@ extension VPNManager { } private func observeStrategy() { - strategy.observe(into: MutableObservableVPNState(currentState)) { profile, error in - - // UI is certainly interested in configuration errors - self.configurationError.send((profile, error)) - } + strategy.observe(into: MutableObservableVPNState(currentState)) } private func observeProfileManager() { @@ -173,7 +167,11 @@ extension VPNManager { .removeDuplicates() .sink { newProfile in Task { - await self.willUpdateCurrentProfile(newProfile) + do { + try await self.willUpdateCurrentProfile(newProfile) + } catch { + pp_log.error("Unable to apply profile update: \(error)") + } } }.store(in: &cancellables) } @@ -187,7 +185,7 @@ extension VPNManager { pp_log.debug("Active profile: \(newId)") } - private func willUpdateCurrentProfile(_ newProfile: Profile) async { + private func willUpdateCurrentProfile(_ newProfile: Profile) async throws { defer { lastProfile = newProfile } @@ -254,9 +252,9 @@ extension VPNManager { return } if shouldReconnect { - await reconnect(newProfile) + try await reconnect(newProfile) } else { - await reinstate(newProfile) + try await reinstate(newProfile) } } } @@ -267,7 +265,7 @@ private extension VPNManager { func vpnConfigurationParameters(withProfile profile: Profile) throws -> VPNConfigurationParameters { if profile.requiresCredentials { guard !profile.account.isEmpty else { - throw PassepartoutError.missingAccount + throw Passepartout.VPNError.missingAccount(profile: profile) } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeManagerStrategy.swift similarity index 93% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeStrategy.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeManagerStrategy.swift index 4e523469..909a17cb 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeManagerStrategy.swift @@ -1,5 +1,5 @@ // -// UpgradeStrategy.swift +// UpgradeManagerStrategy.swift // Passepartout // // Created by Davide De Rosa on 3/20/22. @@ -26,7 +26,7 @@ import Foundation import PassepartoutCore -public protocol UpgradeStrategy { +public protocol UpgradeManagerStrategy { func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool) func migratedProfilesToV2() -> [Profile] diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift index 4a04c1ac..34eff8ee 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift @@ -29,7 +29,7 @@ import PassepartoutCore import PassepartoutProviders public protocol VPNManagerStrategy { - func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void) + func observe(into state: MutableObservableVPNState) func reinstate(_ parameters: VPNConfigurationParameters) async diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift index 8366ce3f..11deb7ef 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift @@ -30,7 +30,7 @@ import TunnelKitManager import TunnelKitOpenVPN extension Profile.OpenVPNSettings: TunnelKitConfigurationProviding { - func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration { + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) -> TunnelKitVPNConfiguration { var customBuilder = configuration.builder() // tolerate widest range of certificates diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutProviders+TunnelKit.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutProviders+TunnelKit.swift index 3a81fcc6..61c9588b 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutProviders+TunnelKit.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutProviders+TunnelKit.swift @@ -100,7 +100,7 @@ extension OpenVPN.ConfigurationBuilder { } guard !remotes.isEmpty else { pp_log.warning("Excluding hostname but server has no ipAddresses either") - throw PassepartoutError.missingProviderServer + throw Passepartout.VPNError.emptyEndpoints(server: server) } self.remotes = remotes diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift index 9e349ba2..5d4a4518 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift @@ -43,7 +43,7 @@ extension Profile { // infer remotes from preset + server guard let selectedServer = providerServer(providerManager) else { - throw PassepartoutError.missingProviderServer + throw Passepartout.VPNError.providerServerNotFound(profile: self) } let server: ProviderServer if providerRandomizesServer ?? false { @@ -51,14 +51,14 @@ extension Profile { let servers = providerManager.servers(forLocation: location) guard let randomServerId = servers.randomElement()?.id, let randomServer = providerManager.server(withId: randomServerId) else { - throw PassepartoutError.missingProviderServer + throw Passepartout.VPNError.providerServerNotFound(profile: self) } server = randomServer } else { server = selectedServer } guard let preset = providerPreset(server) else { - throw PassepartoutError.missingProviderPreset + throw Passepartout.VPNError.providerPresetNotFound(profile: self) } guard var builder = preset.openVPNConfiguration?.builder() else { fatalError("Preset \(preset.id) has no OpenVPN configuration") diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift index cfe30b1c..0de18222 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore import PassepartoutVPN import TunnelKitOpenVPN import TunnelKitWireGuard @@ -43,7 +44,15 @@ extension ProfileManager { let wg = try WireGuard.Configuration(wgQuickConfig: contents) return Profile(header, configuration: wg) } catch WireGuard.ConfigurationError.invalidLine { - throw ovpnError + switch ovpnError { + case .encryptionPassphrase, .unableToDecrypt: + throw Passepartout.ProfileError.decryptionFailure(error: ovpnError) + + default: + throw Passepartout.ProfileError.importFailure(error: ovpnError) + } + } catch let wgError { + throw Passepartout.ProfileError.importFailure(error: wgError) } } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProviderManager+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProviderManager+Extensions.swift index 01cfd986..2e85f88f 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProviderManager+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProviderManager+Extensions.swift @@ -30,7 +30,7 @@ import PassepartoutProviders import PassepartoutVPN extension ProviderManager { - public func fetchRemoteProviderPublisher(forProfile profile: Profile) -> AnyPublisher { + public func fetchRemoteProviderPublisher(forProfile profile: Profile) -> AnyPublisher { guard let providerName = profile.providerName else { fatalError("Not a provider") } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift index 13914938..a43361a5 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift @@ -30,7 +30,7 @@ import TunnelKitManager import TunnelKitWireGuard extension Profile.WireGuardSettings: TunnelKitConfigurationProviding { - func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration { + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) -> TunnelKitVPNConfiguration { var customBuilder = configuration.builder() // network settings diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeManagerStrategy.swift similarity index 94% rename from PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeStrategy.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeManagerStrategy.swift index 6dcf183f..a8b3f71e 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeManagerStrategy.swift @@ -1,5 +1,5 @@ // -// DefaultUpgradeStrategy.swift +// DefaultUpgradeManagerStrategy.swift // Passepartout // // Created by Davide De Rosa on 3/20/22. @@ -34,14 +34,28 @@ import TunnelKitOpenVPNCore private typealias Map = [String: Any] -public final class DefaultUpgradeStrategy: UpgradeStrategy { +private enum UpgradeError: Error { + case json + + case missingId + + case missingOpenVPNConfiguration + + case missingHostname + + case missingEndpointProtocols + + case missingProviderName +} + +public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy { public init() { } } // MARK: Migrate old store -extension DefaultUpgradeStrategy { +extension DefaultUpgradeManagerStrategy { private enum LegacyStoreKey: String, KeyStoreLocation, CaseIterable { case activeProfileId @@ -94,21 +108,7 @@ extension DefaultUpgradeStrategy { // MARK: Migrate to version 2 -extension DefaultUpgradeStrategy { - fileprivate enum MigrationError: Error { - case json - - case missingId - - case missingOpenVPNConfiguration - - case missingHostname - - case missingEndpointProtocols - - case missingProviderName - } - +extension DefaultUpgradeManagerStrategy { private var appGroup: String { "group.com.algoritmico.Passepartout" } @@ -222,7 +222,7 @@ extension DefaultUpgradeStrategy { // private func migratedV1Profile(_ cs: Map, hostMap: Map, authUserPass: Set) throws -> Profile { guard let oldUUIDString = hostMap["id"] as? String else { - throw MigrationError.missingId + throw UpgradeError.missingId } let name = (cs["hostTitles"] as? Map)?[oldUUIDString] as? String ?? oldUUIDString @@ -230,16 +230,16 @@ extension DefaultUpgradeStrategy { // configuration guard let params = hostMap["parameters"] as? Map else { - throw MigrationError.missingOpenVPNConfiguration + throw UpgradeError.missingOpenVPNConfiguration } guard var ovpn = params["sessionConfiguration"] as? Map else { - throw MigrationError.missingOpenVPNConfiguration + throw UpgradeError.missingOpenVPNConfiguration } guard let hostname = ovpn["hostname"] as? String else { - throw MigrationError.missingHostname + throw UpgradeError.missingHostname } guard let rawEps = ovpn["endpointProtocols"] as? [String] else { - throw MigrationError.missingEndpointProtocols + throw UpgradeError.missingEndpointProtocols } let eps = rawEps.compactMap(EndpointProtocol.init(rawValue:)) ovpn["remotes"] = eps.map { @@ -273,7 +273,7 @@ extension DefaultUpgradeStrategy { // private func migratedV1Profile(_ cs: Map, providerMap: Map) throws -> Profile { guard let name = providerMap["name"] as? String else { - throw MigrationError.missingProviderName + throw UpgradeError.missingProviderName } let header = Profile.Header(name: name, providerName: name) @@ -384,7 +384,7 @@ private extension URL { func asJSON() throws -> Map { let data = try Data(contentsOf: self) guard let json = try JSONSerialization.jsonObject(with: data) as? Map else { - throw DefaultUpgradeStrategy.MigrationError.json + throw UpgradeError.json } return json } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift index 9cc266e4..7ae3f455 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift @@ -67,8 +67,6 @@ public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy private var currentState: MutableObservableVPNState? - private var onConfigurationError: ((Profile, Error) -> Void)? - private let vpnState = CurrentValueSubject(.init()) private var dataCountTimer: AnyCancellable? @@ -121,9 +119,8 @@ public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy // MARK: Actions extension TunnelKitVPNManagerStrategy { - public func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void) { + public func observe(into state: MutableObservableVPNState) { currentState = state - self.onConfigurationError = onConfigurationError // use this to drop redundant NE notifications vpnState @@ -322,7 +319,7 @@ private extension TunnelKitVPNManagerStrategy { } settings = hostSettings } - return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) + return settings.tunnelKitConfiguration(appGroup, parameters: parameters) case .wireGuard: let settings: Profile.WireGuardSettings @@ -334,11 +331,10 @@ private extension TunnelKitVPNManagerStrategy { } settings = hostSettings } - return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) + return settings.tunnelKitConfiguration(appGroup, parameters: parameters) } } catch { pp_log.error("Unable to build TunnelKitVPNConfiguration: \(error)") - onConfigurationError?(profile, error) throw error } }