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:
parent
aac04c4008
commit
f7013a98a9
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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 {
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue