Separate App/Tunnel responsibilities (#984)

Sort out the increasing mess coming from:

- AppContext*
- Dependencies
- Shared*

by doing the following:

- Keep in the "Shared" folder only the entities actually shared by
App/Tunnel
  - Create TunnelContext
  - Move AppContext and related to the App/Context folder
  - Move TunnelContext and related to the Tunnel/Context folder
- Delete Shared+* extensions, use AppContext/TunnelContext singletons
from the app
- Create a Dependencies factory singleton to create entities in a single
place
  - Split extensions by domain
- Make it clear with `func` vs `var` when a dependency method returns a
new instance
This commit is contained in:
Davide 2024-12-08 18:56:39 +01:00 committed by GitHub
parent aac04c4008
commit f7013a98a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 647 additions and 510 deletions

View File

@ -27,7 +27,7 @@ import Foundation
import PassepartoutKit import PassepartoutKit
@MainActor @MainActor
public protocol ProfileProcessor { public protocol ProfileProcessor: Sendable {
func isIncluded(_ profile: Profile) -> Bool func isIncluded(_ profile: Profile) -> Bool
func preview(from profile: Profile) -> ProfilePreview func preview(from profile: Profile) -> ProfilePreview
@ -38,12 +38,12 @@ public protocol ProfileProcessor {
} }
@MainActor @MainActor
public protocol AppTunnelProcessor { public protocol AppTunnelProcessor: Sendable {
func title(for profile: Profile) -> String func title(for profile: Profile) -> String
func willInstall(_ profile: Profile) throws -> Profile func willInstall(_ profile: Profile) throws -> Profile
} }
public protocol PacketTunnelProcessor { public protocol PacketTunnelProcessor: Sendable {
nonisolated func willStart(_ profile: Profile) throws -> Profile nonisolated func willStart(_ profile: Profile) throws -> Profile
} }

View File

@ -31,7 +31,7 @@ import PassepartoutKit
import UITesting import UITesting
@MainActor @MainActor
public final class AppContext: ObservableObject { public final class AppContext: ObservableObject, Sendable {
public let iapManager: IAPManager public let iapManager: IAPManager
public let migrationManager: MigrationManager public let migrationManager: MigrationManager

View File

@ -213,7 +213,7 @@ extension ProfileEditor {
preferences = try preferencesManager.preferences(forProfile: profile) preferences = try preferencesManager.preferences(forProfile: profile)
} catch { } catch {
preferences = [:] preferences = [:]
pp_log(.app, .error, "Unable to load preferences for profile \(profile.id): \(error)") pp_log(.App.profiles, .error, "Unable to load preferences for profile \(profile.id): \(error)")
} }
removedModules = [:] removedModules = [:]
} }
@ -233,7 +233,7 @@ extension ProfileEditor {
} }
return newProfile return newProfile
} catch { } catch {
pp_log(.app, .fault, "Unable to save edited profile: \(error)") pp_log(.App.profiles, .fault, "Unable to save edited profile: \(error)")
throw error throw error
} }
} }

View File

