Refactor domain errors (#310)
This commit is contained in:
parent
3a06d6c984
commit
278efaf347
|
@ -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)
|
||||||
|
|
|
@ -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 */,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)."
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 typealias VPNConfigurationError = (profile: Profile, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue