From 39396cc848e3e7a8b8873e321896de7fdddb5d9b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 24 Jun 2024 16:39:43 +0100 Subject: [PATCH] Subscription refactoring 4 (#2871) Task/Issue URL: https://app.asana.com/0/1205842942115003/1206805455884775/f **Description**: - Subscription API services and flows abstracted using protocols - Mocks created for all protocols - Some APIs improved by changing params' names --- DuckDuckGo.xcodeproj/project.pbxproj | 28 ++++- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../xcschemes/sandbox-test-tool.xcscheme | 2 +- DuckDuckGo/Application/AppDelegate.swift | 4 +- .../Surveys/SurveyRemoteMessaging.swift | 20 ++-- ...ataBrokerProtectionFeatureGatekeeper.swift | 6 +- .../Model/HomePageContinueSetUpModel.swift | 6 +- DuckDuckGo/Menus/MainMenu.swift | 6 +- .../NavigationBar/View/MoreOptionsMenu.swift | 16 +-- .../View/NavigationBarViewController.swift | 2 +- .../VPNRedditSessionWorkaround.swift | 4 +- ...rkProtectionSubscriptionEventHandler.swift | 6 +- .../MacPacketTunnelProvider.swift | 12 +- .../SubscriptionEnvironment+Default.swift | 2 +- ...riptionManager+StandardConfiguration.swift | 26 ++--- .../Subscription/SubscriptionUIHandler.swift | 73 ++++++------ .../SubscriptionAppStoreRestorer.swift | 76 +++++------- .../SubscriptionPagesUserScript.swift | 109 +++++++----------- .../Subscription/SubscriptionUIHandling.swift | 19 ++- DuckDuckGo/Tab/UserScripts/UserScripts.swift | 4 +- .../VPNMetadataCollector.swift | 8 +- .../Waitlist/VPNFeatureGatekeeper.swift | 6 +- ...taBrokerAuthenticationManagerBuilder.swift | 8 +- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 8 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 20 ++-- DuckDuckGoVPN/NetworkProtectionBouncer.swift | 4 +- IntegrationTests/Tab/AddressBarTests.swift | 2 +- .../DataBrokerProtection/Package.swift | 2 +- ...BrokerProtectionSubscriptionManaging.swift | 8 +- .../NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- .../DebugMenu/DebugPurchaseModel.swift | 8 +- .../DebugPurchaseViewController.swift | 4 +- .../DebugMenu/SubscriptionDebugMenu.swift | 29 ++--- .../PreferencesSubscriptionModel.swift | 36 +++--- .../ActivateSubscriptionAccessModel.swift | 4 +- .../Model/ShareSubscriptionAccessModel.swift | 6 +- .../SubscriptionAccessViewController.swift | 4 +- .../DBP/Mocks/DataBrokerProtectionMocks.swift | 8 +- .../DownloadListCoordinatorTests.swift | 2 +- .../HomePage/SurveyRemoteMessagingTests.swift | 6 +- UnitTests/Menus/MoreOptionsMenuTests.swift | 19 +-- ...criptionAttributionPixelHandlerTests.swift | 0 .../SubscriptionFeatureAvailabilityMock.swift | 31 +++++ .../SubscriptionRedirectManagerTests.swift | 0 .../SubscriptionUIHandlerMock.swift | 68 +++++++++++ 46 files changed, 407 insertions(+), 313 deletions(-) rename UnitTests/{Subscriptions => Subscription}/SubscriptionAttributionPixelHandlerTests.swift (100%) create mode 100644 UnitTests/Subscription/SubscriptionFeatureAvailabilityMock.swift rename UnitTests/{Subscriptions => Subscription}/SubscriptionRedirectManagerTests.swift (100%) create mode 100644 UnitTests/Subscription/SubscriptionUIHandlerMock.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index af391943ae..b88531d82c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2601,6 +2601,10 @@ F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; F1B33DF72BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; + F1B8EC7A2C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B8EC792C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift */; }; + F1B8EC7B2C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B8EC792C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift */; }; + F1B8EC7C2C29958900D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B8EC792C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift */; }; + F1B8EC7D2C29958A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B8EC792C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift */; }; F1C5763E2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */; }; F1C5763F2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */; }; F1C70D792BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */; }; @@ -2655,6 +2659,10 @@ F1DF95E42BD1807C0045E591 /* Crashes in Frameworks */ = {isa = PBXBuildFile; productRef = 537FC71EA5115A983FAF3170 /* Crashes */; }; F1DF95E52BD1807C0045E591 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = DC3F73D49B2D44464AFEFCD8 /* Subscription */; }; F1DF95E72BD188B60045E591 /* LoginItems in Frameworks */ = {isa = PBXBuildFile; productRef = F1DF95E62BD188B60045E591 /* LoginItems */; }; + F1F861152C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */; }; + F1F861162C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */; }; + F1F861172C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */; }; + F1F861182C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */; }; F1FDC9382BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC93A2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; @@ -4129,6 +4137,7 @@ F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; + F1B8EC792C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFeatureAvailabilityMock.swift; sourceTree = ""; }; F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandling.swift; sourceTree = ""; }; F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionLoginItemInterface.swift; sourceTree = ""; }; F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; @@ -4137,6 +4146,7 @@ F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionAttributionPixelHandler.swift; sourceTree = ""; }; F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionRedirectManager.swift; sourceTree = ""; }; + F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandlerMock.swift; sourceTree = ""; }; F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VPNSettings+Environment.swift"; sourceTree = ""; }; F41D174025CB131900472416 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = ""; }; F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearanceExtension.swift; sourceTree = ""; }; @@ -6382,13 +6392,15 @@ path = Attribution; sourceTree = ""; }; - 9F64346E2BECB9FB00D2D8A0 /* Subscriptions */ = { + 9F64346E2BECB9FB00D2D8A0 /* Subscription */ = { isa = PBXGroup; children = ( + F1B8EC792C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift */, 9F64346F2BECBA2800D2D8A0 /* SubscriptionRedirectManagerTests.swift */, 9F0660722BECC71200B8EEF1 /* SubscriptionAttributionPixelHandlerTests.swift */, + F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */, ); - path = Subscriptions; + path = Subscription; sourceTree = ""; }; 9F872D9B2B9058B000138637 /* Extensions */ = { @@ -6715,7 +6727,7 @@ 858A798626A99D9000A75A42 /* SecureVault */, B6DA440F2616C0F200DD1EC2 /* Statistics */, AA63744E24C9BB4A00AB2AC4 /* Suggestions */, - 9F64346E2BECB9FB00D2D8A0 /* Subscriptions */, + 9F64346E2BECB9FB00D2D8A0 /* Subscription */, AA92ACAE24EFE1F5005F41C9 /* Tab */, AAC9C01224CAFBB700AD1325 /* TabBar */, B6CA4822298CDC0B0067ECCE /* TabExtensionsTests */, @@ -10362,6 +10374,7 @@ 3706FDDF293F661700E42796 /* BookmarkSidebarTreeControllerTests.swift in Sources */, 3706FDE0293F661700E42796 /* TabIndexTests.swift in Sources */, 9F26060F2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */, + F1B8EC7B2C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */, 4B9DB0572A983B55000927DB /* MockNotificationService.swift in Sources */, 3706FDE4293F661700E42796 /* TabLazyLoaderTests.swift in Sources */, @@ -10524,6 +10537,7 @@ 3706FE47293F661700E42796 /* URLExtensionTests.swift in Sources */, 9F8D57332BCCCB9A00AEA660 /* UserDefaultsBookmarkFoldersStoreTests.swift in Sources */, 1DB9617B29F1D06D00CF5568 /* InternalUserDeciderMock.swift in Sources */, + F1F861172C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */, 317295D52AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift in Sources */, 3706FE49293F661700E42796 /* BookmarkNodePathTests.swift in Sources */, 1DE03425298BC7F000CAB3D7 /* InternalUserDeciderStoreMock.swift in Sources */, @@ -10651,6 +10665,7 @@ B603973529BEF86200902A34 /* HTTPSUpgradeIntegrationTests.swift in Sources */, B644B44029D57299003FA9AB /* SuggestionLoadingMock.swift in Sources */, 1D8B7D6B2A38BF060045C6F6 /* FireproofDomainsStoreMock.swift in Sources */, + F1F861182C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */, B630E80229C887ED00363609 /* NSErrorAdditionalInfo.swift in Sources */, 3706FEA3293F662100E42796 /* CoreDataEncryptionTests.swift in Sources */, B60C6F8929B1CAB7007BFAA8 /* TestRunHelperInitializer.m in Sources */, @@ -10667,6 +10682,7 @@ B603972D29BEDF2100902A34 /* ExpectedNavigationExtension.swift in Sources */, 3706FEA6293F662100E42796 /* EncryptionKeyStoreTests.swift in Sources */, B6F5656A299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, + F1B8EC7D2C29958A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, B693766F2B6B5F27005BD9D4 /* ErrorPageTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -10693,6 +10709,7 @@ B603973429BEF86200902A34 /* HTTPSUpgradeIntegrationTests.swift in Sources */, 4B1AD91725FC46FB00261379 /* CoreDataEncryptionTests.swift in Sources */, B644B43F29D57298003FA9AB /* SuggestionLoadingMock.swift in Sources */, + F1F861162C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */, 1D8B7D6A2A38BF050045C6F6 /* FireproofDomainsStoreMock.swift in Sources */, B630E7FF29C887ED00363609 /* NSErrorAdditionalInfo.swift in Sources */, 7BA4727D26F01BC400EAA165 /* CoreDataTestUtilities.swift in Sources */, @@ -10709,6 +10726,7 @@ B603972C29BEDF2100902A34 /* ExpectedNavigationExtension.swift in Sources */, 4B1AD8D525FC38DD00261379 /* EncryptionKeyStoreTests.swift in Sources */, B6F56568299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, + F1B8EC7C2C29958900D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, B693766E2B6B5F27005BD9D4 /* ErrorPageTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -11826,6 +11844,7 @@ 1D7693FF2BE3A1AA0016A22B /* DockCustomizerMock.swift in Sources */, 4B8AD0B127A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift in Sources */, 9FA5A0B02BC9039200153786 /* BookmarkFolderStoreMock.swift in Sources */, + F1F861152C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift in Sources */, B69B50472726C5C200758A2B /* VariantManagerTests.swift in Sources */, 8546DE6225C03056000CA5E1 /* UserAgentTests.swift in Sources */, 9F26060B2B85C20A00819292 /* AddEditBookmarkDialogViewModelTests.swift in Sources */, @@ -11867,6 +11886,7 @@ AA0F3DB7261A566C0077F2D9 /* SuggestionLoadingMock.swift in Sources */, B6E6BA162BA2CF5F008AA7E1 /* SandboxTestToolNotifications.swift in Sources */, B60C6F8129B1B4AD007BFAA8 /* TestRunHelper.swift in Sources */, + F1B8EC7A2C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, 9F872DA02B90644800138637 /* ContextualMenuTests.swift in Sources */, 4B9292BE2667103100AD2C21 /* PasteboardFolderTests.swift in Sources */, 4B9292C52667104B00AD2C21 /* CoreDataTestUtilities.swift in Sources */, @@ -13022,7 +13042,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 159.0.2; + version = 160.0.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6fc2acfeed..1a4c434c1e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "28687a848cff47267e64e3bfcdf5c92effbee5ce", - "version" : "159.0.2" + "revision" : "70ea98c091350a5dde9d6e5d6b05f064e5a36ec0", + "version" : "160.0.0" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme index 41730d7069..eb7e5e26bb 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> Result - + func getSubscription(accessToken: String) async -> Result } final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { @@ -43,24 +42,25 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { private let messageRequest: HomePageRemoteMessagingRequest private let messageStorage: SurveyRemoteMessagingStorage - private let accountManager: AccountManaging + private let accountManager: AccountManager private let subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching private let vpnActivationDateStore: WaitlistActivationDateStore private let pirActivationDateStore: WaitlistActivationDateStore private let minimumRefreshInterval: TimeInterval private let userDefaults: UserDefaults - convenience init(subscriptionManager: SubscriptionManaging) { + convenience init(subscriptionManager: SubscriptionManager) { + let subscriptionFetcher = SurveyRemoteMessageSubscriptionFetcher(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) #if DEBUG || REVIEW self.init( accountManager: subscriptionManager.accountManager, - subscriptionFetcher: subscriptionManager.subscriptionService, + subscriptionFetcher: subscriptionFetcher, minimumRefreshInterval: .seconds(30) ) #else self.init( accountManager: subscriptionManager.accountManager, - subscriptionFetcher: subscriptionManager.subscriptionService, + subscriptionFetcher: subscriptionFetcher, minimumRefreshInterval: .hours(1) ) #endif @@ -69,7 +69,7 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { init( messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.surveysRequest(), messageStorage: SurveyRemoteMessagingStorage = DefaultSurveyRemoteMessagingStorage.surveys(), - accountManager: AccountManaging, + accountManager: AccountManager, subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching, vpnActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), pirActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp), @@ -280,10 +280,10 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } -extension SubscriptionService: SurveyRemoteMessageSubscriptionFetching { +struct SurveyRemoteMessageSubscriptionFetcher: SurveyRemoteMessageSubscriptionFetching { + let subscriptionEndpointService: SubscriptionEndpointService func getSubscription(accessToken: String) async -> Result { - return await self.getSubscription(accessToken: accessToken, cachePolicy: .returnCacheDataElseLoad) + return await subscriptionEndpointService.getSubscription(accessToken: accessToken, cachePolicy: .returnCacheDataElseLoad) } - } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index c2c4d65768..8437d6962c 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -42,7 +42,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature private let userDefaults: UserDefaults private let waitlistStorage: WaitlistStorage private let subscriptionAvailability: SubscriptionFeatureAvailability - private let accountManager: AccountManaging + private let accountManager: AccountManager private let dataBrokerProtectionKey = "data-broker-protection.cleaned-up-from-waitlist-to-privacy-pro" @@ -55,7 +55,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature userDefaults: UserDefaults = .standard, waitlistStorage: WaitlistStorage = DataBrokerProtectionWaitlist().waitlistStorage, subscriptionAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability(), - accountManager: AccountManaging) { + accountManager: AccountManager) { self.privacyConfigurationManager = privacyConfigurationManager self.featureDisabler = featureDisabler self.pixelHandler = pixelHandler @@ -139,7 +139,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature } func arePrerequisitesSatisfied() async -> Bool { - let entitlements = await accountManager.hasEntitlement(for: .dataBrokerProtection, + let entitlements = await accountManager.hasEntitlement(forProductName: .dataBrokerProtection, cachePolicy: .reloadIgnoringLocalCacheData) var hasEntitlements: Bool switch entitlements { diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index 04e9074f65..b9a8f363b8 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -54,7 +54,7 @@ extension HomePage.Models { private let tabCollectionViewModel: TabCollectionViewModel private let emailManager: EmailManager private let duckPlayerPreferences: DuckPlayerPreferencesPersistor - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager @UserDefaultsWrapper(key: .homePageShowAllFeatures, defaultValue: false) var shouldShowAllFeatures: Bool { @@ -111,7 +111,7 @@ extension HomePage.Models { surveyRemoteMessaging: SurveyRemoteMessaging, privacyConfigurationManager: PrivacyConfigurationManaging = AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager, permanentSurveyManager: SurveyManager = PermanentSurveyManager(), - subscriptionManager: SubscriptionManaging = Application.appDelegate.subscriptionManager) { + subscriptionManager: SubscriptionManager = Application.appDelegate.subscriptionManager) { self.defaultBrowserProvider = defaultBrowserProvider self.dockCustomizer = dockCustomizer self.dataImportProvider = dataImportProvider @@ -338,7 +338,7 @@ extension HomePage.Models { var subscription: Subscription? if let token = subscriptionManager.accountManager.accessToken { - switch await subscriptionManager.subscriptionService.getSubscription( + switch await subscriptionManager.subscriptionEndpointService.getSubscription( accessToken: token, cachePolicy: .returnCacheDataElseLoad ) { diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 20ba2ab6fa..5967061ca6 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -624,14 +624,14 @@ import SubscriptionUI let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - var currentEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + var currentEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) let updateServiceEnvironment: (SubscriptionEnvironment.ServiceEnvironment) -> Void = { env in currentEnvironment.serviceEnvironment = env - SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) + DefaultSubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) } let updatePurchasingPlatform: (SubscriptionEnvironment.PurchasePlatform) -> Void = { platform in currentEnvironment.purchasePlatform = platform - SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) + DefaultSubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) } SubscriptionDebugMenu(currentEnvironment: currentEnvironment, diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index feba5cd2a8..1878726e15 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -57,9 +57,10 @@ final class MoreOptionsMenu: NSMenu { private let passwordManagerCoordinator: PasswordManagerCoordinating private let internalUserDecider: InternalUserDecider private lazy var sharingMenu: NSMenu = SharingMenu(title: UserText.shareMenuItem) - private let accountManager: AccountManaging + private let accountManager: AccountManager private let vpnFeatureGatekeeper: VPNFeatureGatekeeper + private let subscriptionFeatureAvailability: SubscriptionFeatureAvailability required init(coder: NSCoder) { fatalError("MoreOptionsMenu: Bad initializer") @@ -69,14 +70,16 @@ final class MoreOptionsMenu: NSMenu { emailManager: EmailManager = EmailManager(), passwordManagerCoordinator: PasswordManagerCoordinator, vpnFeatureGatekeeper: VPNFeatureGatekeeper, + subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability(), sharingMenu: NSMenu? = nil, internalUserDecider: InternalUserDecider, - accountManager: AccountManaging) { + accountManager: AccountManager) { self.tabCollectionViewModel = tabCollectionViewModel self.emailManager = emailManager self.passwordManagerCoordinator = passwordManagerCoordinator self.vpnFeatureGatekeeper = vpnFeatureGatekeeper + self.subscriptionFeatureAvailability = subscriptionFeatureAvailability self.internalUserDecider = internalUserDecider self.accountManager = accountManager @@ -293,7 +296,7 @@ final class MoreOptionsMenu: NSMenu { private func addSubscriptionItems() { var items: [NSMenuItem] = [] - if DefaultSubscriptionFeatureAvailability().isFeatureAvailable && !accountManager.isUserAuthenticated { + if subscriptionFeatureAvailability.isFeatureAvailable && !accountManager.isUserAuthenticated { items.append(contentsOf: makeInactiveSubscriptionItems()) } else { items.append(contentsOf: makeActiveSubscriptionItems()) // this adds NETP and DBP only if conditionally enabled @@ -309,7 +312,6 @@ final class MoreOptionsMenu: NSMenu { private func makeActiveSubscriptionItems() -> [NSMenuItem] { var items: [NSMenuItem] = [] - let subscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability() let networkProtectionItem: NSMenuItem networkProtectionItem = makeNetworkProtectionItem() @@ -320,7 +322,7 @@ final class MoreOptionsMenu: NSMenu { Task { let isMenuItemEnabled: Bool - switch await accountManager.hasEntitlement(for: .networkProtection) { + switch await accountManager.hasEntitlement(forProductName: .networkProtection) { case let .success(result): isMenuItemEnabled = result case .failure: @@ -345,7 +347,7 @@ final class MoreOptionsMenu: NSMenu { Task { let isMenuItemEnabled: Bool - switch await accountManager.hasEntitlement(for: .dataBrokerProtection) { + switch await accountManager.hasEntitlement(forProductName: .dataBrokerProtection) { case let .success(result): isMenuItemEnabled = result case .failure: @@ -375,7 +377,7 @@ final class MoreOptionsMenu: NSMenu { Task { let isMenuItemEnabled: Bool - switch await accountManager.hasEntitlement(for: .identityTheftRestoration) { + switch await accountManager.hasEntitlement(forProductName: .identityTheftRestoration) { case let .success(result): isMenuItemEnabled = result case .failure: diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 007eef5f71..f49eb166d1 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -71,7 +71,7 @@ final class NavigationBarViewController: NSViewController { return progressView }() - private var subscriptionManager: SubscriptionManaging { + private var subscriptionManager: SubscriptionManager { Application.appDelegate.subscriptionManager } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift index 848f27c564..3949a24805 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift @@ -25,11 +25,11 @@ import Common final class VPNRedditSessionWorkaround { - private let accountManager: AccountManaging + private let accountManager: AccountManager private let ipcClient: VPNControllerXPCClient private let statusReporter: NetworkProtectionStatusReporter - init(accountManager: AccountManaging, + init(accountManager: AccountManager, ipcClient: VPNControllerXPCClient = .shared, statusReporter: NetworkProtectionStatusReporter) { self.accountManager = accountManager diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 6e3fa7ea0b..127197a709 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -25,14 +25,14 @@ import NetworkProtectionUI final class NetworkProtectionSubscriptionEventHandler { - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager private let tunnelController: TunnelController private let networkProtectionTokenStorage: NetworkProtectionTokenStore private let vpnUninstaller: VPNUninstalling private let userDefaults: UserDefaults private var cancellables = Set() - init(subscriptionManager: SubscriptionManaging, + init(subscriptionManager: SubscriptionManager, tunnelController: TunnelController, networkProtectionTokenStorage: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), vpnUninstaller: VPNUninstalling, @@ -48,7 +48,7 @@ final class NetworkProtectionSubscriptionEventHandler { private func subscribeToEntitlementChanges() { Task { - switch await subscriptionManager.accountManager.hasEntitlement(for: .networkProtection) { + switch await subscriptionManager.accountManager.hasEntitlement(forProductName: .networkProtection) { case .success(let hasEntitlements): Task { await handleEntitlementsChange(hasEntitlements: hasEntitlements) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 1846810bdf..85de5fca42 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -381,15 +381,15 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { subscriptionEnvironment.serviceEnvironment = .staging } - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = AccountManager(accessTokenStorage: tokenStore, + let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let accountManager = DefaultAccountManager(accessTokenStorage: tokenStore, entitlementsCache: entitlementsCache, - subscriptionService: subscriptionService, - authService: authService) + subscriptionEndpointService: subscriptionEndpointService, + authEndpointService: authEndpointService) let entitlementsCheck = { - await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + await accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) } let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) diff --git a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift index fdb1353662..64459679ba 100644 --- a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift +++ b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift @@ -37,7 +37,7 @@ extension SubscriptionEnvironment { } } -extension SubscriptionManager { +extension DefaultSubscriptionManager { static public func getSavedOrDefaultEnvironment(userDefaults: UserDefaults) -> SubscriptionEnvironment { if let savedEnvironment = loadEnvironmentFrom(userDefaults: userDefaults) { diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index 552b8e28b7..e9562194eb 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -20,37 +20,37 @@ import Foundation import Subscription import Common -extension SubscriptionManager { +extension DefaultSubscriptionManager { // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root public convenience init() { // MARK: - Configure Subscription let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = AccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionService: subscriptionService, - authService: authService) + let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, + entitlementsCache: entitlementsCache, + subscriptionEndpointService: subscriptionEndpointService, + authEndpointService: authEndpointService) if #available(macOS 12.0, *) { - let storePurchaseManager = StorePurchaseManager() + let storePurchaseManager = DefaultStorePurchaseManager() self.init(storePurchaseManager: storePurchaseManager, accountManager: accountManager, - subscriptionService: subscriptionService, - authService: authService, + subscriptionEndpointService: subscriptionEndpointService, + authEndpointService: authEndpointService, subscriptionEnvironment: subscriptionEnvironment) } else { self.init(accountManager: accountManager, - subscriptionService: subscriptionService, - authService: authService, + subscriptionEndpointService: subscriptionEndpointService, + authEndpointService: authEndpointService, subscriptionEnvironment: subscriptionEnvironment) } } diff --git a/DuckDuckGo/Subscription/SubscriptionUIHandler.swift b/DuckDuckGo/Subscription/SubscriptionUIHandler.swift index 0ac6b8320c..e8093dea45 100644 --- a/DuckDuckGo/Subscription/SubscriptionUIHandler.swift +++ b/DuckDuckGo/Subscription/SubscriptionUIHandler.swift @@ -16,17 +16,18 @@ // limitations under the License. // -import Foundation +import AppKit import SubscriptionUI +import WebKit @MainActor final class SubscriptionUIHandler: SubscriptionUIHandling { - fileprivate var currentWindow: NSWindow? { windowControllersManagerProvider().lastKeyMainWindowController?.window } - fileprivate var currentMainViewController: MainViewController? { - windowControllersManagerProvider().lastKeyMainWindowController?.mainViewController - } fileprivate var windowControllersManager: WindowControllersManager { windowControllersManagerProvider() } + fileprivate var mainWindowController: MainWindowController? { windowControllersManager.lastKeyMainWindowController } + fileprivate var currentWindow: NSWindow? { mainWindowController?.window } + fileprivate var currentMainViewController: MainViewController? { mainWindowController?.mainViewController } + typealias WindowControllersManagerProvider = () -> WindowControllersManager fileprivate nonisolated let windowControllersManagerProvider: WindowControllersManagerProvider fileprivate var progressViewController: ProgressViewController? @@ -38,8 +39,9 @@ final class SubscriptionUIHandler: SubscriptionUIHandling { // MARK: - SubscriptionUIHandling func presentProgressViewController(withTitle: String) { - progressViewController = ProgressViewController(title: UserText.purchasingSubscriptionTitle) - currentMainViewController?.presentAsSheet(progressViewController!) + let newProgressViewController = ProgressViewController(title: UserText.purchasingSubscriptionTitle) + currentMainViewController?.presentAsSheet(newProgressViewController) + progressViewController = newProgressViewController } func dismissProgressViewController() { @@ -65,44 +67,35 @@ final class SubscriptionUIHandler: SubscriptionUIHandling { currentMainViewController?.presentAsSheet(newSubscriptionAccessViewController) } - func show(alertType: SubscriptionAlertType, text: String? = nil, firstButtonAction: (() -> Void)? = nil) { - - var alert: NSAlert? - switch alertType { - case .somethingWentWrong: - alert = .somethingWentWrongAlert() - case .subscriptionNotFound: - alert = .subscriptionNotFoundAlert() - case .subscriptionInactive: - alert = .subscriptionInactiveAlert() - case .subscriptionFound: - alert = .subscriptionFoundAlert() - case .appleIDSyncFailed: - guard let text else { - assertionFailure("Trying to present appleIDSyncFailed alert without required text") - return + @discardableResult + func dismissProgressViewAndShow(alertType: SubscriptionAlertType, text: String?) async -> NSApplication.ModalResponse { + dismissProgressViewController() + return await show(alertType: alertType, text: text) + } + + @discardableResult + func show(alertType: SubscriptionAlertType, text: String?) async -> NSApplication.ModalResponse { + var alert: NSAlert { + switch alertType { + case .somethingWentWrong: + return .somethingWentWrongAlert() + case .subscriptionNotFound: + return .subscriptionNotFoundAlert() + case .subscriptionInactive: + return .subscriptionInactiveAlert() + case .subscriptionFound: + return .subscriptionFoundAlert() + case .appleIDSyncFailed: + return .appleIDSyncFailedAlert(text: text ?? "Error") } - alert = .appleIDSyncFailedAlert(text: text) } - guard let alert else { - assertionFailure("Missing subscription alert") - return + guard let currentWindow else { + assertionFailure("Missing current window") + return .alertSecondButtonReturn } - currentWindow?.show(alert, firstButtonAction: firstButtonAction) - } - - func show(alertType: SubscriptionAlertType) { - show(alertType: alertType, text: nil, firstButtonAction: nil) - } - - func show(alertType: SubscriptionAlertType, firstButtonAction: (() -> Void)?) { - show(alertType: alertType, text: nil, firstButtonAction: firstButtonAction) - } - - func show(alertType: SubscriptionAlertType, text: String?) { - show(alertType: alertType, text: text, firstButtonAction: nil) + return await alert.beginSheetModal(for: currentWindow) } func showTab(with content: Tab.TabContent) { diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 8b4491fa43..abd215ec5e 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -25,51 +25,43 @@ import PixelKit @available(macOS 12.0, *) struct SubscriptionAppStoreRestorer { - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager @MainActor var window: NSWindow? { WindowControllersManager.shared.lastKeyMainWindowController?.window } let subscriptionErrorReporter = SubscriptionErrorReporter() let uiHandler: SubscriptionUIHandling - public init(subscriptionManager: SubscriptionManaging, + public init(subscriptionManager: SubscriptionManager, uiHandler: SubscriptionUIHandling) { self.subscriptionManager = subscriptionManager self.uiHandler = uiHandler } - // swiftlint:disable:next cyclomatic_complexity func restoreAppStoreSubscription() async { + await uiHandler.presentProgressViewController(withTitle: UserText.restoringSubscriptionTitle) - defer { - Task { @MainActor in - uiHandler.dismissProgressViewController() - } - } - - Task { @MainActor in - uiHandler.presentProgressViewController(withTitle: UserText.restoringSubscriptionTitle) - } - - let syncResult = await subscriptionManager.storePurchaseManager().syncAppleIDAccount() + do { + try await subscriptionManager.storePurchaseManager().syncAppleIDAccount() + await continueRestore() + } catch { + await uiHandler.dismissProgressViewController() - switch syncResult { - case .success: - break - case .failure(let error): switch error as? StoreKitError { case .some(.userCancelled): - return - default: break - } - - Task { @MainActor in - uiHandler.show(alertType: .appleIDSyncFailed, text: error.localizedDescription) + default: + let alertResponse = await uiHandler.show(alertType: .appleIDSyncFailed, text: error.localizedDescription) + if alertResponse == .alertFirstButtonReturn { + await uiHandler.presentProgressViewController(withTitle: UserText.restoringSubscriptionTitle) + await continueRestore() + } } } + } - let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) + private func continueRestore() async { + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() - + await uiHandler.dismissProgressViewController() switch result { case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) @@ -79,7 +71,6 @@ struct SubscriptionAppStoreRestorer { default: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureOther, frequency: .dailyAndCount) } - switch error { case .missingAccountOrTransactions: subscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) @@ -93,34 +84,31 @@ struct SubscriptionAppStoreRestorer { } } } -} - -@available(macOS 12.0, *) -extension SubscriptionAppStoreRestorer { // MARK: - UI interactions - @MainActor - func showSomethingWentWrongAlert() { + func showSomethingWentWrongAlert() async { PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) - uiHandler.show(alertType: .somethingWentWrong) + await uiHandler.show(alertType: .somethingWentWrong) } - @MainActor - func showSubscriptionNotFoundAlert() { - uiHandler.show(alertType: .subscriptionNotFound, firstButtonAction: { + func showSubscriptionNotFoundAlert() async { + switch await uiHandler.show(alertType: .subscriptionNotFound) { + case .alertFirstButtonReturn: let url = subscriptionManager.url(for: .purchase) - uiHandler.showTab(with: .subscription(url)) + await uiHandler.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) + default: return + } } - @MainActor - func showSubscriptionInactiveAlert() { - uiHandler.show(alertType: .subscriptionInactive, firstButtonAction: { + func showSubscriptionInactiveAlert() async { + switch await uiHandler.show(alertType: .subscriptionInactive) { + case .alertFirstButtonReturn: let url = subscriptionManager.url(for: .purchase) - uiHandler.showTab(with: .subscription(url)) + await uiHandler.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) + default: return + } } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index ae017978c4..da91ad1ef7 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -78,8 +78,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { .exact(hostname: "duckduckgo.com"), .exact(hostname: "abrown.duckduckgo.com") ]) - let subscriptionManager: SubscriptionManaging - var accountManager: AccountManaging { subscriptionManager.accountManager } + let subscriptionManager: SubscriptionManager + var accountManager: AccountManager { subscriptionManager.accountManager } var subscriptionPlatform: SubscriptionEnvironment.PurchasePlatform { subscriptionManager.currentEnvironment.purchasePlatform } let stripePurchaseFlow: StripePurchaseFlow @@ -87,11 +87,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { let subscriptionSuccessPixelHandler: SubscriptionAttributionPixelHandler let uiHandler: SubscriptionUIHandling - public init(subscriptionManager: SubscriptionManaging, + public init(subscriptionManager: SubscriptionManager, subscriptionSuccessPixelHandler: SubscriptionAttributionPixelHandler = PrivacyProSubscriptionAttributionPixelHandler(), + stripePurchaseFlow: StripePurchaseFlow, uiHandler: SubscriptionUIHandling) { self.subscriptionManager = subscriptionManager - self.stripePurchaseFlow = StripePurchaseFlow(subscriptionManager: subscriptionManager) + self.stripePurchaseFlow = stripePurchaseFlow self.subscriptionSuccessPixelHandler = subscriptionSuccessPixelHandler self.uiHandler = uiHandler } @@ -223,15 +224,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { subscriptionSuccessPixelHandler.origin = await originFrom(originalMessage: message) if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, *) { - defer { - Task { @MainActor in - uiHandler.dismissProgressViewController() - } - } - guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") subscriptionErrorReporter.report(subscriptionActivationError: .generalError) + await uiHandler.dismissProgressViewController() return nil } @@ -245,12 +241,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") subscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) await showSubscriptionFoundAlert(originalMessage: message) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) return nil } let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String - let appStorePurchaseFlow = AppStorePurchaseFlow(subscriptionManager: subscriptionManager) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + appStoreRestoreFlow: DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager)) os_log(.info, log: .subscription, "[Purchase] Purchasing") switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { @@ -278,6 +276,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { if error != .cancelledByUser { await showSomethingWentWrongAlert() + } else { + await uiHandler.dismissProgressViewController() } await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) return nil @@ -286,8 +286,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await uiHandler.updateProgressViewController(title: UserText.completingPurchaseTitle) os_log(.info, log: .subscription, "[Purchase] Completing purchase") - - switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { + let completePurchaseResult = await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) + switch completePurchaseResult { case .success(let purchaseUpdate): os_log(.info, log: .subscription, "[Purchase] Purchase complete") PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) @@ -313,6 +313,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { DispatchQueue.main.async { NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) } + await uiHandler.dismissProgressViewController() return nil case .internalError: assertionFailure("Internal error") @@ -323,15 +324,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } } else if subscriptionPlatform == .stripe { let emailAccessToken = try? EmailManager().getToken() - let result = await stripePurchaseFlow.prepareSubscriptionPurchase(emailAccessToken: emailAccessToken) - switch result { case .success(let success): await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: success) case .failure(let error): await showSomethingWentWrongAlert() - switch error { case .noProductsFound: subscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) @@ -342,6 +340,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } } + await uiHandler.dismissProgressViewController() return nil } @@ -385,30 +384,20 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { case .personalInformationRemoval: PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .unique) NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) - Task { @MainActor in - self.uiHandler.showTab(with: .dataBrokerProtection) - } + await uiHandler.showTab(with: .dataBrokerProtection) case .identityTheftRestoration: PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) let url = subscriptionManager.url(for: .identityTheftRestoration) - Task { @MainActor in - self.uiHandler.showTab(with: .identityTheftRestoration(url)) - } + await uiHandler.showTab(with: .identityTheftRestoration(url)) } return nil } func completeStripePayment(params: Any, original: WKScriptMessage) async throws -> Encodable? { - Task { @MainActor in - uiHandler.presentProgressViewController(withTitle: UserText.completingPurchaseTitle) - } - + await uiHandler.presentProgressViewController(withTitle: UserText.completingPurchaseTitle) await stripePurchaseFlow.completeSubscriptionPurchase() - - Task { @MainActor in - uiHandler.dismissProgressViewController() - } + await uiHandler.dismissProgressViewController() PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) subscriptionSuccessPixelHandler.fireSuccessfulSubscriptionAttributionPixel() @@ -457,7 +446,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } @MainActor - func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) async { + func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) { pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) } @@ -475,51 +464,37 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { let url = originalMessage.webView?.url return url?.getParameter(named: AttributionParameter.origin) } -} -extension SubscriptionPagesUseSubscriptionFeature { + // MARK: - UI interactions - // MARK: - UI interactions - - @MainActor - func showSomethingWentWrongAlert() { + func showSomethingWentWrongAlert() async { PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) - uiHandler.show(alertType: .somethingWentWrong) - } - - @MainActor - func showSubscriptionNotFoundAlert() { - uiHandler.show(alertType: .subscriptionNotFound, firstButtonAction: { - let url = self.subscriptionManager.url(for: .purchase) - self.uiHandler.showTab(with: .subscription(url)) + switch await uiHandler.dismissProgressViewAndShow(alertType: .somethingWentWrong, text: nil) { + case .alertFirstButtonReturn: + let url = subscriptionManager.url(for: .purchase) + await uiHandler.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) + default: return + } } - @MainActor - func showSubscriptionInactiveAlert() { - uiHandler.show(alertType: .subscriptionInactive, firstButtonAction: { - let url = self.subscriptionManager.url(for: .purchase) - self.uiHandler.showTab(with: .subscription(url)) - PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) - } + func showSubscriptionFoundAlert(originalMessage: WKScriptMessage) async { - @MainActor - func showSubscriptionFoundAlert(originalMessage: WKScriptMessage) { - uiHandler.show(alertType: .subscriptionFound, firstButtonAction: { + switch await uiHandler.dismissProgressViewAndShow(alertType: .subscriptionFound, text: nil) { + case .alertFirstButtonReturn: if #available(macOS 12.0, *) { - Task { - let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: self.subscriptionManager) - let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() - switch result { - case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) - case .failure: break - } + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: self.subscriptionManager) + let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() + switch result { + case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) + case .failure: break + } + Task { @MainActor in originalMessage.webView?.reload() } } - }) + default: return + } } } @@ -537,8 +512,8 @@ extension SubscriptionPagesUseSubscriptionFeature: SubscriptionAccessActionHandl } func subscriptionAccessActionOpenURLHandler(url: URL) { - Task { @MainActor in - self.uiHandler.showTab(with: .subscription(url)) + Task { + await self.uiHandler.showTab(with: .subscription(url)) } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionUIHandling.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionUIHandling.swift index 6176f80e29..f4512d1ac9 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionUIHandling.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionUIHandling.swift @@ -16,8 +16,9 @@ // limitations under the License. // -import Foundation +import AppKit import SubscriptionUI +import WebKit @MainActor protocol SubscriptionUIHandling { @@ -31,14 +32,24 @@ protocol SubscriptionUIHandling { func presentSubscriptionAccessViewController(handler: SubscriptionAccessActionHandling, message: WKScriptMessage) // MARK: Alerts - func show(alertType: SubscriptionAlertType) - func show(alertType: SubscriptionAlertType, firstButtonAction: (() -> Void)?) - func show(alertType: SubscriptionAlertType, text: String?) + @discardableResult + func dismissProgressViewAndShow(alertType: SubscriptionAlertType, text: String?) async -> NSApplication.ModalResponse + @discardableResult + func show(alertType: SubscriptionAlertType, text: String?) async -> NSApplication.ModalResponse // MARK: Tab func showTab(with content: Tab.TabContent) } +@MainActor +extension SubscriptionUIHandling { + + @discardableResult + func show(alertType: SubscriptionAlertType) async -> NSApplication.ModalResponse { + return await show(alertType: alertType, text: nil) + } +} + enum SubscriptionAlertType { case somethingWentWrong case subscriptionNotFound diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index 75dd486ab0..a94866e522 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -95,7 +95,9 @@ final class UserScripts: UserScriptsProvider { } if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - let delegate = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: Application.appDelegate.subscriptionManager, + let subscriptionManager = Application.appDelegate.subscriptionManager + let delegate = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, + stripePurchaseFlow: DefaultStripePurchaseFlow(subscriptionManager: subscriptionManager), uiHandler: Application.appDelegate.subscriptionUIHandler) subscriptionPagesUserScript.registerSubfeature(delegate: delegate) userScripts.append(subscriptionPagesUserScript) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index e4e435edda..99a84e0842 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -122,11 +122,11 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusReporter: NetworkProtectionStatusReporter private let ipcClient: VPNControllerXPCClient private let defaults: UserDefaults - private let accountManager: AccountManaging + private let accountManager: AccountManager private let settings: VPNSettings init(defaults: UserDefaults = .netP, - accountManager: AccountManaging) { + accountManager: AccountManager) { let ipcClient = VPNControllerXPCClient.shared ipcClient.register { _ in } @@ -156,7 +156,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { func updateSettings() { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) } @@ -319,7 +319,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { - let hasVPNEntitlement = (try? await accountManager.hasEntitlement(for: .networkProtection).get()) ?? false + let hasVPNEntitlement = (try? await accountManager.hasEntitlement(forProductName: .networkProtection).get()) ?? false return .init( hasPrivacyProAccount: accountManager.isUserAuthenticated, hasVPNEntitlement: hasVPNEntitlement diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index 1771c828ae..f661940e1a 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -43,14 +43,14 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let privacyConfigurationManager: PrivacyConfigurationManaging private let defaults: UserDefaults - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), vpnUninstaller: VPNUninstalling = VPNUninstaller(), defaults: UserDefaults = .netP, log: OSLog = .networkProtection, - subscriptionManager: SubscriptionManaging) { + subscriptionManager: SubscriptionManager) { self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionFeatureActivation = networkProtectionFeatureActivation @@ -73,7 +73,7 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { return false } - switch await subscriptionManager.accountManager.hasEntitlement(for: .networkProtection) { + switch await subscriptionManager.accountManager.hasEntitlement(forProductName: .networkProtection) { case .success(let hasEntitlement): return hasEntitlement case .failure(let error): diff --git a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift b/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift index b07a7c2796..0fb44dfb1c 100644 --- a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift +++ b/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift @@ -23,7 +23,7 @@ import Subscription final public class DataBrokerAuthenticationManagerBuilder { static func buildAuthenticationManager(redeemUseCase: RedeemUseCase = RedeemUseCase(), - subscriptionManager: SubscriptionManaging) -> DataBrokerProtectionAuthenticationManager { + subscriptionManager: SubscriptionManager) -> DataBrokerProtectionAuthenticationManager { let subscriptionManager = DataBrokerProtectionSubscriptionManager(subscriptionManager: subscriptionManager) return DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) @@ -31,8 +31,8 @@ final public class DataBrokerAuthenticationManagerBuilder { } } -extension AccountManager: DataBrokerProtectionAccountManaging { - public func hasEntitlement(for cachePolicy: CachePolicy) async -> Result { - await hasEntitlement(for: .dataBrokerProtection, cachePolicy: .reloadIgnoringLocalCacheData) +extension DefaultAccountManager: DataBrokerProtectionAccountManaging { + public func hasEntitlement(for cachePolicy: APICachePolicy) async -> Result { + await hasEntitlement(forProductName: .dataBrokerProtection, cachePolicy: .reloadIgnoringLocalCacheData) } } diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 70465069d4..96fc3c0878 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -29,7 +29,7 @@ import Subscription @objc(Application) final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { private let _delegate: DuckDuckGoDBPBackgroundAgentAppDelegate - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager override init() { os_log(.error, log: .dbpBackgroundAgent, "🟢 DBP background Agent starting: %{public}d", NSRunningApplication.current.processIdentifier) @@ -68,7 +68,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { } // Configure Subscription - subscriptionManager = SubscriptionManager() + subscriptionManager = DefaultSubscriptionManager() _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) @@ -87,10 +87,10 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele private let settings = DataBrokerProtectionSettings() private var cancellables = Set() private var statusBarMenu: StatusBarMenu? - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager private var manager: DataBrokerProtectionAgentManager? - init(subscriptionManager: SubscriptionManaging) { + init(subscriptionManager: SubscriptionManager) { self.subscriptionManager = subscriptionManager } diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 3f496db8cb..2fca4da921 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -34,7 +34,7 @@ import VPNAppLauncher @objc(Application) final class DuckDuckGoVPNApplication: NSApplication { - public let accountManager: AccountManaging + public let accountManager: AccountManager private let _delegate: DuckDuckGoVPNAppDelegate override init() { @@ -54,17 +54,17 @@ final class DuckDuckGoVPNApplication: NSApplication { // MARK: - Configure Subscription let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - accountManager = AccountManager(accessTokenStorage: accessTokenStorage, + accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, entitlementsCache: entitlementsCache, - subscriptionService: subscriptionService, - authService: authService) + subscriptionEndpointService: subscriptionEndpointService, + authEndpointService: authEndpointService) _delegate = DuckDuckGoVPNAppDelegate(bouncer: NetworkProtectionBouncer(accountManager: accountManager), accountManager: accountManager, @@ -131,11 +131,11 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private let appLauncher = AppLauncher() private let bouncer: NetworkProtectionBouncer - private let accountManager: AccountManaging + private let accountManager: AccountManager private let accessTokenStorage: SubscriptionTokenKeychainStorage public init(bouncer: NetworkProtectionBouncer, - accountManager: AccountManaging, + accountManager: AccountManager, accessTokenStorage: SubscriptionTokenKeychainStorage, subscriptionEnvironment: SubscriptionEnvironment) { self.bouncer = bouncer @@ -417,7 +417,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private func setUpSubscriptionMonitoring() { guard accountManager.isUserAuthenticated else { return } let entitlementsCheck = { - await self.accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + await self.accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) } Task { diff --git a/DuckDuckGoVPN/NetworkProtectionBouncer.swift b/DuckDuckGoVPN/NetworkProtectionBouncer.swift index e9acf969b5..537d7cadca 100644 --- a/DuckDuckGoVPN/NetworkProtectionBouncer.swift +++ b/DuckDuckGoVPN/NetworkProtectionBouncer.swift @@ -27,9 +27,9 @@ import Subscription /// final class NetworkProtectionBouncer { - let accountManager: AccountManaging + let accountManager: AccountManager - init(accountManager: AccountManaging) { + init(accountManager: AccountManager) { self.accountManager = accountManager } diff --git a/IntegrationTests/Tab/AddressBarTests.swift b/IntegrationTests/Tab/AddressBarTests.swift index 37325e3063..ed274185f6 100644 --- a/IntegrationTests/Tab/AddressBarTests.swift +++ b/IntegrationTests/Tab/AddressBarTests.swift @@ -531,7 +531,7 @@ class AddressBarTests: XCTestCase { _=window.makeFirstResponder(addressBarTextField) type("some-text") - try await Task.sleep(interval: 0.01) + try await Task.sleep(interval: 1.0) try await didFinishNavigation.value XCTAssertTrue(isAddressBarFirstResponder) XCTAssertEqual(addressBarValue, "some-text") diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index ff936ad9ec..26a46f80fc 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "159.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "160.0.0"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), ], diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift index c2035d4c06..b120914f52 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift @@ -28,7 +28,7 @@ public protocol DataBrokerProtectionSubscriptionManaging { public final class DataBrokerProtectionSubscriptionManager: DataBrokerProtectionSubscriptionManaging { - let subscriptionManager: SubscriptionManaging + let subscriptionManager: SubscriptionManager public var isUserAuthenticated: Bool { subscriptionManager.accountManager.accessToken != nil @@ -38,12 +38,12 @@ public final class DataBrokerProtectionSubscriptionManager: DataBrokerProtection subscriptionManager.accountManager.accessToken } - public init(subscriptionManager: SubscriptionManaging) { + public init(subscriptionManager: SubscriptionManager) { self.subscriptionManager = subscriptionManager } public func hasValidEntitlement() async throws -> Bool { - switch await subscriptionManager.accountManager.hasEntitlement(for: .dataBrokerProtection, + switch await subscriptionManager.accountManager.hasEntitlement(forProductName: .dataBrokerProtection, cachePolicy: .reloadIgnoringLocalCacheData) { case let .success(result): return result @@ -58,5 +58,5 @@ public final class DataBrokerProtectionSubscriptionManager: DataBrokerProtection /// This protocol exists only as a wrapper on top of the AccountManager since it is a concrete type on BSK public protocol DataBrokerProtectionAccountManaging { var accessToken: String? { get } - func hasEntitlement(for cachePolicy: AccountManager.CachePolicy) async -> Result + func hasEntitlement(for cachePolicy: APICachePolicy) async -> Result } diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 748ba34fb9..caaf9c8169 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -32,7 +32,7 @@ let package = Package( .library(name: "VPNAppLauncher", targets: ["VPNAppLauncher"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "159.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "160.0.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.1"), .package(path: "../AppLauncher"), .package(path: "../UDSHelper"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 5baa5ee823..414b9283ce 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "159.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "160.0.0"), .package(path: "../SwiftUIExtensions") ], targets: [ diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift index c25e2b486e..75756d515c 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift @@ -23,14 +23,14 @@ import Subscription @available(macOS 12.0, *) public final class DebugPurchaseModel: ObservableObject { - var purchaseManager: StorePurchaseManager - let appStorePurchaseFlow: AppStorePurchaseFlow + var purchaseManager: DefaultStorePurchaseManager + let appStorePurchaseFlow: DefaultAppStorePurchaseFlow @Published var subscriptions: [SubscriptionRowModel] - init(manager: StorePurchaseManager, + init(manager: DefaultStorePurchaseManager, subscriptions: [SubscriptionRowModel] = [], - appStorePurchaseFlow: AppStorePurchaseFlow) { + appStorePurchaseFlow: DefaultAppStorePurchaseFlow) { self.purchaseManager = manager self.subscriptions = subscriptions self.appStorePurchaseFlow = appStorePurchaseFlow diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift index 8cc9b5a07a..fdc220c84b 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift @@ -25,7 +25,7 @@ import Subscription @available(macOS 12.0, *) public final class DebugPurchaseViewController: NSViewController { - private let manager: StorePurchaseManager + private let manager: DefaultStorePurchaseManager private let model: DebugPurchaseModel private var cancellables = Set() @@ -34,7 +34,7 @@ public final class DebugPurchaseViewController: NSViewController { fatalError("init(coder:) has not been implemented") } - public init(storePurchaseManager: StorePurchaseManager, appStorePurchaseFlow: AppStorePurchaseFlow) { + public init(storePurchaseManager: DefaultStorePurchaseManager, appStorePurchaseFlow: DefaultAppStorePurchaseFlow) { manager = storePurchaseManager model = DebugPurchaseModel(manager: manager, appStorePurchaseFlow: appStorePurchaseFlow) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index fd16741a1d..5b51c92043 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -31,19 +31,19 @@ public final class SubscriptionDebugMenu: NSMenuItem { private var purchasePlatformItem: NSMenuItem? var currentViewController: () -> NSViewController? - let subscriptionManager: SubscriptionManaging - var accountManager: AccountManaging { + let subscriptionManager: SubscriptionManager + var accountManager: AccountManager { subscriptionManager.accountManager } private var _purchaseManager: Any? @available(macOS 12.0, *) - fileprivate var purchaseManager: StorePurchaseManager { + fileprivate var purchaseManager: DefaultStorePurchaseManager { if _purchaseManager == nil { - _purchaseManager = StorePurchaseManager() + _purchaseManager = DefaultStorePurchaseManager() } // swiftlint:disable:next force_cast - return _purchaseManager as! StorePurchaseManager + return _purchaseManager as! DefaultStorePurchaseManager } required init(coder: NSCoder) { @@ -56,7 +56,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, - subscriptionManager: SubscriptionManaging) { + subscriptionManager: SubscriptionManager) { self.currentEnvironment = currentEnvironment self.updateServiceEnvironment = updateServiceEnvironment self.updatePurchasingPlatform = updatePurchasingPlatform @@ -192,7 +192,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { func validateToken() { Task { guard let token = accountManager.accessToken else { return } - switch await subscriptionManager.authService.validateToken(accessToken: token) { + switch await subscriptionManager.authEndpointService.validateToken(accessToken: token) { case .success(let response): showAlert(title: "Validate token", message: "\(response)") case .failure(let error): @@ -208,7 +208,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] for entitlement in entitlements { - if case let .success(result) = await accountManager.hasEntitlement(for: entitlement, cachePolicy: .reloadIgnoringLocalCacheData) { + if case let .success(result) = await accountManager.hasEntitlement(forProductName: entitlement, cachePolicy: .reloadIgnoringLocalCacheData) { let resultSummary = "Entitlement check for \(entitlement.rawValue): \(result)" results.append(resultSummary) print(resultSummary) @@ -223,7 +223,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { func getSubscriptionDetails() { Task { guard let token = accountManager.accessToken else { return } - switch await subscriptionManager.subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { + switch await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { case .success(let response): showAlert(title: "Subscription info", message: "\(response)") case .failure(let error): @@ -235,15 +235,16 @@ public final class SubscriptionDebugMenu: NSMenuItem { @available(macOS 12.0, *) @objc func syncAppleIDAccount() { - Task { - await purchaseManager.syncAppleIDAccount() + Task { @MainActor in + try? await purchaseManager.syncAppleIDAccount() } } @IBAction func showPurchaseView(_ sender: Any?) { if #available(macOS 12.0, *) { - let storePurchaseManager = StorePurchaseManager() - let appStorePurchaseFlow = AppStorePurchaseFlow(subscriptionManager: subscriptionManager) + let storePurchaseManager = DefaultStorePurchaseManager() + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + appStoreRestoreFlow: DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager)) let vc = DebugPurchaseViewController(storePurchaseManager: storePurchaseManager, appStorePurchaseFlow: appStorePurchaseFlow) currentViewController()?.presentAsSheet(vc) } @@ -313,7 +314,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { func restorePurchases(_ sender: Any?) { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager) await appStoreRestoreFlow.restoreAccountFromPastPurchase() } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 406d632e8d..6d088227d3 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -35,8 +35,8 @@ public final class PreferencesSubscriptionModel: ObservableObject { lazy var sheetModel: SubscriptionAccessModel = makeSubscriptionAccessModel() - private let subscriptionManager: SubscriptionManaging - private var accountManager: AccountManaging { + private let subscriptionManager: SubscriptionManager + private var accountManager: AccountManager { subscriptionManager.accountManager } private let openURLHandler: (URL) -> Void @@ -91,7 +91,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { public init(openURLHandler: @escaping (URL) -> Void, userEventHandler: @escaping (UserEvent) -> Void, sheetActionHandler: SubscriptionAccessActionHandlers, - subscriptionManager: SubscriptionManaging) { + subscriptionManager: SubscriptionManager) { self.subscriptionManager = subscriptionManager self.openURLHandler = openURLHandler self.userEventHandler = userEventHandler @@ -101,8 +101,8 @@ public final class PreferencesSubscriptionModel: ObservableObject { if accountManager.isUserAuthenticated { Task { - await self.updateSubscription(with: .returnCacheDataElseLoad) - await self.updateAllEntitlement(with: .returnCacheDataElseLoad) + await self.updateSubscription(cachePolicy: .returnCacheDataElseLoad) + await self.updateAllEntitlement(cachePolicy: .returnCacheDataElseLoad) } } @@ -116,7 +116,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { subscriptionChangeObserver = NotificationCenter.default.addObserver(forName: .subscriptionDidChange, object: nil, queue: .main) { _ in Task { [weak self] in - await self?.updateSubscription(with: .returnCacheDataDontLoad) + await self?.updateSubscription(cachePolicy: .returnCacheDataDontLoad) } } } @@ -189,7 +189,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { case .stripe: Task { guard let accessToken = accountManager.accessToken, let externalID = accountManager.externalID, - case let .success(response) = await subscriptionManager.subscriptionService.getCustomerPortalURL(accessToken: accessToken, externalID: externalID) else { return } + case let .success(response) = await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: accessToken, externalID: externalID) else { return } guard let customerPortalURL = URL(string: response.customerPortalUrl) else { return } openURLHandler(customerPortalURL) @@ -200,7 +200,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func confirmIfSignedInToSameAccount() async -> Bool { if #available(macOS 12.0, *) { guard let lastTransactionJWSRepresentation = await subscriptionManager.storePurchaseManager().mostRecentTransaction() else { return false } - switch await subscriptionManager.authService.storeLogin(signature: lastTransactionJWSRepresentation) { + switch await subscriptionManager.authEndpointService.storeLogin(signature: lastTransactionJWSRepresentation) { case .success(let response): return response.externalID == accountManager.externalID case .failure: @@ -242,7 +242,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager) await appStoreRestoreFlow.restoreAccountFromPastPurchase() fetchAndUpdateSubscriptionDetails() } @@ -263,19 +263,19 @@ public final class PreferencesSubscriptionModel: ObservableObject { self?.fetchSubscriptionDetailsTask = nil } - await self?.updateSubscription(with: .reloadIgnoringLocalCacheData) - await self?.updateAllEntitlement(with: .reloadIgnoringLocalCacheData) + await self?.updateSubscription(cachePolicy: .reloadIgnoringLocalCacheData) + await self?.updateAllEntitlement(cachePolicy: .reloadIgnoringLocalCacheData) } } @MainActor - private func updateSubscription(with cachePolicy: SubscriptionService.CachePolicy) async { + private func updateSubscription(cachePolicy: APICachePolicy) async { guard let token = accountManager.accessToken else { - subscriptionManager.subscriptionService.signOut() + subscriptionManager.subscriptionEndpointService.signOut() return } - switch await subscriptionManager.subscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) { + switch await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token, cachePolicy: cachePolicy) { case .success(let subscription): updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) subscriptionPlatform = subscription.platform @@ -286,22 +286,22 @@ public final class PreferencesSubscriptionModel: ObservableObject { } @MainActor - private func updateAllEntitlement(with cachePolicy: AccountManaging.CachePolicy) async { - switch await self.accountManager.hasEntitlement(for: .networkProtection, cachePolicy: cachePolicy) { + private func updateAllEntitlement(cachePolicy: APICachePolicy) async { + switch await self.accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: cachePolicy) { case let .success(result): hasAccessToVPN = result case .failure: hasAccessToVPN = false } - switch await self.accountManager.hasEntitlement(for: .dataBrokerProtection, cachePolicy: cachePolicy) { + switch await self.accountManager.hasEntitlement(forProductName: .dataBrokerProtection, cachePolicy: cachePolicy) { case let .success(result): hasAccessToDBP = result case .failure: hasAccessToDBP = false } - switch await self.accountManager.hasEntitlement(for: .identityTheftRestoration, cachePolicy: cachePolicy) { + switch await self.accountManager.hasEntitlement(forProductName: .identityTheftRestoration, cachePolicy: cachePolicy) { case let .success(result): hasAccessToITR = result case .failure: diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift index 5d7517bb58..8ded29c862 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift @@ -34,10 +34,10 @@ public final class ActivateSubscriptionAccessModel: SubscriptionAccessModel, Pur public var restorePurchaseDescription = UserText.restorePurchaseDescription public var restorePurchaseButtonTitle = UserText.restorePurchaseButton - let subscriptionManager: SubscriptionManaging + let subscriptionManager: SubscriptionManager public init(actionHandlers: SubscriptionAccessActionHandlers, - subscriptionManager: SubscriptionManaging) { + subscriptionManager: SubscriptionManager) { self.actionHandlers = actionHandlers self.shouldShowRestorePurchase = subscriptionManager.currentEnvironment.purchasePlatform == .appStore self.subscriptionManager = subscriptionManager diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index d67abee49c..004f8c4ffe 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -28,9 +28,9 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { public var emailLabel: String { UserText.email } public var emailDescription: String { hasEmail ? UserText.shareModalHasEmailDescription : UserText.shareModalNoEmailDescription } public var emailButtonTitle: String { hasEmail ? UserText.manageEmailButton : UserText.addEmailButton } - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager - public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, subscriptionManager: SubscriptionManaging) { + public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, subscriptionManager: SubscriptionManager) { self.actionHandlers = actionHandlers self.email = email self.subscriptionManager = subscriptionManager @@ -52,7 +52,7 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { Task { if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, iOS 15.0, *) { - let appStoreAccountManagementFlow = AppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift index f08c95cfb0..daf271127d 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -22,14 +22,14 @@ import SwiftUI public final class SubscriptionAccessViewController: NSViewController { - private let subscriptionManager: SubscriptionManaging + private let subscriptionManager: SubscriptionManager private var actionHandlers: SubscriptionAccessActionHandlers public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public init(subscriptionManager: SubscriptionManaging, + public init(subscriptionManager: SubscriptionManager, actionHandlers: SubscriptionAccessActionHandlers) { self.subscriptionManager = subscriptionManager self.actionHandlers = actionHandlers diff --git a/UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift b/UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift index 43ef8327f4..dfe3442e8a 100644 --- a/UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift +++ b/UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift @@ -20,7 +20,7 @@ import Foundation import Subscription @testable import DuckDuckGo_Privacy_Browser -final class MockAccountManager: AccountManaging { +final class MockAccountManager: AccountManager { var hasEntitlementResult: Result = .success(true) var delegate: AccountManagerKeychainAccessDelegate? @@ -50,18 +50,18 @@ final class MockAccountManager: AccountManaging { func migrateAccessTokenToNewStore() throws { } - func hasEntitlement(for entitlement: Entitlement.ProductName, cachePolicy: CachePolicy) async -> Result { + func hasEntitlement(forProductName productName: Entitlement.ProductName, cachePolicy: APICachePolicy) async -> Result { hasEntitlementResult } - func hasEntitlement(for entitlement: Entitlement.ProductName) async -> Result { + func hasEntitlement(forProductName productName: Entitlement.ProductName) async -> Result { hasEntitlementResult } func updateCache(with entitlements: [Entitlement]) { } - func fetchEntitlements(cachePolicy: CachePolicy) async -> Result<[Entitlement], any Error> { + func fetchEntitlements(cachePolicy: APICachePolicy) async -> Result<[Entitlement], any Error> { .success([]) } diff --git a/UnitTests/FileDownload/DownloadListCoordinatorTests.swift b/UnitTests/FileDownload/DownloadListCoordinatorTests.swift index c318dcf867..c78be9bf91 100644 --- a/UnitTests/FileDownload/DownloadListCoordinatorTests.swift +++ b/UnitTests/FileDownload/DownloadListCoordinatorTests.swift @@ -206,7 +206,7 @@ final class DownloadListCoordinatorTests: XCTestCase { XCTAssertNotNil(url) XCTAssertTrue(FileManager().createFile(atPath: url?.path ?? "", contents: nil)) - await fulfillment(of: [e1, e2], timeout: 1) + await fulfillment(of: [e1, e2], timeout: 5.0) withExtendedLifetime(c) {} XCTAssertTrue(coordinator.hasActiveDownloads) } diff --git a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift index 70c8f330e3..f2be011734 100644 --- a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -27,14 +27,14 @@ final class SurveyRemoteMessagingTests: XCTestCase { private var defaults: UserDefaults! private let testGroupName = "remote-messaging" - private var accountManager: AccountManaging! + private var accountManager: AccountManager! private var subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching! override func setUp() { defaults = UserDefaults(suiteName: testGroupName)! defaults.removePersistentDomain(forName: testGroupName) - accountManager = AccountManagerMock(isUserAuthenticated: true, accessToken: "mock-token") + accountManager = AccountManagerMock(accessToken: "mock-token") subscriptionFetcher = MockSubscriptionFetcher() } @@ -322,7 +322,7 @@ final class MockSubscriptionFetcher: SurveyRemoteMessageSubscriptionFetching { platform: .apple, status: .autoRenewable) - func getSubscription(accessToken: String) async -> Result { + func getSubscription(accessToken: String) async -> Result { return .success(subscription) } diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index a0b8b38f64..101ca06c42 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -22,6 +22,7 @@ import NetworkProtectionUI import XCTest import Subscription import SubscriptionTestingUtilities +import BrowserServicesKit @testable import DuckDuckGo_Privacy_Browser @@ -43,7 +44,8 @@ final class MoreOptionsMenuTests: XCTestCase { passwordManagerCoordinator = PasswordManagerCoordinator() capturingActionDelegate = CapturingOptionsButtonMenuDelegate() internalUserDecider = InternalUserDeciderMock() - accountManager = AccountManagerMock(isUserAuthenticated: true) + accountManager = AccountManagerMock() + accountManager.accessToken = "something" // accountManager.isUserAuthenticated becomes true networkProtectionVisibilityMock = NetworkProtectionVisibilityMock(isInstalled: false, visible: false) moreOptionsMenu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: passwordManagerCoordinator, @@ -69,6 +71,7 @@ final class MoreOptionsMenuTests: XCTestCase { moreOptionsMenu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: passwordManagerCoordinator, vpnFeatureGatekeeper: NetworkProtectionVisibilityMock(isInstalled: false, visible: true), + subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock(isFeatureAvailable: false, isSubscriptionPurchaseAllowed: false), sharingMenu: NSMenu(), internalUserDecider: internalUserDecider, accountManager: accountManager) @@ -96,14 +99,14 @@ final class MoreOptionsMenuTests: XCTestCase { @MainActor func testThatMoreOptionMenuHasTheExpectedItemsNotAuthenticated() { - - accountManager = AccountManagerMock(isUserAuthenticated: false) + accountManager = AccountManagerMock() moreOptionsMenu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, - passwordManagerCoordinator: passwordManagerCoordinator, - vpnFeatureGatekeeper: NetworkProtectionVisibilityMock(isInstalled: false, visible: true), - sharingMenu: NSMenu(), - internalUserDecider: internalUserDecider, - accountManager: accountManager) + passwordManagerCoordinator: passwordManagerCoordinator, + vpnFeatureGatekeeper: NetworkProtectionVisibilityMock(isInstalled: false, visible: true), + subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock(isFeatureAvailable: false, isSubscriptionPurchaseAllowed: false), + sharingMenu: NSMenu(), + internalUserDecider: internalUserDecider, + accountManager: accountManager) XCTAssertEqual(moreOptionsMenu.items[0].title, UserText.sendFeedback) XCTAssertTrue(moreOptionsMenu.items[1].isSeparatorItem) diff --git a/UnitTests/Subscriptions/SubscriptionAttributionPixelHandlerTests.swift b/UnitTests/Subscription/SubscriptionAttributionPixelHandlerTests.swift similarity index 100% rename from UnitTests/Subscriptions/SubscriptionAttributionPixelHandlerTests.swift rename to UnitTests/Subscription/SubscriptionAttributionPixelHandlerTests.swift diff --git a/UnitTests/Subscription/SubscriptionFeatureAvailabilityMock.swift b/UnitTests/Subscription/SubscriptionFeatureAvailabilityMock.swift new file mode 100644 index 0000000000..d6153695f7 --- /dev/null +++ b/UnitTests/Subscription/SubscriptionFeatureAvailabilityMock.swift @@ -0,0 +1,31 @@ +// +// SubscriptionFeatureAvailabilityMock.swift +// +// Copyright © 2024 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 Subscription +import BrowserServicesKit + +public struct SubscriptionFeatureAvailabilityMock: SubscriptionFeatureAvailability { + public var isFeatureAvailable: Bool + public var isSubscriptionPurchaseAllowed: Bool + + public init(isFeatureAvailable: Bool, isSubscriptionPurchaseAllowed: Bool) { + self.isFeatureAvailable = isFeatureAvailable + self.isSubscriptionPurchaseAllowed = isSubscriptionPurchaseAllowed + } +} diff --git a/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift b/UnitTests/Subscription/SubscriptionRedirectManagerTests.swift similarity index 100% rename from UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift rename to UnitTests/Subscription/SubscriptionRedirectManagerTests.swift diff --git a/UnitTests/Subscription/SubscriptionUIHandlerMock.swift b/UnitTests/Subscription/SubscriptionUIHandlerMock.swift new file mode 100644 index 0000000000..f318353224 --- /dev/null +++ b/UnitTests/Subscription/SubscriptionUIHandlerMock.swift @@ -0,0 +1,68 @@ +// +// SubscriptionUIHandlerMock.swift +// +// Copyright © 2024 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 +@testable import DuckDuckGo_Privacy_Browser + +public struct SubscriptionUIHandlerMock: SubscriptionUIHandling { + + public enum UIHandlerMockPerformedAction { + case didPresentProgressViewController + case didDismissProgressViewController + case didUpdateProgressViewController + case didPresentSubscriptionAccessViewController + case didShowAlert(SubscriptionAlertType) + case didShowTab(Tab.TabContent) + } + + let didPerformActionCallback: (_ action: UIHandlerMockPerformedAction) -> Void + + public var alertResponse: NSApplication.ModalResponse? + + public func presentProgressViewController(withTitle: String) { + didPerformActionCallback(.didDismissProgressViewController) + } + + public func dismissProgressViewController() { + didPerformActionCallback(.didDismissProgressViewController) + } + + public func updateProgressViewController(title: String) { + didPerformActionCallback(.didUpdateProgressViewController) + } + + public func presentSubscriptionAccessViewController(handler: SubscriptionAccessActionHandling, message: WKScriptMessage) { + didPerformActionCallback(.didPresentSubscriptionAccessViewController) + } + + @discardableResult + public func dismissProgressViewAndShow(alertType: SubscriptionAlertType, text: String?) async -> NSApplication.ModalResponse { + dismissProgressViewController() + return await show(alertType: alertType, text: text) + } + + @discardableResult + public func show(alertType: SubscriptionAlertType, text: String?) async -> NSApplication.ModalResponse { + didPerformActionCallback(.didShowAlert(alertType)) + return alertResponse! + } + + public func showTab(with content: Tab.TabContent) { + didPerformActionCallback(.didShowTab(content)) + } +}