@ -13,8 +13,7 @@
0E3E22982CE53510005135DF /* AppUITV in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, ); productRef = 0E3E22972CE53510005135DF /* AppUITV */; }; 0E3E22982CE53510005135DF /* AppUITV in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, ); productRef = 0E3E22972CE53510005135DF /* AppUITV */; };
0E3FF4BA2CE3AFBC00BFF640 /* Profiles.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */; }; 0E3FF4BA2CE3AFBC00BFF640 /* Profiles.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */; };
0E3FF4BB2CE3AFBC00BFF640 /* MigrationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */; }; 0E3FF4BB2CE3AFBC00BFF640 /* MigrationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */; };
0E483E812CE64D6B00584B32 /* Shared+Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E7F2CE64D6B00584B32 /* Shared+Tunnel.swift */; }; 0E483E812CE64D6B00584B32 /* TunnelContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E7F2CE64D6B00584B32 /* TunnelContext+Shared.swift */; };
0E483E842CE6501100584B32 /* Shared+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E822CE6501100584B32 /* Shared+App.swift */; };
0E60512C2CE5393C00F763D4 /* PassepartoutImplementations in Frameworks */ = {isa = PBXBuildFile; productRef = 0E60512B2CE5393C00F763D4 /* PassepartoutImplementations */; }; 0E60512C2CE5393C00F763D4 /* PassepartoutImplementations in Frameworks */ = {isa = PBXBuildFile; productRef = 0E60512B2CE5393C00F763D4 /* PassepartoutImplementations */; };
0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */; }; 0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */; };
0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */; }; 0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */; };
@ -29,6 +28,16 @@
0E7F46122CF7F44C00B1C53A /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */; }; 0E7F46122CF7F44C00B1C53A /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */; };
0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8195592CFDA75200CC8FFD /* Dependencies.swift */; }; 0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8195592CFDA75200CC8FFD /* Dependencies.swift */; };
0E81955B2CFDA7BF00CC8FFD /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8195592CFDA75200CC8FFD /* Dependencies.swift */; }; 0E81955B2CFDA7BF00CC8FFD /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8195592CFDA75200CC8FFD /* Dependencies.swift */; };
0E8DFD482D05FA7000531CDE /* TunnelContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD472D05FA7000531CDE /* TunnelContext.swift */; };
0E8DFD4E2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */; };
0E8DFD4F2D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */; };
0E8DFD502D05FE5A00531CDE /* Dependencies+Processors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4D2D05FE5A00531CDE /* Dependencies+Processors.swift */; };
0E8DFD512D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */; };
0E8DFD532D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */; };
0E8DFD542D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */; };
0E8DFD562D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */; };
0E8DFD592D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */; };
0E8DFD5A2D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */; };
0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */; }; 0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */; };
0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */; }; 0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */; };
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; }; 0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; };
@ -42,8 +51,6 @@
0EC332D22B8A1808000B9C2F /* PassepartoutTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 0EC332D22B8A1808000B9C2F /* PassepartoutTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
0EC418D22CF86B7400AC6F2F /* ProfileMenuScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */; }; 0EC418D22CF86B7400AC6F2F /* ProfileMenuScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */; };
0EC797422B9378E000C093B7 /* AppContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797402B9378E000C093B7 /* AppContext+Shared.swift */; }; 0EC797422B9378E000C093B7 /* AppContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797402B9378E000C093B7 /* AppContext+Shared.swift */; };
0EC797432B9378E000C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
0EC797442B93790600C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF72CD0418C008FE259 /* App+macOS.swift */; }; 0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF72CD0418C008FE259 /* App+macOS.swift */; };
0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF92CD04192008FE259 /* App+iOS.swift */; }; 0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF92CD04192008FE259 /* App+iOS.swift */; };
0EDE56EA2CABE40D0082D21C /* Intents.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0EDE56E62CABE40D0082D21C /* Intents.plist */; }; 0EDE56EA2CABE40D0082D21C /* Intents.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0EDE56E62CABE40D0082D21C /* Intents.plist */; };
@ -137,8 +144,7 @@
0E3FF4AE2CE3AF6F00BFF640 /* PassepartoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassepartoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 0E3FF4AE2CE3AF6F00BFF640 /* PassepartoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassepartoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = Profiles.sqlite; sourceTree = "<group>"; }; 0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = Profiles.sqlite; sourceTree = "<group>"; };
0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; }; 0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; };
0E483E7F2CE64D6B00584B32 /* Shared+Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shared+Tunnel.swift"; sourceTree = "<group>"; }; 0E483E7F2CE64D6B00584B32 /* TunnelContext+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelContext+Shared.swift"; sourceTree = "<group>"; };
0E483E822CE6501100584B32 /* Shared+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shared+App.swift"; sourceTree = "<group>"; };
0E5DFDDC2CDB8F9100F2DE70 /* Passepartout.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.storekit; sourceTree = "<group>"; }; 0E5DFDDC2CDB8F9100F2DE70 /* Passepartout.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.storekit; sourceTree = "<group>"; };
0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+Testing.swift"; sourceTree = "<group>"; }; 0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+Testing.swift"; sourceTree = "<group>"; };
0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileManager+Testing.swift"; sourceTree = "<group>"; }; 0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileManager+Testing.swift"; sourceTree = "<group>"; };
@ -159,6 +165,12 @@
0E7F46102CF7F44C00B1C53A /* AppScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = "<group>"; }; 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = "<group>"; };
0E8195592CFDA75200CC8FFD /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; }; 0E8195592CFDA75200CC8FFD /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
0E8D852F2C328CA1005493DE /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; }; 0E8D852F2C328CA1005493DE /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
0E8DFD472D05FA7000531CDE /* TunnelContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelContext.swift; sourceTree = "<group>"; };
0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+CoreData.swift"; sourceTree = "<group>"; };
0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+IAPManager.swift"; sourceTree = "<group>"; };
0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+PassepartoutKit.swift"; sourceTree = "<group>"; };
0E8DFD4D2D05FE5A00531CDE /* Dependencies+Processors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+Processors.swift"; sourceTree = "<group>"; };
0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+PreferencesManager.swift"; sourceTree = "<group>"; };
0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditorScreen.swift; sourceTree = "<group>"; }; 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditorScreen.swift; sourceTree = "<group>"; };
0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; }; 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; };
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; }; 0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; };
@ -172,7 +184,6 @@
0EC332C92B8A1808000B9C2F /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 0EC332C92B8A1808000B9C2F /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileMenuScreen.swift; sourceTree = "<group>"; }; 0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileMenuScreen.swift; sourceTree = "<group>"; };
0EC797402B9378E000C093B7 /* AppContext+Shared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppContext+Shared.swift"; sourceTree = "<group>"; }; 0EC797402B9378E000C093B7 /* AppContext+Shared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppContext+Shared.swift"; sourceTree = "<group>"; };
0EC797412B9378E000C093B7 /* Shared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = "<group>"; };
0ED1EFDA2C33059600CBD9BD /* App.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = App.plist; sourceTree = "<group>"; }; 0ED1EFDA2C33059600CBD9BD /* App.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = App.plist; sourceTree = "<group>"; };
0ED61CF72CD0418C008FE259 /* App+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+macOS.swift"; sourceTree = "<group>"; }; 0ED61CF72CD0418C008FE259 /* App+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+macOS.swift"; sourceTree = "<group>"; };
0ED61CF92CD04192008FE259 /* App+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+iOS.swift"; sourceTree = "<group>"; }; 0ED61CF92CD04192008FE259 /* App+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+iOS.swift"; sourceTree = "<group>"; };
@ -289,15 +300,6 @@
path = Resources; path = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0E6EEEE62CF8CB090076E2B0 /* Testing */ = {
isa = PBXGroup;
children = (
0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */,
0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */,
);
path = Testing;
sourceTree = "<group>";
};
0E757F112CD0CFFC006E13E1 /* LoginItem */ = { 0E757F112CD0CFFC006E13E1 /* LoginItem */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -328,6 +330,7 @@
0E7E3D5A2B9345FD002BBDB4 /* App */ = { 0E7E3D5A2B9345FD002BBDB4 /* App */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0E8DFD462D05F77200531CDE /* Context */,
0ED61CF62CD04174008FE259 /* Platforms */, 0ED61CF62CD04174008FE259 /* Platforms */,
0ED1EFDA2C33059600CBD9BD /* App.plist */, 0ED1EFDA2C33059600CBD9BD /* App.plist */,
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */, 0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */,
@ -343,14 +346,11 @@
0E7E3D612B9345FD002BBDB4 /* Shared */ = { 0E7E3D612B9345FD002BBDB4 /* Shared */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0E6EEEE62CF8CB090076E2B0 /* Testing */,
0EC797402B9378E000C093B7 /* AppContext+Shared.swift */,
0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */,
0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */,
0E8195592CFDA75200CC8FFD /* Dependencies.swift */, 0E8195592CFDA75200CC8FFD /* Dependencies.swift */,
0EC797412B9378E000C093B7 /* Shared.swift */, 0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */,
0E483E822CE6501100584B32 /* Shared+App.swift */, 0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */,
0E483E7F2CE64D6B00584B32 /* Shared+Tunnel.swift */, 0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */,
0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -358,6 +358,7 @@
0E7E3D652B9345FD002BBDB4 /* Tunnel */ = { 0E7E3D652B9345FD002BBDB4 /* Tunnel */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0E8DFD452D05F76900531CDE /* Context */,
0E94EE5C2B93570600588243 /* Tunnel.plist */, 0E94EE5C2B93570600588243 /* Tunnel.plist */,
0E7E3D662B9345FD002BBDB4 /* Tunnel.entitlements */, 0E7E3D662B9345FD002BBDB4 /* Tunnel.entitlements */,
0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */, 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */,
@ -376,6 +377,28 @@
path = UITests; path = UITests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0E8DFD452D05F76900531CDE /* Context */ = {
isa = PBXGroup;
children = (
0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */,
0E8DFD472D05FA7000531CDE /* TunnelContext.swift */,
0E483E7F2CE64D6B00584B32 /* TunnelContext+Shared.swift */,
);
path = Context;
sourceTree = "<group>";
};
0E8DFD462D05F77200531CDE /* Context */ = {
isa = PBXGroup;
children = (
0EC797402B9378E000C093B7 /* AppContext+Shared.swift */,
0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */,
0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */,
0E8DFD4D2D05FE5A00531CDE /* Dependencies+Processors.swift */,
0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */,
);
path = Context;
sourceTree = "<group>";
};
0E916B7A2CF811DE0072921A /* Extensions */ = { 0E916B7A2CF811DE0072921A /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -684,6 +707,10 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */, 0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */,
0E8DFD4E2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */,
0E8DFD4F2D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */,
0E8DFD502D05FE5A00531CDE /* Dependencies+Processors.swift in Sources */,
0E8DFD512D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */,
0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */, 0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */,
0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */, 0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */,
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */, 0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */,
@ -693,8 +720,7 @@
0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */, 0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */,
0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */, 0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */,
0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */, 0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */,
0E483E842CE6501100584B32 /* Shared+App.swift in Sources */, 0E8DFD592D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */,
0EC797432B9378E000C093B7 /* Shared.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -738,8 +764,12 @@
0E81955B2CFDA7BF00CC8FFD /* Dependencies.swift in Sources */, 0E81955B2CFDA7BF00CC8FFD /* Dependencies.swift in Sources */,
0EAEC8AA2D05DB8D001AA50C /* DefaultTunnelProcessor.swift in Sources */, 0EAEC8AA2D05DB8D001AA50C /* DefaultTunnelProcessor.swift in Sources */,
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */, 0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */,
0E483E812CE64D6B00584B32 /* Shared+Tunnel.swift in Sources */, 0E483E812CE64D6B00584B32 /* TunnelContext+Shared.swift in Sources */,
0EC797442B93790600C093B7 /* Shared.swift in Sources */, 0E8DFD482D05FA7000531CDE /* TunnelContext.swift in Sources */,
0E8DFD532D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */,
0E8DFD542D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */,
0E8DFD562D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */,
0E8DFD5A2D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -33,8 +33,9 @@ import UITesting
final class AppDelegate: NSObject { final class AppDelegate: NSObject {
let context: AppContext = { let context: AppContext = {
if AppCommandLine.contains(.uiTesting) { if AppCommandLine.contains(.uiTesting) {
let dependencies: Dependencies = .shared
pp_log(.app, .info, "UI tests: mock AppContext") pp_log(.app, .info, "UI tests: mock AppContext")
return .forUITesting(withRegistry: .shared) return .forUITesting(withRegistry: dependencies.registry)
} }
return .shared return .shared
}() }()

View File

@ -0,0 +1,259 @@
//
// AppContext+Shared.swift
// Passepartout
//
// Created by Davide De Rosa on 2/24/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 <http://www.gnu.org/licenses/>.
//
import AppData
import AppDataProfiles
import AppDataProviders
import CommonLibrary
import CommonUtils
import Foundation
import LegacyV2
import PassepartoutKit
import UILibrary
import UITesting
extension AppContext {
static let shared: AppContext = {
let dependencies: Dependencies = .shared
let iapManager = IAPManager(
customUserLevel: dependencies.customUserLevel,
inAppHelper: dependencies.simulatedAppProductHelper(),
receiptReader: dependencies.simulatedAppReceiptReader(),
betaChecker: dependencies.betaChecker(),
productsAtBuild: dependencies.productsAtBuild()
)
let processor = dependencies.appProcessor(with: iapManager)
let profileManager: ProfileManager = {
let remoteRepositoryBlock: (Bool) -> ProfileRepository = {
let remoteStore = CoreDataPersistentStore(
logger: dependencies.coreDataLogger(),
containerName: Constants.shared.containers.remoteProfiles,
model: AppData.cdProfilesModel,
cloudKitIdentifier: $0 ? BundleConfiguration.mainString(for: .cloudKitId) : nil,
author: nil
)
return AppData.cdProfileRepositoryV3(
registry: dependencies.registry,
coder: CodableProfileCoder(),
context: remoteStore.context,
observingResults: true,
onResultError: {
pp_log(.App.profiles, .error, "Unable to decode remote profile: \($0)")
return .ignore
}
)
}
return ProfileManager(
repository: dependencies.mainProfileRepository(),
backupRepository: dependencies.backupProfileRepository(),
remoteRepositoryBlock: remoteRepositoryBlock,
mirrorsRemoteRepository: dependencies.mirrorsRemoteRepository,
processor: processor
)
}()
let tunnel = ExtendedTunnel(
tunnel: Tunnel(strategy: dependencies.tunnelStrategy()),
environment: dependencies.tunnelEnvironment(),
processor: processor,
interval: Constants.shared.tunnel.refreshInterval
)
let providerManager: ProviderManager = {
let store = CoreDataPersistentStore(
logger: dependencies.coreDataLogger(),
containerName: Constants.shared.containers.providers,
model: AppData.cdProvidersModel,
cloudKitIdentifier: nil,
author: nil
)
let repository = AppData.cdProviderRepositoryV3(context: store.backgroundContext)
return ProviderManager(repository: repository)
}()
let migrationManager: MigrationManager = {
let profileStrategy = ProfileV2MigrationStrategy(
coreDataLogger: dependencies.coreDataLogger(),
profilesContainer: .init(
Constants.shared.containers.legacyV2,
BundleConfiguration.mainString(for: .legacyV2CloudKitId)
),
tvProfilesContainer: .init(
Constants.shared.containers.legacyV2TV,
BundleConfiguration.mainString(for: .legacyV2TVCloudKitId)
)
)
let migrationSimulation: MigrationManager.Simulation?
if AppCommandLine.contains(.fakeMigration) {
migrationSimulation = MigrationManager.Simulation(
fakeProfiles: true,
maxMigrationTime: 3.0,
randomFailures: true
)
} else {
migrationSimulation = nil
}
return MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
}()
let preferencesManager = dependencies.preferencesManager(withCloudKit: true)
return AppContext(
iapManager: iapManager,
migrationManager: migrationManager,
profileManager: profileManager,
providerManager: providerManager,
preferencesManager: preferencesManager,
registry: dependencies.registry,
tunnel: tunnel,
tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt
)
}()
}
// MARK: - Dependencies
private extension Dependencies {
var customUserLevel: AppUserLevel? {
guard let userLevelString = BundleConfiguration.mainIntegerIfPresent(for: .userLevel),
let userLevel = AppUserLevel(rawValue: userLevelString) else {
return nil
}
return userLevel
}
func simulatedAppProductHelper() -> any AppProductHelper {
if AppCommandLine.contains(.fakeIAP) {
return FakeAppProductHelper()
}
return appProductHelper()
}
func simulatedAppReceiptReader() -> AppReceiptReader {
if AppCommandLine.contains(.fakeIAP) {
guard let mockHelper = simulatedAppProductHelper() as? FakeAppProductHelper else {
fatalError("When .isFakeIAP, simulatedInAppHelper is expected to be MockAppProductHelper")
}
return mockHelper.receiptReader
}
return FallbackReceiptReader(
main: StoreKitReceiptReader(),
beta: betaReceiptURL.map {
KvittoReceiptReader(url: $0)
}
)
}
var betaReceiptURL: URL? {
Bundle.main.appStoreProductionReceiptURL
}
}
// MARK: Simulator
#if targetEnvironment(simulator)
private extension Dependencies {
func tunnelStrategy() -> TunnelObservableStrategy {
FakeTunnelStrategy(environment: .shared, dataCountInterval: 1000)
}
func mainProfileRepository() -> ProfileRepository {
coreDataProfileRepository(observingResults: true)
}
func backupProfileRepository() -> ProfileRepository? {
nil
}
}
#else
// MARK: Device
private extension Dependencies {
func tunnelStrategy() -> TunnelObservableStrategy {
neStrategy()
}
func mainProfileRepository() -> ProfileRepository {
neProfileRepository()
}
func backupProfileRepository() -> ProfileRepository? {
coreDataProfileRepository(observingResults: false)
}
}
#endif
// MARK: Common
private extension Dependencies {
var mirrorsRemoteRepository: Bool {
#if os(tvOS)
true
#else
false
#endif
}
func neProfileRepository() -> ProfileRepository {
NEProfileRepository(repository: neStrategy()) {
profileTitle($0)
}
}
func neStrategy() -> NETunnelStrategy {
NETunnelStrategy(
bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
coder: neProtocolCoder(),
environment: tunnelEnvironment()
)
}
func coreDataProfileRepository(observingResults: Bool) -> ProfileRepository {
let store = CoreDataPersistentStore(
logger: coreDataLogger(),
containerName: Constants.shared.containers.localProfiles,
model: AppData.cdProfilesModel,
cloudKitIdentifier: nil,
author: nil
)
return AppData.cdProfileRepositoryV3(
registry: registry,
coder: CodableProfileCoder(),
context: store.context,
observingResults: observingResults,
onResultError: {
pp_log(.App.profiles, .error, "Unable to decode local profile: \($0)")
return .ignore
}
)
}
}

View File

@ -31,21 +31,19 @@ import UILibrary
extension AppContext { extension AppContext {
static func forUITesting(withRegistry registry: Registry) -> AppContext { static func forUITesting(withRegistry registry: Registry) -> AppContext {
let dependencies: Dependencies = .shared
let iapManager = IAPManager( let iapManager = IAPManager(
customUserLevel: .subscriber, customUserLevel: .subscriber,
inAppHelper: Dependencies.IAPManager.inAppHelper, inAppHelper: dependencies.appProductHelper(),
receiptReader: FakeAppReceiptReader(), receiptReader: FakeAppReceiptReader(),
betaChecker: TestFlightChecker(), betaChecker: TestFlightChecker(),
productsAtBuild: { _ in productsAtBuild: { _ in
[] []
} }
) )
let processor = DefaultAppProcessor(iapManager: iapManager) { let processor = dependencies.appProcessor(with: iapManager)
$0.localizedPreview let profileManager: ProfileManager = .forUITesting(
} withRegistry: dependencies.registry,
let profileManager: ProfileManager = .forTesting(
withRegistry: .shared,
processor: processor processor: processor
) )
let tunnelEnvironment = InMemoryEnvironment() let tunnelEnvironment = InMemoryEnvironment()
@ -59,13 +57,14 @@ extension AppContext {
repository: InMemoryProviderRepository() repository: InMemoryProviderRepository()
) )
let migrationManager = MigrationManager() let migrationManager = MigrationManager()
let preferencesManager = PreferencesManager()
return AppContext( return AppContext(
iapManager: iapManager, iapManager: iapManager,
migrationManager: migrationManager, migrationManager: migrationManager,
profileManager: profileManager, profileManager: profileManager,
providerManager: providerManager, providerManager: providerManager,
preferencesManager: PreferencesManager(), preferencesManager: preferencesManager,
registry: registry, registry: registry,
tunnel: tunnel, tunnel: tunnel,
tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt

View File

@ -30,21 +30,28 @@ import PassepartoutKit
final class DefaultAppProcessor: Sendable { final class DefaultAppProcessor: Sendable {
private let iapManager: IAPManager private let iapManager: IAPManager
private let preview: @Sendable (Profile) -> ProfilePreview private let title: @Sendable (Profile) -> String
init(iapManager: IAPManager, preview: @escaping @Sendable (Profile) -> ProfilePreview) { init(
iapManager: IAPManager,
title: @escaping @Sendable (Profile) -> String
) {
self.iapManager = iapManager self.iapManager = iapManager
self.preview = preview self.title = title
} }
} }
extension DefaultAppProcessor: ProfileProcessor { extension DefaultAppProcessor: ProfileProcessor {
func isIncluded(_ profile: Profile) -> Bool { func isIncluded(_ profile: Profile) -> Bool {
Dependencies.ProfileManager.isIncluded(iapManager, profile) #if os(tvOS)
profile.attributes.isAvailableForTV == true
#else
true
#endif
} }
func preview(from profile: Profile) -> ProfilePreview { func preview(from profile: Profile) -> ProfilePreview {
preview(profile) profile.localizedPreview
} }
func requiredFeatures(_ profile: Profile) -> Set<AppFeature>? { func requiredFeatures(_ profile: Profile) -> Set<AppFeature>? {
@ -65,7 +72,7 @@ extension DefaultAppProcessor: ProfileProcessor {
extension DefaultAppProcessor: AppTunnelProcessor { extension DefaultAppProcessor: AppTunnelProcessor {
func title(for profile: Profile) -> String { func title(for profile: Profile) -> String {
Dependencies.ProfileManager.sharedTitle(profile) title(profile)
} }
func willInstall(_ profile: Profile) throws -> Profile { func willInstall(_ profile: Profile) throws -> Profile {

View File

@ -0,0 +1,43 @@
//
// Dependencies+Processors.swift
// Passepartout
//
// Created by Davide De Rosa on 12/2/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 <http://www.gnu.org/licenses/>.
//
import CommonLibrary
import Foundation
import PassepartoutKit
import UILibrary
extension Dependencies {
func appProcessor(with iapManager: IAPManager) -> DefaultAppProcessor {
DefaultAppProcessor(
iapManager: iapManager,
title: profileTitle
)
}
@Sendable
nonisolated func profileTitle(_ profile: Profile) -> String {
String(format: Constants.shared.tunnel.profileTitleFormat, profile.name)
}
}

View File

@ -28,7 +28,7 @@ import Foundation
import PassepartoutKit import PassepartoutKit
extension ProfileManager { extension ProfileManager {
public static func forTesting(withRegistry registry: Registry, processor: ProfileProcessor) -> ProfileManager { public static func forUITesting(withRegistry registry: Registry, processor: ProfileProcessor) -> ProfileManager {
let repository = InMemoryProfileRepository() let repository = InMemoryProfileRepository()
let remoteRepository = InMemoryProfileRepository() let remoteRepository = InMemoryProfileRepository()
let manager = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in let manager = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
@ -67,7 +67,7 @@ extension ProfileManager {
try await manager.save(profile, isLocal: true, remotelyShared: parameters.isShared) try await manager.save(profile, isLocal: true, remotelyShared: parameters.isShared)
} }
} catch { } catch {
pp_log(.App.profiles, .error, "Unable to build mock ProfileManager: \(error)") pp_log(.App.profiles, .error, "Unable to build ProfileManager for UI testing: \(error)")
} }
} }

View File

@ -1,218 +0,0 @@
//
// AppContext+Shared.swift
// Passepartout
//
// Created by Davide De Rosa on 2/24/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 <http://www.gnu.org/licenses/>.
//
import AppData
import AppDataProfiles
import AppDataProviders
import CommonLibrary
import CommonUtils
import Foundation
import LegacyV2
import PassepartoutKit
import UILibrary
import UITesting
// shared registry and environment are picked from Shared.swift
extension AppContext {
static let shared: AppContext = {
let iapManager: IAPManager = .sharedForApp
let processor = DefaultAppProcessor(iapManager: iapManager) {
$0.localizedPreview
}
// MARK: ProfileManager
let remoteRepositoryBlock: (Bool) -> ProfileRepository = {
let remoteStore = CoreDataPersistentStore(
logger: .default,
containerName: Constants.shared.containers.remoteProfiles,
model: AppData.cdProfilesModel,
cloudKitIdentifier: $0 ? BundleConfiguration.mainString(for: .cloudKitId) : nil,
author: nil
)
return AppData.cdProfileRepositoryV3(
registry: .shared,
coder: CodableProfileCoder(),
context: remoteStore.context,
observingResults: true
) { error in
pp_log(.app, .error, "Unable to decode remote result: \(error)")
return .ignore
}
}
let profileManager = ProfileManager(
repository: Dependencies.ProfileManager.mainProfileRepository,
backupRepository: Dependencies.ProfileManager.backupProfileRepository,
remoteRepositoryBlock: remoteRepositoryBlock,
mirrorsRemoteRepository: Dependencies.ProfileManager.mirrorsRemoteRepository,
processor: processor
)
// MARK: ExtendedTunnel
let tunnel = ExtendedTunnel(
tunnel: Tunnel(strategy: Dependencies.ExtendedTunnel.strategy),
environment: .shared,
processor: processor,
interval: Constants.shared.tunnel.refreshInterval
)
// MARK: ProviderManager
let providerManager: ProviderManager = {
let store = CoreDataPersistentStore(
logger: .default,
containerName: Constants.shared.containers.providers,
model: AppData.cdProvidersModel,
cloudKitIdentifier: nil,
author: nil
)
let repository = AppData.cdProviderRepositoryV3(context: store.backgroundContext)
return ProviderManager(repository: repository)
}()
// MARK: MigrationManager
let profileStrategy = ProfileV2MigrationStrategy(
coreDataLogger: .default,
profilesContainer: .init(
Constants.shared.containers.legacyV2,
BundleConfiguration.mainString(for: .legacyV2CloudKitId)
),
tvProfilesContainer: .init(
Constants.shared.containers.legacyV2TV,
BundleConfiguration.mainString(for: .legacyV2TVCloudKitId)
)
)
let migrationSimulation: MigrationManager.Simulation?
if AppCommandLine.contains(.fakeMigration) {
migrationSimulation = MigrationManager.Simulation(
fakeProfiles: true,
maxMigrationTime: 3.0,
randomFailures: true
)
} else {
migrationSimulation = nil
}
let migrationManager = MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
return AppContext(
iapManager: iapManager,
migrationManager: migrationManager,
profileManager: profileManager,
providerManager: providerManager,
preferencesManager: .sharedForApp,
registry: .shared,
tunnel: tunnel,
tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt
)
}()
}
// MARK: - Dependencies
// MARK: Simulator
#if targetEnvironment(simulator)
private extension Dependencies.ExtendedTunnel {
static var strategy: TunnelObservableStrategy {
FakeTunnelStrategy(environment: .shared, dataCountInterval: 1000)
}
}
@MainActor
private extension Dependencies.ProfileManager {
static var mainProfileRepository: ProfileRepository {
coreDataProfileRepository(observingResults: true)
}
static var backupProfileRepository: ProfileRepository? {
nil
}
}
#else
// MARK: Device
@MainActor
private extension Dependencies.ExtendedTunnel {
static var strategy: TunnelObservableStrategy {
Dependencies.ProfileManager.neStrategy
}
}
@MainActor
private extension Dependencies.ProfileManager {
static var mainProfileRepository: ProfileRepository {
neProfileRepository
}
static var backupProfileRepository: ProfileRepository? {
coreDataProfileRepository(observingResults: false)
}
}
#endif
// MARK: Common
@MainActor
private extension Dependencies.ProfileManager {
static let neProfileRepository: ProfileRepository = {
NEProfileRepository(repository: neStrategy) {
sharedTitle($0)
}
}()
static let neStrategy: NETunnelStrategy = {
NETunnelStrategy(
bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
coder: Registry.sharedProtocolCoder,
environment: .shared
)
}()
static func coreDataProfileRepository(observingResults: Bool) -> ProfileRepository {
let store = CoreDataPersistentStore(
logger: .default,
containerName: Constants.shared.containers.localProfiles,
model: AppData.cdProfilesModel,
cloudKitIdentifier: nil,
author: nil
)
return AppData.cdProfileRepositoryV3(
registry: .shared,
coder: CodableProfileCoder(),
context: store.context,
observingResults: observingResults
) { error in
pp_log(.app, .error, "Unable to decode local result: \(error)")
return .ignore
}
}
}

View File

@ -0,0 +1,44 @@
//
// Dependencies+CoreData.swift
// Passepartout
//
// Created by Davide De Rosa on 12/2/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 <http://www.gnu.org/licenses/>.
//
import CommonUtils
import Foundation
import PassepartoutKit
extension Dependencies {
func coreDataLogger() -> CoreDataPersistentStoreLogger {
DefaultCoreDataPersistentStoreLogger()
}
}
private struct DefaultCoreDataPersistentStoreLogger: CoreDataPersistentStoreLogger {
func debug(_ msg: String) {
pp_log(.app, .info, msg)
}
func warning(_ msg: String) {
pp_log(.app, .error, msg)
}
}

View File

@ -0,0 +1,65 @@
//
// Dependencies+IAPManager.swift
// Passepartout
//
// Created by Davide De Rosa on 12/2/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 <http://www.gnu.org/licenses/>.
//
import CommonLibrary
import CommonUtils
import Foundation
import PassepartoutKit
extension Dependencies {
func appProductHelper() -> any AppProductHelper {
StoreKitHelper(
products: AppProduct.all,
inAppIdentifier: {
let prefix = BundleConfiguration.mainString(for: .iapBundlePrefix)
return "\(prefix).\($0.rawValue)"
}
)
}
func betaChecker() -> BetaChecker {
TestFlightChecker()
}
func productsAtBuild() -> BuildProducts<AppProduct> {
{
#if os(iOS)
if $0 <= 2016 {
return [.Full.iOS]
} else if $0 <= 3000 {
return [.Features.networkSettings]
}
return []
#elseif os(macOS)
if $0 <= 3000 {
return [.Features.networkSettings]
}
return []
#else
return []
#endif
}
}
}

View File

@ -1,8 +1,8 @@
// //
// Shared.swift // Dependencies+PassepartoutKit.swift
// Passepartout // Passepartout
// //
// Created by Davide De Rosa on 2/25/24. // Created by Davide De Rosa on 12/2/24.
// Copyright (c) 2024 Davide De Rosa. All rights reserved. // Copyright (c) 2024 Davide De Rosa. All rights reserved.
// //
// https://github.com/passepartoutvpn // https://github.com/passepartoutvpn
@ -23,17 +23,35 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
import AppData
import AppDataPreferences
import CommonLibrary
import CommonUtils
import CPassepartoutOpenVPNOpenSSL import CPassepartoutOpenVPNOpenSSL
import Foundation import Foundation
import PassepartoutKit import PassepartoutKit
import PassepartoutWireGuardGo import PassepartoutWireGuardGo
extension Registry { extension Dependencies {
static let shared = Registry( var registry: Registry {
Self.sharedRegistry
}
func neProtocolCoder() -> NEProtocolCoder {
KeychainNEProtocolCoder(
tunnelBundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
registry: registry,
coder: CodableProfileCoder(),
keychain: AppleKeychain(group: BundleConfiguration.mainString(for: .keychainGroupId))
)
}
func tunnelEnvironment() -> TunnelEnvironment {
AppGroupEnvironment(
appGroup: BundleConfiguration.mainString(for: .groupId),
prefix: "PassepartoutKit."
)
}
}
private extension Dependencies {
static let sharedRegistry = Registry(
withKnownHandlers: true, withKnownHandlers: true,
allImplementations: [ allImplementations: [
OpenVPNModule.Implementation( OpenVPNModule.Implementation(
@ -69,59 +87,4 @@ extension Registry {
) )
] ]
) )
static var sharedProtocolCoder: KeychainNEProtocolCoder {
KeychainNEProtocolCoder(
tunnelBundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
registry: .shared,
coder: CodableProfileCoder(),
keychain: AppleKeychain(group: BundleConfiguration.mainString(for: .keychainGroupId))
)
}
}
extension TunnelEnvironment where Self == AppGroupEnvironment {
static var shared: Self {
AppGroupEnvironment(
appGroup: BundleConfiguration.mainString(for: .groupId),
prefix: "PassepartoutKit."
)
}
}
extension PreferencesManager {
static func sharedImplementation(withCloudKit: Bool) -> PreferencesManager {
let preferencesStore = CoreDataPersistentStore(
logger: .default,
containerName: Constants.shared.containers.preferences,
baseURL: BundleConfiguration.urlForGroupDocuments,
model: AppData.cdPreferencesModel,
cloudKitIdentifier: withCloudKit ? BundleConfiguration.mainString(for: .cloudKitPreferencesId) : nil,
author: nil
)
return PreferencesManager(
modulesRepository: AppData.cdModulePreferencesRepositoryV3(context: preferencesStore.context),
providersFactory: {
try AppData.cdProviderPreferencesRepositoryV3(context: preferencesStore.context, providerId: $0)
}
)
}
}
// MARK: - Logging
extension CoreDataPersistentStoreLogger where Self == DefaultCoreDataPersistentStoreLogger {
static var `default`: CoreDataPersistentStoreLogger {
DefaultCoreDataPersistentStoreLogger()
}
}
struct DefaultCoreDataPersistentStoreLogger: CoreDataPersistentStoreLogger {
func debug(_ msg: String) {
pp_log(.app, .info, msg)
}
func warning(_ msg: String) {
pp_log(.app, .error, msg)
}
} }

View File

@ -0,0 +1,50 @@
//
// Dependencies+PreferencesManager.swift
// Passepartout
//
// Created by Davide De Rosa on 12/2/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 <http://www.gnu.org/licenses/>.
//
import AppData
import AppDataPreferences
import CommonLibrary
import CommonUtils
import Foundation
import PassepartoutKit
extension Dependencies {
func preferencesManager(withCloudKit: Bool) -> PreferencesManager {
let preferencesStore = CoreDataPersistentStore(
logger: coreDataLogger(),
containerName: Constants.shared.containers.preferences,
baseURL: BundleConfiguration.urlForGroupDocuments,
model: AppData.cdPreferencesModel,
cloudKitIdentifier: withCloudKit ? BundleConfiguration.mainString(for: .cloudKitPreferencesId) : nil,
author: nil
)
return PreferencesManager(
modulesRepository: AppData.cdModulePreferencesRepositoryV3(context: preferencesStore.context),
providersFactory: {
try AppData.cdProviderPreferencesRepositoryV3(context: preferencesStore.context, providerId: $0)
}
)
}
}

View File

@ -23,72 +23,9 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
import CommonLibrary
import CommonUtils
import Foundation import Foundation
import PassepartoutKit
enum Dependencies {
enum ExtendedTunnel {
}
enum IAPManager {
}
enum ProfileManager {
}
}
extension Dependencies.IAPManager {
@MainActor @MainActor
static let inAppHelper = StoreKitHelper( struct Dependencies {
products: AppProduct.all, static let shared = Dependencies()
inAppIdentifier: {
let prefix = BundleConfiguration.mainString(for: .iapBundlePrefix)
return "\(prefix).\($0.rawValue)"
}
)
static var betaChecker: BetaChecker {
TestFlightChecker()
}
static let productsAtBuild: BuildProducts<AppProduct> = {
#if os(iOS)
if $0 <= 2016 {
return [.Full.iOS]
} else if $0 <= 3000 {
return [.Features.networkSettings]
}
return []
#elseif os(macOS)
if $0 <= 3000 {
return [.Features.networkSettings]
}
return []
#else
return []
#endif
}
}
extension Dependencies.ProfileManager {
static let sharedTitle: @Sendable (Profile) -> String = {
String(format: Constants.shared.tunnel.profileTitleFormat, $0.name)
}
#if os(tvOS)
static let mirrorsRemoteRepository = true
static let isIncluded: @MainActor @Sendable (CommonLibrary.IAPManager, Profile) -> Bool = {
$1.attributes.isAvailableForTV == true
}
#else
static let mirrorsRemoteRepository = false
static let isIncluded: @MainActor @Sendable (CommonLibrary.IAPManager, Profile) -> Bool = { _, _ in
true
}
#endif
} }

View File

@ -1,84 +0,0 @@
//
// Shared+App.swift
// Passepartout
//
// Created by Davide De Rosa on 11/14/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 <http://www.gnu.org/licenses/>.
//
import CommonLibrary
import CommonUtils
import Foundation
import PassepartoutKit
import UITesting
extension IAPManager {
static let sharedForApp = IAPManager(
customUserLevel: Dependencies.IAPManager.customUserLevel,
inAppHelper: Dependencies.IAPManager.simulatedInAppHelper,
receiptReader: Dependencies.IAPManager.simulatedAppReceiptReader,
betaChecker: Dependencies.IAPManager.betaChecker,
productsAtBuild: Dependencies.IAPManager.productsAtBuild
)
}
extension PreferencesManager {
static let sharedForApp = PreferencesManager.sharedImplementation(withCloudKit: true)
}
// MARK: - Dependencies
private extension Dependencies.IAPManager {
static var customUserLevel: AppUserLevel? {
guard let userLevelString = BundleConfiguration.mainIntegerIfPresent(for: .userLevel),
let userLevel = AppUserLevel(rawValue: userLevelString) else {
return nil
}
return userLevel
}
@MainActor
static let simulatedInAppHelper: any AppProductHelper = {
if AppCommandLine.contains(.fakeIAP) {
return FakeAppProductHelper()
}
return inAppHelper
}()
@MainActor
static var simulatedAppReceiptReader: AppReceiptReader {
if AppCommandLine.contains(.fakeIAP) {
guard let mockHelper = simulatedInAppHelper as? FakeAppProductHelper else {
fatalError("When .isFakeIAP, simulatedInAppHelper is expected to be MockAppProductHelper")
}
return mockHelper.receiptReader
}
return FallbackReceiptReader(
main: StoreKitReceiptReader(),
beta: betaReceiptURL.map {
KvittoReceiptReader(url: $0)
}
)
}
static var betaReceiptURL: URL? {
Bundle.main.appStoreProductionReceiptURL
}
}

View File

@ -1,5 +1,5 @@
// //
// Shared+Tunnel.swift // TunnelContext+Shared.swift
// Passepartout // Passepartout
// //
// Created by Davide De Rosa on 11/14/24. // Created by Davide De Rosa on 11/14/24.
@ -28,32 +28,37 @@ import CommonUtils
import Foundation import Foundation
import PassepartoutKit import PassepartoutKit
extension IAPManager { extension TunnelContext {
static let sharedForTunnel = IAPManager( static let shared: TunnelContext = {
inAppHelper: Dependencies.IAPManager.inAppHelper, let dependencies: Dependencies = .shared
receiptReader: Dependencies.IAPManager.tunnelReceiptReader, let iapManager = IAPManager(
betaChecker: Dependencies.IAPManager.betaChecker, inAppHelper: dependencies.appProductHelper(),
productsAtBuild: Dependencies.IAPManager.productsAtBuild receiptReader: dependencies.tunnelReceiptReader(),
betaChecker: dependencies.betaChecker(),
productsAtBuild: dependencies.productsAtBuild()
) )
} let processor: PacketTunnelProcessor = {
let preferencesManager = dependencies.preferencesManager(withCloudKit: false)
extension PreferencesManager { return DefaultTunnelProcessor(preferencesManager: preferencesManager)
static let sharedForTunnel = PreferencesManager.sharedImplementation(withCloudKit: false) }()
return TunnelContext(
iapManager: iapManager,
processor: processor
)
}()
} }
// MARK: - Dependencies // MARK: - Dependencies
private extension Dependencies.IAPManager { private extension Dependencies {
func tunnelReceiptReader() -> AppReceiptReader {
@MainActor
static var tunnelReceiptReader: AppReceiptReader {
FallbackReceiptReader( FallbackReceiptReader(
main: StoreKitReceiptReader(), main: StoreKitReceiptReader(),
beta: KvittoReceiptReader(url: betaReceiptURL) beta: KvittoReceiptReader(url: betaReceiptURL)
) )
} }
static var betaReceiptURL: URL { var betaReceiptURL: URL {
BundleConfiguration.urlForBetaReceipt // copied by AppContext.onLaunch BundleConfiguration.urlForBetaReceipt // copied by AppContext.onLaunch
} }
} }

View File

@ -0,0 +1,34 @@
//
// TunnelContext.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 <http://www.gnu.org/licenses/>.
//
import CommonLibrary
import Foundation
@MainActor
struct TunnelContext {
let iapManager: IAPManager
let processor: PacketTunnelProcessor
}

View File

@ -28,6 +28,13 @@ import CommonLibrary
import PassepartoutKit import PassepartoutKit
final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
@MainActor
private let context: TunnelContext = .shared
@MainActor
private let dependencies: Dependencies = .shared
private var fwd: NEPTPForwarder? private var fwd: NEPTPForwarder?
override func startTunnel(options: [String: NSObject]? = nil) async throws { override func startTunnel(options: [String: NSObject]? = nil) async throws {
@ -36,19 +43,18 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
parameters: Constants.shared.log, parameters: Constants.shared.log,
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key) logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
) )
let processor = DefaultTunnelProcessor(preferencesManager: .sharedForTunnel)
do { do {
fwd = try await NEPTPForwarder( fwd = try await NEPTPForwarder(
provider: self, provider: self,
decoder: Registry.sharedProtocolCoder, decoder: dependencies.neProtocolCoder(),
registry: .shared, registry: dependencies.registry,
environment: .shared, environment: dependencies.tunnelEnvironment(),
profileBlock: processor.willStart profileBlock: context.processor.willStart
) )
guard let fwd else { guard let fwd else {
fatalError("NEPTPForwarder nil without throwing error?") fatalError("NEPTPForwarder nil without throwing error?")
} }
try await checkEligibility(of: fwd.profile, environment: .shared) try await checkEligibility(of: fwd.profile, environment: dependencies.tunnelEnvironment())
try await fwd.startTunnel(options: options) try await fwd.startTunnel(options: options)
} catch { } catch {
pp_log(.app, .fault, "Unable to start tunnel: \(error)") pp_log(.app, .fault, "Unable to start tunnel: \(error)")
@ -85,14 +91,10 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
@MainActor @MainActor
private extension PacketTunnelProvider { private extension PacketTunnelProvider {
var iapManager: IAPManager {
.sharedForTunnel
}
func checkEligibility(of profile: Profile, environment: TunnelEnvironment) async throws { func checkEligibility(of profile: Profile, environment: TunnelEnvironment) async throws {
await iapManager.reloadReceipt() await context.iapManager.reloadReceipt()
do { do {
try iapManager.verify(profile) try context.iapManager.verify(profile)
} catch { } catch {
let error = PassepartoutError(.App.ineligibleProfile) let error = PassepartoutError(.App.ineligibleProfile)
environment.setEnvironmentValue(error.code, forKey: TunnelEnvironmentKeys.lastErrorCode) environment.setEnvironmentValue(error.code, forKey: TunnelEnvironmentKeys.lastErrorCode)