Refactor domain errors (#310)

This commit is contained in:
Davide De Rosa 2023-07-02 12:51:50 +02:00 committed by GitHub
parent 3a06d6c984
commit 278efaf347
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 530 additions and 357 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Changed
- Internal error handling. [#310](https://github.com/passepartoutvpn/passepartout-apple/pull/310)
### Fixed ### Fixed
- Allow wildcards in proxy bypass domains. [#296](https://github.com/passepartoutvpn/passepartout-apple/issues/296) - Allow wildcards in proxy bypass domains. [#296](https://github.com/passepartoutvpn/passepartout-apple/issues/296)

View File

@ -30,6 +30,8 @@
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; }; 0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; };
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; }; 0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; };
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.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 */; }; 0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; };
0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.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 */; }; 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 */; }; 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 */; }; 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.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 */; }; 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 */; }; 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 */; }; 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 = "<group>"; }; 0E0F4C6329C84B5A0022E884 /* LockableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockableView.swift; sourceTree = "<group>"; };
0E0F4C6529C84CF60022E884 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; }; 0E0F4C6529C84CF60022E884 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; };
0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = "<group>"; }; 0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = "<group>"; };
0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Errors+L10n.swift"; sourceTree = "<group>"; };
0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = "<group>"; }; 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = "<group>"; };
0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileItem.swift; sourceTree = "<group>"; }; 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileItem.swift; sourceTree = "<group>"; };
@ -339,6 +344,7 @@
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; }; 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; };
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; }; 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; };
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = "<group>"; }; 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = "<group>"; };
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = "<group>"; }; 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = "<group>"; };
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; }; 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Provider.swift"; sourceTree = "<group>"; }; 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Provider.swift"; sourceTree = "<group>"; };
@ -594,6 +600,7 @@
children = ( children = (
0E021D9B284E68580077EF5D /* AppContext.swift */, 0E021D9B284E68580077EF5D /* AppContext.swift */,
0E293856285A73BC002A6E0E /* AppContext+Shared.swift */, 0E293856285A73BC002A6E0E /* AppContext+Shared.swift */,
0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */,
0E293850285A70AC002A6E0E /* AppPreference.swift */, 0E293850285A70AC002A6E0E /* AppPreference.swift */,
); );
path = Context; path = Context;
@ -608,6 +615,7 @@
0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */, 0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */,
0E7577D62816A3B200081CBE /* DestructiveButton.swift */, 0E7577D62816A3B200081CBE /* DestructiveButton.swift */,
0EDE02C127F61C79000FBE3C /* EditableTextList.swift */, 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */,
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */,
0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */, 0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */,
0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */, 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */,
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */, 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */,
@ -629,6 +637,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */, 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */,
0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */,
0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */, 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */,
0E71ACE227C0F2E300F85C4B /* Providers+L10n.swift */, 0E71ACE227C0F2E300F85C4B /* Providers+L10n.swift */,
0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */, 0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */,
@ -1445,6 +1454,7 @@
0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */, 0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */,
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */, 0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */,
0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */, 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */,
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */,
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */, 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */,
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */, 0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */,
0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */, 0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */,
@ -1539,6 +1549,7 @@
0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */, 0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */,
0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */, 0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */,
0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */, 0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */,
0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */,
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */, 0E53249927D26B51002565C3 /* ProductManager.swift in Sources */,
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */, 0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */,
0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */, 0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */,
@ -1556,6 +1567,7 @@
0E021D9D284E68580077EF5D /* AppContext.swift in Sources */, 0E021D9D284E68580077EF5D /* AppContext.swift in Sources */,
0E2DE72527DCDF550067B9E1 /* WireGuard+L10n.swift in Sources */, 0E2DE72527DCDF550067B9E1 /* WireGuard+L10n.swift in Sources */,
0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */, 0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */,
0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */,
0ED1D6DC27DBA41700983466 /* DiagnosticsView+OpenVPN.swift in Sources */, 0ED1D6DC27DBA41700983466 /* DiagnosticsView+OpenVPN.swift in Sources */,
0ED30DCC27EA197D0057D8A3 /* RevealingSecureField.swift in Sources */, 0ED30DCC27EA197D0057D8A3 /* RevealingSecureField.swift in Sources */,
0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */, 0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */,

View File

@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/passepartoutvpn/tunnelkit", "repositoryURL": "https://github.com/passepartoutvpn/tunnelkit",
"state": { "state": {
"branch": null, "branch": null,
"revision": "8f066a9e4821f041693c8262ba01ef49ab0084ae", "revision": "729e8973cfbb40330e046439417650e6bf993105",
"version": "6.0.0" "version": null
} }
}, },
{ {

View File

@ -48,8 +48,10 @@ final class AppContext {
configureObjects(coreContext: coreContext) configureObjects(coreContext: coreContext)
} }
}
private func configureObjects(coreContext: CoreContext) { private extension AppContext {
func configureObjects(coreContext: CoreContext) {
coreContext.vpnManager.isOnDemandRulesSupported = { coreContext.vpnManager.isOnDemandRulesSupported = {
self.isEligibleForOnDemandRules() self.isEligibleForOnDemandRules()
} }
@ -75,7 +77,7 @@ final class AppContext {
} }
// eligibility: ignore network settings if ineligible // eligibility: ignore network settings if ineligible
private func isEligibleForNetworkSettings() -> Bool { func isEligibleForNetworkSettings() -> Bool {
guard productManager.isEligible(forFeature: .networkSettings) else { guard productManager.isEligible(forFeature: .networkSettings) else {
pp_log.warning("Ignore network settings, not eligible") pp_log.warning("Ignore network settings, not eligible")
return false return false
@ -84,7 +86,7 @@ final class AppContext {
} }
// eligibility: reset on-demand rules if no trusted networks // eligibility: reset on-demand rules if no trusted networks
private func isEligibleForOnDemandRules() -> Bool { func isEligibleForOnDemandRules() -> Bool {
guard productManager.isEligible(forFeature: .trustedNetworks) else { guard productManager.isEligible(forFeature: .trustedNetworks) else {
pp_log.warning("Ignore on-demand rules, not eligible for trusted networks") pp_log.warning("Ignore on-demand rules, not eligible for trusted networks")
return false return false

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)
}
}
}

View File

@ -29,12 +29,6 @@ import Kvitto
import PassepartoutLibrary import PassepartoutLibrary
import StoreKit import StoreKit
enum ProductError: Error {
case uneligible
case beta
}
final class ProductManager: NSObject, ObservableObject { final class ProductManager: NSObject, ObservableObject {
enum AppType: Int { enum AppType: Int {
case freemium = 0 case freemium = 0

View File

@ -29,14 +29,6 @@ import PassepartoutLibrary
@MainActor @MainActor
extension IntentDispatcher { extension IntentDispatcher {
private enum IntentError: Error {
case notProvider(UUID)
case serverNotFound(UUID)
case activeAndConnected(UUID)
}
typealias VPNIntentActivity = IntentActivity<VPNManager> typealias VPNIntentActivity = IntentActivity<VPNManager>
static let enableVPN = VPNIntentActivity(name: Constants.Activities.enableVPN) { _, vpnManager in static let enableVPN = VPNIntentActivity(name: Constants.Activities.enableVPN) { _, vpnManager in
@ -47,6 +39,7 @@ extension IntentDispatcher {
try await vpnManager.connectWithActiveProfile(toServer: nil) try await vpnManager.connectWithActiveProfile(toServer: nil)
} catch { } catch {
pp_log.error("Unable to connect with active profile: \(error)") 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) _ = try await vpnManager.connect(with: profileId)
} catch { } catch {
pp_log.error("Unable to connect with profile \(profileId): \(error)") 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) _ = try await vpnManager.connect(with: profileId, toServer: newServerId)
} catch { } catch {
pp_log.error("Unable to connect with profile \(profileId): \(error)") pp_log.error("Unable to connect with profile \(profileId): \(error)")
ErrorHandler.shared.handle(error)
} }
} }
} }
@ -139,6 +134,7 @@ extension IntentDispatcher {
} }
} catch { } catch {
pp_log.error("Unable to modify cellular trust: \(error)") pp_log.error("Unable to modify cellular trust: \(error)")
ErrorHandler.shared.handle(error)
} }
} }
} }
@ -156,6 +152,7 @@ extension IntentDispatcher {
} }
} catch { } catch {
pp_log.error("Unable to modify Wi-Fi trust: \(error)") pp_log.error("Unable to modify Wi-Fi trust: \(error)")
ErrorHandler.shared.handle(error)
} }
} }
} }

View File

@ -111,7 +111,15 @@ final class DefaultLightProviderManager: LightProviderManager {
return return
} }
Task { 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)
}
} }
} }
} }

View File

@ -34,6 +34,7 @@ struct PassepartoutApp: App {
WindowGroup { WindowGroup {
MainView() MainView()
.withoutTitleBar() .withoutTitleBar()
.withErrorHandler()
.onIntentActivity(IntentDispatcher.connectVPN) .onIntentActivity(IntentDispatcher.connectVPN)
.onIntentActivity(IntentDispatcher.disableVPN) .onIntentActivity(IntentDispatcher.disableVPN)
.onIntentActivity(IntentDispatcher.enableVPN) .onIntentActivity(IntentDispatcher.enableVPN)

View File

@ -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())
}
}

View File

@ -185,7 +185,7 @@ extension GenericCreditsView {
do { do {
content = try String(contentsOf: url) content = try String(contentsOf: url)
} catch { } catch {
content = error.localizedDescription content = AppError(error).localizedDescription
} }
} }
} }

View File

@ -56,7 +56,11 @@ extension SceneDelegate {
switch shortcutItem.type { switch shortcutItem.type {
case ShortcutType.enableVPN.rawValue: case ShortcutType.enableVPN.rawValue:
Task { Task {
do {
try await VPNManager.shared.connectWithActiveProfile(toServer: nil) try await VPNManager.shared.connectWithActiveProfile(toServer: nil)
} catch {
ErrorHandler.shared.handle(error)
}
} }
case ShortcutType.disableVPN.rawValue: case ShortcutType.disableVPN.rawValue:

View File

@ -26,6 +26,7 @@
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@MainActor
final class SceneDelegate: UIResponder, UIWindowSceneDelegate { final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidEnterBackground(_ scene: UIScene) { func sceneDidEnterBackground(_ scene: UIScene) {
ProfileManager.shared.persist() ProfileManager.shared.persist()

View File

@ -84,16 +84,12 @@ extension AddHostView {
try? FileManager.default.removeItem(at: url) try? FileManager.default.removeItem(at: url)
} }
} catch { } catch {
switch error { if case Passepartout.ProfileError.decryptionFailure = error {
case OpenVPN.ConfigurationError.encryptionPassphrase,
OpenVPN.ConfigurationError.unableToDecrypt:
requiresPassphrase = true requiresPassphrase = true
} else {
default:
requiresPassphrase = false requiresPassphrase = false
} }
setMessage(forParsingError: error) setErrorMessage(for: error)
} }
} }
@ -108,8 +104,12 @@ extension AddHostView {
return true return true
} }
private mutating func setMessage(forParsingError error: Error) { private mutating func setErrorMessage(for error: Error) {
errorMessage = error.localizedVPNParsingDescription setErrorMessage(AppError(error).localizedDescription)
}
private mutating func setErrorMessage(_ message: String) {
errorMessage = message.withTrailingDot
} }
} }
} }

View File

@ -96,10 +96,10 @@ extension AddProviderView {
) { ) {
doSelectProvider(metadata, server) doSelectProvider(metadata, server)
} else { } else {
errorMessage = L10n.AddProfile.Provider.Errors.noDefaultServer setErrorMessage(L10n.AddProfile.Provider.Errors.noDefaultServer)
} }
} catch { } catch {
errorMessage = error.localizedDescription setErrorMessage(for: error)
} }
pendingOperation = nil pendingOperation = nil
} }
@ -119,7 +119,7 @@ extension AddProviderView {
priority: .remoteThenBundle priority: .remoteThenBundle
).async() ).async()
} catch { } catch {
errorMessage = error.localizedDescription setErrorMessage(for: error)
} }
pendingOperation = nil pendingOperation = nil
} }
@ -128,6 +128,14 @@ extension AddProviderView {
func presentPaywall() { func presentPaywall() {
isPaywallPresented = true 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) profileManager.saveProfile(finalProfile, isActive: nil)
return finalProfile return finalProfile
} }
private mutating func setMessage(forError error: Error) {
errorMessage = error.localizedDescription
}
} }
} }

View File

@ -31,14 +31,10 @@ struct DonateView: View {
enum AlertType: Identifiable { enum AlertType: Identifiable {
case thankYou case thankYou
case purchaseFailed(Error)
// XXX: alert ids // XXX: alert ids
var id: Int { var id: Int {
switch self { switch self {
case .thankYou: return 1 case .thankYou: return 1
case .purchaseFailed: return 2
} }
} }
} }
@ -81,13 +77,6 @@ struct DonateView: View {
message: Text(L10n.Donate.Alerts.Purchase.Success.message), message: Text(L10n.Donate.Alerts.Purchase.Success.message),
dismissButton: .cancel(Text(L10n.Global.Strings.ok)) 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): 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 pendingDonationIdentifier = nil
} }

View File

@ -41,14 +41,10 @@ struct OrganizerView: View {
enum AlertType: Identifiable { enum AlertType: Identifiable {
case subscribeReddit case subscribeReddit
case error(String, String)
// XXX: alert ids // XXX: alert ids
var id: Int { var id: Int {
switch self { switch self {
case .subscribeReddit: return 1 case .subscribeReddit: return 1
case .error: return 2
} }
} }
} }
@ -93,11 +89,6 @@ struct OrganizerView: View {
onCompletion: onHostFileImporterResult onCompletion: onHostFileImporterResult
).onOpenURL(perform: onOpenURL) ).onOpenURL(perform: onOpenURL)
.themePrimaryView() .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 { private var hiddenSceneView: some View {
@ -109,6 +100,8 @@ struct OrganizerView: View {
} }
extension OrganizerView { extension OrganizerView {
@MainActor
private func onHostFileImporterResult(_ result: Result<[URL], Error>) { private func onHostFileImporterResult(_ result: Result<[URL], Error>) {
switch result { switch result {
case .success(let urls): case .success(let urls):
@ -116,16 +109,13 @@ extension OrganizerView {
assertionFailure("Empty URLs from file importer?") assertionFailure("Empty URLs from file importer?")
return return
} }
Task { @MainActor in Task {
await Task.maybeWait(forMilliseconds: Constants.Delays.xxxPresentFileImporter) await Task.maybeWait(forMilliseconds: Constants.Delays.xxxPresentFileImporter)
addProfileModalType = .addHost(url, false) addProfileModalType = .addHost(url, false)
} }
case .failure(let error): case .failure(let error):
alertType = .error( ErrorHandler.shared.handle(error, title: L10n.Menu.Contextual.AddProfile.fromFiles)
L10n.Menu.Contextual.AddProfile.fromFiles,
error.localizedDescription
)
} }
} }
@ -157,13 +147,6 @@ extension OrganizerView {
didHandleSubreddit = true didHandleSubreddit = true
} }
) )
case .error(let title, let errorDescription):
return Alert(
title: Text(title),
message: Text(errorDescription),
dismissButton: .cancel(Text(L10n.Global.Strings.ok))
)
} }
} }
} }

View File

