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:
parent
7c42263fb1
commit
a0da930d98
|
@ -35,4 +35,5 @@ jobs:
|
|||
go-version: "^1.17"
|
||||
- name: Run tests
|
||||
run: |
|
||||
bundle exec fastlane --env beta,ios scan
|
||||
cd PassepartoutLibrary
|
||||
swift test
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import PassepartoutCore
|
||||
import PassepartoutLibrary
|
||||
|
||||
public protocol UpgradeManagerStrategy {
|
||||
func migrate(store: KeyValueStore, lastVersion: String?)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import PassepartoutLibrary
|
||||
|
||||
struct PaywallView: View {
|
||||
@ObservedObject private var productManager: ProductManager
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"])
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)")!
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -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
|
|
@ -1,4 +1,5 @@
|
|||
@_exported import PassepartoutCore
|
||||
@_exported import PassepartoutFrontend
|
||||
@_exported import PassepartoutProviders
|
||||
@_exported import PassepartoutProvidersImpl
|
||||
@_exported import PassepartoutVPN
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
//
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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:))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
// }
|
||||
//}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
// }
|
||||
//}
|
|
@ -1,2 +0,0 @@
|
|||
scheme "PassepartoutTests"
|
||||
device "iPhone 14"
|
Loading…
Reference in New Issue