mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-02-16 04:42:11 +00:00
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
|
||||
|
||||
### Changed
|
||||
|
||||
- Internal error handling. [#310](https://github.com/passepartoutvpn/passepartout-apple/pull/310)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Allow wildcards in proxy bypass domains. [#296](https://github.com/passepartoutvpn/passepartout-apple/issues/296)
|
||||
|
@ -30,6 +30,8 @@
|
||||
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; };
|
||||
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; };
|
||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; };
|
||||
0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */; };
|
||||
0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */; };
|
||||
0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; };
|
||||
0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; };
|
||||
0E1F562B287F0EF100F8ADD7 /* ProviderProfileItem+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */; };
|
||||
@ -50,6 +52,7 @@
|
||||
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; };
|
||||
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
|
||||
0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */; };
|
||||
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */; };
|
||||
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */; };
|
||||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; };
|
||||
0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */; };
|
||||
@ -317,6 +320,8 @@
|
||||
0E0F4C6329C84B5A0022E884 /* LockableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockableView.swift; sourceTree = "<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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -339,6 +344,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -594,6 +600,7 @@
|
||||
children = (
|
||||
0E021D9B284E68580077EF5D /* AppContext.swift */,
|
||||
0E293856285A73BC002A6E0E /* AppContext+Shared.swift */,
|
||||
0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */,
|
||||
0E293850285A70AC002A6E0E /* AppPreference.swift */,
|
||||
);
|
||||
path = Context;
|
||||
@ -608,6 +615,7 @@
|
||||
0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */,
|
||||
0E7577D62816A3B200081CBE /* DestructiveButton.swift */,
|
||||
0EDE02C127F61C79000FBE3C /* EditableTextList.swift */,
|
||||
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */,
|
||||
0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */,
|
||||
0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */,
|
||||
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */,
|
||||
@ -629,6 +637,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */,
|
||||
0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */,
|
||||
0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */,
|
||||
0E71ACE227C0F2E300F85C4B /* Providers+L10n.swift */,
|
||||
0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */,
|
||||
@ -1445,6 +1454,7 @@
|
||||
0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */,
|
||||
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */,
|
||||
0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */,
|
||||
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */,
|
||||
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */,
|
||||
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */,
|
||||
0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */,
|
||||
@ -1539,6 +1549,7 @@
|
||||
0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */,
|
||||
0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */,
|
||||
0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */,
|
||||
0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */,
|
||||
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */,
|
||||
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */,
|
||||
0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */,
|
||||
@ -1556,6 +1567,7 @@
|
||||
0E021D9D284E68580077EF5D /* AppContext.swift in Sources */,
|
||||
0E2DE72527DCDF550067B9E1 /* WireGuard+L10n.swift in Sources */,
|
||||
0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */,
|
||||
0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */,
|
||||
0ED1D6DC27DBA41700983466 /* DiagnosticsView+OpenVPN.swift in Sources */,
|
||||
0ED30DCC27EA197D0057D8A3 /* RevealingSecureField.swift in Sources */,
|
||||
0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */,
|
||||
|
@ -51,8 +51,8 @@
|
||||
"repositoryURL": "https://github.com/passepartoutvpn/tunnelkit",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "8f066a9e4821f041693c8262ba01ef49ab0084ae",
|
||||
"version": "6.0.0"
|
||||
"revision": "729e8973cfbb40330e046439417650e6bf993105",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -48,8 +48,10 @@ final class AppContext {
|
||||
|
||||
configureObjects(coreContext: coreContext)
|
||||
}
|
||||
}
|
||||
|
||||
private func configureObjects(coreContext: CoreContext) {
|
||||
private extension AppContext {
|
||||
func configureObjects(coreContext: CoreContext) {
|
||||
coreContext.vpnManager.isOnDemandRulesSupported = {
|
||||
self.isEligibleForOnDemandRules()
|
||||
}
|
||||
@ -75,7 +77,7 @@ final class AppContext {
|
||||
}
|
||||
|
||||
// eligibility: ignore network settings if ineligible
|
||||
private func isEligibleForNetworkSettings() -> Bool {
|
||||
func isEligibleForNetworkSettings() -> Bool {
|
||||
guard productManager.isEligible(forFeature: .networkSettings) else {
|
||||
pp_log.warning("Ignore network settings, not eligible")
|
||||
return false
|
||||
@ -84,7 +86,7 @@ final class AppContext {
|
||||
}
|
||||
|
||||
// eligibility: reset on-demand rules if no trusted networks
|
||||
private func isEligibleForOnDemandRules() -> Bool {
|
||||
func isEligibleForOnDemandRules() -> Bool {
|
||||
guard productManager.isEligible(forFeature: .trustedNetworks) else {
|
||||
pp_log.warning("Ignore on-demand rules, not eligible for trusted networks")
|
||||
return false
|
||||
|
49
Passepartout/App/Context/AppError.swift
Normal file
49
Passepartout/App/Context/AppError.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -29,12 +29,6 @@ import Kvitto
|
||||
import PassepartoutLibrary
|
||||
import StoreKit
|
||||
|
||||
enum ProductError: Error {
|
||||
case uneligible
|
||||
|
||||
case beta
|
||||
}
|
||||
|
||||
final class ProductManager: NSObject, ObservableObject {
|
||||
enum AppType: Int {
|
||||
case freemium = 0
|
||||
|
@ -29,14 +29,6 @@ import PassepartoutLibrary
|
||||
|
||||
@MainActor
|
||||
extension IntentDispatcher {
|
||||
private enum IntentError: Error {
|
||||
case notProvider(UUID)
|
||||
|
||||
case serverNotFound(UUID)
|
||||
|
||||
case activeAndConnected(UUID)
|
||||
}
|
||||
|
||||
typealias VPNIntentActivity = IntentActivity<VPNManager>
|
||||
|
||||
static let enableVPN = VPNIntentActivity(name: Constants.Activities.enableVPN) { _, vpnManager in
|
||||
@ -47,6 +39,7 @@ extension IntentDispatcher {
|
||||
try await vpnManager.connectWithActiveProfile(toServer: nil)
|
||||
} catch {
|
||||
pp_log.error("Unable to connect with active profile: \(error)")
|
||||
ErrorHandler.shared.handle(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,6 +71,7 @@ extension IntentDispatcher {
|
||||
_ = try await vpnManager.connect(with: profileId)
|
||||
} catch {
|
||||
pp_log.error("Unable to connect with profile \(profileId): \(error)")
|
||||
ErrorHandler.shared.handle(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,6 +101,7 @@ extension IntentDispatcher {
|
||||
_ = try await vpnManager.connect(with: profileId, toServer: newServerId)
|
||||
} catch {
|
||||
pp_log.error("Unable to connect with profile \(profileId): \(error)")
|
||||
ErrorHandler.shared.handle(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,6 +134,7 @@ extension IntentDispatcher {
|
||||
}
|
||||
} catch {
|
||||
pp_log.error("Unable to modify cellular trust: \(error)")
|
||||
ErrorHandler.shared.handle(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,6 +152,7 @@ extension IntentDispatcher {
|
||||
}
|
||||
} catch {
|
||||
pp_log.error("Unable to modify Wi-Fi trust: \(error)")
|
||||
ErrorHandler.shared.handle(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,15 @@ final class DefaultLightProviderManager: LightProviderManager {
|
||||
return
|
||||
}
|
||||
Task {
|
||||
try await providerManager.fetchProviderPublisher(withName: name, vpnProtocol: vpnProtocolType, priority: .remoteThenBundle).async()
|
||||
do {
|
||||
try await providerManager.fetchProviderPublisher(
|
||||
withName: name,
|
||||
vpnProtocol: vpnProtocolType,
|
||||
priority: .remoteThenBundle
|
||||
).async()
|
||||
} catch {
|
||||
ErrorHandler.shared.handle(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ struct PassepartoutApp: App {
|
||||
WindowGroup {
|
||||
MainView()
|
||||
.withoutTitleBar()
|
||||
.withErrorHandler()
|
||||
.onIntentActivity(IntentDispatcher.connectVPN)
|
||||
.onIntentActivity(IntentDispatcher.disableVPN)
|
||||
.onIntentActivity(IntentDispatcher.enableVPN)
|
||||
|
69
Passepartout/App/Reusable/ErrorHandler.swift
Normal file
69
Passepartout/App/Reusable/ErrorHandler.swift
Normal 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())
|
||||
}
|
||||
}
|
@ -185,7 +185,7 @@ extension GenericCreditsView {
|
||||
do {
|
||||
content = try String(contentsOf: url)
|
||||
} catch {
|
||||
content = error.localizedDescription
|
||||
content = AppError(error).localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,11 @@ extension SceneDelegate {
|
||||
switch shortcutItem.type {
|
||||
case ShortcutType.enableVPN.rawValue:
|
||||
Task {
|
||||
try await VPNManager.shared.connectWithActiveProfile(toServer: nil)
|
||||
do {
|
||||
try await VPNManager.shared.connectWithActiveProfile(toServer: nil)
|
||||
} catch {
|
||||
ErrorHandler.shared.handle(error)
|
||||
}
|
||||
}
|
||||
|
||||
case ShortcutType.disableVPN.rawValue:
|
||||
|
@ -26,6 +26,7 @@
|
||||
import PassepartoutLibrary
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
ProfileManager.shared.persist()
|
||||
|
@ -84,16 +84,12 @@ extension AddHostView {
|
||||
try? FileManager.default.removeItem(at: url)
|
||||
}
|
||||
} catch {
|
||||
switch error {
|
||||
case OpenVPN.ConfigurationError.encryptionPassphrase,
|
||||
OpenVPN.ConfigurationError.unableToDecrypt:
|
||||
|
||||
if case Passepartout.ProfileError.decryptionFailure = error {
|
||||
requiresPassphrase = true
|
||||
|
||||
default:
|
||||
} else {
|
||||
requiresPassphrase = false
|
||||
}
|
||||
setMessage(forParsingError: error)
|
||||
setErrorMessage(for: error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,8 +104,12 @@ extension AddHostView {
|
||||
return true
|
||||
}
|
||||
|
||||
private mutating func setMessage(forParsingError error: Error) {
|
||||
errorMessage = error.localizedVPNParsingDescription
|
||||
private mutating func setErrorMessage(for error: Error) {
|
||||
setErrorMessage(AppError(error).localizedDescription)
|
||||
}
|
||||
|
||||
private mutating func setErrorMessage(_ message: String) {
|
||||
errorMessage = message.withTrailingDot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,10 +96,10 @@ extension AddProviderView {
|
||||
) {
|
||||
doSelectProvider(metadata, server)
|
||||
} else {
|
||||
errorMessage = L10n.AddProfile.Provider.Errors.noDefaultServer
|
||||
setErrorMessage(L10n.AddProfile.Provider.Errors.noDefaultServer)
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
setErrorMessage(for: error)
|
||||
}
|
||||
pendingOperation = nil
|
||||
}
|
||||
@ -119,7 +119,7 @@ extension AddProviderView {
|
||||
priority: .remoteThenBundle
|
||||
).async()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
setErrorMessage(for: error)
|
||||
}
|
||||
pendingOperation = nil
|
||||
}
|
||||
@ -128,6 +128,14 @@ extension AddProviderView {
|
||||
func presentPaywall() {
|
||||
isPaywallPresented = true
|
||||
}
|
||||
|
||||
private func setErrorMessage(for error: Error) {
|
||||
setErrorMessage(AppError(error).localizedDescription)
|
||||
}
|
||||
|
||||
private func setErrorMessage(_ message: String) {
|
||||
errorMessage = message.withTrailingDot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,10 +181,6 @@ extension AddProviderView.NameView {
|
||||
profileManager.saveProfile(finalProfile, isActive: nil)
|
||||
return finalProfile
|
||||
}
|
||||
|
||||
private mutating func setMessage(forError error: Error) {
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,14 +31,10 @@ struct DonateView: View {
|
||||
enum AlertType: Identifiable {
|
||||
case thankYou
|
||||
|
||||
case purchaseFailed(Error)
|
||||
|
||||
// XXX: alert ids
|
||||
var id: Int {
|
||||
switch self {
|
||||
case .thankYou: return 1
|
||||
|
||||
case .purchaseFailed: return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,13 +77,6 @@ struct DonateView: View {
|
||||
message: Text(L10n.Donate.Alerts.Purchase.Success.message),
|
||||
dismissButton: .cancel(Text(L10n.Global.Strings.ok))
|
||||
)
|
||||
|
||||
case .purchaseFailed(let error):
|
||||
return Alert(
|
||||
title: Text(L10n.Donate.title),
|
||||
message: Text(L10n.Donate.Alerts.Purchase.Failure.message(error.localizedDescription)),
|
||||
dismissButton: .cancel(Text(L10n.Global.Strings.ok))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +129,10 @@ extension DonateView {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
alertType = .purchaseFailed(error)
|
||||
ErrorHandler.shared.handle(
|
||||
title: L10n.Donate.title,
|
||||
message: L10n.Donate.Alerts.Purchase.Failure.message(AppError(error).localizedDescription)
|
||||
)
|
||||
}
|
||||
pendingDonationIdentifier = nil
|
||||
}
|
||||
|
@ -41,14 +41,10 @@ struct OrganizerView: View {
|
||||
enum AlertType: Identifiable {
|
||||
case subscribeReddit
|
||||
|
||||
case error(String, String)
|
||||
|
||||
// XXX: alert ids
|
||||
var id: Int {
|
||||
switch self {
|
||||
case .subscribeReddit: return 1
|
||||
|
||||
case .error: return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,11 +89,6 @@ struct OrganizerView: View {
|
||||
onCompletion: onHostFileImporterResult
|
||||
).onOpenURL(perform: onOpenURL)
|
||||
.themePrimaryView()
|
||||
|
||||
// VPN configuration error publisher (no need to observe VPNManager)
|
||||
.onReceive(VPNManager.shared.configurationError) {
|
||||
alertType = .error($0.profile.header.name, $0.error.localizedAppDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private var hiddenSceneView: some View {
|
||||
@ -109,6 +100,8 @@ struct OrganizerView: View {
|
||||
}
|
||||
|
||||
extension OrganizerView {
|
||||
|
||||
@MainActor
|
||||
private func onHostFileImporterResult(_ result: Result<[URL], Error>) {
|
||||
switch result {
|
||||
case .success(let urls):
|
||||
@ -116,16 +109,13 @@ extension OrganizerView {
|
||||
assertionFailure("Empty URLs from file importer?")
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
Task {
|
||||
await Task.maybeWait(forMilliseconds: Constants.Delays.xxxPresentFileImporter)
|
||||
addProfileModalType = .addHost(url, false)
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
alertType = .error(
|
||||
L10n.Menu.Contextual.AddProfile.fromFiles,
|
||||
error.localizedDescription
|
||||
)
|
||||
ErrorHandler.shared.handle(error, title: L10n.Menu.Contextual.AddProfile.fromFiles)
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,13 +147,6 @@ extension OrganizerView {
|
||||
didHandleSubreddit = true
|
||||
}
|
||||
)
|
||||
|
||||
case .error(let title, let errorDescription):
|
||||
return Alert(
|
||||
title: Text(title),
|
||||
message: Text(errorDescription),
|
||||
dismissButton: .cancel(Text(L10n.Global.Strings.ok))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,20 +29,6 @@ import SwiftUI
|
||||
|
||||
extension PaywallView {
|
||||
struct PurchaseView: View {
|
||||
private enum AlertType: Identifiable {
|
||||
case purchaseFailed(SKProduct, Error)
|
||||
|
||||
case restoreFailed(Error)
|
||||
|
||||
var id: Int {
|
||||
switch self {
|
||||
case .purchaseFailed: return 1
|
||||
|
||||
case .restoreFailed: return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate enum PurchaseState {
|
||||
case purchasing(SKProduct)
|
||||
|
||||
@ -59,8 +45,6 @@ extension PaywallView {
|
||||
|
||||
private let feature: LocalProduct?
|
||||
|
||||
@State private var alertType: AlertType?
|
||||
|
||||
@State private var purchaseState: PurchaseState?
|
||||
|
||||
init(isPresented: Binding<Bool>, feature: LocalProduct? = nil) {
|
||||
@ -74,7 +58,6 @@ extension PaywallView {
|
||||
productsSection
|
||||
.disabled(purchaseState != nil)
|
||||
}.navigationTitle(Unlocalized.appName)
|
||||
.alert(item: $alertType, content: presentedAlert)
|
||||
|
||||
// reloading
|
||||
.onAppear {
|
||||
@ -86,28 +69,6 @@ extension PaywallView {
|
||||
}.themeAnimation(on: productManager.isRefreshingProducts)
|
||||
}
|
||||
|
||||
private func presentedAlert(_ alertType: AlertType) -> Alert {
|
||||
switch alertType {
|
||||
case .purchaseFailed(let product, let error):
|
||||
return Alert(
|
||||
title: Text(product.localizedTitle),
|
||||
message: Text(error.localizedDescription),
|
||||
dismissButton: .default(Text(L10n.Global.Strings.ok)) {
|
||||
purchaseState = nil
|
||||
}
|
||||
)
|
||||
|
||||
case .restoreFailed(let error):
|
||||
return Alert(
|
||||
title: Text(L10n.Paywall.Items.Restore.title),
|
||||
message: Text(error.localizedDescription),
|
||||
dismissButton: .default(Text(L10n.Global.Strings.ok)) {
|
||||
purchaseState = nil
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var productsSection: some View {
|
||||
Section {
|
||||
if !productManager.isRefreshingProducts {
|
||||
@ -164,7 +125,12 @@ extension PaywallView.PurchaseView {
|
||||
|
||||
case .failure(let error):
|
||||
pp_log.error("Unable to purchase: \(error)")
|
||||
alertType = .purchaseFailed(product, error)
|
||||
ErrorHandler.shared.handle(
|
||||
title: product.localizedTitle,
|
||||
message: AppError(error).localizedDescription
|
||||
) {
|
||||
purchaseState = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,7 +141,12 @@ extension PaywallView.PurchaseView {
|
||||
productManager.restorePurchases {
|
||||
if let error = $0 {
|
||||
pp_log.error("Unable to restore purchases: \(error)")
|
||||
alertType = .restoreFailed(error)
|
||||
ErrorHandler.shared.handle(
|
||||
title: L10n.Paywall.Items.Restore.title,
|
||||
message: AppError(error).localizedDescription
|
||||
) {
|
||||
purchaseState = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
isPresented = false
|
||||
|
@ -98,6 +98,8 @@ struct VPNToggle: View {
|
||||
} catch {
|
||||
pp_log.warning("Unable to connect to profile \(profile.id): \(error)")
|
||||
canToggle = true
|
||||
|
||||
ErrorHandler.shared.handle(error, title: profile.header.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ final class CoreContext {
|
||||
|
||||
upgradeManager = UpgradeManager(
|
||||
store: store,
|
||||
strategy: DefaultUpgradeStrategy()
|
||||
strategy: DefaultUpgradeManagerStrategy()
|
||||
)
|
||||
|
||||
let remoteProvidersStrategy = APIRemoteProvidersStrategy(
|
||||
|
@ -26,46 +26,13 @@
|
||||
import Foundation
|
||||
import PassepartoutLibrary
|
||||
|
||||
extension Error {
|
||||
var localizedAppDescription: String {
|
||||
if let errorDescription = (self as? PassepartoutError)?.localizedAppDescription, !errorDescription.isEmpty {
|
||||
return "\(errorDescription)."
|
||||
}
|
||||
return localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
extension PassepartoutError {
|
||||
var localizedAppDescription: String? {
|
||||
let V = L10n.Global.Errors.self
|
||||
switch self {
|
||||
case .missingProfile:
|
||||
return V.missingProfile
|
||||
|
||||
case .missingAccount:
|
||||
return V.missingAccount
|
||||
|
||||
case .missingProviderServer:
|
||||
return V.missingProviderServer
|
||||
|
||||
case .missingProviderPreset:
|
||||
return V.missingProviderPreset
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ObservableVPNState {
|
||||
func localizedStatusDescription(isActiveProfile: Bool, withErrors: Bool, dataCountIfAvailable: Bool) -> String {
|
||||
guard isActiveProfile && isEnabled else {
|
||||
return L10n.Tunnelkit.Vpn.disabled
|
||||
}
|
||||
if withErrors {
|
||||
if let errorDescription = lastError?.localizedVPNDescription, !errorDescription.isEmpty {
|
||||
return errorDescription
|
||||
}
|
||||
if withErrors, let lastError {
|
||||
return AppError(lastError).localizedDescription
|
||||
}
|
||||
if dataCountIfAvailable, vpnStatus == .connected, let dataCount = dataCount {
|
||||
return dataCount.localizedDescription
|
||||
|
93
Passepartout/AppShared/L10n/Errors+L10n.swift
Normal file
93
Passepartout/AppShared/L10n/Errors+L10n.swift
Normal 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)."
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PassepartoutLibrary
|
||||
import TunnelKitOpenVPN
|
||||
|
||||
extension OpenVPN.Cipher {
|
||||
@ -160,3 +161,77 @@ extension OpenVPN.PullMask {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelKitOpenVPNError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
let V = L10n.Tunnelkit.Errors.Vpn.self
|
||||
switch self {
|
||||
case .socketActivity, .timeout:
|
||||
return V.timeout
|
||||
|
||||
case .dnsFailure:
|
||||
return V.dns
|
||||
|
||||
case .tlsInitialization, .tlsServerVerification, .tlsHandshake:
|
||||
return V.tls
|
||||
|
||||
case .authentication:
|
||||
return V.auth
|
||||
|
||||
case .encryptionInitialization, .encryptionData:
|
||||
return V.encryption
|
||||
|
||||
case .serverCompression, .lzo:
|
||||
return V.compression
|
||||
|
||||
case .networkChanged:
|
||||
return V.network
|
||||
|
||||
case .routing:
|
||||
return V.routing
|
||||
|
||||
case .gatewayUnattainable:
|
||||
return V.gateway
|
||||
|
||||
case .serverShutdown:
|
||||
return V.shutdown
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenVPN.ConfigurationError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
let V = L10n.Tunnelkit.Errors.Openvpn.self
|
||||
switch self {
|
||||
case .encryptionPassphrase:
|
||||
pp_log.error("Could not parse configuration URL: unable to decrypt, passphrase required")
|
||||
return V.passphraseRequired
|
||||
|
||||
case .unableToDecrypt(let error):
|
||||
pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)")
|
||||
return V.decryption
|
||||
|
||||
case .malformed(let option):
|
||||
pp_log.error("Could not parse configuration URL: malformed option, \(option)")
|
||||
return V.malformed(option)
|
||||
|
||||
case .missingConfiguration(let option):
|
||||
pp_log.error("Could not parse configuration URL: missing configuration, \(option)")
|
||||
return V.requiredOption(option)
|
||||
|
||||
case .unsupportedConfiguration(var option):
|
||||
if option.contains("external") {
|
||||
option.append(" (see FAQ)")
|
||||
}
|
||||
pp_log.error("Could not parse configuration URL: unsupported configuration, \(option)")
|
||||
return V.unsupportedOption(option)
|
||||
|
||||
case .continuationPushReply:
|
||||
assertionFailure("This is a server-side configuration parsing error")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,115 +107,3 @@ extension IPv6Settings.Route {
|
||||
"\(destination)/\(prefixLength) -> \(gateway ?? "*")"
|
||||
}
|
||||
}
|
||||
|
||||
extension Error {
|
||||
var localizedVPNDescription: String? {
|
||||
if let ovpnError = self as? OpenVPNProviderError {
|
||||
return ovpnErrorDescription(ovpnError)
|
||||
}
|
||||
if let wgError = self as? WireGuardProviderError {
|
||||
return wgErrorDescription(wgError)
|
||||
}
|
||||
if let neError = self as? NEVPNError {
|
||||
return neErrorDescription(neError)
|
||||
}
|
||||
return localizedDescription
|
||||
}
|
||||
|
||||
private func ovpnErrorDescription(_ error: OpenVPNProviderError) -> String? {
|
||||
let V = L10n.Tunnelkit.Errors.Vpn.self
|
||||
switch error {
|
||||
case .socketActivity, .timeout:
|
||||
return V.timeout
|
||||
|
||||
case .dnsFailure:
|
||||
return V.dns
|
||||
|
||||
case .tlsInitialization, .tlsServerVerification, .tlsHandshake:
|
||||
return V.tls
|
||||
|
||||
case .authentication:
|
||||
return V.auth
|
||||
|
||||
case .encryptionInitialization, .encryptionData:
|
||||
return V.encryption
|
||||
|
||||
case .serverCompression, .lzo:
|
||||
return V.compression
|
||||
|
||||
case .networkChanged:
|
||||
return V.network
|
||||
|
||||
case .routing:
|
||||
return V.routing
|
||||
|
||||
case .gatewayUnattainable:
|
||||
return V.gateway
|
||||
|
||||
case .serverShutdown:
|
||||
return V.shutdown
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func wgErrorDescription(_ error: WireGuardProviderError) -> String? {
|
||||
let V = L10n.Tunnelkit.Errors.Vpn.self
|
||||
switch error {
|
||||
case .dnsResolutionFailure:
|
||||
return V.dns
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func neErrorDescription(_ error: NEVPNError) -> String? {
|
||||
error.localizedDescription.capitalized
|
||||
}
|
||||
}
|
||||
|
||||
extension Error {
|
||||
var localizedVPNParsingDescription: String? {
|
||||
if let ovpnError = self as? OpenVPN.ConfigurationError {
|
||||
return ovpnConfigurationErrorDescription(ovpnError)
|
||||
} else if let wgError = self as? WireGuard.ConfigurationError {
|
||||
return wgConfigurationErrorDescription(wgError)
|
||||
}
|
||||
pp_log.error("Could not parse configuration URL: \(localizedDescription)")
|
||||
return L10n.Tunnelkit.Errors.parsing(localizedDescription)
|
||||
}
|
||||
|
||||
private func ovpnConfigurationErrorDescription(_ error: OpenVPN.ConfigurationError) -> String {
|
||||
let V = L10n.Tunnelkit.Errors.Openvpn.self
|
||||
switch error {
|
||||
case .encryptionPassphrase:
|
||||
pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)")
|
||||
return V.passphraseRequired
|
||||
|
||||
case .unableToDecrypt(let error):
|
||||
pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)")
|
||||
return V.decryption
|
||||
|
||||
case .malformed(let option):
|
||||
pp_log.error("Could not parse configuration URL: malformed option, \(option)")
|
||||
return V.malformed(option)
|
||||
|
||||
case .missingConfiguration(let option):
|
||||
pp_log.error("Could not parse configuration URL: missing configuration, \(option)")
|
||||
return V.requiredOption(option)
|
||||
|
||||
case .unsupportedConfiguration(var option):
|
||||
if option.contains("external") {
|
||||
option.append(" (see FAQ)")
|
||||
}
|
||||
pp_log.error("Could not parse configuration URL: unsupported configuration, \(option)")
|
||||
return V.unsupportedOption(option)
|
||||
}
|
||||
}
|
||||
|
||||
private func wgConfigurationErrorDescription(_ error: WireGuard.ConfigurationError) -> String {
|
||||
error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
@ -24,4 +24,20 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TunnelKitWireGuardCore
|
||||
import TunnelKitWireGuard
|
||||
|
||||
extension TunnelKitWireGuardError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
let V = L10n.Tunnelkit.Errors.Vpn.self
|
||||
switch self {
|
||||
case .dnsResolutionFailure:
|
||||
return V.dns
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WireGuard.ConfigurationError: LocalizedError {
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ let package = Package(
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", from: "6.0.0"),
|
||||
// .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", .revision("ac362f90ef1c8b64fca113be8521312d85248b48")),
|
||||
// .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", from: "6.0.0"),
|
||||
.package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", .revision("729e8973cfbb40330e046439417650e6bf993105")),
|
||||
// .package(name: "TunnelKit", path: "../../tunnelkit"),
|
||||
.package(url: "https://github.com/zoul/generic-json-swift", from: "2.0.0"),
|
||||
.package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver", from: "1.9.0")
|
||||
@ -69,8 +69,8 @@ let package = Package(
|
||||
dependencies: [
|
||||
"PassepartoutProviders",
|
||||
.product(name: "TunnelKit", package: "TunnelKit"),
|
||||
.product(name: "TunnelKitOpenVPN", package: "TunnelKit"), // FIXME: arch, drop this
|
||||
.product(name: "TunnelKitWireGuard", package: "TunnelKit"), // FIXME: arch, drop this
|
||||
.product(name: "TunnelKitOpenVPN", package: "TunnelKit"),
|
||||
.product(name: "TunnelKitWireGuard", package: "TunnelKit"),
|
||||
]),
|
||||
.target(
|
||||
name: "PassepartoutProviders",
|
||||
|
@ -34,15 +34,3 @@ public class Passepartout {
|
||||
|
||||
public var logger: Logger = DefaultLogger()
|
||||
}
|
||||
|
||||
public struct PassepartoutError: Error, Equatable {
|
||||
private let string: String
|
||||
|
||||
public init(_ string: String) {
|
||||
self.string = string
|
||||
}
|
||||
|
||||
public static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.string == rhs.string
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
public func fetchProvidersIndexPublisher(priority: RemoteProvidersPriority) -> AnyPublisher<Void, Error> {
|
||||
public func fetchProvidersIndexPublisher(priority: RemoteProvidersPriority) -> AnyPublisher<Void, Passepartout.ProviderError> {
|
||||
guard !isRateLimited(indexActionName) else {
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
.setFailureType(to: Passepartout.ProviderError.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
@ -101,13 +101,15 @@ public final class ProviderManager: ObservableObject, RateLimited {
|
||||
return savePublisher
|
||||
.map {
|
||||
self.didUpdateProviders.send()
|
||||
}.mapError {
|
||||
.fetchFailure(error: $0)
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: RemoteProvidersPriority) -> AnyPublisher<Void, Error> {
|
||||
public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: RemoteProvidersPriority) -> AnyPublisher<Void, Passepartout.ProviderError> {
|
||||
guard !isRateLimited(providerName) else {
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
.setFailureType(to: Passepartout.ProviderError.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
@ -123,6 +125,8 @@ public final class ProviderManager: ObservableObject, RateLimited {
|
||||
return savePublisher
|
||||
.map {
|
||||
self.didUpdateProviders.send()
|
||||
}.mapError {
|
||||
.fetchFailure(error: $0)
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Errors.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 6/21/22.
|
||||
// Created by Davide De Rosa on 5/30/23.
|
||||
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
@ -25,15 +25,28 @@
|
||||
|
||||
import Foundation
|
||||
import PassepartoutCore
|
||||
import PassepartoutProviders
|
||||
|
||||
extension PassepartoutError {
|
||||
public static let missingProfile = Self("missingProfile")
|
||||
extension Passepartout {
|
||||
public enum ProfileError: Error {
|
||||
case importFailure(error: Error)
|
||||
|
||||
public static let missingAccount = Self("missingAccount")
|
||||
case decryptionFailure(error: Error)
|
||||
|
||||
public static let missingProviderServer = Self("missingProviderServer")
|
||||
case notFound(profileId: UUID)
|
||||
|
||||
public static let missingProviderPreset = Self("missingProviderPreset")
|
||||
case failedToFetchProvider(profileId: UUID, error: Error)
|
||||
}
|
||||
|
||||
public enum VPNError: Error {
|
||||
case notProvider(profile: Profile)
|
||||
|
||||
case providerServerNotFound(profile: Profile)
|
||||
|
||||
case providerPresetNotFound(profile: Profile)
|
||||
|
||||
case missingAccount(profile: Profile)
|
||||
|
||||
case emptyEndpoints(server: ProviderServer)
|
||||
}
|
||||
}
|
||||
|
||||
public typealias VPNConfigurationError = (profile: Profile, error: Error)
|
||||
|
@ -142,7 +142,7 @@ extension ProfileManager {
|
||||
public func liveProfileEx(withId id: UUID) throws -> ProfileEx {
|
||||
guard let profile = liveProfile(withId: id) else {
|
||||
pp_log.error("Profile not found: \(id)")
|
||||
throw PassepartoutError.missingProfile
|
||||
throw Passepartout.ProfileError.notFound(profileId: id)
|
||||
}
|
||||
pp_log.info("Found profile: \(profile.logDescription)")
|
||||
return (profile, isProfileReady(profile))
|
||||
@ -483,7 +483,7 @@ extension ProfileManager {
|
||||
pp_log.info("Finished!")
|
||||
} catch {
|
||||
pp_log.error("Unable to import missing provider: \(error)")
|
||||
throw PassepartoutError.missingProfile
|
||||
throw Passepartout.ProfileError.failedToFetchProvider(profileId: profile.id, error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public final class UpgradeManager: ObservableObject {
|
||||
|
||||
private let store: KeyValueStore
|
||||
|
||||
private let strategy: UpgradeStrategy
|
||||
private let strategy: UpgradeManagerStrategy
|
||||
|
||||
// MARK: State
|
||||
|
||||
@ -41,7 +41,7 @@ public final class UpgradeManager: ObservableObject {
|
||||
|
||||
public init(
|
||||
store: KeyValueStore,
|
||||
strategy: UpgradeStrategy
|
||||
strategy: UpgradeManagerStrategy
|
||||
) {
|
||||
self.store = store
|
||||
self.strategy = strategy
|
||||
|
@ -67,7 +67,7 @@ extension VPNManager {
|
||||
}
|
||||
|
||||
profileManager.activateProfile(profile)
|
||||
await reconnect(profile)
|
||||
try await reconnect(profile)
|
||||
return profile
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ extension VPNManager {
|
||||
var profile = result.profile
|
||||
guard profile.isProvider else {
|
||||
assertionFailure("Profile \(profile.logDescription) is not a provider")
|
||||
throw PassepartoutError.missingProfile
|
||||
throw Passepartout.VPNError.notProvider(profile: profile)
|
||||
}
|
||||
if !result.isReady {
|
||||
try await profileManager.makeProfileReady(profile)
|
||||
@ -86,7 +86,7 @@ extension VPNManager {
|
||||
let oldServerId = profile.providerServerId
|
||||
guard let newServer = providerManager.server(withId: newServerId) else {
|
||||
pp_log.warning("Server \(newServerId) not found")
|
||||
throw PassepartoutError.missingProviderServer
|
||||
throw Passepartout.VPNError.providerServerNotFound(profile: profile)
|
||||
}
|
||||
guard !profileManager.isActiveProfile(profileId) ||
|
||||
currentState.vpnStatus != .connected ||
|
||||
@ -104,7 +104,7 @@ extension VPNManager {
|
||||
pp_log.debug("Active profile is current, will reconnect via observation")
|
||||
return profile
|
||||
}
|
||||
await reconnect(profile)
|
||||
try await reconnect(profile)
|
||||
return profile
|
||||
}
|
||||
|
||||
@ -122,6 +122,6 @@ extension VPNManager {
|
||||
pp_log.debug("Active profile is current, will reinstate via observation")
|
||||
return
|
||||
}
|
||||
await reinstate(profile)
|
||||
try await reinstate(profile)
|
||||
}
|
||||
}
|
||||
|
@ -60,8 +60,6 @@ public final class VPNManager: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
public let configurationError = PassthroughSubject<VPNConfigurationError, Never>()
|
||||
|
||||
// MARK: Internals
|
||||
|
||||
private var lastProfile: Profile = .placeholder
|
||||
@ -84,7 +82,7 @@ public final class VPNManager: ObservableObject {
|
||||
currentState = ObservableVPNState()
|
||||
}
|
||||
|
||||
func reinstate(_ profile: Profile) async {
|
||||
func reinstate(_ profile: Profile) async throws {
|
||||
pp_log.info("Reinstating VPN")
|
||||
clearLastError()
|
||||
do {
|
||||
@ -92,11 +90,11 @@ public final class VPNManager: ObservableObject {
|
||||
await strategy.reinstate(parameters)
|
||||
} catch {
|
||||
pp_log.error("Unable to build configuration: \(error)")
|
||||
configurationError.send((profile, error))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func reconnect(_ profile: Profile) async {
|
||||
func reconnect(_ profile: Profile) async throws {
|
||||
pp_log.info("Reconnecting VPN (with new configuration)")
|
||||
clearLastError()
|
||||
do {
|
||||
@ -104,7 +102,7 @@ public final class VPNManager: ObservableObject {
|
||||
await strategy.connect(parameters)
|
||||
} catch {
|
||||
pp_log.error("Unable to build configuration: \(error)")
|
||||
configurationError.send((profile, error))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,11 +149,7 @@ extension VPNManager {
|
||||
}
|
||||
|
||||
private func observeStrategy() {
|
||||
strategy.observe(into: MutableObservableVPNState(currentState)) { profile, error in
|
||||
|
||||
// UI is certainly interested in configuration errors
|
||||
self.configurationError.send((profile, error))
|
||||
}
|
||||
strategy.observe(into: MutableObservableVPNState(currentState))
|
||||
}
|
||||
|
||||
private func observeProfileManager() {
|
||||
@ -173,7 +167,11 @@ extension VPNManager {
|
||||
.removeDuplicates()
|
||||
.sink { newProfile in
|
||||
Task {
|
||||
await self.willUpdateCurrentProfile(newProfile)
|
||||
do {
|
||||
try await self.willUpdateCurrentProfile(newProfile)
|
||||
} catch {
|
||||
pp_log.error("Unable to apply profile update: \(error)")
|
||||
}
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
}
|
||||
@ -187,7 +185,7 @@ extension VPNManager {
|
||||
pp_log.debug("Active profile: \(newId)")
|
||||
}
|
||||
|
||||
private func willUpdateCurrentProfile(_ newProfile: Profile) async {
|
||||
private func willUpdateCurrentProfile(_ newProfile: Profile) async throws {
|
||||
defer {
|
||||
lastProfile = newProfile
|
||||
}
|
||||
@ -254,9 +252,9 @@ extension VPNManager {
|
||||
return
|
||||
}
|
||||
if shouldReconnect {
|
||||
await reconnect(newProfile)
|
||||
try await reconnect(newProfile)
|
||||
} else {
|
||||
await reinstate(newProfile)
|
||||
try await reinstate(newProfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -267,7 +265,7 @@ private extension VPNManager {
|
||||
func vpnConfigurationParameters(withProfile profile: Profile) throws -> VPNConfigurationParameters {
|
||||
if profile.requiresCredentials {
|
||||
guard !profile.account.isEmpty else {
|
||||
throw PassepartoutError.missingAccount
|
||||
throw Passepartout.VPNError.missingAccount(profile: profile)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// UpgradeStrategy.swift
|
||||
// UpgradeManagerStrategy.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 3/20/22.
|
||||
@ -26,7 +26,7 @@
|
||||
import Foundation
|
||||
import PassepartoutCore
|
||||
|
||||
public protocol UpgradeStrategy {
|
||||
public protocol UpgradeManagerStrategy {
|
||||
func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool)
|
||||
|
||||
func migratedProfilesToV2() -> [Profile]
|
@ -29,7 +29,7 @@ import PassepartoutCore
|
||||
import PassepartoutProviders
|
||||
|
||||
public protocol VPNManagerStrategy {
|
||||
func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void)
|
||||
func observe(into state: MutableObservableVPNState)
|
||||
|
||||
func reinstate(_ parameters: VPNConfigurationParameters) async
|
||||
|
||||
|
@ -30,7 +30,7 @@ import TunnelKitManager
|
||||
import TunnelKitOpenVPN
|
||||
|
||||
extension Profile.OpenVPNSettings: TunnelKitConfigurationProviding {
|
||||
func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration {
|
||||
func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) -> TunnelKitVPNConfiguration {
|
||||
var customBuilder = configuration.builder()
|
||||
|
||||
// tolerate widest range of certificates
|
||||
|
@ -100,7 +100,7 @@ extension OpenVPN.ConfigurationBuilder {
|
||||
}
|
||||
guard !remotes.isEmpty else {
|
||||
pp_log.warning("Excluding hostname but server has no ipAddresses either")
|
||||
throw PassepartoutError.missingProviderServer
|
||||
throw Passepartout.VPNError.emptyEndpoints(server: server)
|
||||
}
|
||||
|
||||
self.remotes = remotes
|
||||
|
@ -43,7 +43,7 @@ extension Profile {
|
||||
|
||||
// infer remotes from preset + server
|
||||
guard let selectedServer = providerServer(providerManager) else {
|
||||
throw PassepartoutError.missingProviderServer
|
||||
throw Passepartout.VPNError.providerServerNotFound(profile: self)
|
||||
}
|
||||
let server: ProviderServer
|
||||
if providerRandomizesServer ?? false {
|
||||
@ -51,14 +51,14 @@ extension Profile {
|
||||
let servers = providerManager.servers(forLocation: location)
|
||||
guard let randomServerId = servers.randomElement()?.id,
|
||||
let randomServer = providerManager.server(withId: randomServerId) else {
|
||||
throw PassepartoutError.missingProviderServer
|
||||
throw Passepartout.VPNError.providerServerNotFound(profile: self)
|
||||
}
|
||||
server = randomServer
|
||||
} else {
|
||||
server = selectedServer
|
||||
}
|
||||
guard let preset = providerPreset(server) else {
|
||||
throw PassepartoutError.missingProviderPreset
|
||||
throw Passepartout.VPNError.providerPresetNotFound(profile: self)
|
||||
}
|
||||
guard var builder = preset.openVPNConfiguration?.builder() else {
|
||||
fatalError("Preset \(preset.id) has no OpenVPN configuration")
|
||||
|
@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PassepartoutCore
|
||||
import PassepartoutVPN
|
||||
import TunnelKitOpenVPN
|
||||
import TunnelKitWireGuard
|
||||
@ -43,7 +44,15 @@ extension ProfileManager {
|
||||
let wg = try WireGuard.Configuration(wgQuickConfig: contents)
|
||||
return Profile(header, configuration: wg)
|
||||
} catch WireGuard.ConfigurationError.invalidLine {
|
||||
throw ovpnError
|
||||
switch ovpnError {
|
||||
case .encryptionPassphrase, .unableToDecrypt:
|
||||
throw Passepartout.ProfileError.decryptionFailure(error: ovpnError)
|
||||
|
||||
default:
|
||||
throw Passepartout.ProfileError.importFailure(error: ovpnError)
|
||||
}
|
||||
} catch let wgError {
|
||||
throw Passepartout.ProfileError.importFailure(error: wgError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import PassepartoutProviders
|
||||
import PassepartoutVPN
|
||||
|
||||
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 {
|
||||
fatalError("Not a provider")
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import TunnelKitManager
|
||||
import TunnelKitWireGuard
|
||||
|
||||
extension Profile.WireGuardSettings: TunnelKitConfigurationProviding {
|
||||
func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration {
|
||||
func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) -> TunnelKitVPNConfiguration {
|
||||
var customBuilder = configuration.builder()
|
||||
|
||||
// network settings
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// DefaultUpgradeStrategy.swift
|
||||
// DefaultUpgradeManagerStrategy.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 3/20/22.
|
||||
@ -34,14 +34,28 @@ import TunnelKitOpenVPNCore
|
||||
|
||||
private typealias Map = [String: Any]
|
||||
|
||||
public final class DefaultUpgradeStrategy: UpgradeStrategy {
|
||||
private enum UpgradeError: Error {
|
||||
case json
|
||||
|
||||
case missingId
|
||||
|
||||
case missingOpenVPNConfiguration
|
||||
|
||||
case missingHostname
|
||||
|
||||
case missingEndpointProtocols
|
||||
|
||||
case missingProviderName
|
||||
}
|
||||
|
||||
public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Migrate old store
|
||||
|
||||
extension DefaultUpgradeStrategy {
|
||||
extension DefaultUpgradeManagerStrategy {
|
||||
private enum LegacyStoreKey: String, KeyStoreLocation, CaseIterable {
|
||||
case activeProfileId
|
||||
|
||||
@ -94,21 +108,7 @@ extension DefaultUpgradeStrategy {
|
||||
|
||||
// MARK: Migrate to version 2
|
||||
|
||||
extension DefaultUpgradeStrategy {
|
||||
fileprivate enum MigrationError: Error {
|
||||
case json
|
||||
|
||||
case missingId
|
||||
|
||||
case missingOpenVPNConfiguration
|
||||
|
||||
case missingHostname
|
||||
|
||||
case missingEndpointProtocols
|
||||
|
||||
case missingProviderName
|
||||
}
|
||||
|
||||
extension DefaultUpgradeManagerStrategy {
|
||||
private var appGroup: String {
|
||||
"group.com.algoritmico.Passepartout"
|
||||
}
|
||||
@ -222,7 +222,7 @@ extension DefaultUpgradeStrategy {
|
||||
//
|
||||
private func migratedV1Profile(_ cs: Map, hostMap: Map, authUserPass: Set<String>) throws -> Profile {
|
||||
guard let oldUUIDString = hostMap["id"] as? String else {
|
||||
throw MigrationError.missingId
|
||||
throw UpgradeError.missingId
|
||||
}
|
||||
|
||||
let name = (cs["hostTitles"] as? Map)?[oldUUIDString] as? String ?? oldUUIDString
|
||||
@ -230,16 +230,16 @@ extension DefaultUpgradeStrategy {
|
||||
|
||||
// configuration
|
||||
guard let params = hostMap["parameters"] as? Map else {
|
||||
throw MigrationError.missingOpenVPNConfiguration
|
||||
throw UpgradeError.missingOpenVPNConfiguration
|
||||
}
|
||||
guard var ovpn = params["sessionConfiguration"] as? Map else {
|
||||
throw MigrationError.missingOpenVPNConfiguration
|
||||
throw UpgradeError.missingOpenVPNConfiguration
|
||||
}
|
||||
guard let hostname = ovpn["hostname"] as? String else {
|
||||
throw MigrationError.missingHostname
|
||||
throw UpgradeError.missingHostname
|
||||
}
|
||||
guard let rawEps = ovpn["endpointProtocols"] as? [String] else {
|
||||
throw MigrationError.missingEndpointProtocols
|
||||
throw UpgradeError.missingEndpointProtocols
|
||||
}
|
||||
let eps = rawEps.compactMap(EndpointProtocol.init(rawValue:))
|
||||
ovpn["remotes"] = eps.map {
|
||||
@ -273,7 +273,7 @@ extension DefaultUpgradeStrategy {
|
||||
//
|
||||
private func migratedV1Profile(_ cs: Map, providerMap: Map) throws -> Profile {
|
||||
guard let name = providerMap["name"] as? String else {
|
||||
throw MigrationError.missingProviderName
|
||||
throw UpgradeError.missingProviderName
|
||||
}
|
||||
|
||||
let header = Profile.Header(name: name, providerName: name)
|
||||
@ -384,7 +384,7 @@ private extension URL {
|
||||
func asJSON() throws -> Map {
|
||||
let data = try Data(contentsOf: self)
|
||||
guard let json = try JSONSerialization.jsonObject(with: data) as? Map else {
|
||||
throw DefaultUpgradeStrategy.MigrationError.json
|
||||
throw UpgradeError.json
|
||||
}
|
||||
return json
|
||||
}
|
@ -67,8 +67,6 @@ public final class TunnelKitVPNManagerStrategy<VPNType: VPN>: VPNManagerStrategy
|
||||
|
||||
private var currentState: MutableObservableVPNState?
|
||||
|
||||
private var onConfigurationError: ((Profile, Error) -> Void)?
|
||||
|
||||
private let vpnState = CurrentValueSubject<AtomicState, Never>(.init())
|
||||
|
||||
private var dataCountTimer: AnyCancellable?
|
||||
@ -121,9 +119,8 @@ public final class TunnelKitVPNManagerStrategy<VPNType: VPN>: VPNManagerStrategy
|
||||
// MARK: Actions
|
||||
|
||||
extension TunnelKitVPNManagerStrategy {
|
||||
public func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void) {
|
||||
public func observe(into state: MutableObservableVPNState) {
|
||||
currentState = state
|
||||
self.onConfigurationError = onConfigurationError
|
||||
|
||||
// use this to drop redundant NE notifications
|
||||
vpnState
|
||||
@ -322,7 +319,7 @@ private extension TunnelKitVPNManagerStrategy {
|
||||
}
|
||||
settings = hostSettings
|
||||
}
|
||||
return try settings.tunnelKitConfiguration(appGroup, parameters: parameters)
|
||||
return settings.tunnelKitConfiguration(appGroup, parameters: parameters)
|
||||
|
||||
case .wireGuard:
|
||||
let settings: Profile.WireGuardSettings
|
||||
@ -334,11 +331,10 @@ private extension TunnelKitVPNManagerStrategy {
|
||||
}
|
||||
settings = hostSettings
|
||||
}
|
||||
return try settings.tunnelKitConfiguration(appGroup, parameters: parameters)
|
||||
return settings.tunnelKitConfiguration(appGroup, parameters: parameters)
|
||||
}
|
||||
} catch {
|
||||
pp_log.error("Unable to build TunnelKitVPNConfiguration: \(error)")
|
||||
onConfigurationError?(profile, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user