@ -29,20 +29,6 @@ import SwiftUI
extension PaywallView { extension PaywallView {
struct PurchaseView: View { 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 { fileprivate enum PurchaseState {
case purchasing(SKProduct) case purchasing(SKProduct)
@ -59,8 +45,6 @@ extension PaywallView {
private let feature: LocalProduct? private let feature: LocalProduct?
@State private var alertType: AlertType?
@State private var purchaseState: PurchaseState? @State private var purchaseState: PurchaseState?
init(isPresented: Binding<Bool>, feature: LocalProduct? = nil) { init(isPresented: Binding<Bool>, feature: LocalProduct? = nil) {
@ -74,7 +58,6 @@ extension PaywallView {
productsSection productsSection
.disabled(purchaseState != nil) .disabled(purchaseState != nil)
}.navigationTitle(Unlocalized.appName) }.navigationTitle(Unlocalized.appName)
.alert(item: $alertType, content: presentedAlert)
// reloading // reloading
.onAppear { .onAppear {
@ -86,28 +69,6 @@ extension PaywallView {
}.themeAnimation(on: productManager.isRefreshingProducts) }.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 { private var productsSection: some View {
Section { Section {
if !productManager.isRefreshingProducts { if !productManager.isRefreshingProducts {
@ -164,7 +125,12 @@ extension PaywallView.PurchaseView {
case .failure(let error): case .failure(let error):
pp_log.error("Unable to purchase: \(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 { productManager.restorePurchases {
if let error = $0 { if let error = $0 {
pp_log.error("Unable to restore purchases: \(error)") 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 return
} }
isPresented = false isPresented = false

View File

@ -98,6 +98,8 @@ struct VPNToggle: View {
} catch { } catch {
pp_log.warning("Unable to connect to profile \(profile.id): \(error)") pp_log.warning("Unable to connect to profile \(profile.id): \(error)")
canToggle = true canToggle = true
ErrorHandler.shared.handle(error, title: profile.header.name)
} }
} }
} }

View File

@ -76,7 +76,7 @@ final class CoreContext {
upgradeManager = UpgradeManager( upgradeManager = UpgradeManager(
store: store, store: store,
strategy: DefaultUpgradeStrategy() strategy: DefaultUpgradeManagerStrategy()
) )
let remoteProvidersStrategy = APIRemoteProvidersStrategy( let remoteProvidersStrategy = APIRemoteProvidersStrategy(

View File

@ -26,46 +26,13 @@
import Foundation import Foundation
import PassepartoutLibrary 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 { extension ObservableVPNState {
func localizedStatusDescription(isActiveProfile: Bool, withErrors: Bool, dataCountIfAvailable: Bool) -> String { func localizedStatusDescription(isActiveProfile: Bool, withErrors: Bool, dataCountIfAvailable: Bool) -> String {
guard isActiveProfile && isEnabled else { guard isActiveProfile && isEnabled else {
return L10n.Tunnelkit.Vpn.disabled return L10n.Tunnelkit.Vpn.disabled
} }
if withErrors { if withErrors, let lastError {
if let errorDescription = lastError?.localizedVPNDescription, !errorDescription.isEmpty { return AppError(lastError).localizedDescription
return errorDescription
}
} }
if dataCountIfAvailable, vpnStatus == .connected, let dataCount = dataCount { if dataCountIfAvailable, vpnStatus == .connected, let dataCount = dataCount {
return dataCount.localizedDescription return dataCount.localizedDescription

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)."
}
}

View File

@ -24,6 +24,7 @@
// //
import Foundation import Foundation
import PassepartoutLibrary
import TunnelKitOpenVPN import TunnelKitOpenVPN
extension OpenVPN.Cipher { 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
}
}
}

View File

@ -107,115 +107,3 @@ extension IPv6Settings.Route {
"\(destination)/\(prefixLength) -> \(gateway ?? "*")" "\(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
}
}

View File

@ -24,4 +24,20 @@
// //
import Foundation 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 {
}

View File

@ -23,8 +23,8 @@ let package = Package(
dependencies: [ dependencies: [
// Dependencies declare other packages that this package depends on. // Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"), // .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", from: "6.0.0"),
// .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", .revision("ac362f90ef1c8b64fca113be8521312d85248b48")), .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", .revision("729e8973cfbb40330e046439417650e6bf993105")),
// .package(name: "TunnelKit", path: "../../tunnelkit"), // .package(name: "TunnelKit", path: "../../tunnelkit"),
.package(url: "https://github.com/zoul/generic-json-swift", from: "2.0.0"), .package(url: "https://github.com/zoul/generic-json-swift", from: "2.0.0"),
.package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver", from: "1.9.0") .package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver", from: "1.9.0")
@ -69,8 +69,8 @@ let package = Package(
dependencies: [ dependencies: [
"PassepartoutProviders", "PassepartoutProviders",
.product(name: "TunnelKit", package: "TunnelKit"), .product(name: "TunnelKit", package: "TunnelKit"),
.product(name: "TunnelKitOpenVPN", package: "TunnelKit"), // FIXME: arch, drop this .product(name: "TunnelKitOpenVPN", package: "TunnelKit"),
.product(name: "TunnelKitWireGuard", package: "TunnelKit"), // FIXME: arch, drop this .product(name: "TunnelKitWireGuard", package: "TunnelKit"),
]), ]),
.target( .target(
name: "PassepartoutProviders", name: "PassepartoutProviders",

View File

@ -34,15 +34,3 @@ public class Passepartout {
public var logger: Logger = DefaultLogger() 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
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//
import Foundation
import PassepartoutCore
extension Passepartout {
public enum ProviderError: Error {
case fetchFailure(error: Error)
}
}

View File

@ -88,10 +88,10 @@ public final class ProviderManager: ObservableObject, RateLimited {
// MARK: Modification // MARK: Modification
public func fetchProvidersIndexPublisher(priority: RemoteProvidersPriority) -> AnyPublisher<Void, Error> { public func fetchProvidersIndexPublisher(priority: RemoteProvidersPriority) -> AnyPublisher<Void, Passepartout.ProviderError> {
guard !isRateLimited(indexActionName) else { guard !isRateLimited(indexActionName) else {
return Just(()) return Just(())
.setFailureType(to: Error.self) .setFailureType(to: Passepartout.ProviderError.self)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -101,13 +101,15 @@ public final class ProviderManager: ObservableObject, RateLimited {
return savePublisher return savePublisher
.map { .map {
self.didUpdateProviders.send() self.didUpdateProviders.send()
}.mapError {
.fetchFailure(error: $0)
}.eraseToAnyPublisher() }.eraseToAnyPublisher()
} }
public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: RemoteProvidersPriority) -> AnyPublisher<Void, Error> { public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: RemoteProvidersPriority) -> AnyPublisher<Void, Passepartout.ProviderError> {
guard !isRateLimited(providerName) else { guard !isRateLimited(providerName) else {
return Just(()) return Just(())
.setFailureType(to: Error.self) .setFailureType(to: Passepartout.ProviderError.self)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -123,6 +125,8 @@ public final class ProviderManager: ObservableObject, RateLimited {
return savePublisher return savePublisher
.map { .map {
self.didUpdateProviders.send() self.didUpdateProviders.send()
}.mapError {
.fetchFailure(error: $0)
}.eraseToAnyPublisher() }.eraseToAnyPublisher()
} }

View File

@ -2,7 +2,7 @@
// Errors.swift // Errors.swift
// Passepartout // 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. // Copyright (c) 2023 Davide De Rosa. All rights reserved.
// //
// https://github.com/passepartoutvpn // https://github.com/passepartoutvpn
@ -25,15 +25,28 @@
import Foundation import Foundation
import PassepartoutCore import PassepartoutCore
import PassepartoutProviders
extension PassepartoutError { extension Passepartout {
public static let missingProfile = Self("missingProfile") 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)

View File

@ -142,7 +142,7 @@ extension ProfileManager {
public func liveProfileEx(withId id: UUID) throws -> ProfileEx { public func liveProfileEx(withId id: UUID) throws -> ProfileEx {
guard let profile = liveProfile(withId: id) else { guard let profile = liveProfile(withId: id) else {
pp_log.error("Profile not found: \(id)") pp_log.error("Profile not found: \(id)")
throw PassepartoutError.missingProfile throw Passepartout.ProfileError.notFound(profileId: id)
} }
pp_log.info("Found profile: \(profile.logDescription)") pp_log.info("Found profile: \(profile.logDescription)")
return (profile, isProfileReady(profile)) return (profile, isProfileReady(profile))
@ -483,7 +483,7 @@ extension ProfileManager {
pp_log.info("Finished!") pp_log.info("Finished!")
} catch { } catch {
pp_log.error("Unable to import missing provider: \(error)") pp_log.error("Unable to import missing provider: \(error)")
throw PassepartoutError.missingProfile throw Passepartout.ProfileError.failedToFetchProvider(profileId: profile.id, error: error)
} }
} }
} }

View File

@ -33,7 +33,7 @@ public final class UpgradeManager: ObservableObject {
private let store: KeyValueStore private let store: KeyValueStore
private let strategy: UpgradeStrategy private let strategy: UpgradeManagerStrategy
// MARK: State // MARK: State
@ -41,7 +41,7 @@ public final class UpgradeManager: ObservableObject {
public init( public init(
store: KeyValueStore, store: KeyValueStore,
strategy: UpgradeStrategy strategy: UpgradeManagerStrategy
) { ) {
self.store = store self.store = store
self.strategy = strategy self.strategy = strategy

View File

@ -67,7 +67,7 @@ extension VPNManager {
} }
profileManager.activateProfile(profile) profileManager.activateProfile(profile)
await reconnect(profile) try await reconnect(profile)
return profile return profile
} }
@ -77,7 +77,7 @@ extension VPNManager {
var profile = result.profile var profile = result.profile
guard profile.isProvider else { guard profile.isProvider else {
assertionFailure("Profile \(profile.logDescription) is not a provider") assertionFailure("Profile \(profile.logDescription) is not a provider")
throw PassepartoutError.missingProfile throw Passepartout.VPNError.notProvider(profile: profile)
} }
if !result.isReady { if !result.isReady {
try await profileManager.makeProfileReady(profile) try await profileManager.makeProfileReady(profile)
@ -86,7 +86,7 @@ extension VPNManager {
let oldServerId = profile.providerServerId let oldServerId = profile.providerServerId
guard let newServer = providerManager.server(withId: newServerId) else { guard let newServer = providerManager.server(withId: newServerId) else {
pp_log.warning("Server \(newServerId) not found") pp_log.warning("Server \(newServerId) not found")
throw PassepartoutError.missingProviderServer throw Passepartout.VPNError.providerServerNotFound(profile: profile)
} }
guard !profileManager.isActiveProfile(profileId) || guard !profileManager.isActiveProfile(profileId) ||
currentState.vpnStatus != .connected || currentState.vpnStatus != .connected ||
@ -104,7 +104,7 @@ extension VPNManager {
pp_log.debug("Active profile is current, will reconnect via observation") pp_log.debug("Active profile is current, will reconnect via observation")
return profile return profile
} }
await reconnect(profile) try await reconnect(profile)
return profile return profile
} }
@ -122,6 +122,6 @@ extension VPNManager {
pp_log.debug("Active profile is current, will reinstate via observation") pp_log.debug("Active profile is current, will reinstate via observation")
return return
} }
await reinstate(profile) try await reinstate(profile)
} }
} }

View File

@ -60,8 +60,6 @@ public final class VPNManager: ObservableObject {
} }
} }
public let configurationError = PassthroughSubject<VPNConfigurationError, Never>()
// MARK: Internals // MARK: Internals
private var lastProfile: Profile = .placeholder private var lastProfile: Profile = .placeholder
@ -84,7 +82,7 @@ public final class VPNManager: ObservableObject {
currentState = ObservableVPNState() currentState = ObservableVPNState()
} }
func reinstate(_ profile: Profile) async { func reinstate(_ profile: Profile) async throws {
pp_log.info("Reinstating VPN") pp_log.info("Reinstating VPN")
clearLastError() clearLastError()
do { do {
@ -92,11 +90,11 @@ public final class VPNManager: ObservableObject {
await strategy.reinstate(parameters) await strategy.reinstate(parameters)
} catch { } catch {
pp_log.error("Unable to build configuration: \(error)") 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)") pp_log.info("Reconnecting VPN (with new configuration)")
clearLastError() clearLastError()
do { do {
@ -104,7 +102,7 @@ public final class VPNManager: ObservableObject {
await strategy.connect(parameters) await strategy.connect(parameters)
} catch { } catch {
pp_log.error("Unable to build configuration: \(error)") pp_log.error("Unable to build configuration: \(error)")
configurationError.send((profile, error)) throw error
} }
} }
@ -151,11 +149,7 @@ extension VPNManager {
} }
private func observeStrategy() { private func observeStrategy() {
strategy.observe(into: MutableObservableVPNState(currentState)) { profile, error in strategy.observe(into: MutableObservableVPNState(currentState))
// UI is certainly interested in configuration errors
self.configurationError.send((profile, error))
}
} }
private func observeProfileManager() { private func observeProfileManager() {
@ -173,7 +167,11 @@ extension VPNManager {
.removeDuplicates() .removeDuplicates()
.sink { newProfile in .sink { newProfile in
Task { Task {
await self.willUpdateCurrentProfile(newProfile) do {
try await self.willUpdateCurrentProfile(newProfile)
} catch {
pp_log.error("Unable to apply profile update: \(error)")
}
} }
}.store(in: &cancellables) }.store(in: &cancellables)
} }
@ -187,7 +185,7 @@ extension VPNManager {
pp_log.debug("Active profile: \(newId)") pp_log.debug("Active profile: \(newId)")
} }
private func willUpdateCurrentProfile(_ newProfile: Profile) async { private func willUpdateCurrentProfile(_ newProfile: Profile) async throws {
defer { defer {
lastProfile = newProfile lastProfile = newProfile
} }
@ -254,9 +252,9 @@ extension VPNManager {
return return
} }
if shouldReconnect { if shouldReconnect {
await reconnect(newProfile) try await reconnect(newProfile)
} else { } else {
await reinstate(newProfile) try await reinstate(newProfile)
} }
} }
} }
@ -267,7 +265,7 @@ private extension VPNManager {
func vpnConfigurationParameters(withProfile profile: Profile) throws -> VPNConfigurationParameters { func vpnConfigurationParameters(withProfile profile: Profile) throws -> VPNConfigurationParameters {
if profile.requiresCredentials { if profile.requiresCredentials {
guard !profile.account.isEmpty else { guard !profile.account.isEmpty else {
throw PassepartoutError.missingAccount throw Passepartout.VPNError.missingAccount(profile: profile)
} }
} }

View File

@ -1,5 +1,5 @@
// //
// UpgradeStrategy.swift // UpgradeManagerStrategy.swift
// Passepartout // Passepartout
// //
// Created by Davide De Rosa on 3/20/22. // Created by Davide De Rosa on 3/20/22.
@ -26,7 +26,7 @@
import Foundation import Foundation
import PassepartoutCore import PassepartoutCore
public protocol UpgradeStrategy { public protocol UpgradeManagerStrategy {
func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool) func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool)
func migratedProfilesToV2() -> [Profile] func migratedProfilesToV2() -> [Profile]

View File

@ -29,7 +29,7 @@ import PassepartoutCore
import PassepartoutProviders import PassepartoutProviders
public protocol VPNManagerStrategy { public protocol VPNManagerStrategy {
func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void) func observe(into state: MutableObservableVPNState)
func reinstate(_ parameters: VPNConfigurationParameters) async func reinstate(_ parameters: VPNConfigurationParameters) async

View File

@ -30,7 +30,7 @@ import TunnelKitManager
import TunnelKitOpenVPN import TunnelKitOpenVPN
extension Profile.OpenVPNSettings: TunnelKitConfigurationProviding { extension Profile.OpenVPNSettings: TunnelKitConfigurationProviding {
func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration { func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) -> TunnelKitVPNConfiguration {
var customBuilder = configuration.builder() var customBuilder = configuration.builder()
// tolerate widest range of certificates // tolerate widest range of certificates

View File

@ -100,7 +100,7 @@ extension OpenVPN.ConfigurationBuilder {
} }
guard !remotes.isEmpty else { guard !remotes.isEmpty else {
pp_log.warning("Excluding hostname but server has no ipAddresses either") pp_log.warning("Excluding hostname but server has no ipAddresses either")
throw PassepartoutError.missingProviderServer throw Passepartout.VPNError.emptyEndpoints(server: server)
} }
self.remotes = remotes self.remotes = remotes

View File

@ -43,7 +43,7 @@ extension Profile {
// infer remotes from preset + server // infer remotes from preset + server
guard let selectedServer = providerServer(providerManager) else { guard let selectedServer = providerServer(providerManager) else {
throw PassepartoutError.missingProviderServer throw Passepartout.VPNError.providerServerNotFound(profile: self)
} }
let server: ProviderServer let server: ProviderServer
if providerRandomizesServer ?? false { if providerRandomizesServer ?? false {
@ -51,14 +51,14 @@ extension Profile {
let servers = providerManager.servers(forLocation: location) let servers = providerManager.servers(forLocation: location)
guard let randomServerId = servers.randomElement()?.id, guard let randomServerId = servers.randomElement()?.id,
let randomServer = providerManager.server(withId: randomServerId) else { let randomServer = providerManager.server(withId: randomServerId) else {
throw PassepartoutError.missingProviderServer throw Passepartout.VPNError.providerServerNotFound(profile: self)
} }
server = randomServer server = randomServer
} else { } else {
server = selectedServer server = selectedServer
} }
guard let preset = providerPreset(server) else { guard let preset = providerPreset(server) else {
throw PassepartoutError.missingProviderPreset throw Passepartout.VPNError.providerPresetNotFound(profile: self)
} }
guard var builder = preset.openVPNConfiguration?.builder() else { guard var builder = preset.openVPNConfiguration?.builder() else {
fatalError("Preset \(preset.id) has no OpenVPN configuration") fatalError("Preset \(preset.id) has no OpenVPN configuration")

View File

@ -24,6 +24,7 @@
// //
import Foundation import Foundation
import PassepartoutCore
import PassepartoutVPN import PassepartoutVPN
import TunnelKitOpenVPN import TunnelKitOpenVPN
import TunnelKitWireGuard import TunnelKitWireGuard
@ -43,7 +44,15 @@ extension ProfileManager {
let wg = try WireGuard.Configuration(wgQuickConfig: contents) let wg = try WireGuard.Configuration(wgQuickConfig: contents)
return Profile(header, configuration: wg) return Profile(header, configuration: wg)
} catch WireGuard.ConfigurationError.invalidLine { } 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)
} }
} }
} }

View File

@ -30,7 +30,7 @@ import PassepartoutProviders
import PassepartoutVPN import PassepartoutVPN
extension ProviderManager { extension ProviderManager {
public func fetchRemoteProviderPublisher(forProfile profile: Profile) -> AnyPublisher<Void, Error> { public func fetchRemoteProviderPublisher(forProfile profile: Profile) -> AnyPublisher<Void, Passepartout.ProviderError> {
guard let providerName = profile.providerName else { guard let providerName = profile.providerName else {
fatalError("Not a provider") fatalError("Not a provider")
} }

View File

@ -30,7 +30,7 @@ import TunnelKitManager
import TunnelKitWireGuard import TunnelKitWireGuard
extension Profile.WireGuardSettings: TunnelKitConfigurationProviding { extension Profile.WireGuardSettings: TunnelKitConfigurationProviding {
func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration { func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) -> TunnelKitVPNConfiguration {
var customBuilder = configuration.builder() var customBuilder = configuration.builder()
// network settings // network settings

View File

@ -1,5 +1,5 @@
// //
// DefaultUpgradeStrategy.swift // DefaultUpgradeManagerStrategy.swift
// Passepartout // Passepartout
// //
// Created by Davide De Rosa on 3/20/22. // Created by Davide De Rosa on 3/20/22.
@ -34,14 +34,28 @@ import TunnelKitOpenVPNCore
private typealias Map = [String: Any] 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() { public init() {
} }
} }
// MARK: Migrate old store // MARK: Migrate old store
extension DefaultUpgradeStrategy { extension DefaultUpgradeManagerStrategy {
private enum LegacyStoreKey: String, KeyStoreLocation, CaseIterable { private enum LegacyStoreKey: String, KeyStoreLocation, CaseIterable {
case activeProfileId case activeProfileId
@ -94,21 +108,7 @@ extension DefaultUpgradeStrategy {
// MARK: Migrate to version 2 // MARK: Migrate to version 2
extension DefaultUpgradeStrategy { extension DefaultUpgradeManagerStrategy {
fileprivate enum MigrationError: Error {
case json
case missingId
case missingOpenVPNConfiguration
case missingHostname
case missingEndpointProtocols
case missingProviderName
}
private var appGroup: String { private var appGroup: String {
"group.com.algoritmico.Passepartout" "group.com.algoritmico.Passepartout"
} }
@ -222,7 +222,7 @@ extension DefaultUpgradeStrategy {
// //
private func migratedV1Profile(_ cs: Map, hostMap: Map, authUserPass: Set<String>) throws -> Profile { private func migratedV1Profile(_ cs: Map, hostMap: Map, authUserPass: Set<String>) throws -> Profile {
guard let oldUUIDString = hostMap["id"] as? String else { guard let oldUUIDString = hostMap["id"] as? String else {
throw MigrationError.missingId throw UpgradeError.missingId
} }
let name = (cs["hostTitles"] as? Map)?[oldUUIDString] as? String ?? oldUUIDString let name = (cs["hostTitles"] as? Map)?[oldUUIDString] as? String ?? oldUUIDString
@ -230,16 +230,16 @@ extension DefaultUpgradeStrategy {
// configuration // configuration
guard let params = hostMap["parameters"] as? Map else { guard let params = hostMap["parameters"] as? Map else {
throw MigrationError.missingOpenVPNConfiguration throw UpgradeError.missingOpenVPNConfiguration
} }
guard var ovpn = params["sessionConfiguration"] as? Map else { guard var ovpn = params["sessionConfiguration"] as? Map else {
throw MigrationError.missingOpenVPNConfiguration throw UpgradeError.missingOpenVPNConfiguration
} }
guard let hostname = ovpn["hostname"] as? String else { guard let hostname = ovpn["hostname"] as? String else {
throw MigrationError.missingHostname throw UpgradeError.missingHostname
} }
guard let rawEps = ovpn["endpointProtocols"] as? [String] else { guard let rawEps = ovpn["endpointProtocols"] as? [String] else {
throw MigrationError.missingEndpointProtocols throw UpgradeError.missingEndpointProtocols
} }
let eps = rawEps.compactMap(EndpointProtocol.init(rawValue:)) let eps = rawEps.compactMap(EndpointProtocol.init(rawValue:))
ovpn["remotes"] = eps.map { ovpn["remotes"] = eps.map {
@ -273,7 +273,7 @@ extension DefaultUpgradeStrategy {
// //
private func migratedV1Profile(_ cs: Map, providerMap: Map) throws -> Profile { private func migratedV1Profile(_ cs: Map, providerMap: Map) throws -> Profile {
guard let name = providerMap["name"] as? String else { guard let name = providerMap["name"] as? String else {
throw MigrationError.missingProviderName throw UpgradeError.missingProviderName
} }
let header = Profile.Header(name: name, providerName: name) let header = Profile.Header(name: name, providerName: name)
@ -384,7 +384,7 @@ private extension URL {
func asJSON() throws -> Map { func asJSON() throws -> Map {
let data = try Data(contentsOf: self) let data = try Data(contentsOf: self)
guard let json = try JSONSerialization.jsonObject(with: data) as? Map else { guard let json = try JSONSerialization.jsonObject(with: data) as? Map else {
throw DefaultUpgradeStrategy.MigrationError.json throw UpgradeError.json
} }
return json return json
} }

View File

@ -67,8 +67,6 @@ public final class TunnelKitVPNManagerStrategy<VPNType: VPN>: VPNManagerStrategy
private var currentState: MutableObservableVPNState? private var currentState: MutableObservableVPNState?
private var onConfigurationError: ((Profile, Error) -> Void)?
private let vpnState = CurrentValueSubject<AtomicState, Never>(.init()) private let vpnState = CurrentValueSubject<AtomicState, Never>(.init())
private var dataCountTimer: AnyCancellable? private var dataCountTimer: AnyCancellable?
@ -121,9 +119,8 @@ public final class TunnelKitVPNManagerStrategy<VPNType: VPN>: VPNManagerStrategy
// MARK: Actions // MARK: Actions
extension TunnelKitVPNManagerStrategy { extension TunnelKitVPNManagerStrategy {
public func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void) { public func observe(into state: MutableObservableVPNState) {
currentState = state currentState = state
self.onConfigurationError = onConfigurationError
// use this to drop redundant NE notifications // use this to drop redundant NE notifications
vpnState vpnState
@ -322,7 +319,7 @@ private extension TunnelKitVPNManagerStrategy {
} }
settings = hostSettings settings = hostSettings
} }
return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) return settings.tunnelKitConfiguration(appGroup, parameters: parameters)
case .wireGuard: case .wireGuard:
let settings: Profile.WireGuardSettings let settings: Profile.WireGuardSettings
@ -334,11 +331,10 @@ private extension TunnelKitVPNManagerStrategy {
} }
settings = hostSettings settings = hostSettings
} }
return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) return settings.tunnelKitConfiguration(appGroup, parameters: parameters)
} }
} catch { } catch {
pp_log.error("Unable to build TunnelKitVPNConfiguration: \(error)") pp_log.error("Unable to build TunnelKitVPNConfiguration: \(error)")
onConfigurationError?(profile, error)
throw error throw error
} }
} }