diff --git a/Library/Sources/AppDataPreferences/AppData+Preferences.swift b/Library/Sources/AppDataPreferences/AppData+Preferences.swift
index 25758ae3..a772a792 100644
--- a/Library/Sources/AppDataPreferences/AppData+Preferences.swift
+++ b/Library/Sources/AppDataPreferences/AppData+Preferences.swift
@@ -28,12 +28,10 @@ import CoreData
import Foundation
extension AppData {
-
- @MainActor
- public static let cdPreferencesModel: NSManagedObjectModel = {
+ public static var cdPreferencesModel: NSManagedObjectModel {
guard let model: NSManagedObjectModel = .mergedModel(from: [.module]) else {
fatalError("Unable to build Core Data model (Preferences v3)")
}
return model
- }()
+ }
}
diff --git a/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift b/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift
index a5c2f4fd..4515b5d9 100644
--- a/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift
+++ b/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift
@@ -33,7 +33,7 @@ public final class ExtendedTunnel: ObservableObject {
private let environment: TunnelEnvironment
- private let processor: TunnelProcessor?
+ private let processor: AppTunnelProcessor?
private let interval: TimeInterval
@@ -56,7 +56,7 @@ public final class ExtendedTunnel: ObservableObject {
public init(
tunnel: Tunnel,
environment: TunnelEnvironment,
- processor: TunnelProcessor? = nil,
+ processor: AppTunnelProcessor? = nil,
interval: TimeInterval
) {
self.tunnel = tunnel
diff --git a/Library/Sources/CommonLibrary/Strategy/InAppProcessor.swift b/Library/Sources/CommonLibrary/Strategy/InAppProcessor.swift
deleted file mode 100644
index bbf88125..00000000
--- a/Library/Sources/CommonLibrary/Strategy/InAppProcessor.swift
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-// InAppProcessor.swift
-// Passepartout
-//
-// Created by Davide De Rosa on 10/6/24.
-// Copyright (c) 2024 Davide De Rosa. All rights reserved.
-//
-// https://github.com/passepartoutvpn
-//
-// This file is part of Passepartout.
-//
-// Passepartout is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Passepartout is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Passepartout. If not, see .
-//
-
-import Foundation
-import PassepartoutKit
-
-public final class InAppProcessor: ObservableObject, Sendable {
- private let iapManager: IAPManager
-
- public nonisolated let _title: (Profile) -> String
-
- private nonisolated let _isIncluded: (IAPManager, Profile) -> Bool
-
- private nonisolated let _preview: (Profile) -> ProfilePreview
-
- private nonisolated let _requiredFeatures: (IAPManager, Profile) -> Set?
-
- private nonisolated let _willRebuild: (IAPManager, Profile.Builder) throws -> Profile.Builder
-
- private nonisolated let _willInstall: (IAPManager, Profile) throws -> Profile
-
- public init(
- iapManager: IAPManager,
- title: @escaping (Profile) -> String,
- isIncluded: @escaping (IAPManager, Profile) -> Bool,
- preview: @escaping (Profile) -> ProfilePreview,
- requiredFeatures: @escaping (IAPManager, Profile) -> Set?,
- willRebuild: @escaping (IAPManager, Profile.Builder) throws -> Profile.Builder,
- willInstall: @escaping (IAPManager, Profile) throws -> Profile
- ) {
- self.iapManager = iapManager
- _title = title
- _isIncluded = isIncluded
- _preview = preview
- _requiredFeatures = requiredFeatures
- _willRebuild = willRebuild
- _willInstall = willInstall
- }
-}
-
-// MARK: - ProfileProcessor
-
-extension InAppProcessor: ProfileProcessor {
- public func title(for profile: Profile) -> String {
- _title(profile)
- }
-
- public func isIncluded(_ profile: Profile) -> Bool {
- _isIncluded(iapManager, profile)
- }
-
- public func preview(from profile: Profile) -> ProfilePreview {
- _preview(profile)
- }
-
- public func requiredFeatures(_ profile: Profile) -> Set? {
- _requiredFeatures(iapManager, profile)
- }
-
- public func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder {
- try _willRebuild(iapManager, builder)
- }
-}
-
-// MARK: - TunnelProcessor
-
-extension InAppProcessor: TunnelProcessor {
- public func willInstall(_ profile: Profile) throws -> Profile {
- try _willInstall(iapManager, profile)
- }
-}
diff --git a/Library/Sources/CommonLibrary/Strategy/ProfileProcessor.swift b/Library/Sources/CommonLibrary/Strategy/Processors.swift
similarity index 79%
rename from Library/Sources/CommonLibrary/Strategy/ProfileProcessor.swift
rename to Library/Sources/CommonLibrary/Strategy/Processors.swift
index 01b8ee97..1aba6bc7 100644
--- a/Library/Sources/CommonLibrary/Strategy/ProfileProcessor.swift
+++ b/Library/Sources/CommonLibrary/Strategy/Processors.swift
@@ -1,5 +1,5 @@
//
-// ProfileProcessor.swift
+// Processors.swift
// Passepartout
//
// Created by Davide De Rosa on 11/20/24.
@@ -26,6 +26,7 @@
import Foundation
import PassepartoutKit
+@MainActor
public protocol ProfileProcessor {
func isIncluded(_ profile: Profile) -> Bool
@@ -35,3 +36,14 @@ public protocol ProfileProcessor {
func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder
}
+
+@MainActor
+public protocol AppTunnelProcessor {
+ func title(for profile: Profile) -> String
+
+ func willInstall(_ profile: Profile) throws -> Profile
+}
+
+public protocol PacketTunnelProcessor {
+ nonisolated func willStart(_ profile: Profile) throws -> Profile
+}
diff --git a/Library/Sources/UILibrary/Previews/AppContext+Previews.swift b/Library/Sources/UILibrary/Previews/AppContext+Previews.swift
index acd8d2d5..2fb95290 100644
--- a/Library/Sources/UILibrary/Previews/AppContext+Previews.swift
+++ b/Library/Sources/UILibrary/Previews/AppContext+Previews.swift
@@ -39,27 +39,7 @@ extension AppContext {
[]
}
)
- let processor = InAppProcessor(
- iapManager: iapManager,
- title: {
- "Passepartout.Mock: \($0.name)"
- },
- isIncluded: { _, _ in
- true
- },
- preview: {
- $0.localizedPreview
- },
- requiredFeatures: { _, _ in
- nil
- },
- willRebuild: { _, builder in
- builder
- },
- willInstall: { _, profile in
- profile
- }
- )
+ let processor = MockAppProcessor(iapManager: iapManager)
let profileManager = {
let profiles: [Profile] = (0..<20)
.reduce(into: []) { list, _ in
diff --git a/Library/Sources/UILibrary/Strategy/MockAppProcessor.swift b/Library/Sources/UILibrary/Strategy/MockAppProcessor.swift
new file mode 100644
index 00000000..6e9bb22e
--- /dev/null
+++ b/Library/Sources/UILibrary/Strategy/MockAppProcessor.swift
@@ -0,0 +1,64 @@
+//
+// MockAppProcessor.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 12/8/24.
+// Copyright (c) 2024 Davide De Rosa. All rights reserved.
+//
+// https://github.com/passepartoutvpn
+//
+// This file is part of Passepartout.
+//
+// Passepartout is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Passepartout is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Passepartout. If not, see .
+//
+
+import CommonLibrary
+import Foundation
+import PassepartoutKit
+
+final class MockAppProcessor {
+ private let iapManager: IAPManager
+
+ init(iapManager: IAPManager) {
+ self.iapManager = iapManager
+ }
+}
+
+extension MockAppProcessor: ProfileProcessor {
+ func isIncluded(_ profile: Profile) -> Bool {
+ true
+ }
+
+ func preview(from profile: Profile) -> ProfilePreview {
+ profile.localizedPreview
+ }
+
+ func requiredFeatures(_ profile: Profile) -> Set? {
+ nil
+ }
+
+ func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder {
+ builder
+ }
+}
+
+extension MockAppProcessor: AppTunnelProcessor {
+ func title(for profile: Profile) -> String {
+ "Passepartout.Mock: \(profile.name)"
+ }
+
+ func willInstall(_ profile: Profile) throws -> Profile {
+ profile
+ }
+}
diff --git a/Library/Tests/CommonLibraryTests/Mock/MockTunnelProcessor.swift b/Library/Tests/CommonLibraryTests/Mock/MockTunnelProcessor.swift
index 08df1a96..24c79d24 100644
--- a/Library/Tests/CommonLibraryTests/Mock/MockTunnelProcessor.swift
+++ b/Library/Tests/CommonLibraryTests/Mock/MockTunnelProcessor.swift
@@ -27,7 +27,7 @@ import CommonLibrary
import Foundation
import PassepartoutKit
-final class MockTunnelProcessor: TunnelProcessor {
+final class MockTunnelProcessor: AppTunnelProcessor {
var titleCount = 0
var willInstallCount = 0
diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj
index 89e3aaf3..d034a1ac 100644
--- a/Passepartout.xcodeproj/project.pbxproj
+++ b/Passepartout.xcodeproj/project.pbxproj
@@ -33,6 +33,8 @@
0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */; };
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; };
0EAD6A1B2CF7F79A00CC1F02 /* ScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */; };
+ 0EAEC8A92D05DB8D001AA50C /* DefaultAppProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */; };
+ 0EAEC8AA2D05DB8D001AA50C /* DefaultTunnelProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */; };
0EB08B982CA46F4900A02591 /* AppPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0EB08B962CA46F4900A02591 /* AppPlist.strings */; };
0EBE80DC2BF55C0E00E36A20 /* TunnelLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0EBE80DB2BF55C0E00E36A20 /* TunnelLibrary */; };
0EC066D12C7DC47600D88A94 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */; platformFilter = ios; };
@@ -161,6 +163,8 @@
0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = ""; };
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = ""; };
0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = ""; };
+ 0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAppProcessor.swift; sourceTree = ""; };
+ 0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTunnelProcessor.swift; sourceTree = ""; };
0EB08B972CA46F4900A02591 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppPlist.strings; sourceTree = ""; };
0EBE80DD2BF55C9100E36A20 /* Library */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Library; sourceTree = ""; };
0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
@@ -341,6 +345,8 @@
children = (
0E6EEEE62CF8CB090076E2B0 /* Testing */,
0EC797402B9378E000C093B7 /* AppContext+Shared.swift */,
+ 0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */,
+ 0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */,
0E8195592CFDA75200CC8FFD /* Dependencies.swift */,
0EC797412B9378E000C093B7 /* Shared.swift */,
0E483E822CE6501100584B32 /* Shared+App.swift */,
@@ -681,6 +687,7 @@
0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */,
0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */,
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */,
+ 0EAEC8A92D05DB8D001AA50C /* DefaultAppProcessor.swift in Sources */,
0EC797422B9378E000C093B7 /* AppContext+Shared.swift in Sources */,
0EE8D7E12CD112C200F6600C /* App+tvOS.swift in Sources */,
0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */,
@@ -729,6 +736,7 @@
buildActionMask = 2147483647;
files = (
0E81955B2CFDA7BF00CC8FFD /* Dependencies.swift in Sources */,
+ 0EAEC8AA2D05DB8D001AA50C /* DefaultTunnelProcessor.swift in Sources */,
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */,
0E483E812CE64D6B00584B32 /* Shared+Tunnel.swift in Sources */,
0EC797442B93790600C093B7 /* Shared.swift in Sources */,
diff --git a/Passepartout/Shared/AppContext+Shared.swift b/Passepartout/Shared/AppContext+Shared.swift
index 66b0cf8e..548adbb5 100644
--- a/Passepartout/Shared/AppContext+Shared.swift
+++ b/Passepartout/Shared/AppContext+Shared.swift
@@ -39,7 +39,7 @@ import UITesting
extension AppContext {
static let shared: AppContext = {
let iapManager: IAPManager = .sharedForApp
- let processor = InAppProcessor.sharedImplementation(with: iapManager) {
+ let processor = DefaultAppProcessor(iapManager: iapManager) {
$0.localizedPreview
}
diff --git a/Passepartout/Shared/DefaultAppProcessor.swift b/Passepartout/Shared/DefaultAppProcessor.swift
new file mode 100644
index 00000000..86c48b68
--- /dev/null
+++ b/Passepartout/Shared/DefaultAppProcessor.swift
@@ -0,0 +1,83 @@
+//
+// DefaultAppProcessor.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 10/6/24.
+// Copyright (c) 2024 Davide De Rosa. All rights reserved.
+//
+// https://github.com/passepartoutvpn
+//
+// This file is part of Passepartout.
+//
+// Passepartout is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Passepartout is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Passepartout. If not, see .
+//
+
+import CommonLibrary
+import Foundation
+import PassepartoutKit
+
+final class DefaultAppProcessor: Sendable {
+ private let iapManager: IAPManager
+
+ private let preview: @Sendable (Profile) -> ProfilePreview
+
+ init(iapManager: IAPManager, preview: @escaping @Sendable (Profile) -> ProfilePreview) {
+ self.iapManager = iapManager
+ self.preview = preview
+ }
+}
+
+extension DefaultAppProcessor: ProfileProcessor {
+ func isIncluded(_ profile: Profile) -> Bool {
+ Dependencies.ProfileManager.isIncluded(iapManager, profile)
+ }
+
+ func preview(from profile: Profile) -> ProfilePreview {
+ preview(profile)
+ }
+
+ func requiredFeatures(_ profile: Profile) -> Set? {
+ do {
+ try iapManager.verify(profile)
+ return nil
+ } catch AppError.ineligibleProfile(let requiredFeatures) {
+ return requiredFeatures
+ } catch {
+ return nil
+ }
+ }
+
+ func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder {
+ builder
+ }
+}
+
+extension DefaultAppProcessor: AppTunnelProcessor {
+ func title(for profile: Profile) -> String {
+ Dependencies.ProfileManager.sharedTitle(profile)
+ }
+
+ func willInstall(_ profile: Profile) throws -> Profile {
+ try iapManager.verify(profile)
+
+ // validate provider modules
+ do {
+ _ = try profile.withProviderModules()
+ return profile
+ } catch {
+ pp_log(.app, .error, "Unable to inject provider modules: \(error)")
+ throw error
+ }
+ }
+}
diff --git a/Library/Sources/CommonLibrary/Strategy/TunnelProcessor.swift b/Passepartout/Shared/DefaultTunnelProcessor.swift
similarity index 66%
rename from Library/Sources/CommonLibrary/Strategy/TunnelProcessor.swift
rename to Passepartout/Shared/DefaultTunnelProcessor.swift
index 73b431fe..14c9b1cd 100644
--- a/Library/Sources/CommonLibrary/Strategy/TunnelProcessor.swift
+++ b/Passepartout/Shared/DefaultTunnelProcessor.swift
@@ -1,8 +1,8 @@
//
-// TunnelProcessor.swift
+// DefaultTunnelProcessor.swift
// Passepartout
//
-// Created by Davide De Rosa on 11/20/24.
+// Created by Davide De Rosa on 12/8/24.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
@@ -23,11 +23,20 @@
// along with Passepartout. If not, see .
//
+import CommonLibrary
import Foundation
import PassepartoutKit
-public protocol TunnelProcessor {
- func title(for profile: Profile) -> String
+final class DefaultTunnelProcessor: Sendable {
+ private let preferencesManager: PreferencesManager
- func willInstall(_ profile: Profile) throws -> Profile
+ init(preferencesManager: PreferencesManager) {
+ self.preferencesManager = preferencesManager
+ }
+}
+
+extension DefaultTunnelProcessor: PacketTunnelProcessor {
+ func willStart(_ profile: Profile) throws -> Profile {
+ profile
+ }
}
diff --git a/Passepartout/Shared/Shared.swift b/Passepartout/Shared/Shared.swift
index 8e2e1c5b..081bf7ed 100644
--- a/Passepartout/Shared/Shared.swift
+++ b/Passepartout/Shared/Shared.swift
@@ -89,51 +89,7 @@ extension TunnelEnvironment where Self == AppGroupEnvironment {
}
}
-extension InAppProcessor {
-
- @MainActor
- static func sharedImplementation(with iapManager: IAPManager, preview: @escaping (Profile) -> ProfilePreview) -> InAppProcessor {
- InAppProcessor(
- iapManager: iapManager,
- title: {
- Dependencies.ProfileManager.sharedTitle($0)
- },
- isIncluded: {
- Dependencies.ProfileManager.isIncluded($0, $1)
- },
- preview: preview,
- requiredFeatures: { iap, profile in
- do {
- try iap.verify(profile)
- return nil
- } catch AppError.ineligibleProfile(let requiredFeatures) {
- return requiredFeatures
- } catch {
- return nil
- }
- },
- willRebuild: { _, builder in
- builder
- },
- willInstall: { iap, profile in
- try iap.verify(profile)
-
- // validate provider modules
- do {
- _ = try profile.withProviderModules()
- return profile
- } catch {
- pp_log(.app, .error, "Unable to inject provider modules: \(error)")
- throw error
- }
- }
- )
- }
-}
-
extension PreferencesManager {
-
- @MainActor
static func sharedImplementation(withCloudKit: Bool) -> PreferencesManager {
let preferencesStore = CoreDataPersistentStore(
logger: .default,
diff --git a/Passepartout/Shared/Testing/AppContext+Testing.swift b/Passepartout/Shared/Testing/AppContext+Testing.swift
index 33f4bc29..7ec9f00f 100644
--- a/Passepartout/Shared/Testing/AppContext+Testing.swift
+++ b/Passepartout/Shared/Testing/AppContext+Testing.swift
@@ -40,7 +40,7 @@ extension AppContext {
[]
}
)
- let processor = InAppProcessor.sharedImplementation(with: iapManager) {
+ let processor = DefaultAppProcessor(iapManager: iapManager) {
$0.localizedPreview
}
diff --git a/Passepartout/Tunnel/PacketTunnelProvider.swift b/Passepartout/Tunnel/PacketTunnelProvider.swift
index 9a165977..af2a9e89 100644
--- a/Passepartout/Tunnel/PacketTunnelProvider.swift
+++ b/Passepartout/Tunnel/PacketTunnelProvider.swift
@@ -36,12 +36,14 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
parameters: Constants.shared.log,
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
)
+ let processor = DefaultTunnelProcessor(preferencesManager: .sharedForTunnel)
do {
fwd = try await NEPTPForwarder(
provider: self,
decoder: Registry.sharedProtocolCoder,
registry: .shared,
- environment: .shared
+ environment: .shared,
+ profileBlock: processor.willStart
)
guard let fwd else {
fatalError("NEPTPForwarder nil without throwing error?")