Refactor and test ProductManager (#437)

Carefully drop the StoreKit and Kvitto dependencies for ProductManager
to be testable.

Rebuild test target completely to start writing meaningful tests in
general.
This commit is contained in:
Davide De Rosa 2023-12-20 20:43:39 +01:00 committed by GitHub
parent 7c42263fb1
commit a0da930d98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1213 additions and 1515 deletions

View File

@ -35,4 +35,5 @@ jobs:
go-version: "^1.17"
- name: Run tests
run: |
bundle exec fastlane --env beta,ios scan
cd PassepartoutLibrary
swift test

View File

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
0E021D9C284E68580077EF5D /* CoreContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E021D9A284E68580077EF5D /* CoreContext.swift */; };
0E021D9D284E68580077EF5D /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E021D9B284E68580077EF5D /* AppContext.swift */; };
0E0392772818732D00827C10 /* BuildProducts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0392762818732D00827C10 /* BuildProducts.swift */; };
0E039279281890B100827C10 /* AddHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E039278281890B100827C10 /* AddHostView.swift */; };
0E04F0062883462E00BFCE1C /* LightUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E04F0052883462E00BFCE1C /* LightUtils.swift */; };
0E04F0072883462E00BFCE1C /* LightUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E04F0052883462E00BFCE1C /* LightUtils.swift */; };
@ -29,12 +28,10 @@
0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */; };
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; };
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; };
0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */; };
0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */; };
0E1B5F5C29C506AD00FE7D18 /* DiagnosticsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* DiagnosticsSection.swift */; };
0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; };
0E1F562B287F0EF100F8ADD7 /* ProviderProfileItem+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */; };
0E293851285A70AC002A6E0E /* AppPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E293850285A70AC002A6E0E /* AppPreference.swift */; };
0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E293856285A73BC002A6E0E /* AppContext+Shared.swift */; };
0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */; };
0E2A8D4F27B04BBA00207D04 /* OrganizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */; };
@ -44,15 +41,18 @@
0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Extensions.swift */; };
0E2DE71F27DCD0290067B9E1 /* TunnelKit+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */; };
0E2DE72527DCDF550067B9E1 /* WireGuard+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2DE72427DCDF550067B9E1 /* WireGuard+L10n.swift */; };
0E2E0B6F2B335A8E00E3204A /* AppPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B6D2B335A8E00E3204A /* AppPreference.swift */; };
0E2E0B702B335A8E00E3204A /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B6E2B335A8E00E3204A /* AppError.swift */; };
0E2E0B752B335AAB00E3204A /* IntentsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B712B335AAB00E3204A /* IntentsManager.swift */; };
0E2E0B762B335AAB00E3204A /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B722B335AAB00E3204A /* UpgradeManagerStrategy.swift */; };
0E2E0B772B335AAB00E3204A /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B732B335AAB00E3204A /* UpgradeManager.swift */; };
0E2E0B782B335AAB00E3204A /* PersistenceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B742B335AAB00E3204A /* PersistenceManager.swift */; };
0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */; };
0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */; };
0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */; };
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; };
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */; };
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */; };
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */; };
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */; };
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */; };
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */; };
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; };
@ -68,7 +68,6 @@
0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */; };
0E49F6BD27D7639000385834 /* EndpointAdvancedView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BC27D7639000385834 /* EndpointAdvancedView+WireGuard.swift */; };
0E49F6BF27D764AF00385834 /* EndpointAdvancedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BE27D764AF00385834 /* EndpointAdvancedView.swift */; };
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E53249627D26B51002565C3 /* ProductManager.swift */; };
0E53249D27D28FC7002565C3 /* Kvitto in Frameworks */ = {isa = PBXBuildFile; productRef = 0E53249C27D28FC7002565C3 /* Kvitto */; };
0E5324A927D2AC55002565C3 /* LongContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5324A827D2AC55002565C3 /* LongContentView.swift */; };
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */; };
@ -106,9 +105,7 @@
0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACFA27C12E5300F85C4B /* VersionView.swift */; };
0E71ACFD27C1321A00F85C4B /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */; };
0E7577DF2817E22C00081CBE /* VPNToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7577DE2817E22C00081CBE /* VPNToggle.swift */; };
0E7A8C0A2A1D410500780F4B /* PersistenceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C092A1D410400780F4B /* PersistenceManager.swift */; };
0E7A8C0C2A1D4A6100780F4B /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0E7A8C0B2A1D4A6100780F4B /* PassepartoutLibrary */; };
0E7A8C0E2A1D4A6E00780F4B /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0E7A8C0D2A1D4A6E00780F4B /* PassepartoutLibrary */; };
0E7A8C0F2A1D54DE00780F4B /* Picker+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */; };
0E7A8C102A1D54DE00780F4B /* Picker+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */; };
0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */; };
@ -138,7 +135,6 @@
0E96D31128721855005EFBCF /* Constants+Mac.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E96D31028721855005EFBCF /* Constants+Mac.swift */; };
0E96D31428721FC3005EFBCF /* ObservableProcessTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E96D31328721FC3005EFBCF /* ObservableProcessTransformer.swift */; };
0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; };
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C232F27F47032007D5FC7 /* IntentsManager.swift */; };
0E9C233327F47E95007D5FC7 /* IntentDispatcher+Activities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */; };
0E9C3B6F27FC573E00D0F02E /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E9C3B6E27FC573E00D0F02E /* CloudKit.framework */; };
0E9E5AEF27B44CF1008C95DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E9E5AE227B44CF1008C95DA /* Localizable.strings */; };
@ -146,11 +142,9 @@
0EA1D84728805EAE00F3CA48 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0EA1D84628805EAE00F3CA48 /* Flags.xcassets */; };
0EA591162733DDDA0096F796 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 0EA591142733DDDA0096F796 /* Intents.intentdefinition */; };
0EA9030B287045F70087BC73 /* SystemMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9030A287045F70087BC73 /* SystemMenu.swift */; };
0EB13BC0290E8C8D003CB654 /* PassepartoutTestsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB13BBF290E8C8D003CB654 /* PassepartoutTestsApp.swift */; };
0EB17EA727D226B400D473B5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; };
0EB17EA927D226C900D473B5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; };
0EB17EAA27D226C900D473B5 /* Constants+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA527D2263700D473B5 /* Constants+App.swift */; };
0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA327D2263700D473B5 /* LocalProduct.swift */; };
0EB17EBA27D2560300D473B5 /* PassepartoutProviders+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */; };
0EB2B1482733FB6F007705AB /* PassepartoutOpenVPNTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
0EB3413027C7761A00483410 /* Binding+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB3412F27C7761A00483410 /* Binding+Extensions.swift */; };
@ -165,6 +159,8 @@
0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */; };
0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC075F27EC587900208AD9 /* SwiftGen+Strings.swift */; };
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE880E281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift */; };
0EBF254E2B334C980045C547 /* StoreKitReceiptReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBF254D2B334C980045C547 /* StoreKitReceiptReader.swift */; };
0EBF25502B334CA00045C547 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBF254F2B334CA00045C547 /* DefaultUpgradeManagerStrategy.swift */; };
0ECB78E9285F5DE300B0E460 /* PassepartoutMac.bundle in Embed Plugins */ = {isa = PBXBuildFile; fileRef = 0ECB78DA285F52F700B0E460 /* PassepartoutMac.bundle */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
0ECF71EE27B6A99300CDB528 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF71ED27B6A99300CDB528 /* AccountView.swift */; };
0ED1A5FD2B2B98CC00A0EA90 /* Constants+Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1A5FC2B2B98CC00A0EA90 /* Constants+Tunnel.swift */; };
@ -217,13 +213,6 @@
remoteGlobalIDString = 0E41BD96286711C3006346B4;
remoteInfo = PassepartoutLauncher;
};
0EB13BBB290E82F1003CB654 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0E57F63020C83FC5008323CF /* Project object */;
proxyType = 1;
remoteGlobalIDString = 0ECF71F327B6D9CD00CDB528;
remoteInfo = WireGuardGo;
};
0EB2B1492733FB6F007705AB /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0E57F63020C83FC5008323CF /* Project object */;
@ -301,9 +290,7 @@
/* Begin PBXFileReference section */
0E021D9A284E68580077EF5D /* CoreContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreContext.swift; sourceTree = "<group>"; };
0E021D9B284E68580077EF5D /* AppContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = "<group>"; };
0E0392762818732D00827C10 /* BuildProducts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildProducts.swift; sourceTree = "<group>"; };
0E039278281890B100827C10 /* AddHostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostView.swift; sourceTree = "<group>"; };
0E0480372A1A4AE000462F2F /* Passepartout.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.xctestplan; sourceTree = "<group>"; };
0E04F0052883462E00BFCE1C /* LightUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightUtils.swift; sourceTree = "<group>"; };
0E04F0082883466500BFCE1C /* DefaultLightUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultLightUtils.swift; sourceTree = "<group>"; };
0E065F102813269500062CAF /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
@ -321,14 +308,12 @@
0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SceneDelegate+Shortcuts.swift"; sourceTree = "<group>"; };
0E0F4C6329C84B5A0022E884 /* LockableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockableView.swift; sourceTree = "<group>"; };
0E0F4C6529C84CF60022E884 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; };
0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Errors+L10n.swift"; sourceTree = "<group>"; };
0E1B5F5B29C506AC00FE7D18 /* DiagnosticsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsSection.swift; sourceTree = "<group>"; };
0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileItem.swift; sourceTree = "<group>"; };
0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProviderProfileItem+ViewModel.swift"; sourceTree = "<group>"; };
0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
0E293850285A70AC002A6E0E /* AppPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreference.swift; sourceTree = "<group>"; };
0E293856285A73BC002A6E0E /* AppContext+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+Shared.swift"; sourceTree = "<group>"; };
0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassepartoutApp.swift; sourceTree = "<group>"; };
0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizerView.swift; sourceTree = "<group>"; };
@ -338,15 +323,18 @@
0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelKit+Extensions.swift"; sourceTree = "<group>"; };
0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelKit+L10n.swift"; sourceTree = "<group>"; };
0E2DE72427DCDF550067B9E1 /* WireGuard+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WireGuard+L10n.swift"; sourceTree = "<group>"; };
0E2E0B6D2B335A8E00E3204A /* AppPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppPreference.swift; sourceTree = "<group>"; };
0E2E0B6E2B335A8E00E3204A /* AppError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
0E2E0B712B335AAB00E3204A /* IntentsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentsManager.swift; sourceTree = "<group>"; };
0E2E0B722B335AAB00E3204A /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0E2E0B732B335AAB00E3204A /* UpgradeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
0E2E0B742B335AAB00E3204A /* PersistenceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistenceManager.swift; sourceTree = "<group>"; };
0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenVPN+L10n.swift"; sourceTree = "<group>"; };
0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Core+L10n.swift"; sourceTree = "<group>"; };
0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericVersionView.swift; sourceTree = "<group>"; };
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; };
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; };
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = "<group>"; };
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = "<group>"; };
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
@ -365,7 +353,6 @@
0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointAdvancedView+OpenVPN.swift"; sourceTree = "<group>"; };
0E49F6BC27D7639000385834 /* EndpointAdvancedView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointAdvancedView+WireGuard.swift"; sourceTree = "<group>"; };
0E49F6BE27D764AF00385834 /* EndpointAdvancedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointAdvancedView.swift; sourceTree = "<group>"; };
0E53249627D26B51002565C3 /* ProductManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductManager.swift; sourceTree = "<group>"; };
0E5324A827D2AC55002565C3 /* LongContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongContentView.swift; sourceTree = "<group>"; };
0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyledPicker.swift; sourceTree = "<group>"; };
0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointView+OpenVPN.swift"; sourceTree = "<group>"; };
@ -404,7 +391,6 @@
0E7577DE2817E22C00081CBE /* VPNToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNToggle.swift; sourceTree = "<group>"; };
0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+OpenVPN.swift"; sourceTree = "<group>"; };
0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+Network.swift"; sourceTree = "<group>"; };
0E7A8C092A1D410400780F4B /* PersistenceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceManager.swift; sourceTree = "<group>"; };
0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostViewModel.swift; sourceTree = "<group>"; };
0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Configuration.swift"; sourceTree = "<group>"; };
0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Extra.swift"; sourceTree = "<group>"; };
@ -428,7 +414,6 @@
0E96D31028721855005EFBCF /* Constants+Mac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constants+Mac.swift"; sourceTree = "<group>"; };
0E96D31328721FC3005EFBCF /* ObservableProcessTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableProcessTransformer.swift; sourceTree = "<group>"; };
0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
0E9C232F27F47032007D5FC7 /* IntentsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentsManager.swift; sourceTree = "<group>"; };
0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntentDispatcher+Activities.swift"; sourceTree = "<group>"; };
0E9C3B6E27FC573E00D0F02E /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
0E9E5AE327B44CF1008C95DA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -460,10 +445,7 @@
0EA5912C2733DDFC0096F796 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = "<group>"; };
0EA5912E2733DDFD0096F796 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = "<group>"; };
0EA9030A287045F70087BC73 /* SystemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemMenu.swift; sourceTree = "<group>"; };
0EB13BAC290E825E003CB654 /* PassepartoutTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PassepartoutTests.app; sourceTree = BUILT_PRODUCTS_DIR; };
0EB13BBF290E8C8D003CB654 /* PassepartoutTestsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassepartoutTestsApp.swift; sourceTree = "<group>"; };
0EB17EA127D2263700D473B5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
0EB17EA327D2263700D473B5 /* LocalProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalProduct.swift; sourceTree = "<group>"; };
0EB17EA527D2263700D473B5 /* Constants+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constants+App.swift"; sourceTree = "<group>"; };
0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PassepartoutProviders+Extensions.swift"; sourceTree = "<group>"; };
0EB3412F27C7761A00483410 /* Binding+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binding+Extensions.swift"; sourceTree = "<group>"; };
@ -487,6 +469,8 @@
0EBE2FD72360F89600F0D5AB /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
0EBE2FD82360F89600F0D5AB /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = "<group>"; };
0EBE880E281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+ProfileRow.swift"; sourceTree = "<group>"; };
0EBF254D2B334C980045C547 /* StoreKitReceiptReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitReceiptReader.swift; sourceTree = "<group>"; };
0EBF254F2B334CA00045C547 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0ECB78DA285F52F700B0E460 /* PassepartoutMac.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassepartoutMac.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
0ECB78E1285F53ED00B0E460 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
0ECB78EA2861D1F300B0E460 /* PassepartoutLibrary */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = PassepartoutLibrary; sourceTree = "<group>"; };
@ -553,14 +537,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
0EB13BA9290E825E003CB654 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0E7A8C0E2A1D4A6E00780F4B /* PassepartoutLibrary in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
0ECB78D7285F52F700B0E460 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -624,6 +600,18 @@
path = Reusable;
sourceTree = "<group>";
};
0E2E0B792B335B3C00E3204A /* Managers */ = {
isa = PBXGroup;
children = (
0EBF254F2B334CA00045C547 /* DefaultUpgradeManagerStrategy.swift */,
0E2E0B712B335AAB00E3204A /* IntentsManager.swift */,
0E2E0B742B335AAB00E3204A /* PersistenceManager.swift */,
0E2E0B732B335AAB00E3204A /* UpgradeManager.swift */,
0E2E0B722B335AAB00E3204A /* UpgradeManagerStrategy.swift */,
);
path = Managers;
sourceTree = "<group>";
};
0E34A2B827CAA8EA00C73B67 /* L10n */ = {
isa = PBXGroup;
children = (
@ -701,31 +689,17 @@
path = Views;
sourceTree = "<group>";
};
0E3A593F2A54ACC900B3FE40 /* Managers */ = {
isa = PBXGroup;
children = (
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */,
0E9C232F27F47032007D5FC7 /* IntentsManager.swift */,
0E7A8C092A1D410400780F4B /* PersistenceManager.swift */,
0E53249627D26B51002565C3 /* ProductManager.swift */,
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */,
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */,
);
path = Managers;
sourceTree = "<group>";
};
0E3A59402A54AD4300B3FE40 /* Domain */ = {
isa = PBXGroup;
children = (
0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */,
0E293850285A70AC002A6E0E /* AppPreference.swift */,
0E0392762818732D00827C10 /* BuildProducts.swift */,
0EA591142733DDDA0096F796 /* Intents.intentdefinition */,
0E2E0B6E2B335A8E00E3204A /* AppError.swift */,
0E2E0B6D2B335A8E00E3204A /* AppPreference.swift */,
0EA591122733DD4E0096F796 /* IntentDispatcher.swift */,
0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */,
0EB17EA327D2263700D473B5 /* LocalProduct.swift */,
0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */,
0EA591142733DDDA0096F796 /* Intents.intentdefinition */,
0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */,
0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */,
0EBF254D2B334C980045C547 /* StoreKitReceiptReader.swift */,
);
path = Domain;
sourceTree = "<group>";
@ -830,9 +804,7 @@
children = (
0EE315DB2733104700F5D461 /* Packages */,
0E23B4A12298559800304C30 /* Config.xcconfig */,
0E0480372A1A4AE000462F2F /* Passepartout.xctestplan */,
0E9AA982259F7674003FAFF1 /* Passepartout */,
0EB13BAD290E825E003CB654 /* PassepartoutTests */,
0E57F63920C83FC5008323CF /* Products */,
374B9F085E1148C37CF9117A /* Frameworks */,
);
@ -846,7 +818,6 @@
0ED2B34A27D3C77800FD8EA9 /* PassepartoutWireGuardTunnel.appex */,
0E41BD97286711C3006346B4 /* PassepartoutLauncher.app */,
0ECB78DA285F52F700B0E460 /* PassepartoutMac.bundle */,
0EB13BAC290E825E003CB654 /* PassepartoutTests.app */,
);
name = Products;
sourceTree = "<group>";
@ -902,9 +873,9 @@
0E29385A285A749E002A6E0E /* Context */,
0E3A59402A54AD4300B3FE40 /* Domain */,
0E49F6C927DB398100385834 /* Extensions */,
0E3A593F2A54ACC900B3FE40 /* Managers */,
0E34A2B827CAA8EA00C73B67 /* L10n */,
0E5467F12867A52B00F74D1C /* Mac */,
0E2E0B792B335B3C00E3204A /* Managers */,
0E2C171C27CB6307007E8488 /* Reusable */,
0E35C0AE280EF8A80071FA35 /* Views */,
0E6059CA27FCC5DE003F4063 /* Assets.xcassets */,
@ -925,14 +896,6 @@
path = App;
sourceTree = "<group>";
};
0EB13BAD290E825E003CB654 /* PassepartoutTests */ = {
isa = PBXGroup;
children = (
0EB13BBF290E8C8D003CB654 /* PassepartoutTestsApp.swift */,
);
path = PassepartoutTests;
sourceTree = "<group>";
};
0ECB78D1285F4F4000B0E460 /* AppShared */ = {
isa = PBXGroup;
children = (
@ -1100,27 +1063,6 @@
productReference = 0E57F63820C83FC5008323CF /* Passepartout.app */;
productType = "com.apple.product-type.application";
};
0EB13BAB290E825E003CB654 /* PassepartoutTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0EB13BBA290E8260003CB654 /* Build configuration list for PBXNativeTarget "PassepartoutTests" */;
buildPhases = (
0EB13BA8290E825E003CB654 /* Sources */,
0EB13BA9290E825E003CB654 /* Frameworks */,
0EB13BAA290E825E003CB654 /* Resources */,
);
buildRules = (
);
dependencies = (
0EB13BBC290E82F1003CB654 /* PBXTargetDependency */,
);
name = PassepartoutTests;
packageProductDependencies = (
0E7A8C0D2A1D4A6E00780F4B /* PassepartoutLibrary */,
);
productName = PassepartoutTests;
productReference = 0EB13BAC290E825E003CB654 /* PassepartoutTests.app */;
productType = "com.apple.product-type.application";
};
0ECB78D9285F52F700B0E460 /* PassepartoutMac */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0ECB78DD285F52F700B0E460 /* Build configuration list for PBXNativeTarget "PassepartoutMac" */;
@ -1188,7 +1130,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1400;
LastSwiftUpdateCheck = 1510;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "Davide De Rosa";
TargetAttributes = {
@ -1213,10 +1155,6 @@
};
};
};
0EB13BAB290E825E003CB654 = {
CreatedOnToolsVersion = 14.0.1;
LastSwiftMigration = 1400;
};
0ECB78D9285F52F700B0E460 = {
CreatedOnToolsVersion = 13.4;
LastSwiftMigration = 1340;
@ -1269,7 +1207,6 @@
0E57F63720C83FC5008323CF /* Passepartout */,
0ECB78D9285F52F700B0E460 /* PassepartoutMac */,
0E41BD96286711C3006346B4 /* PassepartoutLauncher */,
0EB13BAB290E825E003CB654 /* PassepartoutTests */,
0EDE8DBE20C86910004C739C /* OpenVPNTunnel */,
0ECF71F327B6D9CD00CDB528 /* WireGuardGo */,
0ED2B33E27D3C77800FD8EA9 /* WireGuardTunnel */,
@ -1300,13 +1237,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
0EB13BAA290E825E003CB654 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
0ECB78D8285F52F700B0E460 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -1439,6 +1369,7 @@
0EA591162733DDDA0096F796 /* Intents.intentdefinition in Sources */,
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */,
0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */,
0E2E0B762B335AAB00E3204A /* UpgradeManagerStrategy.swift in Sources */,
0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */,
0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Extensions.swift in Sources */,
0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */,
@ -1448,13 +1379,13 @@
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */,
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */,
0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */,
0EBF25502B334CA00045C547 /* DefaultUpgradeManagerStrategy.swift in Sources */,
0EB90CC129C25BBD00E64628 /* InteractiveConnectionView.swift in Sources */,
0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */,
0E04F0092883466500BFCE1C /* DefaultLightUtils.swift in Sources */,
0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */,
0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */,
0E7A8C0F2A1D54DE00780F4B /* Picker+OpenVPN.swift in Sources */,
0E0392772818732D00827C10 /* BuildProducts.swift in Sources */,
0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */,
0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */,
0E3FC6862867A3F9009B851C /* AppDelegate.swift in Sources */,
@ -1478,7 +1409,6 @@
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
0E5468002867AC9A00F74D1C /* MacUtils.swift in Sources */,
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */,
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */,
0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */,
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */,
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Restricted.swift in Sources */,
@ -1494,12 +1424,15 @@
0E0838FA2877325A00A34EC0 /* LightProviderManager.swift in Sources */,
0ED7D62F2867328A009F2F8F /* Constants+Library.swift in Sources */,
0E5467FA2867AA0A00F74D1C /* MacBundleDelegate.swift in Sources */,
0EBF254E2B334C980045C547 /* StoreKitReceiptReader.swift in Sources */,
0E0BD27327B2EA2C00583AC5 /* MainView.swift in Sources */,
0E0F4C5A29C761850022E884 /* SceneDelegate.swift in Sources */,
0EB17EBA27D2560300D473B5 /* PassepartoutProviders+Extensions.swift in Sources */,
0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */,
0E2E0B6F2B335A8E00E3204A /* AppPreference.swift in Sources */,
0E5468062867AEC500F74D1C /* MacMenu.swift in Sources */,
0E71ACE327C0F2E400F85C4B /* Providers+L10n.swift in Sources */,
0E2E0B752B335AAB00E3204A /* IntentsManager.swift in Sources */,
0E71ACF127C1073800F85C4B /* ProviderLocationView.swift in Sources */,
0E2A8D4F27B04BBA00207D04 /* OrganizerView.swift in Sources */,
0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */,
@ -1509,13 +1442,11 @@
0E96D2FC2871D94E005EFBCF /* DefaultLightProfileManager.swift in Sources */,
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */,
0E7A8C102A1D54DE00780F4B /* Picker+Network.swift in Sources */,
0E293851285A70AC002A6E0E /* AppPreference.swift in Sources */,
0E2E0B702B335A8E00E3204A /* AppError.swift in Sources */,
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */,
0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */,
0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */,
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */,
0E021D9C284E68580077EF5D /* CoreContext.swift in Sources */,
0E7A8C0A2A1D410500780F4B /* PersistenceManager.swift in Sources */,
A38D607728AFCFD20005C271 /* SettingsView.swift in Sources */,
0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */,
0E7577DF2817E22C00081CBE /* VPNToggle.swift in Sources */,
@ -1525,7 +1456,7 @@
0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */,
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */,
0E2E0B772B335AAB00E3204A /* UpgradeManager.swift in Sources */,
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */,
0E039279281890B100827C10 /* AddHostView.swift in Sources */,
0E5467F72867A57000F74D1C /* MacBridge.swift in Sources */,
@ -1536,14 +1467,11 @@
0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */,
0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */,
0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */,
0E2E0B782B335AAB00E3204A /* PersistenceManager.swift in Sources */,
0EF8C5A828213C510053CE89 /* OrganizerView+Profiles.swift in Sources */,
0E3CD483280DAE92007075C0 /* ProfileView+MainMenu.swift in Sources */,
0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */,
0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */,
0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */,
0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */,
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */,
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */,
0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */,
0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */,
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */,
@ -1566,14 +1494,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
0EB13BA8290E825E003CB654 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0EB13BC0290E8C8D003CB654 /* PassepartoutTestsApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
0ED2B34027D3C77800FD8EA9 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -1603,11 +1523,6 @@
target = 0E41BD96286711C3006346B4 /* PassepartoutLauncher */;
targetProxy = 0E41BDAA286713F6006346B4 /* PBXContainerItemProxy */;
};
0EB13BBC290E82F1003CB654 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */;
targetProxy = 0EB13BBB290E82F1003CB654 /* PBXContainerItemProxy */;
};
0EB2B14A2733FB6F007705AB /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 0EDE8DBE20C86910004C739C /* OpenVPNTunnel */;
@ -1710,7 +1625,6 @@
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = DTDYD63ZX9;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Passepartout/Launcher/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -1735,7 +1649,6 @@
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = DTDYD63ZX9;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Passepartout/Launcher/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -1789,6 +1702,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CURRENT_PROJECT_VERSION = 3548;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "$(CFG_TEAM_ID)";
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -1856,6 +1770,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CURRENT_PROJECT_VERSION = 3548;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "$(CFG_TEAM_ID)";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -1889,7 +1804,6 @@
CODE_SIGN_ENTITLEMENTS = Passepartout/App/App.entitlements;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 3548;
DEVELOPMENT_TEAM = DTDYD63ZX9;
INFOPLIST_FILE = Passepartout/App/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1916,7 +1830,6 @@
CODE_SIGN_ENTITLEMENTS = Passepartout/App/App.entitlements;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 3548;
DEVELOPMENT_TEAM = DTDYD63ZX9;
INFOPLIST_FILE = Passepartout/App/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1932,75 +1845,6 @@
};
name = Release;
};
0EB13BB8290E8260003CB654 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.algoritmico.ios.PassepartoutTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
0EB13BB9290E8260003CB654 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.algoritmico.ios.PassepartoutTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
0ECB78DB285F52F700B0E460 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -2012,7 +1856,6 @@
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 3548;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Passepartout/Mac/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LD_RUNPATH_SEARCH_PATHS = (
@ -2046,7 +1889,6 @@
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 3548;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Passepartout/Mac/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LD_RUNPATH_SEARCH_PATHS = (
@ -2076,7 +1918,6 @@
COPY_PHASE_STRIP = NO;
DEBUGGING_SYMBOLS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = DTDYD63ZX9;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
@ -2094,7 +1935,6 @@
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = DTDYD63ZX9;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CFLAGS = "";
@ -2108,7 +1948,6 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = DTDYD63ZX9;
INFOPLIST_FILE = Passepartout/Tunnel/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -2129,7 +1968,6 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = DTDYD63ZX9;
INFOPLIST_FILE = Passepartout/Tunnel/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -2150,7 +1988,6 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = DTDYD63ZX9;
INFOPLIST_FILE = Passepartout/Tunnel/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -2171,7 +2008,6 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = DTDYD63ZX9;
INFOPLIST_FILE = Passepartout/Tunnel/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -2217,15 +2053,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0EB13BBA290E8260003CB654 /* Build configuration list for PBXNativeTarget "PassepartoutTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0EB13BB8290E8260003CB654 /* Debug */,
0EB13BB9290E8260003CB654 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0ECB78DD285F52F700B0E460 /* Build configuration list for PBXNativeTarget "PassepartoutMac" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -2285,10 +2112,6 @@
isa = XCSwiftPackageProductDependency;
productName = PassepartoutLibrary;
};
0E7A8C0D2A1D4A6E00780F4B /* PassepartoutLibrary */ = {
isa = XCSwiftPackageProductDependency;
productName = PassepartoutLibrary;
};
0ED2B33827D3C49800FD8EA9 /* OpenVPNAppExtension */ = {
isa = XCSwiftPackageProductDependency;
productName = OpenVPNAppExtension;

View File

@ -68,7 +68,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -78,54 +79,6 @@
ReferencedContainer = "container:Passepartout.xcodeproj">
</BuildableReference>
</MacroExpansion>
<TestPlans>
<TestPlanReference
reference = "container:Passepartout.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutCoreTests"
BuildableName = "PassepartoutCoreTests"
BlueprintName = "PassepartoutCoreTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutProvidersTests"
BuildableName = "PassepartoutProvidersTests"
BlueprintName = "PassepartoutProvidersTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutServicesTests"
BuildableName = "PassepartoutServicesTests"
BlueprintName = "PassepartoutServicesTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutVPNTests"
BuildableName = "PassepartoutVPNTests"
BlueprintName = "PassepartoutVPNTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"

View File

@ -1,118 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB13BAB290E825E003CB654"
BuildableName = "PassepartoutTests.app"
BlueprintName = "PassepartoutTests"
ReferencedContainer = "container:Passepartout.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutCoreTests"
BuildableName = "PassepartoutCoreTests"
BlueprintName = "PassepartoutCoreTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutProvidersTests"
BuildableName = "PassepartoutProvidersTests"
BlueprintName = "PassepartoutProvidersTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutServicesTests"
BuildableName = "PassepartoutServicesTests"
BlueprintName = "PassepartoutServicesTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutVPNTests"
BuildableName = "PassepartoutVPNTests"
BlueprintName = "PassepartoutVPNTests"
ReferencedContainer = "container:PassepartoutLibrary">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB13BAB290E825E003CB654"
BuildableName = "PassepartoutTests.app"
BlueprintName = "PassepartoutTests"
ReferencedContainer = "container:Passepartout.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB13BAB290E825E003CB654"
BuildableName = "PassepartoutTests.app"
BlueprintName = "PassepartoutTests"
ReferencedContainer = "container:Passepartout.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,73 +0,0 @@
{
"configurations" : [
{
"id" : "38DB0D3B-F3E5-4BD4-B413-B9C92AE7A2F5",
"name" : "Configuration 1",
"options" : {
}
}
],
"defaultOptions" : {
"codeCoverage" : false,
"commandLineArgumentEntries" : [
{
"argument" : "-com.apple.CoreData.SQLDebug 0"
},
{
"argument" : "-com.apple.CoreData.Logging.stderr 0 "
},
{
"argument" : "-com.apple.CoreData.CloudKitDebug 0"
},
{
"argument" : "-com.apple.CoreData.ConcurrencyDebug 0"
},
{
"argument" : "-com.apple.CoreData.MigrationDebug 0"
}
],
"environmentVariableEntries" : [
{
"key" : "APP_TYPE",
"value" : "2"
},
{
"key" : "LOG_LEVEL",
"value" : "0"
}
],
"targetForVariableExpansion" : {
"containerPath" : "container:Passepartout.xcodeproj",
"identifier" : "0E57F63720C83FC5008323CF",
"name" : "Passepartout"
}
},
"testTargets" : [
{
"enabled" : false,
"target" : {
"containerPath" : "container:PassepartoutLibrary",
"identifier" : "PassepartoutCoreTests",
"name" : "PassepartoutCoreTests"
}
},
{
"enabled" : false,
"target" : {
"containerPath" : "container:PassepartoutLibrary",
"identifier" : "PassepartoutProvidersTests",
"name" : "PassepartoutProvidersTests"
}
},
{
"enabled" : false,
"target" : {
"containerPath" : "container:PassepartoutLibrary",
"identifier" : "PassepartoutVPNTests",
"name" : "PassepartoutVPNTests"
}
}
],
"version" : 1
}

View File

@ -52,15 +52,15 @@ extension Constants {
}
enum InApp {
static var overriddenAppType: ProductManager.AppType? {
static var overriddenAppType: AppType? {
if let envString = ProcessInfo.processInfo.environment["APP_TYPE"],
let envValue = Int(envString),
let testAppType = ProductManager.AppType(rawValue: envValue) {
let testAppType = AppType(rawValue: envValue) {
return testAppType
}
if let infoValue: Int = bundleConfig("app_type"),
let testAppType = ProductManager.AppType(rawValue: infoValue) {
let testAppType = AppType(rawValue: infoValue) {
return testAppType
}
@ -228,9 +228,6 @@ extension Constants {
// @available(*, deprecated, message: "File importer stops showing again after closing with swipe down")
static let xxxPresentFileImporter = 200
// @available(*, deprecated, message: "Edited shortcut is outdated in delegate")
static let xxxReloadEditedShortcut = 200
}
enum Rating {

View File

@ -57,6 +57,8 @@ final class AppContext {
upgradeManager.migrate(toVersion: Constants.Global.appVersionNumber)
productManager = ProductManager(
inApp: StoreKitInApp<LocalProduct>(),
receiptReader: StoreKitReceiptReader(),
overriddenAppType: Constants.InApp.overriddenAppType,
buildProducts: Constants.InApp.buildProducts
)

View File

@ -0,0 +1,79 @@
//
// StoreKitReceiptReader.swift
// Passepartout
//
// Created by Davide De Rosa on 12/20/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import Kvitto
import PassepartoutLibrary
struct StoreKitReceiptReader: ReceiptReader {
func receipt(for appType: AppType) -> InAppReceipt? {
guard let url = Bundle.main.appStoreReceiptURL else {
pp_log.warning("No App Store receipt found!")
return nil
}
let receipt = Receipt(contentsOfURL: url)
let fallbackReceipt: Receipt? = {
// in TestFlight, attempt fallback to existing release receipt
if appType == .beta {
guard let receipt else {
let releaseUrl = url.deletingLastPathComponent().appendingPathComponent("receipt")
guard releaseUrl != url else {
assertionFailure("How can release URL be equal to sandbox URL in TestFlight?")
return nil
}
pp_log.warning("Sandbox receipt not found, falling back to Release receipt")
return Receipt(contentsOfURL: releaseUrl)
}
return receipt
}
return receipt
}()
return fallbackReceipt?.asInAppReceipt
}
}
private extension Receipt {
var asInAppReceipt: InAppReceipt {
var originalBuildNumber: Int?
var purchaseReceipts: [InAppReceipt.PurchaseReceipt]?
if let originalAppVersion, let buildNumber = Int(originalAppVersion) {
originalBuildNumber = buildNumber
}
if let inAppPurchaseReceipts {
purchaseReceipts = inAppPurchaseReceipts
.map {
InAppReceipt.PurchaseReceipt(productIdentifier: $0.productIdentifier,
cancellationDate: $0.cancellationDate,
originalPurchaseDate: $0.originalPurchaseDate)
}
}
return InAppReceipt(originalBuildNumber: originalBuildNumber,
purchaseReceipts: purchaseReceipts)
}
}

View File

@ -26,11 +26,11 @@
import Foundation
import PassepartoutLibrary
public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
public init() {
final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
init() {
}
public func migrate(store: KeyValueStore, lastVersion: String?) {
func migrate(store: KeyValueStore, lastVersion: String?) {
// legacy check before lastVersion was even stored
let isUpgradeFromBefore_2_2_0: Bool? = store.value(forLocation: UpgradeManager.StoreKey.existingKeyBefore_2_2_0)
@ -47,6 +47,6 @@ public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
pp_log.debug("Upgrade from \(lastVersion)")
}
public func migrateData() {
func migrateData() {
}
}

View File

@ -92,7 +92,7 @@ extension IntentsManager: INUIEditVoiceShortcutViewControllerDelegate {
//
// so damn it, reload manually after a delay
Task {
await Task.maybeWait(forMilliseconds: Constants.Delays.xxxReloadEditedShortcut)
await Task.maybeWait(forMilliseconds: 200)
await reloadShortcuts()
}
}

View File

@ -27,7 +27,7 @@ import Foundation
import PassepartoutLibrary
@MainActor
public final class UpgradeManager: ObservableObject {
final class UpgradeManager: ObservableObject {
// MARK: Initialization
@ -37,9 +37,9 @@ public final class UpgradeManager: ObservableObject {
// MARK: State
@Published public private(set) var isDoingMigrations = true
@Published private(set) var isDoingMigrations = true
public init(
init(
store: KeyValueStore,
strategy: UpgradeManagerStrategy
) {
@ -47,14 +47,14 @@ public final class UpgradeManager: ObservableObject {
self.strategy = strategy
}
public func migrate(toVersion currentVersion: String) {
func migrate(toVersion currentVersion: String) {
if let lastVersion, currentVersion > lastVersion {
strategy.migrate(store: store, lastVersion: lastVersion)
}
lastVersion = currentVersion
}
public func migrateData(profileManager: ProfileManager) {
func migrateData(profileManager: ProfileManager) {
isDoingMigrations = false
}
}

View File

@ -24,7 +24,7 @@
//
import Foundation
import PassepartoutCore
import PassepartoutLibrary
public protocol UpgradeManagerStrategy {
func migrate(store: KeyValueStore, lastVersion: String?)

View File

@ -24,7 +24,6 @@
//
import PassepartoutLibrary
import StoreKit
import SwiftUI
struct DonateView: View {
@ -113,7 +112,7 @@ private extension DonateView {
}
@ViewBuilder
func productRow(_ product: SKProduct) -> some View {
func productRow(_ product: InAppProduct) -> some View {
HStack {
Button(product.localizedTitle) {
purchaseProduct(product)
@ -132,10 +131,10 @@ private extension DonateView {
}
private extension ProductManager {
var donations: [SKProduct] {
var donations: [InAppProduct] {
products.filter { product in
LocalProduct.allDonations.contains {
$0.matchesStoreKitProduct(product)
$0.matchesInAppProduct(product)
}
}.sorted {
$0.price.decimalValue < $1.price.decimalValue
@ -146,7 +145,7 @@ private extension ProductManager {
// MARK: -
private extension DonateView {
func purchaseProduct(_ product: SKProduct) {
func purchaseProduct(_ product: InAppProduct) {
pendingDonationIdentifier = product.productIdentifier
productManager.purchase(product, completionHandler: handlePurchaseResult)
}

View File

@ -24,13 +24,12 @@
//
import PassepartoutLibrary
import StoreKit
import SwiftUI
extension PaywallView {
struct PurchaseView: View {
fileprivate enum PurchaseState {
case purchasing(SKProduct)
case purchasing(InAppProduct)
case restoring
}
@ -70,7 +69,7 @@ extension PaywallView {
}
private struct PurchaseRow: View {
var product: SKProduct?
var product: InAppProduct?
let title: String
@ -93,7 +92,7 @@ private struct PurchaseRow: View {
}
}
private typealias RowModel = (product: SKProduct, extra: String?)
private typealias RowModel = (product: InAppProduct, extra: String?)
// MARK: -
@ -136,14 +135,14 @@ private extension PaywallView.PurchaseView {
}
private extension PaywallView.PurchaseView {
var skFeature: SKProduct? {
var skFeature: InAppProduct? {
guard let feature = feature else {
return nil
}
return productManager.product(withIdentifier: feature)
}
var skPlatformVersion: SKProduct? {
var skPlatformVersion: InAppProduct? {
#if targetEnvironment(macCatalyst)
productManager.product(withIdentifier: .fullVersion_macOS)
#else
@ -152,7 +151,7 @@ private extension PaywallView.PurchaseView {
}
// hide full version if already bought the other platform version
var skFullVersion: SKProduct? {
var skFullVersion: InAppProduct? {
#if targetEnvironment(macCatalyst)
guard !productManager.hasPurchased(.fullVersion_iOS) else {
return nil
@ -209,14 +208,14 @@ private extension PurchaseRow {
@ViewBuilder
var actionButton: some View {
if let product = product {
if let product {
purchaseButton(product)
} else {
restoreButton
}
}
func purchaseButton(_ product: SKProduct) -> some View {
func purchaseButton(_ product: InAppProduct) -> some View {
HStack {
Button(title, action: action)
Spacer()
@ -245,7 +244,7 @@ private extension PurchaseRow {
// MARK: -
private extension PaywallView.PurchaseView {
func purchaseProduct(_ product: SKProduct) {
func purchaseProduct(_ product: InAppProduct) {
purchaseState = .purchasing(product)
productManager.purchase(product) {

View File

@ -24,6 +24,7 @@
//
import SwiftUI
import PassepartoutLibrary
struct PaywallView: View {
@ObservedObject private var productManager: ProductManager

View File

@ -1,45 +0,0 @@
{
"configurations" : [
{
"id" : "2A4B150B-39A2-417B-98FD-1C5C74C26E3E",
"name" : "Test Scheme Action",
"options" : {
}
}
],
"defaultOptions" : {
"codeCoverage" : false
},
"testTargets" : [
{
"target" : {
"containerPath" : "container:",
"identifier" : "PassepartoutCoreTests",
"name" : "PassepartoutCoreTests"
}
},
{
"target" : {
"containerPath" : "container:",
"identifier" : "PassepartoutProvidersTests",
"name" : "PassepartoutProvidersTests"
}
},
{
"target" : {
"containerPath" : "container:",
"identifier" : "PassepartoutServicesTests",
"name" : "PassepartoutServicesTests"
}
},
{
"target" : {
"containerPath" : "container:",
"identifier" : "PassepartoutVPNTests",
"name" : "PassepartoutVPNTests"
}
}
],
"version" : 1
}

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutCoreTests"
BuildableName = "PassepartoutCoreTests"
BlueprintName = "PassepartoutCoreTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,39 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutLibrary_PassepartoutVPNImpl"
BuildableName = "PassepartoutLibrary_PassepartoutVPNImpl"
BlueprintName = "PassepartoutLibrary_PassepartoutVPNImpl"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutLibrary_PassepartoutCoreTests"
BuildableName = "PassepartoutLibrary_PassepartoutCoreTests"
BlueprintName = "PassepartoutLibrary_PassepartoutCoreTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
@ -56,9 +28,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutVPN"
BuildableName = "PassepartoutVPN"
BlueprintName = "PassepartoutVPN"
BlueprintIdentifier = "PassepartoutLibrary"
BuildableName = "PassepartoutLibrary"
BlueprintName = "PassepartoutLibrary"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
@ -76,62 +48,6 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutCore"
BuildableName = "PassepartoutCore"
BlueprintName = "PassepartoutCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutProviders"
BuildableName = "PassepartoutProviders"
BlueprintName = "PassepartoutProviders"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutServices"
BuildableName = "PassepartoutServices"
BlueprintName = "PassepartoutServices"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutVPNImpl"
BuildableName = "PassepartoutVPNImpl"
BlueprintName = "PassepartoutVPNImpl"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
@ -146,6 +62,20 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutFrontendTests"
BuildableName = "PassepartoutFrontendTests"
BlueprintName = "PassepartoutFrontendTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
@ -160,90 +90,6 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutServicesTests"
BuildableName = "PassepartoutServicesTests"
BlueprintName = "PassepartoutServicesTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutVPNTests"
BuildableName = "PassepartoutVPNTests"
BlueprintName = "PassepartoutVPNTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutServicesImpl"
BuildableName = "PassepartoutServicesImpl"
BlueprintName = "PassepartoutServicesImpl"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutProvidersImpl"
BuildableName = "PassepartoutProvidersImpl"
BlueprintName = "PassepartoutProvidersImpl"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutLibrary_PassepartoutProvidersImpl"
BuildableName = "PassepartoutLibrary_PassepartoutProvidersImpl"
BlueprintName = "PassepartoutLibrary_PassepartoutProvidersImpl"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutLibrary"
BuildableName = "PassepartoutLibrary"
BlueprintName = "PassepartoutLibrary"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@ -263,6 +109,16 @@
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutFrontendTests"
BuildableName = "PassepartoutFrontendTests"
BlueprintName = "PassepartoutFrontendTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
@ -283,16 +139,6 @@
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutVPNTests"
BuildableName = "PassepartoutVPNTests"
BlueprintName = "PassepartoutVPNTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
@ -315,9 +161,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutLibrary_PassepartoutVPNImpl"
BuildableName = "PassepartoutLibrary_PassepartoutVPNImpl"
BlueprintName = "PassepartoutLibrary_PassepartoutVPNImpl"
BlueprintIdentifier = "OpenVPNAppExtension"
BuildableName = "OpenVPNAppExtension"
BlueprintName = "OpenVPNAppExtension"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@ -26,13 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:.swiftpm/PassepartoutLibrary.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutProvidersTests"
BuildableName = "PassepartoutProvidersTests"
BlueprintName = "PassepartoutProvidersTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PassepartoutVPNTests"
BuildableName = "PassepartoutVPNTests"
BlueprintName = "PassepartoutVPNTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -13,6 +13,14 @@ let package = Package(
.library(
name: "PassepartoutLibrary",
targets: ["PassepartoutLibrary"]),
.library(
name: "PassepartoutInterfaces",
targets: [
"PassepartoutCore",
"PassepartoutFrontend",
"PassepartoutProviders",
"PassepartoutServices"
]),
.library(
name: "OpenVPNAppExtension",
targets: ["OpenVPNAppExtension"]),
@ -72,6 +80,11 @@ let package = Package(
.product(name: "TunnelKitOpenVPN", package: "TunnelKit"),
.product(name: "TunnelKitWireGuard", package: "TunnelKit"),
]),
.target(
name: "PassepartoutFrontend",
dependencies: [
"PassepartoutProviders"
]),
.target(
name: "PassepartoutProviders",
dependencies: [
@ -104,17 +117,20 @@ let package = Package(
// MARK: Tests
.testTarget(
name: "PassepartoutVPNTests",
dependencies: ["PassepartoutVPNImpl"]),
.testTarget(
name: "PassepartoutProvidersTests",
dependencies: ["PassepartoutProvidersImpl"]),
.testTarget(
name: "PassepartoutCoreTests",
dependencies: ["PassepartoutCore"],
resources: [
.process("Resources")
])
]),
.testTarget(
name: "PassepartoutFrontendTests",
dependencies: ["PassepartoutFrontend"]),
.testTarget(
name: "PassepartoutProvidersTests",
dependencies: ["PassepartoutProviders"]),
.testTarget(
name: "PassepartoutServicesTests",
dependencies: ["PassepartoutServices"])
]
)

View File

@ -24,9 +24,8 @@
//
import Foundation
import StoreKit
public enum InAppPurchaseResult {
public enum InAppPurchaseResult: Sendable {
case done
case cancelled
@ -36,188 +35,57 @@ public enum InAppError: Error {
case unknown
}
public final class InApp<PID: Hashable & RawRepresentable>: NSObject,
SKProductsRequestDelegate, SKPaymentTransactionObserver
where PID.RawValue == String {
public struct InAppProduct: Sendable {
public let productIdentifier: String
public typealias ProductObserver = ([PID: SKProduct]) -> Void
public let localizedTitle: String
public typealias TransactionObserver = (Result<InAppPurchaseResult, Error>) -> Void
public let localizedDescription: String
public typealias RestoreObserver = (Bool, PID?, Error?) -> Void
public let price: NSDecimalNumber
public typealias FailureObserver = (Error) -> Void
private var productsMap: [PID: SKProduct]
public var products: [SKProduct] {
[SKProduct](productsMap.values)
}
private var productObservers: [ProductObserver]
private var productFailureObserver: FailureObserver?
private var transactionObservers: [String: TransactionObserver]
private var restoreObservers: [RestoreObserver]
public override init() {
productsMap = [:]
productObservers = []
productFailureObserver = nil
transactionObservers = [:]
restoreObservers = []
super.init()
SKPaymentQueue.default().add(self)
}
deinit {
SKPaymentQueue.default().remove(self)
}
public func requestProducts(withIdentifiers identifiers: [PID], completionHandler: ProductObserver?, failureHandler: FailureObserver?) {
let req = SKProductsRequest(productIdentifiers: Set(identifiers.map { $0.rawValue }))
req.delegate = self
if let observer = completionHandler {
productObservers.append(observer)
}
productFailureObserver = failureHandler
req.start()
}
@discardableResult
public func purchase(productWithIdentifier productIdentifier: PID, completionHandler: @escaping TransactionObserver) -> Bool {
guard let product = productsMap[productIdentifier] else {
return false
}
purchase(product: product, completionHandler: completionHandler)
return true
}
public func purchase(product: SKProduct, completionHandler: @escaping TransactionObserver) {
let queue = SKPaymentQueue.default()
let payment = SKPayment(product: product)
transactionObservers[product.productIdentifier] = completionHandler
queue.add(payment)
}
public func restorePurchases(completionHandler: @escaping RestoreObserver) {
let queue = SKPaymentQueue.default()
restoreObservers.append(completionHandler)
queue.restoreCompletedTransactions()
}
public func product(withIdentifier productIdentifier: PID) -> SKProduct? {
productsMap[productIdentifier]
}
// MARK: SKProductsRequestDelegate
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
DispatchQueue.main.async {
self.receiveProducts(response.products)
}
}
public func request(_ request: SKRequest, didFailWithError error: Error) {
if request as? SKProductsRequest != nil {
DispatchQueue.main.async {
self.productFailureObserver?(error)
}
}
DispatchQueue.main.async {
self.transactionObservers.removeAll()
}
}
private func receiveProducts(_ products: [SKProduct]) {
productsMap.removeAll()
for p in products {
guard let pid = PID(rawValue: p.productIdentifier) else {
continue
}
productsMap[pid] = p
}
productObservers.forEach { $0(productsMap) }
productObservers.removeAll()
}
// MARK: SKPaymentTransactionObserver
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
DispatchQueue.main.async {
let currentRestoreObservers = self.restoreObservers
for tx in transactions {
let productIdentifier = tx.payment.productIdentifier
let observer = self.transactionObservers[productIdentifier]
switch tx.transactionState {
case .purchased:
queue.finishTransaction(tx)
observer?(.success(.done))
case .restored:
queue.finishTransaction(tx)
observer?(.success(.done))
for r in currentRestoreObservers {
guard let pid = PID(rawValue: productIdentifier) else {
continue
}
r(false, pid, nil)
}
case .failed:
queue.finishTransaction(tx)
if let skError = tx.error as? SKError, skError.code == .paymentCancelled {
observer?(.success(.cancelled))
} else {
observer?(.failure(tx.error ?? InAppError.unknown))
for r in currentRestoreObservers {
guard let pid = PID(rawValue: productIdentifier) else {
continue
}
r(false, pid, tx.error)
}
}
default:
break
}
}
}
}
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
DispatchQueue.main.async {
for r in self.restoreObservers {
r(true, nil, nil)
}
self.restoreObservers.removeAll()
}
}
public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
DispatchQueue.main.async {
for r in self.restoreObservers {
r(true, nil, error)
}
self.restoreObservers.removeAll()
}
}
public let localizedPrice: String?
}
extension SKProduct {
private var localizedCurrencyFormatter: NumberFormatter {
let fmt = NumberFormatter()
fmt.locale = priceLocale
fmt.numberStyle = .currency
return fmt
public protocol InAppProtocol {
associatedtype ProductIdentifier: Hashable
func canMakePurchases() -> Bool
func requestProducts(withIdentifiers identifiers: [ProductIdentifier]) async throws -> [ProductIdentifier: InAppProduct]
func purchase(productWithIdentifier productIdentifier: ProductIdentifier) async throws -> InAppPurchaseResult
func restorePurchases() async throws
func products() -> [InAppProduct]
func product(withIdentifier productIdentifier: ProductIdentifier) -> InAppProduct?
func setTransactionsObserver(_ block: @escaping () -> Void)
}
public struct InAppReceipt: Sendable {
public struct PurchaseReceipt: Sendable {
public let productIdentifier: String?
public let cancellationDate: Date?
public let originalPurchaseDate: Date?
public init(productIdentifier: String?, cancellationDate: Date?, originalPurchaseDate: Date?) {
self.productIdentifier = productIdentifier
self.cancellationDate = cancellationDate
self.originalPurchaseDate = originalPurchaseDate
}
}
public var localizedPrice: String? {
localizedCurrencyFormatter.string(from: price)
public let originalBuildNumber: Int?
public let purchaseReceipts: [PurchaseReceipt]?
public init(originalBuildNumber: Int?, purchaseReceipts: [PurchaseReceipt]?) {
self.originalBuildNumber = originalBuildNumber
self.purchaseReceipts = purchaseReceipts
}
}

View File

@ -0,0 +1,297 @@
//
// StoreKitInApp.swift
// Passepartout
//
// Created by Davide De Rosa on 12/19/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import StoreKit
@MainActor
public final class StoreKitInApp<PID>: InAppProtocol where PID: Hashable & RawRepresentable,
PID.RawValue == String {
public typealias ProductIdentifier = PID
private let impl: StoreKitInAppImpl<PID>
public init() {
impl = StoreKitInAppImpl()
}
public nonisolated func canMakePurchases() -> Bool {
SKPaymentQueue.canMakePayments()
}
public func requestProducts(withIdentifiers identifiers: [PID]) async throws -> [PID: InAppProduct] {
try await withCheckedThrowingContinuation { continuation in
impl.requestProducts(withIdentifiers: identifiers) { products in
continuation.resume(returning: products.reduce(into: [:]) { map, pidProduct in
map[pidProduct.key] = pidProduct.value.asInAppProduct
})
} failureHandler: { error in
continuation.resume(throwing: error)
}
}
}
public func purchase(productWithIdentifier productIdentifier: PID) async throws -> InAppPurchaseResult {
try await withCheckedThrowingContinuation { continuation in
impl.purchase(productWithIdentifier: productIdentifier) { result in
do {
let value = try result.get()
continuation.resume(returning: value)
} catch {
continuation.resume(throwing: error)
}
}
}
}
public func restorePurchases() async throws {
try await withCheckedThrowingContinuation { continuation in
impl.restorePurchases { finished, _, _ in
if finished {
continuation.resume()
}
}
}
}
public nonisolated func products() -> [InAppProduct] {
impl.products.map(\.asInAppProduct)
}
public nonisolated func product(withIdentifier productIdentifier: PID) -> InAppProduct? {
guard let skProduct = impl.product(withIdentifier: productIdentifier) else {
return nil
}
return skProduct.asInAppProduct
}
public nonisolated func setTransactionsObserver(_ block: @escaping () -> Void) {
impl.onTransactionsUpdated = block
}
}
private final class StoreKitInAppImpl<PID: Hashable & RawRepresentable>: NSObject,
SKProductsRequestDelegate, SKPaymentTransactionObserver
where PID.RawValue == String {
typealias ProductObserver = ([PID: SKProduct]) -> Void
typealias TransactionObserver = (Result<InAppPurchaseResult, Error>) -> Void
typealias RestoreObserver = (Bool, PID?, Error?) -> Void
typealias FailureObserver = (Error) -> Void
private var productsMap: [PID: SKProduct]
var products: [SKProduct] {
Array(productsMap.values)
}
private var productObservers: [ProductObserver]
private var productFailureObserver: FailureObserver?
private var transactionObservers: [String: TransactionObserver]
private var restoreObservers: [RestoreObserver]
var onTransactionsUpdated: (() -> Void)?
override init() {
productsMap = [:]
productObservers = []
productFailureObserver = nil
transactionObservers = [:]
restoreObservers = []
super.init()
SKPaymentQueue.default().add(self)
}
deinit {
SKPaymentQueue.default().remove(self)
}
func requestProducts(withIdentifiers identifiers: [PID], completionHandler: ProductObserver?, failureHandler: FailureObserver?) {
let req = SKProductsRequest(productIdentifiers: Set(identifiers.map { $0.rawValue }))
req.delegate = self
if let observer = completionHandler {
productObservers.append(observer)
}
productFailureObserver = failureHandler
req.start()
}
@discardableResult
func purchase(productWithIdentifier productIdentifier: PID, completionHandler: @escaping TransactionObserver) -> Bool {
guard let product = productsMap[productIdentifier] else {
return false
}
purchase(product: product, completionHandler: completionHandler)
return true
}
func purchase(product: SKProduct, completionHandler: @escaping TransactionObserver) {
let queue = SKPaymentQueue.default()
let payment = SKPayment(product: product)
transactionObservers[product.productIdentifier] = completionHandler
queue.add(payment)
}
func restorePurchases(completionHandler: @escaping RestoreObserver) {
let queue = SKPaymentQueue.default()
restoreObservers.append(completionHandler)
queue.restoreCompletedTransactions()
}
func product(withIdentifier productIdentifier: PID) -> SKProduct? {
productsMap[productIdentifier]
}
// MARK: SKProductsRequestDelegate
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
DispatchQueue.main.async {
self.receiveProducts(response.products)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
if request as? SKProductsRequest != nil {
DispatchQueue.main.async {
self.productFailureObserver?(error)
}
}
DispatchQueue.main.async {
self.transactionObservers.removeAll()
}
}
private func receiveProducts(_ products: [SKProduct]) {
productsMap.removeAll()
for p in products {
guard let pid = PID(rawValue: p.productIdentifier) else {
continue
}
productsMap[pid] = p
}
productObservers.forEach { $0(productsMap) }
productObservers.removeAll()
}
// MARK: SKPaymentTransactionObserver
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
DispatchQueue.main.async { [weak self] in
guard let self else {
return
}
let currentRestoreObservers = self.restoreObservers
for tx in transactions {
let productIdentifier = tx.payment.productIdentifier
let observer = self.transactionObservers[productIdentifier]
switch tx.transactionState {
case .purchased:
queue.finishTransaction(tx)
observer?(.success(.done))
case .restored:
queue.finishTransaction(tx)
observer?(.success(.done))
for r in currentRestoreObservers {
guard let pid = PID(rawValue: productIdentifier) else {
continue
}
r(false, pid, nil)
}
case .failed:
queue.finishTransaction(tx)
if let skError = tx.error as? SKError, skError.code == .paymentCancelled {
observer?(.success(.cancelled))
} else {
observer?(.failure(tx.error ?? InAppError.unknown))
for r in currentRestoreObservers {
guard let pid = PID(rawValue: productIdentifier) else {
continue
}
r(false, pid, tx.error)
}
}
default:
break
}
}
self.onTransactionsUpdated?()
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
DispatchQueue.main.async {
for r in self.restoreObservers {
r(true, nil, nil)
}
self.restoreObservers.removeAll()
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
DispatchQueue.main.async {
for r in self.restoreObservers {
r(true, nil, error)
}
self.restoreObservers.removeAll()
}
}
}
extension SKProduct {
public var asInAppProduct: InAppProduct {
InAppProduct(productIdentifier: productIdentifier,
localizedTitle: localizedTitle,
localizedDescription: localizedDescription,
price: price,
localizedPrice: localizedPrice)
}
}
private extension SKProduct {
var localizedCurrencyFormatter: NumberFormatter {
let fmt = NumberFormatter()
fmt.locale = priceLocale
fmt.numberStyle = .currency
return fmt
}
var localizedPrice: String? {
localizedCurrencyFormatter.string(from: price)
}
}

View File

@ -1,8 +1,8 @@
//
// ProfilesTests.swift
// AppType.swift
// Passepartout
//
// Created by Davide De Rosa on 4/7/22.
// Created by Davide De Rosa on 12/19/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
@ -23,13 +23,24 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
@testable import PassepartoutVPN
import XCTest
import Foundation
final class ProfilesTests: XCTestCase {
override func setUp() {
}
public enum AppType: Int {
case undefined = -1
override func tearDown() {
case freemium = 0
case beta = 1
case fullVersion = 2
public var isRestricted: Bool {
switch self {
case .undefined, .beta:
return true
default:
return false
}
}
}

View File

@ -25,18 +25,18 @@
import Foundation
struct BuildProducts {
public struct BuildProducts {
private let productsAtBuild: (Int) -> [LocalProduct]
init(productsAtBuild: @escaping (Int) -> [LocalProduct]) {
public init(productsAtBuild: @escaping (Int) -> [LocalProduct]) {
self.productsAtBuild = productsAtBuild
}
func products(atBuild build: Int) -> [LocalProduct] {
public func products(atBuild build: Int) -> [LocalProduct] {
productsAtBuild(build)
}
func hasProduct(_ product: LocalProduct, atBuild build: Int) -> Bool {
public func hasProduct(_ product: LocalProduct, atBuild build: Int) -> Bool {
productsAtBuild(build).contains(product)
}
}

View File

@ -24,10 +24,10 @@
//
import Foundation
import PassepartoutLibrary
import StoreKit
import PassepartoutCore
import PassepartoutProviders
struct LocalProduct: RawRepresentable, Equatable, Hashable {
public struct LocalProduct: RawRepresentable, Hashable, Sendable {
private static let bundleSubdomain = "ios"
private static let bundle = "com.algoritmico.\(bundleSubdomain).Passepartout"
@ -40,19 +40,19 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
// MARK: Donations
static let tinyDonation = LocalProduct(donationDescription: "Tiny")
public static let tinyDonation = LocalProduct(donationDescription: "Tiny")
static let smallDonation = LocalProduct(donationDescription: "Small")
public static let smallDonation = LocalProduct(donationDescription: "Small")
static let mediumDonation = LocalProduct(donationDescription: "Medium")
public static let mediumDonation = LocalProduct(donationDescription: "Medium")
static let bigDonation = LocalProduct(donationDescription: "Big")
public static let bigDonation = LocalProduct(donationDescription: "Big")
static let hugeDonation = LocalProduct(donationDescription: "Huge")
public static let hugeDonation = LocalProduct(donationDescription: "Huge")
static let maxiDonation = LocalProduct(donationDescription: "Maxi")
public static let maxiDonation = LocalProduct(donationDescription: "Maxi")
static let allDonations: [LocalProduct] = [
public static let allDonations: [LocalProduct] = [
.tinyDonation,
.smallDonation,
.mediumDonation,
@ -67,19 +67,19 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
// MARK: Features
static let allProviders = LocalProduct(featureId: "all_providers")
public static let allProviders = LocalProduct(featureId: "all_providers")
static let networkSettings = LocalProduct(featureId: "network_settings")
public static let networkSettings = LocalProduct(featureId: "network_settings")
static let trustedNetworks = LocalProduct(featureId: "trusted_networks")
public static let trustedNetworks = LocalProduct(featureId: "trusted_networks")
static let siriShortcuts = LocalProduct(featureId: "siri")
public static let siriShortcuts = LocalProduct(featureId: "siri")
static let fullVersion_iOS = LocalProduct(featureId: "full_version")
public static let fullVersion_iOS = LocalProduct(featureId: "full_version")
static let fullVersion_macOS = LocalProduct(featureId: "full_mac_version")
public static let fullVersion_macOS = LocalProduct(featureId: "full_mac_version")
static let fullVersion = LocalProduct(featureId: "full_multi_version")
public static let fullVersion = LocalProduct(featureId: "full_multi_version")
static let allFeatures: [LocalProduct] = [
.allProviders,
@ -101,35 +101,45 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
allDonations + allFeatures// + allProviders
}
var isDonation: Bool {
public var isDonation: Bool {
rawValue.hasPrefix(LocalProduct.donationsBundle)
}
var isFeature: Bool {
public var isFeature: Bool {
rawValue.hasPrefix(LocalProduct.featuresBundle)
}
var isProvider: Bool {
public var isProvider: Bool {
rawValue.hasPrefix(LocalProduct.providersBundle)
}
public var isPlatformVersion: Bool {
switch self {
case .fullVersion_iOS, .fullVersion_macOS:
return true
default:
return false
}
}
// MARK: RawRepresentable
let rawValue: String
public let rawValue: String
init?(rawValue: String) {
public init?(rawValue: String) {
self.rawValue = rawValue
}
}
extension LocalProduct {
func matchesStoreKitProduct(_ skProduct: SKProduct) -> Bool {
skProduct.productIdentifier == rawValue
public func matchesInAppProduct(_ iaProduct: InAppProduct) -> Bool {
iaProduct.productIdentifier == rawValue
}
}
extension ProviderName {
var product: LocalProduct {
public var product: LocalProduct {
.init(rawValue: "\(LocalProduct.providersBundle).\(inApp)")!
}
}

View File

@ -1,8 +1,8 @@
//
// PassepartoutTestsApp.swift
// ReceiptReader.swift
// Passepartout
//
// Created by Davide De Rosa on 10/30/22.
// Created by Davide De Rosa on 12/19/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
@ -23,13 +23,9 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import SwiftUI
import Foundation
import PassepartoutCore
@main
struct PassepartoutTestsApp: App {
var body: some Scene {
WindowGroup {
EmptyView()
}
}
public protocol ReceiptReader {
func receipt(for appType: AppType) -> InAppReceipt?
}

View File

@ -25,48 +25,35 @@
import Combine
import Foundation
import Kvitto
import PassepartoutLibrary
import StoreKit
import PassepartoutCore
import PassepartoutProviders
public protocol LocalInApp: InAppProtocol where ProductIdentifier == LocalProduct {
}
extension StoreKitInApp: LocalInApp where ProductIdentifier == LocalProduct {
}
@MainActor
final class ProductManager: NSObject, ObservableObject {
enum AppType: Int {
case undefined = -1
public final class ProductManager: NSObject, ObservableObject {
private let inApp: any LocalInApp
case freemium = 0
case beta = 1
case fullVersion = 2
var isRestricted: Bool {
switch self {
case .undefined, .beta:
return true
default:
return false
}
}
}
private let receiptReader: ReceiptReader
private let overriddenAppType: AppType?
let buildProducts: BuildProducts
public let buildProducts: BuildProducts
let didRefundProducts = PassthroughSubject<Void, Never>()
public let didRefundProducts = PassthroughSubject<Void, Never>()
@Published private(set) var appType: AppType
@Published public private(set) var appType: AppType
@Published private(set) var isRefreshingProducts = false
@Published public private(set) var isRefreshingProducts = false
@Published private(set) var products: [SKProduct]
@Published public private(set) var products: [InAppProduct]
//
private let inApp: InApp<LocalProduct>
private var purchasedAppBuild: Int?
private var purchasedFeatures: Set<LocalProduct>
@ -86,15 +73,17 @@ final class ProductManager: NSObject, ObservableObject {
}
}
private var refreshRequest: SKReceiptRefreshRequest?
init(overriddenAppType: AppType?, buildProducts: BuildProducts) {
public init(inApp: any LocalInApp,
receiptReader: ReceiptReader,
overriddenAppType: AppType? = nil,
buildProducts: BuildProducts) {
self.overriddenAppType = overriddenAppType
self.receiptReader = receiptReader
self.buildProducts = buildProducts
appType = .undefined
products = []
inApp = InApp()
self.inApp = inApp
purchasedAppBuild = nil
purchasedFeatures = []
purchaseDates = [:]
@ -102,8 +91,10 @@ final class ProductManager: NSObject, ObservableObject {
super.init()
inApp.setTransactionsObserver { [weak self] in
self?.reloadReceipt()
}
reloadReceipt()
SKPaymentQueue.default().add(self)
refreshProducts()
Task {
@ -114,15 +105,11 @@ final class ProductManager: NSObject, ObservableObject {
}
}
deinit {
SKPaymentQueue.default().remove(self)
public func canMakePayments() -> Bool {
inApp.canMakePurchases()
}
func canMakePayments() -> Bool {
SKPaymentQueue.canMakePayments()
}
func refreshProducts() {
public func refreshProducts() {
let ids = LocalProduct.all
guard !ids.isEmpty else {
return
@ -132,23 +119,26 @@ final class ProductManager: NSObject, ObservableObject {
return
}
isRefreshingProducts = true
inApp.requestProducts(withIdentifiers: ids, completionHandler: { _ in
pp_log.debug("In-app products: \(self.inApp.products.map { $0.productIdentifier })")
Task {
do {
let productsMap = try await inApp.requestProducts(withIdentifiers: ids)
pp_log.debug("In-app products: \(productsMap.keys.map(\.rawValue))")
self.products = self.inApp.products
self.isRefreshingProducts = false
}, failureHandler: {
pp_log.warning("Unable to list products: \($0)")
self.isRefreshingProducts = false
})
products = Array(productsMap.values)
isRefreshingProducts = false
} catch {
pp_log.warning("Unable to list products: \(error)")
isRefreshingProducts = false
}
}
}
func product(withIdentifier identifier: LocalProduct) -> SKProduct? {
public func product(withIdentifier identifier: LocalProduct) -> InAppProduct? {
inApp.product(withIdentifier: identifier)
}
func featureProducts(including: [LocalProduct]) -> [SKProduct] {
inApp.products.filter {
public func featureProducts(including: [LocalProduct]) -> [InAppProduct] {
inApp.products().filter {
guard let p = LocalProduct(rawValue: $0.productIdentifier) else {
return false
}
@ -162,8 +152,8 @@ final class ProductManager: NSObject, ObservableObject {
}
}
func featureProducts(excluding: [LocalProduct]) -> [SKProduct] {
inApp.products.filter {
public func featureProducts(excluding: [LocalProduct]) -> [InAppProduct] {
inApp.products().filter {
guard let p = LocalProduct(rawValue: $0.productIdentifier) else {
return false
}
@ -177,35 +167,42 @@ final class ProductManager: NSObject, ObservableObject {
}
}
func purchase(_ product: SKProduct, completionHandler: @escaping (Result<InAppPurchaseResult, Error>) -> Void) {
inApp.purchase(product: product) { result in
if case .success = result {
self.reloadReceipt()
}
DispatchQueue.main.async {
completionHandler(result)
public func purchase(_ product: InAppProduct, completionHandler: @escaping (Result<InAppPurchaseResult, Error>) -> Void) {
guard let pid = LocalProduct(rawValue: product.productIdentifier) else {
pp_log.warning("Unrecognized product: \(product)")
return
}
Task {
do {
let result = try await inApp.purchase(productWithIdentifier: pid)
reloadReceipt()
completionHandler(.success(result))
} catch {
completionHandler(.failure(error))
}
}
}
func restorePurchases(completionHandler: @escaping (Error?) -> Void) {
inApp.restorePurchases { (finished, _, error) in
guard finished else {
return
}
DispatchQueue.main.async {
public func restorePurchases(completionHandler: @escaping (Error?) -> Void) {
Task {
do {
try await inApp.restorePurchases()
completionHandler(nil)
} catch {
completionHandler(error)
}
}
}
}
// MARK: In-app eligibility
// MARK: In-app eligibility
private func isCurrentPlatformVersion() -> Bool {
extension ProductManager {
public func isCurrentPlatformVersion() -> Bool {
purchasedFeatures.contains(isMac ? .fullVersion_macOS : .fullVersion_iOS)
}
private func isFullVersion() -> Bool {
public func isFullVersion() -> Bool {
if appType == .fullVersion {
return true
}
@ -215,42 +212,47 @@ final class ProductManager: NSObject, ObservableObject {
return purchasedFeatures.contains(.fullVersion)
}
func isEligible(forFeature feature: LocalProduct) -> Bool {
public func isEligible(forFeature feature: LocalProduct) -> Bool {
if let purchasedAppBuild = purchasedAppBuild {
if feature == .networkSettings && buildProducts.hasProduct(.networkSettings, atBuild: purchasedAppBuild) {
return true
}
}
if feature.isPlatformVersion {
return purchasedFeatures.contains(feature)
}
return isFullVersion() || purchasedFeatures.contains(feature)
}
func isEligible(forProvider providerName: ProviderName) -> Bool {
public func isEligible(forProvider providerName: ProviderName) -> Bool {
guard providerName != .oeck else {
return true
}
return isEligible(forFeature: providerName.product)
}
func isEligibleForFeedback() -> Bool {
public func isEligibleForFeedback() -> Bool {
appType == .beta || !purchasedFeatures.isEmpty
}
func hasPurchased(_ product: LocalProduct) -> Bool {
public func hasPurchased(_ product: LocalProduct) -> Bool {
purchasedFeatures.contains(product)
}
func purchaseDate(forProduct product: LocalProduct) -> Date? {
public func purchaseDate(forProduct product: LocalProduct) -> Date? {
purchaseDates[product]
}
}
func reloadReceipt(andNotify: Bool = true) {
guard let receipt else {
extension ProductManager {
public func reloadReceipt(andNotify: Bool = true) {
guard let receipt = receiptReader.receipt(for: appType) else {
pp_log.error("Could not parse App Store receipt!")
return
}
if let originalAppVersion = receipt.originalAppVersion, let buildNumber = Int(originalAppVersion) {
purchasedAppBuild = buildNumber
if let originalBuildNumber = receipt.originalBuildNumber {
purchasedAppBuild = originalBuildNumber
}
purchasedFeatures.removeAll()
var newCancelledPurchases: Set<LocalProduct> = []
@ -263,7 +265,7 @@ final class ProductManager: NSObject, ObservableObject {
purchasedFeatures.insert($0)
}
}
if let iapReceipts = receipt.inAppPurchaseReceipts {
if let iapReceipts = receipt.purchaseReceipts {
purchaseDates.removeAll()
pp_log.debug("In-app receipts:")
@ -291,14 +293,6 @@ final class ProductManager: NSObject, ObservableObject {
}
}
extension ProductManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
DispatchQueue.main.async { [weak self] in
self?.reloadReceipt()
}
}
}
private extension ProductManager {
var isMac: Bool {
#if targetEnvironment(macCatalyst)
@ -308,30 +302,6 @@ private extension ProductManager {
#endif
}
var receipt: Receipt? {
guard let url = Bundle.main.appStoreReceiptURL else {
pp_log.warning("No App Store receipt found!")
return nil
}
let receipt = Receipt(contentsOfURL: url)
// in TestFlight, attempt fallback to existing release receipt
if appType == .beta {
guard let receipt else {
let releaseUrl = url.deletingLastPathComponent().appendingPathComponent("receipt")
guard releaseUrl != url else {
assertionFailure("How can release URL be equal to sandbox URL in TestFlight?")
return nil
}
pp_log.warning("Sandbox receipt not found, falling back to Release receipt")
return Receipt(contentsOfURL: releaseUrl)
}
return receipt
}
return receipt
}
func detectRefunds(_ refunds: Set<LocalProduct>) {
let isEligibleForFullVersion = isFullVersion()
let hasCancelledFullVersion: Bool

View File

@ -1,4 +1,5 @@
@_exported import PassepartoutCore
@_exported import PassepartoutFrontend
@_exported import PassepartoutProviders
@_exported import PassepartoutProvidersImpl
@_exported import PassepartoutVPN

View File

@ -58,9 +58,12 @@ private extension Profile.OnDemand {
// apply exceptions (unless .any)
if withCustomRules && policy != .any {
#if os(iOS)
if Utils.hasCellularData() && withMobileNetwork {
rules.append(cellularRule())
}
#endif
#if os(macOS)
if Utils.hasEthernet() && withEthernetNetwork {
if let rule = ethernetRule() {
rules.append(rule)
@ -68,6 +71,7 @@ private extension Profile.OnDemand {
pp_log.warning("Unable to add rule for NEOnDemandRuleInterfaceType.ethernet (not compatible)")
}
}
#endif
let SSIDs = Array(withSSIDs.filter { $1 }.keys)
if !SSIDs.isEmpty {
rules.append(wifiRule(SSIDs: SSIDs))
@ -108,16 +112,20 @@ private extension Profile.OnDemand {
return rule
}
#if os(iOS)
func cellularRule() -> NEOnDemandRule {
networkRule(matchingInterface: .cellular)
}
#endif
#if os(macOS)
func ethernetRule() -> NEOnDemandRule? {
guard let compatibleEthernet = NEOnDemandRuleInterfaceType.compatibleEthernet else {
return nil
}
return networkRule(matchingInterface: compatibleEthernet)
}
#endif
func wifiRule(SSIDs: [String]) -> NEOnDemandRule {
let rule = networkRule(matchingInterface: .wiFi)

View File

@ -0,0 +1,71 @@
//
// MockInApp.swift
// Passepartout
//
// Created by Davide De Rosa on 12/19/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
@testable import PassepartoutCore
@testable import PassepartoutFrontend
final class MockInApp: LocalInApp {
var allProducts: [LocalProduct: InAppProduct]
init() {
allProducts = [:]
}
func canMakePurchases() -> Bool {
true
}
func requestProducts(withIdentifiers identifiers: [LocalProduct]) async throws -> [LocalProduct: InAppProduct] {
allProducts = LocalProduct.all.reduce(into: [:]) {
$0[$1] = InAppProduct(productIdentifier: $1.rawValue,
localizedTitle: $1.rawValue,
localizedDescription: $1.rawValue,
price: 10.0,
localizedPrice: "10.0")
}
return allProducts
}
func purchase(productWithIdentifier productIdentifier: LocalProduct) async throws -> InAppPurchaseResult {
.done
}
func restorePurchases() async throws {
//
}
func products() -> [InAppProduct] {
Array(allProducts.values)
}
func product(withIdentifier productIdentifier: LocalProduct) -> InAppProduct? {
allProducts[productIdentifier]
}
func setTransactionsObserver(_ block: @escaping () -> Void) {
//
}
}

View File

@ -1,8 +1,8 @@
//
// VPNTests.swift
// MockReceiptReader.swift
// Passepartout
//
// Created by Davide De Rosa on 10/29/22.
// Created by Davide De Rosa on 12/19/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
@ -23,14 +23,26 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
@testable import PassepartoutVPN
import XCTest
import Foundation
import PassepartoutCore
@testable import PassepartoutFrontend
final class VPNTests: XCTestCase {
override func setUp() {
final class MockReceiptReader: ReceiptReader {
var receipt: InAppReceipt?
init(receipt: InAppReceipt? = nil) {
self.receipt = receipt
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
func setReceipt(withBuild build: Int, products: [LocalProduct], cancelledProducts: Set<LocalProduct> = []) {
receipt = InAppReceipt(originalBuildNumber: build, purchaseReceipts: products.map {
.init(productIdentifier: $0.rawValue,
cancellationDate: cancelledProducts.contains($0) ? Date() : nil,
originalPurchaseDate: nil)
})
}
}
func receipt(for appType: AppType) -> InAppReceipt? {
receipt
}
}

View File

@ -0,0 +1,140 @@
//
// ProductManagerTests.swift
// Passepartout
//
// Created by Davide De Rosa on 12/19/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
@testable import PassepartoutFrontend
import XCTest
@MainActor
final class ProductManagerTests: XCTestCase {
private let inApp = MockInApp()
private let noBuildProducts = BuildProducts { _ in [] }
func test_givenBuildProducts_whenOlder_thenFullVersion() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 500, products: [])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: BuildProducts { build in
if build <= 1000 {
return [.fullVersion]
}
return []
})
XCTAssertTrue(sut.isEligible(forFeature: .fullVersion))
}
func test_givenBuildProducts_whenNewer_thenFreeVersion() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: BuildProducts { build in
if build <= 1000 {
return [.fullVersion]
}
return []
})
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion))
}
func test_givenPurchase_whenReload_thenCredited() {
let reader = MockReceiptReader()
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion))
reader.setReceipt(withBuild: 1500, products: [.fullVersion])
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion))
sut.reloadReceipt()
XCTAssertTrue(sut.isEligible(forFeature: .fullVersion))
}
func test_givenPurchase_whenCancelled_thenRevoke() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [.fullVersion])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertTrue(sut.isEligible(forFeature: .fullVersion))
reader.setReceipt(withBuild: 1500, products: [.fullVersion], cancelledProducts: [.fullVersion])
XCTAssertTrue(sut.isEligible(forFeature: .fullVersion))
sut.reloadReceipt()
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion))
}
func test_givenFeature_thenIsOnlyEligibleForFeature() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [.siriShortcuts, .networkSettings])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertTrue(sut.isEligible(forFeature: .siriShortcuts))
XCTAssertTrue(sut.isEligible(forFeature: .networkSettings))
XCTAssertFalse(sut.isEligible(forFeature: .trustedNetworks))
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion))
XCTAssertFalse(sut.isFullVersion())
}
func test_givenPlatformVersion_thenIsFullVersionForPlatform() {
let reader = MockReceiptReader()
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
#if targetEnvironment(macCatalyst)
reader.setReceipt(withBuild: 1500, products: [.fullVersion_macOS, .networkSettings])
sut.reloadReceipt()
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion_iOS))
XCTAssertTrue(sut.isEligible(forFeature: .fullVersion_macOS))
#else
reader.setReceipt(withBuild: 1500, products: [.fullVersion_iOS, .networkSettings])
sut.reloadReceipt()
XCTAssertTrue(sut.isEligible(forFeature: .fullVersion_iOS))
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion_macOS))
#endif
XCTAssertTrue(sut.isCurrentPlatformVersion())
XCTAssertTrue(sut.isFullVersion())
XCTAssertTrue(sut.isEligible(forFeature: .fullVersion))
}
func test_givenFullVersion_thenIsEligibleForAnyFeature() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [.fullVersion])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertTrue(LocalProduct
.allFeatures
.filter { !$0.isPlatformVersion }
.allSatisfy(sut.isEligible(forFeature:))
)
}
func test_givenFreeVersion_thenIsNotEligibleForAnyFeature() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertFalse(LocalProduct
.allFeatures
.allSatisfy(sut.isEligible(forFeature:))
)
}
}

View File

@ -0,0 +1,173 @@
//
// ProviderManagerTests.swift
// Passepartout
//
// Created by Davide De Rosa on 3/13/22.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
//import Combine
//import CoreData
//import PassepartoutCore
//import PassepartoutProviders
//@testable import PassepartoutProvidersImpl
//import XCTest
//
//@MainActor
//final class ProvidersTests: XCTestCase {
// private var persistence: ProvidersPersistence!
//
// private var manager: ProviderManager!
//
// private var cancellables: Set<AnyCancellable> = []
//
// override func setUp() {
// persistence = ProvidersPersistence(withName: "ProvidersTests", cloudKit: false, author: nil)
//
// let remoteStrategy = APIRemoteProvidersStrategy(
// appBuild: 10000,
// bundleServices: APIWebServices.bundledServices(withVersion: "v5"),
// remoteServices: APIWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil),
// webServicesRepository: persistence.webServicesRepository()
// )
// manager = ProviderManager(
// localProvidersRepository: persistence.localProvidersRepository(),
// remoteProvidersStrategy: remoteStrategy
// )
//// persistence.truncate()
// }
//
// override func tearDown() {
//// persistence.truncate()
// }
//
// func testFetchLocalIndex() throws {
// let exp = expectation(description: "Local index")
//
// manager.fetchProvidersIndexPublisher(priority: .bundle)
// .sink {
// switch $0 {
// case .finished:
// exp.fulfill()
//
// case .failure(let error):
// pp_log.error("Unable to load remote provider: \(error)")
// exp.fulfill()
// }
// } receiveValue: {
// pp_log.debug("Loaded index")
// }.store(in: &cancellables)
//
// waitForExpectations(timeout: 10.0, handler: nil)
// }
//
// func testFetchRemoteIndex() throws {
// let exp = expectation(description: "Remote index")
//
// manager.fetchProvidersIndexPublisher(priority: .remote)
// .sink {
// switch $0 {
// case .finished:
// exp.fulfill()
//
// case .failure(let error):
// pp_log.error("Unable to load remote provider: \(error)")
// exp.fulfill()
// }
// } receiveValue: {
// pp_log.debug("Loaded index")
// }.store(in: &cancellables)
//
// waitForExpectations(timeout: 10.0, handler: nil)
// }
//
// func testFetchRemoteProvider() async {
// do {
// try await manager.fetchProviderPublisher(withName: .hideme, vpnProtocol: .openVPN, priority: .remote).async()
// pp_log.debug("Loaded provider")
// } catch {
// XCTFail("Unable to load remote provider: \(error)")
// }
// }
//
// func testListProviders() {
// let providers = manager.allProviders()
// providers.forEach {
// pp_log.debug("\($0.name) -> \($0.fullName)")
// }
// }
//
// func testListCategories() async {
// await fetchProvider(.surfshark)
// let categories = manager.categories(.surfshark, vpnProtocol: .openVPN)
// categories.forEach {
// pp_log.debug("Category: \($0.name)")
// $0.locations.forEach {
// pp_log.debug("\t\($0)")
// }
// }
// }
//
// func testListServers() async {
// await fetchProvider(.nordvpn)
// manager.allProviders().filter({ $0.name == .nordvpn }).forEach {
// let location = ProviderLocation(
// providerMetadata: $0,
// vpnProtocol: .openVPN,
// categoryName: "",
// countryCode: "ES",
// servers: nil
// )
//
// let servers = manager.servers(forLocation: location)
// pp_log.debug("\($0.fullName): Servers [\(location.countryCode)] (\(servers.count)): \(servers)")
// }
// }
//
// func testServerId() async {
// await fetchProvider(.nordvpn)
// guard let server = manager.server(.nordvpn, vpnProtocol: .openVPN, apiId: "es143") else {
// return
// }
// pp_log.debug(server)
// }
//
// func testDefaultServer() async {
// await fetchProvider(.protonvpn)
// guard let server = manager.anyDefaultServer(.protonvpn, vpnProtocol: .openVPN) else {
// return
// }
// pp_log.debug(server)
// }
//
// func testServerUniqueId() async {
// await fetchProvider(.nordvpn)
// guard let server = manager.server(withId: "BEA03D24A5854DD17395057DEFBE7D6BEEA981227ACF8949E487443E6B5EF9C7") else {
// return
// }
// pp_log.debug(server)
// XCTAssertEqual(server.apiId, "es143")
// }
//
// private func fetchProvider(_ name: ProviderName) async {
// try? await manager.fetchProvidersIndexPublisher(priority: .bundle).async()
// try? await manager.fetchProviderPublisher(withName: name, vpnProtocol: .openVPN, priority: .bundle).async()
// }
//}

View File

@ -1,173 +0,0 @@
//
// ProvidersTests.swift
// Passepartout
//
// Created by Davide De Rosa on 3/13/22.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Combine
import CoreData
import PassepartoutCore
import PassepartoutProviders
@testable import PassepartoutProvidersImpl
import XCTest
@MainActor
final class ProvidersTests: XCTestCase {
private var persistence: ProvidersPersistence!
private var manager: ProviderManager!
private var cancellables: Set<AnyCancellable> = []
override func setUp() {
persistence = ProvidersPersistence(withName: "ProvidersTests", cloudKit: false, author: nil)
let remoteStrategy = APIRemoteProvidersStrategy(
appBuild: 10000,
bundleServices: APIWebServices.bundledServices(withVersion: "v5"),
remoteServices: APIWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil),
webServicesRepository: persistence.webServicesRepository()
)
manager = ProviderManager(
localProvidersRepository: persistence.localProvidersRepository(),
remoteProvidersStrategy: remoteStrategy
)
// persistence.truncate()
}
override func tearDown() {
// persistence.truncate()
}
func testFetchLocalIndex() throws {
let exp = expectation(description: "Local index")
manager.fetchProvidersIndexPublisher(priority: .bundle)
.sink {
switch $0 {
case .finished:
exp.fulfill()
case .failure(let error):
pp_log.error("Unable to load remote provider: \(error)")
exp.fulfill()
}
} receiveValue: {
pp_log.debug("Loaded index")
}.store(in: &cancellables)
waitForExpectations(timeout: 10.0, handler: nil)
}
func testFetchRemoteIndex() throws {
let exp = expectation(description: "Remote index")
manager.fetchProvidersIndexPublisher(priority: .remote)
.sink {
switch $0 {
case .finished:
exp.fulfill()
case .failure(let error):
pp_log.error("Unable to load remote provider: \(error)")
exp.fulfill()
}
} receiveValue: {
pp_log.debug("Loaded index")
}.store(in: &cancellables)
waitForExpectations(timeout: 10.0, handler: nil)
}
func testFetchRemoteProvider() async {
do {
try await manager.fetchProviderPublisher(withName: .hideme, vpnProtocol: .openVPN, priority: .remote).async()
pp_log.debug("Loaded provider")
} catch {
XCTFail("Unable to load remote provider: \(error)")
}
}
func testListProviders() {
let providers = manager.allProviders()
providers.forEach {
pp_log.debug("\($0.name) -> \($0.fullName)")
}
}
func testListCategories() async {
await fetchProvider(.surfshark)
let categories = manager.categories(.surfshark, vpnProtocol: .openVPN)
categories.forEach {
pp_log.debug("Category: \($0.name)")
$0.locations.forEach {
pp_log.debug("\t\($0)")
}
}
}
func testListServers() async {
await fetchProvider(.nordvpn)
manager.allProviders().filter({ $0.name == .nordvpn }).forEach {
let location = ProviderLocation(
providerMetadata: $0,
vpnProtocol: .openVPN,
categoryName: "",
countryCode: "ES",
servers: nil
)
let servers = manager.servers(forLocation: location)
pp_log.debug("\($0.fullName): Servers [\(location.countryCode)] (\(servers.count)): \(servers)")
}
}
func testServerId() async {
await fetchProvider(.nordvpn)
guard let server = manager.server(.nordvpn, vpnProtocol: .openVPN, apiId: "es143") else {
return
}
pp_log.debug(server)
}
func testDefaultServer() async {
await fetchProvider(.protonvpn)
guard let server = manager.anyDefaultServer(.protonvpn, vpnProtocol: .openVPN) else {
return
}
pp_log.debug(server)
}
func testServerUniqueId() async {
await fetchProvider(.nordvpn)
guard let server = manager.server(withId: "BEA03D24A5854DD17395057DEFBE7D6BEEA981227ACF8949E487443E6B5EF9C7") else {
return
}
pp_log.debug(server)
XCTAssertEqual(server.apiId, "es143")
}
private func fetchProvider(_ name: ProviderName) async {
try? await manager.fetchProvidersIndexPublisher(priority: .bundle).async()
try? await manager.fetchProviderPublisher(withName: name, vpnProtocol: .openVPN, priority: .bundle).async()
}
}

View File

@ -1,96 +0,0 @@
//
// ServicesTests.swift
// Passepartout
//
// Created by Davide De Rosa on 6/11/18.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Combine
import PassepartoutCore
@testable import PassepartoutProvidersImpl
import XCTest
final class ServicesTests: XCTestCase {
let wsLocal = APIWebServices.bundledServices(withVersion: "v5")
let wsRemote = APIWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil)
private var cancellables: Set<AnyCancellable> = []
override func setUp() {
}
override func tearDown() {
}
func testLastModified() {
let fmt = DateFormatter()
fmt.timeZone = TimeZone(abbreviation: "GMT")
fmt.dateFormat = "EEE, dd LLL yyyy HH:mm:ss zzz"
let lmString = "Wed, 23 Oct 2019 17:06:54 GMT"
fmt.locale = Locale(identifier: "en")
XCTAssertNotNil(fmt.date(from: lmString))
fmt.locale = Locale(identifier: "fr-FR")
XCTAssertNil(fmt.date(from: lmString))
}
func testLocalIndex() {
let exp = expectation(description: "")
wsLocal.providersIndex()
.sink {
switch $0 {
case .finished:
break
case .failure(let error):
pp_log.debug(error)
exp.fulfill()
}
} receiveValue: {
pp_log.debug($0)
exp.fulfill()
}.store(in: &cancellables)
waitForExpectations(timeout: 10.0, handler: nil)
}
func testRemoteIndex() {
let exp = expectation(description: "")
wsRemote.providersIndex()
.sink {
switch $0 {
case .finished:
break
case .failure(let error):
pp_log.debug(error)
exp.fulfill()
}
} receiveValue: {
pp_log.debug($0)
exp.fulfill()
}.store(in: &cancellables)
waitForExpectations(timeout: 10.0, handler: nil)
}
}

View File

@ -0,0 +1,96 @@
//
// WebServicesTests.swift
// Passepartout
//
// Created by Davide De Rosa on 6/11/18.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
//import Combine
//import PassepartoutCore
//@testable import PassepartoutProvidersImpl
//import XCTest
//
//final class ServicesTests: XCTestCase {
// let wsLocal = APIWebServices.bundledServices(withVersion: "v5")
//
// let wsRemote = APIWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil)
//
// private var cancellables: Set<AnyCancellable> = []
//
// override func setUp() {
// }
//
// override func tearDown() {
// }
//
// func testLastModified() {
// let fmt = DateFormatter()
// fmt.timeZone = TimeZone(abbreviation: "GMT")
// fmt.dateFormat = "EEE, dd LLL yyyy HH:mm:ss zzz"
//
// let lmString = "Wed, 23 Oct 2019 17:06:54 GMT"
//
// fmt.locale = Locale(identifier: "en")
// XCTAssertNotNil(fmt.date(from: lmString))
// fmt.locale = Locale(identifier: "fr-FR")
// XCTAssertNil(fmt.date(from: lmString))
// }
//
// func testLocalIndex() {
// let exp = expectation(description: "")
// wsLocal.providersIndex()
// .sink {
// switch $0 {
// case .finished:
// break
//
// case .failure(let error):
// pp_log.debug(error)
// exp.fulfill()
// }
// } receiveValue: {
// pp_log.debug($0)
// exp.fulfill()
// }.store(in: &cancellables)
//
// waitForExpectations(timeout: 10.0, handler: nil)
// }
//
// func testRemoteIndex() {
// let exp = expectation(description: "")
// wsRemote.providersIndex()
// .sink {
// switch $0 {
// case .finished:
// break
//
// case .failure(let error):
// pp_log.debug(error)
// exp.fulfill()
// }
// } receiveValue: {
// pp_log.debug($0)
// exp.fulfill()
// }.store(in: &cancellables)
//
// waitForExpectations(timeout: 10.0, handler: nil)
// }
//}

View File

@ -1,2 +0,0 @@
scheme "PassepartoutTests"
device "iPhone 14"