diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 22485df30c..aee2eb2d72 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -766,21 +766,7 @@ D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; D664C7B62B289AA200CBFA76 /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */; }; D664C7B72B289AA200CBFA76 /* PrivacyPro.storekit in Resources */ = {isa = PBXBuildFile; fileRef = D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */; }; - D664C7B82B289AA200CBFA76 /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7972B289AA000CBFA76 /* URL+Subscription.swift */; }; D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */; }; - D664C7BA2B289AA200CBFA76 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79A2B289AA000CBFA76 /* Logging.swift */; }; - D664C7BB2B289AA200CBFA76 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79B2B289AA000CBFA76 /* AccountManager.swift */; }; - D664C7BC2B289AA200CBFA76 /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */; }; - D664C7BD2B289AA200CBFA76 /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79E2B289AA000CBFA76 /* AccountStorage.swift */; }; - D664C7BE2B289AA200CBFA76 /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */; }; - D664C7BF2B289AA200CBFA76 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A12B289AA000CBFA76 /* APIService.swift */; }; - D664C7C02B289AA200CBFA76 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A22B289AA000CBFA76 /* AuthService.swift */; }; - D664C7C12B289AA200CBFA76 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */; }; - D664C7C22B289AA200CBFA76 /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */; }; - D664C7C32B289AA200CBFA76 /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */; }; - D664C7C42B289AA200CBFA76 /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */; }; - D664C7C52B289AA200CBFA76 /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */; }; - D664C7C62B289AA200CBFA76 /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */; }; D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */; }; D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */; }; D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */; }; @@ -790,6 +776,21 @@ D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; }; D69FBF762B28BE3600B505F1 /* SettingsPrivacyProView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */; }; + D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8B2B291CA90054390C /* URL+Subscription.swift */; }; + D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */; }; + D6D12CA12B291CA90054390C /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8D2B291CA90054390C /* Logging.swift */; }; + D6D12CA22B291CA90054390C /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8E2B291CA90054390C /* AccountManager.swift */; }; + D6D12CA32B291CAA0054390C /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */; }; + D6D12CA42B291CAA0054390C /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C912B291CA90054390C /* AccountStorage.swift */; }; + D6D12CA52B291CAA0054390C /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */; }; + D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */; }; + D6D12CA72B291CAA0054390C /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */; }; + D6D12CA82B291CAA0054390C /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C972B291CA90054390C /* PurchaseFlow.swift */; }; + D6D12CA92B291CAA0054390C /* StripePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */; }; + D6D12CAA2B291CAA0054390C /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9B2B291CA90054390C /* SubscriptionService.swift */; }; + D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9C2B291CA90054390C /* APIService.swift */; }; + D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9D2B291CA90054390C /* AuthService.swift */; }; + D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9E2B291CA90054390C /* PurchaseManager.swift */; }; D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; @@ -2402,21 +2403,7 @@ D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; - D664C7972B289AA000CBFA76 /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; - D664C79A2B289AA000CBFA76 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - D664C79B2B289AA000CBFA76 /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; - D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; - D664C79E2B289AA000CBFA76 /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; - D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; - D664C7A12B289AA000CBFA76 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; - D664C7A22B289AA000CBFA76 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; - D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; - D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; - D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; - D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; - D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; - D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; @@ -2426,6 +2413,21 @@ D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; D664C7DC2B28A02800CBFA76 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyProView.swift; sourceTree = ""; }; + D6D12C8B2B291CA90054390C /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; + D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPurchaseEnvironment.swift; sourceTree = ""; }; + D6D12C8D2B291CA90054390C /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + D6D12C8E2B291CA90054390C /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; + D6D12C912B291CA90054390C /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; + D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; + D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; + D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; + D6D12C972B291CA90054390C /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; + D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StripePurchaseFlow.swift; sourceTree = ""; }; + D6D12C9B2B291CA90054390C /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; + D6D12C9C2B291CA90054390C /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; + D6D12C9D2B291CA90054390C /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + D6D12C9E2B291CA90054390C /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -3899,9 +3901,9 @@ 8590CB66268A2E520089F6BF /* RootDebugViewController.swift */, 8590CB68268A4E190089F6BF /* DebugEtagStorage.swift */, 1EDE39D12705D4A100C99C72 /* FileSizeDebugViewController.swift */, + 983D71B02A286E810072E26D /* SyncDebugViewController.swift */, C18ED43B2AB8364400BF3805 /* FileTextPreviewDebugViewController.swift */, F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */, - 983D71B02A286E810072E26D /* SyncDebugViewController.swift */, EE72CA842A862D000043B5B3 /* NetworkProtectionDebugViewController.swift */, ); name = Debug; @@ -4498,14 +4500,10 @@ D664C7922B289AA000CBFA76 /* PrivacyPro */ = { isa = PBXGroup; children = ( - D664C7932B289AA000CBFA76 /* ViewModel */, + D6D12C8A2B291CA90054390C /* Subscription */, D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */, + D664C7932B289AA000CBFA76 /* ViewModel */, D664C7962B289AA000CBFA76 /* Extensions */, - D664C7992B289AA000CBFA76 /* Subscription */, - D664C7A42B289AA000CBFA76 /* Purchase */, - D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */, - D664C7A62B289AA000CBFA76 /* Account */, - D664C7A72B289AA000CBFA76 /* PurchaseFlows */, D664C7AC2B289AA000CBFA76 /* Views */, D664C7B02B289AA000CBFA76 /* UserScripts */, ); @@ -4523,87 +4521,92 @@ D664C7962B289AA000CBFA76 /* Extensions */ = { isa = PBXGroup; children = ( - D664C7972B289AA000CBFA76 /* URL+Subscription.swift */, D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, ); path = Extensions; sourceTree = ""; }; - D664C7992B289AA000CBFA76 /* Subscription */ = { + D664C7AC2B289AA000CBFA76 /* Views */ = { isa = PBXGroup; children = ( - D664C79A2B289AA000CBFA76 /* Logging.swift */, - D664C79B2B289AA000CBFA76 /* AccountManager.swift */, - D664C79C2B289AA000CBFA76 /* AccountStorage */, - D664C79F2B289AA000CBFA76 /* Services */, - D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */, + D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */, + D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */, + D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */, ); - path = Subscription; + path = Views; sourceTree = ""; }; - D664C79C2B289AA000CBFA76 /* AccountStorage */ = { + D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( - D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */, - D664C79E2B289AA000CBFA76 /* AccountStorage.swift */, + D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */, + D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, + D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, + D664C7B42B289AA000CBFA76 /* TestUserScript.swift */, ); - path = AccountStorage; + path = UserScripts; sourceTree = ""; }; - D664C79F2B289AA000CBFA76 /* Services */ = { + D6D12C8A2B291CA90054390C /* Subscription */ = { isa = PBXGroup; children = ( - D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */, - D664C7A12B289AA000CBFA76 /* APIService.swift */, - D664C7A22B289AA000CBFA76 /* AuthService.swift */, + D6D12C8B2B291CA90054390C /* URL+Subscription.swift */, + D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */, + D6D12C8D2B291CA90054390C /* Logging.swift */, + D6D12C8E2B291CA90054390C /* AccountManager.swift */, + D6D12C8F2B291CA90054390C /* AccountStorage */, + D6D12C922B291CA90054390C /* Flows */, + D6D12C9A2B291CA90054390C /* Services */, + D6D12C9E2B291CA90054390C /* PurchaseManager.swift */, ); - path = Services; + path = Subscription; sourceTree = ""; }; - D664C7A42B289AA000CBFA76 /* Purchase */ = { + D6D12C8F2B291CA90054390C /* AccountStorage */ = { isa = PBXGroup; children = ( + D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */, + D6D12C912B291CA90054390C /* AccountStorage.swift */, ); - path = Purchase; + path = AccountStorage; sourceTree = ""; }; - D664C7A62B289AA000CBFA76 /* Account */ = { + D6D12C922B291CA90054390C /* Flows */ = { isa = PBXGroup; children = ( + D6D12C932B291CA90054390C /* AppStore */, + D6D12C972B291CA90054390C /* PurchaseFlow.swift */, + D6D12C982B291CA90054390C /* Stripe */, ); - path = Account; + path = Flows; sourceTree = ""; }; - D664C7A72B289AA000CBFA76 /* PurchaseFlows */ = { + D6D12C932B291CA90054390C /* AppStore */ = { isa = PBXGroup; children = ( - D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */, - D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */, - D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */, - D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */, + D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */, + D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */, + D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */, ); - path = PurchaseFlows; + path = AppStore; sourceTree = ""; }; - D664C7AC2B289AA000CBFA76 /* Views */ = { + D6D12C982B291CA90054390C /* Stripe */ = { isa = PBXGroup; children = ( - D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */, - D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */, - D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */, + D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */, ); - path = Views; + path = Stripe; sourceTree = ""; }; - D664C7B02B289AA000CBFA76 /* UserScripts */ = { + D6D12C9A2B291CA90054390C /* Services */ = { isa = PBXGroup; children = ( - D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */, - D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, - D664C7B42B289AA000CBFA76 /* TestUserScript.swift */, - D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, + D6D12C9B2B291CA90054390C /* SubscriptionService.swift */, + D6D12C9C2B291CA90054390C /* APIService.swift */, + D6D12C9D2B291CA90054390C /* AuthService.swift */, ); - path = UserScripts; + path = Services; sourceTree = ""; }; D6E83C322B1F1279006C8AFB /* Sections */ = { @@ -6442,6 +6445,7 @@ 1E24295E293F57FA00584836 /* LottieView.swift in Sources */, 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */, 4BB697A42B1D99C4003699B5 /* VPNWaitlistActivationDateStore.swift in Sources */, + D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */, 853C5F5B21BFF0AE001F7A05 /* HomeCollectionView.swift in Sources */, 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, @@ -6453,6 +6457,7 @@ B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, + D6D12CA92B291CAA0054390C /* StripePurchaseFlow.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */, @@ -6466,11 +6471,10 @@ F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, + D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */, - D664C7BB2B289AA200CBFA76 /* AccountManager.swift in Sources */, 37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */, - D664C7BE2B289AA200CBFA76 /* SubscriptionService.swift in Sources */, 4BBBBA922B03291700D965DA /* VPNWaitlistUserText.swift in Sources */, F4E1936625AF722F001D2666 /* HighlightCutOutView.swift in Sources */, 1E162605296840D80004127F /* Triangle.swift in Sources */, @@ -6480,7 +6484,6 @@ 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */, F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */, 3151F0F02735802800226F58 /* VoiceSearchViewController.swift in Sources */, - D664C7C02B289AA200CBFA76 /* AuthService.swift in Sources */, 85BDC310243359040053DB07 /* FindInPageUserScript.swift in Sources */, F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */, 984D035824ACCC6F0066CFB8 /* TabViewListCell.swift in Sources */, @@ -6488,7 +6491,9 @@ D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, + D6D12CA42B291CAA0054390C /* AccountStorage.swift in Sources */, D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, + D6D12CA32B291CAA0054390C /* AccountKeychainStorage.swift in Sources */, 8590CB612684D0600089F6BF /* CookieDebugViewController.swift in Sources */, 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, @@ -6528,10 +6533,10 @@ 020108A929A7C1CD00644F9D /* AppTrackerImageCache.swift in Sources */, 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */, 3132FA2A27A0788F00DD7A12 /* QuickLookPreviewHelper.swift in Sources */, + D6D12CA82B291CAA0054390C /* PurchaseFlow.swift in Sources */, C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, - D664C7C12B289AA200CBFA76 /* PurchaseManager.swift in Sources */, 8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */, CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */, 020108A329A561C300644F9D /* AppTPActivityView.swift in Sources */, @@ -6556,6 +6561,7 @@ 85C297042476C1FD0063A335 /* DaxDialogsSettings.swift in Sources */, 8505836F219F424500ED4EDB /* UIViewExtension.swift in Sources */, 8505836E219F424500ED4EDB /* RoundedRectangleView.swift in Sources */, + D6D12CA12B291CA90054390C /* Logging.swift in Sources */, EE8594992A44791C008A6D06 /* NetworkProtectionTunnelController.swift in Sources */, 1EEF123F2850A68A003DDE57 /* PrivacyInfoContainerView.swift in Sources */, F4B0B796252CB35700830156 /* OnboardingWidgetsDetailsViewController.swift in Sources */, @@ -6594,6 +6600,7 @@ 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, + D6D12CA22B291CA90054390C /* AccountManager.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, 83BE9BC3215D69C1009844D9 /* AppConfigurationFetch.swift in Sources */, 1EEC460627A9499600E75FCB /* DownloadsList.swift in Sources */, @@ -6601,12 +6608,14 @@ 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, + D6D12CA52B291CAA0054390C /* AppStorePurchaseFlow.swift in Sources */, F4F6DFB426E6B63700ED7E12 /* BookmarkFolderCell.swift in Sources */, 851B12CC22369931004781BC /* AtbAndVariantCleanup.swift in Sources */, 85F2FFCF2211F8E5006BB258 /* TabSwitcherViewController+KeyCommands.swift in Sources */, 3157B43327F497E90042D3D7 /* SaveLoginView.swift in Sources */, F17922E01E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift in Sources */, 0290472529E8496A0008FE3C /* AppTPActivityIconView.swift in Sources */, + D6D12CA72B291CAA0054390C /* AppStoreAccountManagementFlow.swift in Sources */, D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */, EE458D142ABB652900FC651A /* NetworkProtectionDebugUtilities.swift in Sources */, 8528AE7C212EF4A200D0BD74 /* AppRatingPrompt.swift in Sources */, @@ -6622,7 +6631,6 @@ EE0153ED2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift in Sources */, EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */, B60DFF072872B64B0061E7C2 /* JSAlertController.swift in Sources */, - D664C7C42B289AA200CBFA76 /* AppStoreRestoreFlow.swift in Sources */, 981FED6E22025151008488D7 /* BlankSnapshotViewController.swift in Sources */, 98F3A1DC217B373E0011A0D4 /* DarkTheme.swift in Sources */, 851B128822200575004781BC /* Onboarding.swift in Sources */, @@ -6649,6 +6657,7 @@ 4BBBBA8D2B031B4200D965DA /* VPNWaitlistDebugViewController.swift in Sources */, C160544129D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift in Sources */, 02A54A9A2A094A17000C8FED /* AppTPHomeView.swift in Sources */, + D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */, 31C70B5528045E3500FB6AD1 /* SecureVaultErrorReporter.swift in Sources */, F4CE6D1B257EA33C00D0A6AA /* FireButtonAnimator.swift in Sources */, 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, @@ -6666,9 +6675,9 @@ 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, + D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */, 989B337522D7EF2100437824 /* EmptyCollectionReusableView.swift in Sources */, 8524CC94246C5C8900E59D45 /* DaxDialogViewController.swift in Sources */, - D664C7BA2B289AA200CBFA76 /* Logging.swift in Sources */, F42EF9312614BABE00101FB9 /* ActionSheetDaxDialogViewController.swift in Sources */, EEC02C142B0519DE0045CE11 /* NetworkProtectionVPNLocationViewModel.swift in Sources */, F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */, @@ -6686,12 +6695,10 @@ F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */, F17922DE1E7192E6006E3D97 /* SuggestionTableViewCell.swift in Sources */, 85DB12ED2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift in Sources */, - D664C7BD2B289AA200CBFA76 /* AccountStorage.swift in Sources */, 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */, - D664C7C52B289AA200CBFA76 /* AppStoreAccountManagementFlow.swift in Sources */, 02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, CBD4F13D279EBFA000B20FD7 /* HomeMessageCollectionViewCell.swift in Sources */, @@ -6700,6 +6707,7 @@ 37FCAAB429914C77000E420A /* WindowsWaitlistViewController.swift in Sources */, 31C138A827A3E9C900FFD4B2 /* URLDownloadSession.swift in Sources */, 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */, + D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */, 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */, 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */, CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */, @@ -6752,7 +6760,6 @@ 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */, 980891A32237146B00313A70 /* Feedback.swift in Sources */, F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, - D664C7C22B289AA200CBFA76 /* SubscriptionUserText.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, @@ -6767,8 +6774,8 @@ 373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */, 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */, F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */, - D664C7C32B289AA200CBFA76 /* AppStorePurchaseFlow.swift in Sources */, 85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */, + D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */, 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, @@ -6791,7 +6798,6 @@ 85C2971A248162CA0063A335 /* DaxOnboardingPadViewController.swift in Sources */, F4F6DFB826EA9AA600ED7E12 /* BookmarksTextFieldCell.swift in Sources */, 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, - D664C7C62B289AA200CBFA76 /* PurchaseFlow.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 4B6484ED27FD1E350050A7A1 /* MacBrowserWaitlist.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, @@ -6808,7 +6814,6 @@ C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, - D664C7BF2B289AA200CBFA76 /* APIService.swift in Sources */, 314C92B827C3DD660042EC96 /* QuickLookPreviewView.swift in Sources */, F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */, 020108AE29A7F91600644F9D /* AppTPTrackerCell.swift in Sources */, @@ -6857,11 +6862,10 @@ D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */, 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, - D664C7BC2B289AA200CBFA76 /* AccountKeychainStorage.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, F1D796F41E7C2A410019D451 /* BookmarksDelegate.swift in Sources */, + D6D12CAA2B291CAA0054390C /* SubscriptionService.swift in Sources */, D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */, - D664C7B82B289AA200CBFA76 /* URL+Subscription.swift in Sources */, C1B7B52428941F2A0098FD6A /* RemoteMessageRequest.swift in Sources */, EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, @@ -8208,6 +8212,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8237,6 +8242,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8384,6 +8390,7 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8391,6 +8398,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; SWIFT_VERSION = 5.0; }; name = Debug; @@ -8406,6 +8414,7 @@ CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8539,6 +8548,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8571,6 +8581,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8856,7 +8867,12 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8881,6 +8897,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8909,6 +8926,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9249,6 +9267,7 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9275,6 +9294,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9307,6 +9327,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift b/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift deleted file mode 100644 index 6996c2a111..0000000000 --- a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// AppStorePurchaseFlow.swift -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import StoreKit - -@available(macOS 12.0, iOS 15.0, *) -public final class AppStoreRestoreFlow { - - public typealias Success = (externalID: String, isActive: Bool) - - public enum Error: Swift.Error { - case missingAccountOrTransactions - case pastTransactionAuthenticationFailure - case accessTokenObtainingError -// case subscriptionExpired - case somethingWentWrong - } - - public static func restoreAccountFromPastPurchase() async -> Result { - guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.missingAccountOrTransactions) } - - // Do the store login to get short-lived token - let authToken: String - - switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { - case .success(let response): - authToken = response.authToken - case .failure: - return .failure(.pastTransactionAuthenticationFailure) - } - - let externalID: String - - switch await AccountManager().exchangeAndStoreTokens(with: authToken) { - case .success(let existingExternalID): - externalID = existingExternalID - case .failure: - return .failure(.accessTokenObtainingError) - } - - let accessToken = AccountManager().accessToken ?? "" - var isActive = false - - switch await SubscriptionService.getSubscriptionInfo(token: accessToken) { - case .success(let response): - isActive = response.status != "Expired" && response.status != "Inactive" - case .failure: - return .failure(.somethingWentWrong) - } - - // TOOD: Fix this by probably splitting/changing result of exchangeAndStoreTokens - return .success((externalID: externalID, isActive: isActive)) - } -} diff --git a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift index fb8261ca46..655742298d 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift @@ -176,44 +176,35 @@ public class AccountManager { } } - @discardableResult - public func exchangeAndStoreTokens(with authToken: String) async -> Result { - // Exchange short-lived auth token to a long-lived access token - let accessToken: String + public func exchangeAuthTokenToAccessToken(_ authToken: String) async -> Result { switch await AuthService.getAccessToken(token: authToken) { case .success(let response): - accessToken = response.accessToken + return .success(response.accessToken) case .failure(let error): os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) return .failure(error) } + } + + public typealias AccountDetails = (email: String?, externalID: String) - // Fetch entitlements and account details and store the data + public func fetchAccountDetails(with accessToken: String) async -> Result { switch await AuthService.validateToken(accessToken: accessToken) { case .success(let response): - self.storeAuthToken(token: authToken) - self.storeAccount(token: accessToken, - email: response.account.email, - externalID: response.account.externalID) - - return .success(response.account.externalID) - + return .success(AccountDetails(email: response.account.email, externalID: response.account.externalID)) case .failure(let error): os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) return .failure(error) } } - public func refreshAccountData() async { - guard let accessToken else { return } + public func checkSubscriptionState() async { + guard let token = accessToken else { return } - switch await AuthService.validateToken(accessToken: accessToken) { - case .success(let response): - self.storeAccount(token: accessToken, - email: response.account.email, - externalID: response.account.externalID) - case .failure: - break + if case .success(let response) = await SubscriptionService.getSubscriptionDetails(token: token) { + if !response.isSubscriptionActive { + signOut() + } } } } diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreAccountManagementFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreAccountManagementFlow.swift rename to DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift similarity index 80% rename from DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift rename to DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift index 666ff9e094..2d4497cb00 100644 --- a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift @@ -55,22 +55,30 @@ public final class AppStorePurchaseFlow { } public static func purchaseSubscription(with subscriptionIdentifier: String, emailAccessToken: String?) async -> Result { + let accountManager = AccountManager() let externalID: String // Check for past transactions most recent - switch await AppStoreRestoreFlow.restoreAccountFromPastPurchase() { - case .success(let success): - guard !success.isActive else { return .failure(.activeSubscriptionAlreadyPresent)} - externalID = success.externalID + case .success: + return .failure(.activeSubscriptionAlreadyPresent) case .failure(let error): switch error { - case .missingAccountOrTransactions, .pastTransactionAuthenticationFailure: + case .subscriptionExpired(let expiredAccountDetails): + externalID = expiredAccountDetails.externalID + accountManager.storeAuthToken(token: expiredAccountDetails.authToken) + accountManager.storeAccount(token: expiredAccountDetails.accessToken, email: expiredAccountDetails.email, externalID: expiredAccountDetails.externalID) + case .missingAccountOrTransactions: // No history, create new account switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { case .success(let response): externalID = response.externalID - await AccountManager().exchangeAndStoreTokens(with: response.authToken) + + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(response.authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: response.authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } case .failure: return .failure(.accountCreationFailed) } @@ -93,7 +101,7 @@ public final class AppStorePurchaseFlow { @discardableResult public static func completeSubscriptionPurchase() async -> Result { - let result = await checkForEntitlements(wait: 2.0, retry: 15) + let result = await checkForEntitlements(wait: 2.0, retry: 10) return result ? .success(PurchaseUpdate(type: "completed")) : .failure(.missingEntitlements) } diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift new file mode 100644 index 0000000000..a23a3255f7 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift @@ -0,0 +1,90 @@ +// +// AppStorePurchaseFlow.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import StoreKit + +@available(macOS 12.0, iOS 15.0, *) +public final class AppStoreRestoreFlow { + + // swiftlint:disable:next large_tuple + public typealias RestoredAccountDetails = (authToken: String, accessToken: String, externalID: String, email: String?) + + public enum Error: Swift.Error { + case missingAccountOrTransactions + case pastTransactionAuthenticationError + case failedToObtainAccessToken + case failedToFetchAccountDetails + case failedToFetchSubscriptionDetails + case subscriptionExpired(accountDetails: RestoredAccountDetails) + case somethingWentWrong + } + + public static func restoreAccountFromPastPurchase() async -> Result { + guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.missingAccountOrTransactions) } + + let accountManager = AccountManager() + + // Do the store login to get short-lived token + let authToken: String + + switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { + case .success(let response): + authToken = response.authToken + case .failure: + return .failure(.pastTransactionAuthenticationError) + } + + let accessToken: String + let email: String? + let externalID: String + + switch await accountManager.exchangeAuthTokenToAccessToken(authToken) { + case .success(let exchangedAccessToken): + accessToken = exchangedAccessToken + case .failure: + return .failure(.failedToObtainAccessToken) + } + + switch await accountManager.fetchAccountDetails(with: accessToken) { + case .success(let accountDetails): + email = accountDetails.email + externalID = accountDetails.externalID + case .failure: + return .failure(.failedToFetchAccountDetails) + } + + var isSubscriptionActive = false + + switch await SubscriptionService.getSubscriptionDetails(token: accessToken) { + case .success(let response): + isSubscriptionActive = response.isSubscriptionActive + case .failure: + return .failure(.somethingWentWrong) + } + + if isSubscriptionActive { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: email, externalID: externalID) + return .success(()) + } else { + let details = RestoredAccountDetails(authToken: authToken, accessToken: accessToken, externalID: externalID, email: email) + return .failure(.subscriptionExpired(accountDetails: details)) + } + } +} diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/PurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/PurchaseFlows/PurchaseFlow.swift rename to DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift new file mode 100644 index 0000000000..67070f383b --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift @@ -0,0 +1,91 @@ +// +// StripePurchaseFlow.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import StoreKit + +public final class StripePurchaseFlow { + + public enum Error: Swift.Error { + case noProductsFound + case accountCreationFailed + } + + public static func subscriptionOptions() async -> Result { + + guard case let .success(products) = await SubscriptionService.getProducts(), !products.isEmpty else { return .failure(.noProductsFound) } + + let currency = products.first?.currency ?? "USD" + + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.locale = Locale(identifier: "en_US@currency=\(currency)") + + let options: [SubscriptionOption] = products.map { + var displayPrice = "\($0.price) \($0.currency)" + + if let price = Float($0.price), let formattedPrice = formatter.string(from: price as NSNumber) { + displayPrice = formattedPrice + } + + let cost = SubscriptionOptionCost(displayPrice: displayPrice, recurrence: $0.billingPeriod.lowercased()) + + return SubscriptionOption(id: $0.productId, + cost: cost) + } + + let features = SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) } + + return .success(SubscriptionOptions(platform: SubscriptionPlatformName.stripe.rawValue, + options: options, + features: features)) + } + + public static func prepareSubscriptionPurchase(emailAccessToken: String?) async -> Result { + + var authToken: String = "" + + switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { + case .success(let response): + authToken = response.authToken + AccountManager().storeAuthToken(token: authToken) + case .failure: + return .failure(.accountCreationFailed) + } + + return .success(PurchaseUpdate(type: "redirect", token: authToken)) + } + + public static func completeSubscriptionPurchase() async { + let accountManager = AccountManager() + + if let authToken = accountManager.authToken { + print("Exchanging token") + + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + } + + if #available(macOS 12.0, iOS 15.0, *) { + await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 5) + } + } +} diff --git a/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift index 3c0cf3fe24..fd4032b38b 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift @@ -40,14 +40,9 @@ enum PurchaseManagerError: Error { @available(macOS 12.0, iOS 15.0, *) public final class PurchaseManager: ObservableObject { - static let productIdentifiers = ["subscription.1week", - "subscription.1month", - "subscription.1year", - "review.subscription.1week", - "review.subscription.1month", - "review.subscription.1year", - "ios.subscription.1month", - "ios.subscription.1year"] + static let productIdentifiers = ["ios.subscription.1month", "ios.subscription.1year", + "subscription.1week", "subscription.1month", "subscription.1year", + "review.subscription.1week", "review.subscription.1month", "review.subscription.1year"] public static let shared = PurchaseManager() @@ -121,21 +116,6 @@ public final class PurchaseManager: ObservableObject { } } - @MainActor - func fetchAvailableProducts() async -> [Product] { - print(" -- [PurchaseManager] fetchAvailableProducts()") - - do { - let availableProducts = try await Product.products(for: Self.productIdentifiers) - print(" -- [PurchaseManager] fetchAvailableProducts(): fetched \(availableProducts.count) products") - - return availableProducts - } catch { - print("Error fetching available products: \(error)") - return [] - } - } - @MainActor public func updatePurchasedProducts() async { print(" -- [PurchaseManager] updatePurchasedProducts()") diff --git a/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift index 877a45f245..6c3ed27db1 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift @@ -30,24 +30,39 @@ public struct SubscriptionService: APIService { // MARK: - - public static func getSubscriptionInfo(token: String) async -> Result { - await executeAPICall(method: "GET", endpoint: "subscription", headers: makeAuthorizationHeader(for: token)) + public static func getSubscriptionDetails(token: String) async -> Result { + let result: Result = await executeAPICall(method: "GET", endpoint: "subscription", headers: makeAuthorizationHeader(for: token)) + + switch result { + case .success(let response): + cachedSubscriptionDetailsResponse = response + case .failure: + cachedSubscriptionDetailsResponse = nil + } + + return result } - public struct GetSubscriptionInfoResponse: Decodable { + public struct GetSubscriptionDetailsResponse: Decodable { public let productId: String public let startedAt: Date public let expiresOrRenewsAt: Date public let platform: String public let status: String + + public var isSubscriptionActive: Bool { + status.lowercased() != "expired" && status.lowercased() != "inactive" + } } + public static var cachedSubscriptionDetailsResponse: GetSubscriptionDetailsResponse? + // MARK: - public static func getProducts() async -> Result { await executeAPICall(method: "GET", endpoint: "products") } - + public typealias GetProductsResponse = [GetProductsItem] public struct GetProductsItem: Decodable { @@ -58,4 +73,16 @@ public struct SubscriptionService: APIService { public let currency: String } + // MARK: - + + public static func getCustomerPortalURL(accessToken: String, externalID: String) async -> Result { + var headers = makeAuthorizationHeader(for: accessToken) + headers["externalAccountId"] = externalID + return await executeAPICall(method: "GET", endpoint: "checkout/portal", headers: headers) + } + + public struct GetCustomerPortalURLResponse: Decodable { + public let customerPortalUrl: String + } + } diff --git a/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift b/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift new file mode 100644 index 0000000000..60ac8c3e53 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift @@ -0,0 +1,58 @@ +// +// SubscriptionPurchaseEnvironment.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +public final class SubscriptionPurchaseEnvironment { + + public enum Environment { + case appStore, stripe + } + + public static var current: Environment = .appStore { + didSet { + canPurchase = false + + switch current { + case .appStore: + setupForAppStore() + case .stripe: + setupForStripe() + } + } + } + + public static var canPurchase: Bool = false + + private static func setupForAppStore() { + if #available(macOS 12.0, iOS 15.0, *) { + Task { + await PurchaseManager.shared.updateAvailableProducts() + canPurchase = !PurchaseManager.shared.availableProducts.isEmpty + } + } + } + + private static func setupForStripe() { + Task { + if case let .success(products) = await SubscriptionService.getProducts() { + canPurchase = !products.isEmpty + } + } + } +} diff --git a/DuckDuckGo/PrivacyPro/Extensions/URL+Subscription.swift b/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift similarity index 87% rename from DuckDuckGo/PrivacyPro/Extensions/URL+Subscription.swift rename to DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift index dbb018b53e..78061d4a93 100644 --- a/DuckDuckGo/PrivacyPro/Extensions/URL+Subscription.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift @@ -19,7 +19,7 @@ import Foundation public extension URL { - + static var purchaseSubscription: URL { URL(string: "https://abrown.duckduckgo.com/subscriptions/welcome")! } @@ -28,7 +28,6 @@ public extension URL { URL(string: "https://duckduckgo.com/about")! } - // MARK: - Subscription Email static var activateSubscriptionViaEmail: URL { URL(string: "https://abrown.duckduckgo.com/subscriptions/activate")! @@ -41,4 +40,10 @@ public extension URL { static var manageSubscriptionEmail: URL { URL(string: "https://abrown.duckduckgo.com/subscriptions/manage")! } + + // MARK: - App Store app manage subscription URL + + static var manageSubscriptionsInAppStoreAppURL: URL { + URL(string: "macappstores://apps.apple.com/account/subscriptions")! + } } diff --git a/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift b/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift deleted file mode 100644 index 14e5ba02d3..0000000000 --- a/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// UserText.swift -// DuckDuckGo -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -public struct SubscriptionUserText { - - public static let navigationTitle = NSLocalizedString("nagivation.title", value: "Privacy Pro", comment: "Navigation Bar Title for Feature") - - -} diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 09aed84e03..09c83c23e5 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -115,25 +115,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { await withTransactionInProgress { - let subscriptionOptions: [SubscriptionOption] - - if #available(iOS 15, *) { - let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.monthly) }) - let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.yearly) }) - - guard let monthly, let yearly else { return nil } - - subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: RecurrenceOptions.month)), - SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: RecurrenceOptions.year))] - } else { - return nil + if #available(iOS 15.0, *) { + switch await AppStorePurchaseFlow.subscriptionOptions() { + case .success(let subscriptionOptions): + return subscriptionOptions + case .failure: + // TODO: handle errors - no products found + return nil + } } - - let message = SubscriptionOptions(platform: Constants.os, - options: subscriptionOptions, - features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) - - return message + return nil } } @@ -154,20 +145,37 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec print("Selected: \(subscriptionSelection.id)") - let emailAccessToken = try? EmailManager().getToken() + // Trigger sign in pop-up + switch await PurchaseManager.shared.syncAppleIDAccount() { + case .success: + break + case .failure: + return nil + } + // Check for active subscriptions + if await PurchaseManager.hasActiveSubscription() { + print("hasActiveSubscription: TRUE") + // TODO: Present something here + // await WindowControllersManager.shared.lastKeyMainWindowController?.showSubscriptionFoundAlert(originalMessage: message) + return nil + } + + let emailAccessToken = try? EmailManager().getToken() + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { case .success: break - case .failure(let error): - print("Purchase failed: \(error)") + case .failure: return nil } - await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 15) - - DispatchQueue.main.async { - self.pushAction(method: .onPurchaseUpdate, webView: message.webView!, params: PurchaseUpdate(type: "completed")) + switch await AppStorePurchaseFlow.completeSubscriptionPurchase() { + case .success(let purchaseUpdate): + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) + case .failure: + // TODO: handle errors - missing entitlements on post purchase check + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) } } @@ -176,12 +184,31 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - // WIP + guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + return nil + } + + let authToken = subscriptionValues.token + let accountManager = AccountManager() + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + return nil } func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - // WIP + let accountManager = AccountManager() + if let accessToken = accountManager.accessToken, + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + + // TODO: Navigate back to settings + return nil } @@ -204,12 +231,15 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec return nil } + // MARK: Push actions + enum SubscribeActionName: String { case onPurchaseUpdate } - struct PurchaseUpdate: Codable { - let type: String + @MainActor + func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) async { + pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) } func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index addc18fdd9..a200f0aae8 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -30,7 +30,7 @@ final class SubscriptionFlowViewModel: ObservableObject { let purchaseManager: PurchaseManager let purchaseURL = URL.purchaseSubscription - let viewTitle = SubscriptionUserText.navigationTitle + let viewTitle = "Privacy Pro" @Published var transactionInProgress = false private var cancellables = Set() diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index 2db5693f2a..de2685dac4 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -44,14 +44,16 @@ struct HeadlessWebview: UIViewRepresentable { webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)" // webView.customUserAgent = DefaultUserAgentManager.duckDuckGoUserAgent - webView.load(URLRequest(url: url)) + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + webView.load(URLRequest(url: url)) + } -#if DEBUG +// #if DEBUG if #available(iOS 16.4, *) { webView.isInspectable = true } -#endif +// #endif return webView } diff --git a/DuckDuckGo/SettingsPrivacyProView.swift b/DuckDuckGo/SettingsPrivacyProView.swift index 25dcbdd245..a006850ef1 100644 --- a/DuckDuckGo/SettingsPrivacyProView.swift +++ b/DuckDuckGo/SettingsPrivacyProView.swift @@ -43,12 +43,13 @@ struct SettingsPrivacyProView: View { } var body: some View { - Section(header: Text(UserText.settingsPProSection)) { - - SettingsCustomCell(content: { privacyProDescriptionView }) - - NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { - SettingsCustomCell(content: { learnMoreView }) + if viewModel.canPurchaseSubscription { + Section(header: Text(UserText.settingsPProSection)) { + SettingsCustomCell(content: { privacyProDescriptionView }) + + NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { + SettingsCustomCell(content: { learnMoreView }) + } } } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 6d9e037855..93b7e67f25 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -81,6 +81,7 @@ final class SettingsViewModel: ObservableObject { var shouldShowNoMicrophonePermissionAlert: Bool = false var shouldShowDebugCell: Bool { return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild } var shouldShowPrivacyProCell: Bool { return featureFlagger.isFeatureOn(.privacyPro) } + @Published var canPurchaseSubscription = false var shouldShowNetworkProtectionCell: Bool { #if NETWORK_PROTECTION @@ -227,6 +228,11 @@ extension SettingsViewModel { version: versionProvider.versionAndBuildNumber ) setupSubscribers() +#if SUBSCRIPTION + if #available(iOS 15, *) { + Task { await setupSubscriptionEnvironment() } + } +#endif } private func firePixel(_ event: Pixel.Event) { @@ -243,6 +249,19 @@ extension SettingsViewModel { completion(true) } } + +#if SUBSCRIPTION + @available(iOS 15.0, *) + private func setupSubscriptionEnvironment() async { + await PurchaseManager.shared.updateAvailableProducts() + PurchaseManager.shared.$availableProducts + .receive(on: RunLoop.main) + .sink { [weak self] products in + self?.canPurchaseSubscription = !products.isEmpty + }.store(in: &cancellables) + + } +#endif #if NETWORK_PROTECTION private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 2771af7e10..9e603d7d54 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1378,9 +1378,6 @@ https://duckduckgo.com/mac"; /* No comment provided by engineer. */ "menu.button.hint" = "Browsing Menu"; -/* Navigation Bar Title for Feature */ -"nagivation.title" = "Privacy Pro"; - /* Title for back button in navigation bar */ "navbar.back-button.title" = "Back"; diff --git a/LocalPackages/Subscription/.gitignore b/LocalPackages/Subscription/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/LocalPackages/Subscription/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/Subscription/Package.resolved b/LocalPackages/Subscription/Package.resolved new file mode 100644 index 0000000000..c4a8bbc9a0 --- /dev/null +++ b/LocalPackages/Subscription/Package.resolved @@ -0,0 +1,104 @@ +{ + "pins" : [ + { + "identity" : "bloom_cpp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/bloom_cpp.git", + "state" : { + "revision" : "8076199456290b61b4544bf2f4caf296759906a0", + "version" : "3.0.0" + } + }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "revision" : "4cf8e857cd78e15c64ba37839634970fc675947c", + "version" : "81.4.0" + } + }, + { + "identity" : "content-scope-scripts", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/content-scope-scripts", + "state" : { + "revision" : "aa279a3b006a0b1e009707311283c7fcaed24fb7", + "version" : "4.39.0" + } + }, + { + "identity" : "duckduckgo-autofill", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", + "state" : { + "revision" : "6dd7d696d4e666cedb2f1890a46fe53615226646", + "version" : "8.4.2" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/GRDB.swift.git", + "state" : { + "revision" : "77d9a83191a74e319a5cfa27b0e3145d15607573", + "version" : "2.2.0" + } + }, + { + "identity" : "privacy-dashboard", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/privacy-dashboard", + "state" : { + "revision" : "51e2b46f413bf3ef18afefad631ca70f2c25ef70", + "version" : "1.4.0" + } + }, + { + "identity" : "punycodeswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gumob/PunycodeSwift.git", + "state" : { + "revision" : "4356ec54e073741449640d3d50a1fd24fd1e1b8b", + "version" : "2.1.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version" : "0.5.0" + } + }, + { + "identity" : "sync_crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/sync_crypto", + "state" : { + "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", + "version" : "0.2.0" + } + }, + { + "identity" : "trackerradarkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "state" : { + "revision" : "4684440d03304e7638a2c8086895367e90987463", + "version" : "1.2.1" + } + }, + { + "identity" : "wireguard-apple", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/wireguard-apple", + "state" : { + "revision" : "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", + "version" : "1.1.1" + } + } + ], + "version" : 2 +} diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift new file mode 100644 index 0000000000..c65b6109c7 --- /dev/null +++ b/LocalPackages/Subscription/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Subscription", + platforms: [ .macOS(.v11), .iOS(.v14) ], + products: [ + .library( + name: "Subscription", + targets: ["Subscription"]), + ], + dependencies: [ + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "94.0.0"), + ], + targets: [ + .target( + name: "Subscription", + dependencies: [ + .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), + ]), + .testTarget( + name: "SubscriptionTests", + dependencies: ["Subscription"]), + ] +) diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift new file mode 100644 index 0000000000..32417fc73a --- /dev/null +++ b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import Subscription + +final class SubscriptionTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +}