From 88549ff39f4073e9ee419e884ed77f10ac217a84 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 19 Nov 2024 11:18:13 +0000 Subject: [PATCH 01/59] Initial auth v2 adoption --- DuckDuckGo.xcodeproj/project.pbxproj | 14 +- .../xcshareddata/swiftpm/Package.resolved | 46 +++-- DuckDuckGo/Application/AppDelegate.swift | 22 ++- ...ataBrokerProtectionFeatureGatekeeper.swift | 21 +- .../FreemiumDBPPixelExperimentManaging.swift | 21 +- .../Freemium/DBP/FreemiumDBPFeature.swift | 48 ++--- .../VPNRedditSessionWorkaround.swift | 10 +- ...riptionManager+StandardConfiguration.swift | 155 ++++++++++++--- ...ntityTheftRestorationPagesUserScript.swift | 12 +- ...scriptionPagesUseSubscriptionFeature.swift | 95 +++++---- DuckDuckGo/Tab/UserScripts/UserScripts.swift | 7 +- .../VPNMetadataCollector.swift | 11 +- DuckDuckGo/YoutubePlayer/DuckPlayer.swift | 2 +- .../DebugMenu/SubscriptionDebugMenu.swift | 79 +++----- .../PreferencesSubscriptionModel.swift | 182 ++++++++---------- ...seSubscriptionFeatureForStripeTests.swift} | 29 +-- 16 files changed, 438 insertions(+), 316 deletions(-) rename UnitTests/Subscription/{SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift => SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift} (93%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fe5bf4d2da..a7fe888b84 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -216,8 +216,8 @@ 1EB028352C91C75A005343F6 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 1EB028342C91C75A005343F6 /* Common */; }; 1ED910D52B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; }; 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; }; - 1EEB2D7A2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift */; }; - 1EEB2D7B2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift */; }; + 1EEB2D7A2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift */; }; + 1EEB2D7B2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift */; }; 1EFA1A072C7C7F0E0099F508 /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; 1EFA1A082C7C7F0F0099F508 /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; 31031EB72CC94C6E00684340 /* AIChatRemoteSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31031EB62CC94C6C00684340 /* AIChatRemoteSettingsTests.swift */; }; @@ -3484,7 +3484,7 @@ 1E7E2E932902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPermissionHandler.swift; sourceTree = ""; }; 1E862A882A9FC01200F84D4B /* SubscriptionUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SubscriptionUI; sourceTree = ""; }; 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; - 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift; sourceTree = ""; }; + 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift; sourceTree = ""; }; 31031EB62CC94C6C00684340 /* AIChatRemoteSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatRemoteSettingsTests.swift; sourceTree = ""; }; 31031EB92CC9736500684340 /* AIChatOnboardingTabExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatOnboardingTabExtensionTests.swift; sourceTree = ""; }; 310E79BE294A19A8007C49E8 /* FireproofingReferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FireproofingReferenceTests.swift; sourceTree = ""; }; @@ -4895,6 +4895,7 @@ EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; + F1449EB42CE61461002536E4 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; @@ -7546,7 +7547,7 @@ F1AFDBD12C231B7A00710F2C /* SubscriptionAppStoreRestorerTests.swift */, F1AFDBD32C231B9700710F2C /* SubscriptionErrorReporterTests.swift */, 1E2BEAE32C8B00B5002741A3 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */, - 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift */, + 1EEB2D792C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift */, ); path = Subscription; sourceTree = ""; @@ -7719,6 +7720,7 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( + F1449EB42CE61461002536E4 /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, @@ -12064,7 +12066,7 @@ 31031EBB2CC9736500684340 /* AIChatOnboardingTabExtensionTests.swift in Sources */, 56534DEE29DF252C00121467 /* CapturingDefaultBrowserProvider.swift in Sources */, 561D29C32BDA745B007B91D0 /* MockSyncPausedStateManaging.swift in Sources */, - 1EEB2D7B2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift in Sources */, + 1EEB2D7B2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift in Sources */, 3706FDF8293F661700E42796 /* FileStoreTests.swift in Sources */, 1D838A342C44F0320078373F /* ReleaseNotesParserTests.swift in Sources */, 5603D90729B7B746007F9F01 /* MockTabViewItemDelegate.swift in Sources */, @@ -13945,7 +13947,7 @@ 4BBF09232830812900EE1418 /* FileSystemDSL.swift in Sources */, 7B4C5CF52BE51D640007A164 /* VPNUninstallerTests.swift in Sources */, 4B9DB05C2A983B55000927DB /* WaitlistViewModelTests.swift in Sources */, - 1EEB2D7A2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift in Sources */, + 1EEB2D7A2C986A40000D908B /* SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift in Sources */, 1D1C36E329FAE8DA001FA40C /* FaviconManagerTests.swift in Sources */, 9F0FFFB42BCCAE37007C87DD /* BookmarkAllTabsDialogCoordinatorViewModelTests.swift in Sources */, 4B723E0526B0003E00E14D75 /* DataImportMocks.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index dd2c08b0cc..c49cd366cf 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,22 +27,13 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", - "state" : { - "revision" : "948420e704ea4d9412a4fc3e2c2ab0d5ea5fe5d7", - "version" : "209.1.0" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "adca39c379b1a124f9990e9d0308c374f32f5018", - "version" : "6.32.0" + "revision" : "32c3e2bce3cc20a079d96b3ee23a9cde5d2337c3", + "version" : "6.35.0" } }, { @@ -59,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/GRDB.swift.git", "state" : { - "revision" : "4225b85c9a0c50544e413a1ea1e502c802b44b35", - "version" : "2.4.0" + "revision" : "5b2f6a81099d26ae0f9e38788f51490cd6a4b202", + "version" : "2.4.2" } }, { @@ -72,10 +63,19 @@ "version" : "6.0.1" } }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", + "version" : "4.13.4" + } + }, { "identity" : "lottie-spm", "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm", + "location" : "https://github.com/airbnb/lottie-spm.git", "state" : { "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", "version" : "4.4.3" @@ -135,6 +135,24 @@ "version" : "1.4.0" } }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "06dc63c6d8da54ee11ceb268cde1fa68161afc96", + "version" : "3.9.1" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 1556feef05..ff5122f755 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -141,7 +141,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { ) return VPNRedditSessionWorkaround( - accountManager: subscriptionManager.accountManager, + subscriptionManager: subscriptionManager, ipcClient: ipcClient, statusReporter: statusReporter ) @@ -269,7 +269,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { onboardingStateMachine = ContextualOnboardingStateMachine() - // Configure Subscription + // MARK: - Subscription configuration + subscriptionManager = DefaultSubscriptionManager() subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { return WindowControllersManager.shared @@ -279,6 +280,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { WKWebsiteDataStore.default().httpCookieStore }, eventMapping: SubscriptionCookieManageEventPixelMapping()) + // MARK: - + // Update VPN environment and match the Subscription environment vpnSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) @@ -298,7 +301,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { freemiumDBPFeature = DefaultFreemiumDBPFeature(privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, experimentManager: experimentManager, subscriptionManager: subscriptionManager, - accountManager: subscriptionManager.accountManager, freemiumDBPUserStateManager: freemiumDBPUserStateManager) freemiumDBPPromotionViewCoordinator = FreemiumDBPPromotionViewCoordinator(freemiumDBPUserStateManager: freemiumDBPUserStateManager, freemiumDBPFeature: freemiumDBPFeature) @@ -448,8 +450,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() let freemiumDBPUserStateManager = DefaultFreemiumDBPUserStateManager(userDefaults: .dbp) - let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: - subscriptionManager.accountManager, + let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(subscriptionManager: subscriptionManager, freemiumDBPUserStateManager: freemiumDBPUserStateManager) DataBrokerProtectionAppEvents(featureGatekeeper: pirGatekeeper).applicationDidFinishLaunching() @@ -504,14 +505,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate { NetworkProtectionAppEvents(featureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager)).applicationDidBecomeActive() let freemiumDBPUserStateManager = DefaultFreemiumDBPUserStateManager(userDefaults: .dbp) - let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: - subscriptionManager.accountManager, + let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(subscriptionManager: subscriptionManager, freemiumDBPUserStateManager: freemiumDBPUserStateManager) DataBrokerProtectionAppEvents(featureGatekeeper: pirGatekeeper).applicationDidBecomeActive() - subscriptionManager.refreshCachedSubscriptionAndEntitlements { isSubscriptionActive in - if isSubscriptionActive { + Task { + guard let subscription = try? await subscriptionManager.currentSubscription(refresh: true) else { + return + } + + if subscription.isActive { PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index 9134b30497..281f997ede 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -35,7 +35,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature private let pixelHandler: EventMapping private let userDefaults: UserDefaults private let subscriptionAvailability: SubscriptionFeatureAvailability - private let accountManager: AccountManager + private let subscriptionManager: any SubscriptionManager private let freemiumDBPUserStateManager: FreemiumDBPUserStateManager init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, @@ -43,14 +43,14 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler(), userDefaults: UserDefaults = .standard, subscriptionAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability(), - accountManager: AccountManager, + subscriptionManager: any SubscriptionManager, freemiumDBPUserStateManager: FreemiumDBPUserStateManager) { self.privacyConfigurationManager = privacyConfigurationManager self.featureDisabler = featureDisabler self.pixelHandler = pixelHandler self.userDefaults = userDefaults self.subscriptionAvailability = subscriptionAvailability - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager self.freemiumDBPUserStateManager = freemiumDBPUserStateManager } @@ -87,21 +87,10 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature /// - Returns: Bool indicating prerequisites are satisfied func arePrerequisitesSatisfied() async -> Bool { - let isAuthenticated = accountManager.isUserAuthenticated + let isAuthenticated = subscriptionManager.isUserAuthenticated if !isAuthenticated && freemiumDBPUserStateManager.didActivate { return true } - - let entitlements = await accountManager.hasEntitlement(forProductName: .dataBrokerProtection, - cachePolicy: .reloadIgnoringLocalCacheData) - var hasEntitlements: Bool - switch entitlements { - case .success(let value): - hasEntitlements = value - case .failure: - hasEntitlements = false - } - + var hasEntitlements = subscriptionManager.isEntitlementActive(.dataBrokerProtection) firePrerequisitePixelsAndLogIfNecessary(hasEntitlements: hasEntitlements, isAuthenticatedResult: isAuthenticated) - return hasEntitlements && isAuthenticated } } diff --git a/DuckDuckGo/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManaging.swift b/DuckDuckGo/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManaging.swift index c14894d0fe..119e0631d1 100644 --- a/DuckDuckGo/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManaging.swift +++ b/DuckDuckGo/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManaging.swift @@ -108,10 +108,29 @@ private extension FreemiumDBPPixelExperimentManager { /// Determines if the user is eligible for the experiment based on subscription status and locale. var userIsEligible: Bool { - subscriptionManager.isPotentialPrivacyProSubscriber + isPotentialPrivacyProSubscriber && locale.isUSRegion } + /// Returns true if a user is a "potential" Privacy Pro subscriber. This means: + /// + /// 1. Is eligible to purchase + /// 2. Is not a current subscriber + var isPotentialPrivacyProSubscriber: Bool { + isPrivacyProPurchaseAvailable + && !subscriptionManager.isUserAuthenticated + } + + private var isPrivacyProPurchaseAvailable: Bool { + let platform = subscriptionManager.currentEnvironment.purchasePlatform + switch platform { + case .appStore: + return subscriptionManager.canPurchase + case .stripe: + return true + } + } + /// Checks if the user is not already enrolled in the experiment. var userIsNotEnrolled: Bool { userDefaults.enrollmentDate == nil diff --git a/DuckDuckGo/Freemium/DBP/FreemiumDBPFeature.swift b/DuckDuckGo/Freemium/DBP/FreemiumDBPFeature.swift index 60141b4b7b..39808dc700 100644 --- a/DuckDuckGo/Freemium/DBP/FreemiumDBPFeature.swift +++ b/DuckDuckGo/Freemium/DBP/FreemiumDBPFeature.swift @@ -56,7 +56,26 @@ final class DefaultFreemiumDBPFeature: FreemiumDBPFeature { var isAvailable: Bool { privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(DBPSubfeature.freemium) && experimentManager.isTreatment - && subscriptionManager.isPotentialPrivacyProSubscriber + && isPotentialPrivacyProSubscriber + } + + /// Returns true if a user is a "potential" Privacy Pro subscriber. This means: + /// + /// 1. Is eligible to purchase + /// 2. Is not a current subscriber + var isPotentialPrivacyProSubscriber: Bool { + isPrivacyProPurchaseAvailable + && !subscriptionManager.isUserAuthenticated + } + + private var isPrivacyProPurchaseAvailable: Bool { + let platform = subscriptionManager.currentEnvironment.purchasePlatform + switch platform { + case .appStore: + return subscriptionManager.canPurchase + case .stripe: + return true + } } /// A publisher that emits updates when the availability of the Freemium DBP feature changes. @@ -70,7 +89,6 @@ final class DefaultFreemiumDBPFeature: FreemiumDBPFeature { private let privacyConfigurationManager: PrivacyConfigurationManaging private let experimentManager: FreemiumDBPPixelExperimentManaging private let subscriptionManager: SubscriptionManager - private let accountManager: AccountManager private var freemiumDBPUserStateManager: FreemiumDBPUserStateManager private let notificationCenter: NotificationCenter private lazy var featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler() @@ -92,7 +110,6 @@ final class DefaultFreemiumDBPFeature: FreemiumDBPFeature { init(privacyConfigurationManager: PrivacyConfigurationManaging, experimentManager: FreemiumDBPPixelExperimentManaging, subscriptionManager: SubscriptionManager, - accountManager: AccountManager, freemiumDBPUserStateManager: FreemiumDBPUserStateManager, notificationCenter: NotificationCenter = .default, featureDisabler: DataBrokerProtectionFeatureDisabling? = nil) { @@ -100,7 +117,6 @@ final class DefaultFreemiumDBPFeature: FreemiumDBPFeature { self.privacyConfigurationManager = privacyConfigurationManager self.experimentManager = experimentManager self.subscriptionManager = subscriptionManager - self.accountManager = accountManager self.freemiumDBPUserStateManager = freemiumDBPUserStateManager self.notificationCenter = notificationCenter @@ -158,7 +174,7 @@ private extension DefaultFreemiumDBPFeature { guard freemiumDBPUserStateManager.didActivate else { return false } return !privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(DBPSubfeature.freemium) - && subscriptionManager.isPotentialPrivacyProSubscriber + && isPotentialPrivacyProSubscriber } /// This method offboards a Freemium user if the feature flag was disabled @@ -174,25 +190,3 @@ private extension DefaultFreemiumDBPFeature { } } } - -extension SubscriptionManager { - - /// Returns true if a user is a "potential" Privacy Pro subscriber. This means: - /// - /// 1. Is eligible to purchase - /// 2. Is not a current subscriber - var isPotentialPrivacyProSubscriber: Bool { - isPrivacyProPurchaseAvailable - && !accountManager.isUserAuthenticated - } - - private var isPrivacyProPurchaseAvailable: Bool { - let platform = currentEnvironment.purchasePlatform - switch platform { - case .appStore: - return canPurchase - case .stripe: - return true - } - } -} diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift index 5c376db928..1f80aea936 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift @@ -25,15 +25,15 @@ import Common import os.log final class VPNRedditSessionWorkaround { - - private let accountManager: AccountManager + + private let subscriptionManager: any SubscriptionManager private let ipcClient: VPNControllerXPCClient private let statusReporter: NetworkProtectionStatusReporter - init(accountManager: AccountManager, + init(subscriptionManager: any SubscriptionManager, ipcClient: VPNControllerXPCClient = .shared, statusReporter: NetworkProtectionStatusReporter) { - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager self.ipcClient = ipcClient self.statusReporter = statusReporter self.statusReporter.forceRefresh() @@ -53,7 +53,7 @@ final class VPNRedditSessionWorkaround { @MainActor func installRedditSessionWorkaround(to cookieStore: WKHTTPCookieStore) async { - guard accountManager.isUserAuthenticated, + guard subscriptionManager.isUserAuthenticated, statusReporter.statusObserver.recentValue.isConnected, let redditSessionCookie = HTTPCookie.emptyRedditSession else { return diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index 360ddb541e..ca14d18735 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -20,49 +20,150 @@ import Foundation import Subscription import Common import PixelKit +import Networking +import os.log 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 subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! +// let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) +// vpnSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) +// +// let configuration = URLSessionConfiguration.default +// configuration.httpCookieStorage = nil +// configuration.requestCachePolicy = .reloadIgnoringLocalCacheData +// let urlSession = URLSession(configuration: configuration, +// delegate: SessionDelegate(), +// delegateQueue: nil) +// let apiService = DefaultAPIService(urlSession: urlSession) +// let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging +// +// let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) +// +// // keychain storage +// let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) +// let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(subscriptionAppGroup))) +// let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) +// +// let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, +// legacyTokenStorage: legacyAccountStorage, +// authService: authService) +// +// +// apiService.authorizationRefresherCallback = { _ in +// guard let tokenContainer = tokenStorage.tokenContainer else { +// throw OAuthClientError.internalError("Missing refresh token") +// } +// +// if tokenContainer.decodedAccessToken.isExpired() { +// Logger.OAuth.debug("Refreshing tokens") +// let tokens = try await authClient.getTokens(policy: .localForceRefresh) +// return tokens.accessToken +// } else { +// Logger.general.debug("Trying to refresh valid token, using the old one") +// return tokenContainer.accessToken +// } +// } +// +// let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, +// baseURL: subscriptionEnvironment.serviceEnvironment.url) +// let pixelHandler: SubscriptionManager.PixelHandler = { type in +// switch type { +// case .deadToken: +// // TODO: add pixel +// // Pixel.fire(pixel: .privacyProDeadTokenDetected) +// break +// } +// } +// +// if #available(macOS 12.0, *) { +// let storePurchaseManager = DefaultStorePurchaseManager() +// subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, +// oAuthClient: authClient, +// subscriptionEndpointService: subscriptionEndpointService, +// subscriptionEnvironment: subscriptionEnvironment, +// pixelHandler: pixelHandler) +// } else { +// subscriptionManager = DefaultSubscriptionManager(oAuthClient: authClient, +// subscriptionEndpointService: subscriptionEndpointService, +// subscriptionEnvironment: subscriptionEnvironment, +// pixelHandler: pixelHandler) +// } + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, - key: UserDefaultsCacheKey.subscriptionEntitlements, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) + let configuration = URLSessionConfiguration.default + configuration.httpCookieStorage = nil + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + let urlSession = URLSession(configuration: configuration, + delegate: SessionDelegate(), + delegateQueue: nil) + let apiService = DefaultAPIService(urlSession: urlSession) + let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging + + let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) + + // keychain storage + let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(subscriptionAppGroup))) + let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) + + let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, + legacyTokenStorage: legacyAccountStorage, + authService: authService) + + apiService.authorizationRefresherCallback = { _ in + guard let tokenContainer = tokenStorage.tokenContainer else { + throw OAuthClientError.internalError("Missing refresh token") + } + + if tokenContainer.decodedAccessToken.isExpired() { + Logger.OAuth.debug("Refreshing tokens") + let tokens = try await authClient.getTokens(policy: .localForceRefresh) + return tokens.accessToken + } else { + Logger.general.debug("Trying to refresh valid token, using the old one") + return tokenContainer.accessToken + } + } + let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionEndpointService, - authEndpointService: authEndpointService) + let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, + baseURL: subscriptionEnvironment.serviceEnvironment.url) + + let pixelHandler: SubscriptionManager.PixelHandler = { type in + switch type { + case .deadToken: + // TODO: add pixel + // Pixel.fire(pixel: .privacyProDeadTokenDetected) + break + } + } if #available(macOS 12.0, *) { - let storePurchaseManager = DefaultStorePurchaseManager() - self.init(storePurchaseManager: storePurchaseManager, - accountManager: accountManager, + self.init(storePurchaseManager: DefaultStorePurchaseManager(), + oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - authEndpointService: authEndpointService, - subscriptionEnvironment: subscriptionEnvironment) + subscriptionEnvironment: subscriptionEnvironment, + pixelHandler: pixelHandler) } else { - self.init(accountManager: accountManager, + self.init(oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - authEndpointService: authEndpointService, - subscriptionEnvironment: subscriptionEnvironment) + subscriptionEnvironment: subscriptionEnvironment, + pixelHandler: pixelHandler) } - - accountManager.delegate = self } } -extension DefaultSubscriptionManager: AccountManagerKeychainAccessDelegate { - - public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { - PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), - frequency: .legacyDailyAndCount) - } -} +//extension DefaultSubscriptionManager: AccountManagerKeychainAccessDelegate { +// +// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { +// PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), +// frequency: .legacyDailyAndCount) +// } +//} diff --git a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift index 967b41c8ee..1ed54b4e1e 100644 --- a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift @@ -72,7 +72,7 @@ extension IdentityTheftRestorationPagesUserScript: WKScriptMessageHandler { final class IdentityTheftRestorationPagesFeature: Subfeature { weak var broker: UserScriptMessageBroker? private let subscriptionFeatureAvailability: SubscriptionFeatureAvailability - + private let subscriptionManager: any SubscriptionManager var featureName = "useIdentityTheftRestoration" var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ @@ -80,8 +80,10 @@ final class IdentityTheftRestorationPagesFeature: Subfeature { .exact(hostname: "abrown.duckduckgo.com") ]) - init(subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability()) { + init(subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability(), + subscriptionManager: any SubscriptionManager) { self.subscriptionFeatureAvailability = subscriptionFeatureAvailability + self.subscriptionManager = subscriptionManager } func with(broker: UserScriptMessageBroker) { @@ -99,9 +101,11 @@ final class IdentityTheftRestorationPagesFeature: Subfeature { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = await Application.appDelegate.subscriptionManager.accountManager.accessToken { + do { + let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken return ["token": accessToken] - } else { + } catch { + Logger.subscription.debug("No access token available: \(error)") return [String: String]() } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index 204d9228c5..ff921dc14e 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -36,7 +36,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { .exact(hostname: "abrown.duckduckgo.com") ]) let subscriptionManager: SubscriptionManager - var accountManager: AccountManager { subscriptionManager.accountManager } var subscriptionPlatform: SubscriptionEnvironment.PurchasePlatform { subscriptionManager.currentEnvironment.purchasePlatform } let stripePurchaseFlow: StripePurchaseFlow @@ -130,35 +129,63 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - let authToken = accountManager.authToken ?? "" - return Subscription(token: authToken) +// let authToken = accountManager.authToken ?? "" +// return Subscription(token: authToken) + do { + let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken + return Subscription(token: accessToken) + } catch { + Logger.subscription.debug("No subscription available: \(error)") + return Subscription(token: "") + } } +// func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { +// +// PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .legacyDailyAndCount) +// +// guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { +// assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") +// return nil +// } +// +// let authToken = subscriptionValues.token +// if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), +// case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { +// accountManager.storeAuthToken(token: authToken) +// accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) +// } +// +// return nil +// } func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + // Note: This is called by the web FE when a subscription is retrieved, `params` contains an auth token V1 that will need to be exchanged for a V2. This is a temporary workaround until the FE fully supports v2 auth. - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .legacyDailyAndCount) - - guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { + Logger.subscription.fault("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") return nil } + // Clear subscription Cache + await subscriptionManager.signOut() + let authToken = subscriptionValues.token - if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAuthToken(token: authToken) - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + do { + _ = try await subscriptionManager.exchange(tokenV1: authToken) + Logger.subscription.log("v1 token exchanged for v2") + } catch { + Logger.subscription.error("Failed to exchange v1 token for v2") } - return nil } func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = accountManager.accessToken, - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) - } - +// if let accessToken = accountManager.accessToken, +// case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { +// accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) +// } + try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) DispatchQueue.main.async { [weak self] in self?.notificationCenter.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) } @@ -200,7 +227,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, *) { - guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { + guard let subscriptionSelection: SubscriptionSelection = CodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") subscriptionErrorReporter.report(subscriptionActivationError: .generalError) await uiHandler.dismissProgressViewController() @@ -223,18 +250,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: subscriptionManager.storePurchaseManager()) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) + appStoreRestoreFlow: appStoreRestoreFlow) Logger.subscription.info("[Purchase] Purchasing") - switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id) { case .success(let transactionJWS): purchaseTransactionJWS = transactionJWS case .failure(let error): @@ -345,7 +368,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { let feature: String } - guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { + guard let featureSelection: FeatureSelection = CodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") return nil } @@ -421,9 +444,11 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = accountManager.accessToken { + do { + let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken return ["token": accessToken] - } else { + } catch { + Logger.subscription.debug("No access token available: \(error)") return [String: String]() } } @@ -472,10 +497,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch await uiHandler.dismissProgressViewAndShow(alertType: .subscriptionFound, text: nil) { case .alertFirstButtonReturn: if #available(macOS 12.0, *) { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: subscriptionManager.storePurchaseManager()) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .legacyDailyAndCount) @@ -507,10 +530,8 @@ extension SubscriptionPagesUseSubscriptionFeature: SubscriptionAccessActionHandl func subscriptionAccessActionRestorePurchases(message: WKScriptMessage) { if #available(macOS 12.0, *) { Task { @MainActor in - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: subscriptionManager.storePurchaseManager()) let subscriptionAppStoreRestorer = DefaultSubscriptionAppStoreRestorer(subscriptionManager: self.subscriptionManager, appStoreRestoreFlow: appStoreRestoreFlow, uiHandler: self.uiHandler) diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index a0444dfef0..316e19e239 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -125,9 +125,7 @@ final class UserScripts: UserScriptsProvider { if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { let subscriptionManager = Application.appDelegate.subscriptionManager - let stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService, - accountManager: subscriptionManager.accountManager) + let stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionManager: subscriptionManager) let freemiumDBPPixelExperimentManager = FreemiumDBPPixelExperimentManager(subscriptionManager: subscriptionManager) let delegate = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, stripePurchaseFlow: stripePurchaseFlow, @@ -136,7 +134,8 @@ final class UserScripts: UserScriptsProvider { subscriptionPagesUserScript.registerSubfeature(delegate: delegate) userScripts.append(subscriptionPagesUserScript) - identityTheftRestorationPagesUserScript.registerSubfeature(delegate: IdentityTheftRestorationPagesFeature()) + let identityTheftRestorationPagesFeature = IdentityTheftRestorationPagesFeature(subscriptionManager: subscriptionManager) + identityTheftRestorationPagesUserScript.registerSubfeature(delegate: identityTheftRestorationPagesFeature) userScripts.append(identityTheftRestorationPagesUserScript) } } diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index 778b039358..1f1238d272 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -123,16 +123,16 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusReporter: NetworkProtectionStatusReporter private let ipcClient: VPNControllerXPCClient private let defaults: UserDefaults - private let accountManager: AccountManager + private let subscriptionManager: any SubscriptionManager private let settings: VPNSettings init(defaults: UserDefaults = .netP, - accountManager: AccountManager) { + subscriptionManager: any SubscriptionManager) { let ipcClient = VPNControllerXPCClient.shared ipcClient.register { _ in } - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager self.ipcClient = ipcClient self.defaults = defaults @@ -321,10 +321,9 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { - let hasVPNEntitlement = (try? await accountManager.hasEntitlement(forProductName: .networkProtection).get()) ?? false return .init( - hasPrivacyProAccount: accountManager.isUserAuthenticated, - hasVPNEntitlement: hasVPNEntitlement + hasPrivacyProAccount: subscriptionManager.isUserAuthenticated, + hasVPNEntitlement: subscriptionManager.isEntitlementActive(.networkProtection) ) } diff --git a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift index e739522169..b42abe6fd4 100644 --- a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift +++ b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift @@ -200,7 +200,7 @@ final class DuckPlayer { guard let self else { return nil } - guard let userValues: UserValues = DecodableHelper.decode(from: params) else { + guard let userValues: UserValues = CodableHelper.decode(from: params) else { assertionFailure("YoutubeOverlayUserScript: expected JSON representation of UserValues") return nil } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 128765c197..76c32bc211 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -30,9 +30,6 @@ public final class SubscriptionDebugMenu: NSMenuItem { var currentViewController: () -> NSViewController? let subscriptionManager: SubscriptionManager - var accountManager: AccountManager { - subscriptionManager.accountManager - } private var _purchaseManager: Any? @available(macOS 12.0, *) @@ -166,26 +163,29 @@ public final class SubscriptionDebugMenu: NSMenuItem { @objc func signOut() { - accountManager.signOut() + Task { + await subscriptionManager.signOut() + } } @objc func showAccountDetails() { - let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" - let message = accountManager.isUserAuthenticated ? ["AuthToken: \(accountManager.authToken ?? "")", - "AccessToken: \(accountManager.accessToken ?? "")", - "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil - showAlert(title: title, message: message) + Task { + let title = subscriptionManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" + let token = try? await subscriptionManager.getTokenContainer(policy: .local).accessToken + let message = subscriptionManager.isUserAuthenticated ? ["AccessToken: \(token ?? "")", + "Email: \(subscriptionManager.userEmail ?? "")"].joined(separator: "\n") : nil + showAlert(title: title, message: message) + } } @objc func validateToken() { Task { - guard let token = accountManager.accessToken else { return } - switch await subscriptionManager.authEndpointService.validateToken(accessToken: token) { - case .success(let response): - showAlert(title: "Validate token", message: "\(response)") - case .failure(let error): + do { + let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .local) + showAlert(title: "Valid token", message: tokenContainer.debugDescription) + } catch { showAlert(title: "Validate token", message: "\(error)") } } @@ -193,30 +193,18 @@ public final class SubscriptionDebugMenu: NSMenuItem { @objc func checkEntitlements() { - Task { - var results: [String] = [] - - let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - for entitlement in entitlements { - 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) - } - } - - showAlert(title: "Check Entitlements", message: results.joined(separator: "\n")) - } + let entitlements = subscriptionManager.entitlements + let descriptions = entitlements.map({ entitlement in entitlement.rawValue }) + showAlert(title: "Check Entitlements", message: descriptions.joined(separator: "\n")) } @objc func getSubscriptionDetails() { Task { - guard let token = accountManager.accessToken else { return } - switch await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { - case .success(let response): - showAlert(title: "Subscription info", message: "\(response)") - case .failure(let error): + do { + let subscription = try await subscriptionManager.getSubscription(cachePolicy: .reloadIgnoringLocalCacheData) + showAlert(title: "Subscription info", message: subscription.debugDescription) + } catch { showAlert(title: "Subscription info", message: "\(error)") } } @@ -232,17 +220,14 @@ public final class SubscriptionDebugMenu: NSMenuItem { @IBAction func showPurchaseView(_ sender: Any?) { if #available(macOS 12.0, *) { - let storePurchaseManager = DefaultStorePurchaseManager() - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: subscriptionManager.storePurchaseManager()) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let vc = DebugPurchaseViewController(storePurchaseManager: storePurchaseManager, appStorePurchaseFlow: appStorePurchaseFlow) + appStoreRestoreFlow: appStoreRestoreFlow) + + let vc = DebugPurchaseViewController(storePurchaseManager: DefaultStorePurchaseManager(), + appStorePurchaseFlow: appStorePurchaseFlow) currentViewController()?.presentAsSheet(vc) } } @@ -278,7 +263,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { } private func askAndUpdateServiceEnvironment(to newServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment) { - let alert = makeAlert(title: "Are you sure you want to change the environment to \(newServiceEnvironment.description.capitalized)", + let alert = makeAlert(title: "Are you sure you want to change the environment to \(newServiceEnvironment.rawValue.capitalized)", message: """ Please make sure you have manually removed your current active Subscription and reset all related features. You may also need to change environment of related features. @@ -301,10 +286,8 @@ public final class SubscriptionDebugMenu: NSMenuItem { func restorePurchases(_ sender: Any?) { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: subscriptionManager.storePurchaseManager()) await appStoreRestoreFlow.restoreAccountFromPastPurchase() } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 53dcbb131e..a3b9480146 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -20,12 +20,13 @@ import AppKit import Subscription import struct Combine.AnyPublisher import enum Combine.Publishers +import Networking public final class PreferencesSubscriptionModel: ObservableObject { @Published var isUserAuthenticated: Bool = false @Published var subscriptionDetails: String? - @Published var subscriptionStatus: Subscription.Status? + @Published var subscriptionStatus: PrivacyProSubscription.Status? @Published var hasAccessToVPN: Bool = false @Published var hasAccessToDBP: Bool = false @@ -34,20 +35,17 @@ public final class PreferencesSubscriptionModel: ObservableObject { @Published var email: String? var hasEmail: Bool { !(email?.isEmpty ?? true) } - private var subscriptionPlatform: Subscription.Platform? + private var subscriptionPlatform: PrivacyProSubscription.Platform? lazy var sheetModel = SubscriptionAccessViewModel(actionHandlers: sheetActionHandler, purchasePlatform: subscriptionManager.currentEnvironment.purchasePlatform) private let subscriptionManager: SubscriptionManager - private var accountManager: AccountManager { - subscriptionManager.accountManager - } private let openURLHandler: (URL) -> Void public let userEventHandler: (UserEvent) -> Void private let sheetActionHandler: SubscriptionAccessActionHandlers - private var fetchSubscriptionDetailsTask: Task<(), Never>? +// private var fetchSubscriptionDetailsTask: Task<(), Never>? private var signInObserver: Any? private var signOutObserver: Any? @@ -101,15 +99,15 @@ public final class PreferencesSubscriptionModel: ObservableObject { self.userEventHandler = userEventHandler self.sheetActionHandler = sheetActionHandler - self.isUserAuthenticated = accountManager.isUserAuthenticated + self.isUserAuthenticated = subscriptionManager.isUserAuthenticated - if accountManager.isUserAuthenticated { + if subscriptionManager.isUserAuthenticated { Task { await self.updateSubscription(cachePolicy: .returnCacheDataElseLoad) - await self.loadCachedEntitlements() + await self.loadEntitlements(policy: .local) } - self.email = accountManager.email + self.email = subscriptionManager.userEmail } signInObserver = NotificationCenter.default.addObserver(forName: .accountDidSignIn, object: nil, queue: .main) { [weak self] _ in @@ -143,7 +141,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func updateUserAuthenticatedState(_ isUserAuthenticated: Bool) { self.isUserAuthenticated = isUserAuthenticated - self.email = accountManager.email + self.email = subscriptionManager.userEmail } @MainActor @@ -161,13 +159,13 @@ public final class PreferencesSubscriptionModel: ObservableObject { switch subscriptionPlatform { case .apple: - if await confirmIfSignedInToSameAccount() { +// if await confirmIfSignedInToSameAccount() { // TODO: what is this for? return .navigateToManageSubscription { [weak self] in self?.changePlanOrBilling(for: .appStore) } - } else { - return .presentSheet(.apple) - } +// } else { +// return .presentSheet(.apple) +// } case .google: return .presentSheet(.google) case .stripe: @@ -186,28 +184,27 @@ public final class PreferencesSubscriptionModel: ObservableObject { NSWorkspace.shared.open(subscriptionManager.url(for: .manageSubscriptionsInAppStore)) case .stripe: Task { - guard let accessToken = accountManager.accessToken, let externalID = accountManager.externalID, - case let .success(response) = await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: accessToken, externalID: externalID) else { return } - guard let customerPortalURL = URL(string: response.customerPortalUrl) else { return } - + guard let customerPortalURL = try? await subscriptionManager.getCustomerPortalURL() else { + return + } openURLHandler(customerPortalURL) } } } - private func confirmIfSignedInToSameAccount() async -> Bool { - if #available(macOS 12.0, *) { - guard let lastTransactionJWSRepresentation = await subscriptionManager.storePurchaseManager().mostRecentTransaction() else { return false } - switch await subscriptionManager.authEndpointService.storeLogin(signature: lastTransactionJWSRepresentation) { - case .success(let response): - return response.externalID == accountManager.externalID - case .failure: - return false - } - } - - return false - } +// private func confirmIfSignedInToSameAccount() async -> Bool { +// if #available(macOS 12.0, *) { +// guard let lastTransactionJWSRepresentation = await subscriptionManager.storePurchaseManager().mostRecentTransaction() else { return false } +// switch await subscriptionManager.authEndpointService.storeLogin(signature: lastTransactionJWSRepresentation) { +// case .success(let response): +// return response.externalID == accountManager.externalID +// case .failure: +// return false +// } +// } +// +// return false +// } @MainActor func openVPN() { @@ -256,14 +253,14 @@ public final class PreferencesSubscriptionModel: ObservableObject { } Task { - if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { - if #available(macOS 12.0, iOS 15.0, *) { - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) - await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() - } - } +// if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { +// if #available(macOS 12.0, iOS 15.0, *) { +// let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, +// storePurchaseManager: subscriptionManager.storePurchaseManager(), +// accountManager: subscriptionManager.accountManager) +// await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() +// } +// } // TODO: Double check but I don't think this makes sense in this context Task { @MainActor in userEventHandler(eventType) @@ -275,7 +272,9 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func removeFromThisDeviceAction() { userEventHandler(.removeSubscriptionClick) - accountManager.signOut() + Task { + await subscriptionManager.signOut() + } } @MainActor @@ -293,10 +292,8 @@ public final class PreferencesSubscriptionModel: ObservableObject { if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: DefaultStorePurchaseManager()) await appStoreRestoreFlow.restoreAccountFromPastPurchase() fetchAndUpdateSubscriptionDetails() } @@ -308,80 +305,71 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func fetchAndUpdateSubscriptionDetails() { - self.isUserAuthenticated = accountManager.isUserAuthenticated - - guard fetchSubscriptionDetailsTask == nil else { return } - - fetchSubscriptionDetailsTask = Task { [weak self] in - defer { - self?.fetchSubscriptionDetailsTask = nil - } - + self.isUserAuthenticated = subscriptionManager.isUserAuthenticated + Task { [weak self] in await self?.fetchEmailAndRemoteEntitlements() await self?.updateSubscription(cachePolicy: .reloadIgnoringLocalCacheData) } + +// guard fetchSubscriptionDetailsTask == nil else { return } +// +// fetchSubscriptionDetailsTask = Task { [weak self] in +// defer { +// self?.fetchSubscriptionDetailsTask = nil +// } +// +// await self?.fetchEmailAndRemoteEntitlements() +// await self?.updateSubscription(cachePolicy: .reloadIgnoringLocalCacheData) +// } } @MainActor - private func loadCachedEntitlements() async { - switch await self.accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .returnCacheDataDontLoad) { - case let .success(result): - hasAccessToVPN = result - case .failure: + private func loadEntitlements(policy: TokensCachePolicy) async { + guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: policy) else { hasAccessToVPN = false - } - - switch await self.accountManager.hasEntitlement(forProductName: .dataBrokerProtection, cachePolicy: .returnCacheDataDontLoad) { - case let .success(result): - hasAccessToDBP = result - case .failure: hasAccessToDBP = false - } - - switch await self.accountManager.hasEntitlement(forProductName: .identityTheftRestoration, cachePolicy: .returnCacheDataDontLoad) { - case let .success(result): - hasAccessToITR = result - case .failure: hasAccessToITR = false + return } + + hasAccessToVPN = tokenContainer.decodedAccessToken.hasEntitlement(.networkProtection) + hasAccessToDBP = tokenContainer.decodedAccessToken.hasEntitlement(.dataBrokerProtection) + hasAccessToITR = tokenContainer.decodedAccessToken.hasEntitlement(.identityTheftRestoration) } @MainActor func fetchEmailAndRemoteEntitlements() async { - guard let accessToken = accountManager.accessToken else { return } - - if case let .success(response) = await subscriptionManager.authEndpointService.validateToken(accessToken: accessToken) { - if accountManager.email != response.account.email { - email = response.account.email - accountManager.storeAccount(token: accessToken, email: response.account.email, externalID: response.account.externalID) - } + await loadEntitlements(policy: .localForceRefresh) + guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local) else { return } + email = tokenContainer.decodedAccessToken.email - let entitlements = response.account.entitlements.compactMap { $0.product } - hasAccessToVPN = entitlements.contains(.networkProtection) - hasAccessToDBP = entitlements.contains(.dataBrokerProtection) - hasAccessToITR = entitlements.contains(.identityTheftRestoration) - accountManager.updateCache(with: response.account.entitlements) - } +// guard let accessToken = accountManager.accessToken else { return } +// +// if case let .success(response) = await subscriptionManager.authEndpointService.validateToken(accessToken: accessToken) { +// if accountManager.email != response.account.email { +// email = response.account.email +// accountManager.storeAccount(token: accessToken, email: response.account.email, externalID: response.account.externalID) +// } +// +// let entitlements = response.account.entitlements.compactMap { $0.product } +// hasAccessToVPN = entitlements.contains(.networkProtection) +// hasAccessToDBP = entitlements.contains(.dataBrokerProtection) +// hasAccessToITR = entitlements.contains(.identityTheftRestoration) +// accountManager.updateCache(with: response.account.entitlements) +// } } @MainActor - private func updateSubscription(cachePolicy: APICachePolicy) async { - guard let token = accountManager.accessToken else { - subscriptionManager.subscriptionEndpointService.signOut() + private func updateSubscription(cachePolicy: SubscriptionCachePolicy) async { + guard let subscription = try? await subscriptionManager.getSubscription(cachePolicy: cachePolicy) else { return } - - 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 - subscriptionStatus = subscription.status - case .failure: - break - } + updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) + subscriptionPlatform = subscription.platform + subscriptionStatus = subscription.status } @MainActor - func updateDescription(for date: Date, status: Subscription.Status, period: Subscription.BillingPeriod) { + func updateDescription(for date: Date, status: PrivacyProSubscription.Status, period: PrivacyProSubscription.BillingPeriod) { let formattedDate = dateFormatter.string(from: date) diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift similarity index 93% rename from UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift rename to UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index 272b32ad5a..197d222657 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -1,5 +1,5 @@ // -// SubscriptionPagesUseSubscriptionFeatureTestsForStripe.swift +// SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // @@ -26,9 +26,10 @@ import UserScript @testable import PixelKit import PixelKitTestingUtilities import os.log +import Networking @available(macOS 12.0, *) -final class SubscriptionPagesUseSubscriptionFeatureTestsForStripe: XCTestCase { +final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { private struct Constants { static let userDefaultsSuiteName = "SubscriptionPagesUseSubscriptionFeatureTests" @@ -39,9 +40,9 @@ final class SubscriptionPagesUseSubscriptionFeatureTestsForStripe: XCTestCase { static let email = "dax@duck.com" - static let entitlements = [Entitlement(product: .dataBrokerProtection), - Entitlement(product: .identityTheftRestoration), - Entitlement(product: .networkProtection)] + static let entitlements: [SubscriptionEntitlement] = [.dataBrokerProtection, + .identityTheftRestoration, + .networkProtection] static let mostRecentTransactionJWS = "dGhpcyBpcyBub3QgYSByZWFsIEFw(...)cCBTdG9yZSB0cmFuc2FjdGlvbiBKV1M=" @@ -69,14 +70,14 @@ final class SubscriptionPagesUseSubscriptionFeatureTestsForStripe: XCTestCase { SubscriptionFeature(name: "identity-theft-restoration") ]) - static let validateTokenResponse = ValidateTokenResponse(account: ValidateTokenResponse.Account(email: Constants.email, - entitlements: Constants.entitlements, - externalID: Constants.externalID)) +// static let validateTokenResponse = ValidateTokenResponse(account: ValidateTokenResponse.Account(email: Constants.email, +// entitlements: Constants.entitlements, +// externalID: Constants.externalID)) static let mockParams: [String: String] = [:] @MainActor static let mockScriptMessage = MockWKScriptMessage(name: "", body: "", webView: WKWebView() ) - static let invalidTokenError = APIServiceError.serverError(statusCode: 401, error: "invalid_token") +// static let invalidTokenError = APIServiceError.serverError(statusCode: 401, error: "invalid_token") } var userDefaults: UserDefaults! @@ -86,22 +87,22 @@ final class SubscriptionPagesUseSubscriptionFeatureTestsForStripe: XCTestCase { var accountStorage: AccountKeychainStorageMock! var accessTokenStorage: SubscriptionTokenKeychainStorageMock! - var entitlementsCache: UserDefaultsCache<[Entitlement]>! +// var entitlementsCache: UserDefaultsCache<[Entitlement]>! var subscriptionService: SubscriptionEndpointServiceMock! - var authService: AuthEndpointServiceMock! +// var authService: AuthEndpointServiceMock! var storePurchaseManager: StorePurchaseManagerMock! var subscriptionEnvironment: SubscriptionEnvironment! var appStorePurchaseFlow: AppStorePurchaseFlow! var appStoreRestoreFlow: AppStoreRestoreFlow! - var appStoreAccountManagementFlow: AppStoreAccountManagementFlow! +// var appStoreAccountManagementFlow: AppStoreAccountManagementFlow! var stripePurchaseFlow: StripePurchaseFlow! var subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock! - var accountManager: AccountManager! +// var accountManager: AccountManager! var subscriptionManager: SubscriptionManager! var mockFreemiumDBPExperimentManager: MockFreemiumDBPExperimentManager! @@ -363,7 +364,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTestsForStripe: XCTestCase { } @available(macOS 12.0, *) -extension SubscriptionPagesUseSubscriptionFeatureTestsForStripe { +extension SubscriptionPagesUseSubscriptionFeatureForStripeTests { func ensureUserAuthenticatedState() { accountStorage.authToken = Constants.authToken From 4fa2b0affc38f4e691bfcc539b5e2b5be34ad480 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 20 Nov 2024 15:50:49 +0000 Subject: [PATCH 02/59] DI done --- .../DBP/DataBrokerProtectionManager.swift | 6 +- ...erProtectionSubscriptionEventHandler.swift | 8 +- ...FreemiumDBPFirstProfileSavedNotifier.swift | 10 +- .../NavigationBar/View/MoreOptionsMenu.swift | 40 +++----- ...rkProtection+ConvenienceInitializers.swift | 4 +- .../NetworkProtectionDebugMenu.swift | 2 +- .../NetworkProtectionTunnelController.swift | 25 ++--- ...NetworkProtectionIPCTunnelController.swift | 2 +- ...rkProtectionSubscriptionEventHandler.swift | 27 +---- .../MacPacketTunnelProvider.swift | 65 ++++++++---- .../View/PreferencesRootView.swift | 13 +-- ...RemoteMessagingConfigMatcherProvider.swift | 10 +- ...riptionManager+StandardConfiguration.swift | 1 - ...ntityTheftRestorationPagesUserScript.swift | 1 + .../UnifiedFeedbackFormViewController.swift | 2 +- .../VPNFeedbackFormViewController.swift | 2 +- .../Waitlist/VPNFeatureGatekeeper.swift | 18 ++-- ...taBrokerAuthenticationManagerBuilder.swift | 14 +-- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 3 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 99 ++++++++++--------- ...okerProtectionAuthenticationManaging.swift | 11 ++- ...BrokerProtectionSubscriptionManaging.swift | 90 ++++++++--------- .../DataBrokerProtectionAgentManager.swift | 3 +- UnitTests/Menus/MoreOptionsMenuTests.swift | 2 +- 24 files changed, 222 insertions(+), 236 deletions(-) diff --git a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift index 8bb55f7893..0809c80925 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift @@ -33,10 +33,8 @@ public final class DataBrokerProtectionManager { private lazy var freemiumDBPFirstProfileSavedNotifier: FreemiumDBPFirstProfileSavedNotifier = { let freemiumDBPUserStateManager = DefaultFreemiumDBPUserStateManager(userDefaults: .dbp) - let accountManager = Application.appDelegate.subscriptionManager.accountManager - let freemiumDBPFirstProfileSavedNotifier = FreemiumDBPFirstProfileSavedNotifier(freemiumDBPUserStateManager: freemiumDBPUserStateManager, - accountManager: accountManager) - return freemiumDBPFirstProfileSavedNotifier + return FreemiumDBPFirstProfileSavedNotifier(freemiumDBPUserStateManager: freemiumDBPUserStateManager, + subscriptionManager: Application.appDelegate.subscriptionManager) }() lazy var dataManager: DataBrokerProtectionDataManager = { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index a4ed56afc8..24cc81d548 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -22,6 +22,7 @@ import Subscription import DataBrokerProtection import PixelKit import Common +import Networking final class DataBrokerProtectionSubscriptionEventHandler { @@ -58,16 +59,13 @@ final class DataBrokerProtectionSubscriptionEventHandler { } private func entitlementsDidChange(_ notification: Notification) { - guard let entitlements = notification.userInfo?[UserDefaultsCacheKey.subscriptionEntitlements] as? [Entitlement] else { + guard let entitlements = notification.userInfo?[UserDefaultsCacheKey.subscriptionEntitlements] as? [SubscriptionEntitlement] else { assertionFailure("Missing entitlements are truly unexpected") return } - let hasEntitlements = entitlements.contains { entitlement in - entitlement.product == .dataBrokerProtection - } - + let hasEntitlements = entitlements.contains(.dataBrokerProtection) Task { await entitlementsDidChange(hasEntitlements: hasEntitlements) } diff --git a/DuckDuckGo/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifier.swift b/DuckDuckGo/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifier.swift index d7493816e0..f9e3341bc7 100644 --- a/DuckDuckGo/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifier.swift +++ b/DuckDuckGo/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifier.swift @@ -27,7 +27,7 @@ import OSLog final class FreemiumDBPFirstProfileSavedNotifier: DBPProfileSavedNotifier { private var freemiumDBPUserStateManager: FreemiumDBPUserStateManager - private var accountManager: AccountManager + private var subscriptionManager: SubscriptionManager private let notificationCenter: NotificationCenter /// Initializes the notifier with the necessary dependencies to check user state and post notifications. @@ -36,9 +36,11 @@ final class FreemiumDBPFirstProfileSavedNotifier: DBPProfileSavedNotifier { /// - freemiumDBPUserStateManager: Manages the user state related to Freemium DBP. /// - accountManager: Manages account-related information, such as whether the user is authenticated. /// - notificationCenter: The notification center for posting notifications. Defaults to the system's default notification center. - init(freemiumDBPUserStateManager: FreemiumDBPUserStateManager, accountManager: AccountManager, notificationCenter: NotificationCenter = .default) { + init(freemiumDBPUserStateManager: FreemiumDBPUserStateManager, + subscriptionManager: SubscriptionManager, + notificationCenter: NotificationCenter = .default) { self.freemiumDBPUserStateManager = freemiumDBPUserStateManager - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager self.notificationCenter = notificationCenter } @@ -49,7 +51,7 @@ final class FreemiumDBPFirstProfileSavedNotifier: DBPProfileSavedNotifier { /// /// If all conditions are met, the method posts a `pirProfileSaved` notification via the `NotificationCenter` and records that the notification has been posted. func postProfileSavedNotificationIfPermitted() { - guard !accountManager.isUserAuthenticated + guard !subscriptionManager.isUserAuthenticated && freemiumDBPUserStateManager.didActivate && !freemiumDBPUserStateManager.didPostFirstProfileSavedNotification else { return } diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index d419d26dc7..e1847d95cd 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -59,7 +59,6 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { private let internalUserDecider: InternalUserDecider @MainActor private lazy var sharingMenu: NSMenu = SharingMenu(title: UserText.shareMenuItem) - private var accountManager: AccountManager { subscriptionManager.accountManager } private let subscriptionManager: SubscriptionManager private let freemiumDBPUserStateManager: FreemiumDBPUserStateManager private let freemiumDBPFeature: FreemiumDBPFeature @@ -145,7 +144,7 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { feedbackMenuItem.submenu = FeedbackSubMenu(targetting: self, tabCollectionViewModel: tabCollectionViewModel, subscriptionFeatureAvailability: subscriptionFeatureAvailability, - accountManager: accountManager) + subscriptionManager: subscriptionManager) addItem(feedbackMenuItem) #endif // FEEDBACK @@ -420,7 +419,7 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { let privacyProItem = NSMenuItem(title: UserText.subscriptionOptionsMenuItem).withImage(.subscriptionIcon) - if !accountManager.isUserAuthenticated { + if !subscriptionManager.isUserAuthenticated { privacyProItem.target = self privacyProItem.action = #selector(openSubscriptionPurchasePage(_:)) @@ -431,7 +430,7 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { } else { privacyProItem.submenu = SubscriptionSubMenu(targeting: self, subscriptionFeatureAvailability: DefaultSubscriptionFeatureAvailability(), - accountManager: accountManager) + subscriptionManager: subscriptionManager) addItem(privacyProItem) } } @@ -605,14 +604,14 @@ final class EmailOptionsButtonSubMenu: NSMenu { final class FeedbackSubMenu: NSMenu { private let subscriptionFeatureAvailability: SubscriptionFeatureAvailability - private let accountManager: AccountManager + private let subscriptionManager: any SubscriptionManager init(targetting target: AnyObject, tabCollectionViewModel: TabCollectionViewModel, subscriptionFeatureAvailability: SubscriptionFeatureAvailability, - accountManager: AccountManager) { + subscriptionManager: any SubscriptionManager) { self.subscriptionFeatureAvailability = subscriptionFeatureAvailability - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager super.init(title: UserText.sendFeedback) updateMenuItems(with: tabCollectionViewModel, targetting: target) } @@ -636,7 +635,7 @@ final class FeedbackSubMenu: NSMenu { .withImage(.siteBreakage) addItem(reportBrokenSiteItem) - if subscriptionFeatureAvailability.usesUnifiedFeedbackForm, accountManager.isUserAuthenticated { + if subscriptionFeatureAvailability.usesUnifiedFeedbackForm, subscriptionManager.isUserAuthenticated { addItem(.separator()) let sendPProFeedbackItem = NSMenuItem(title: UserText.sendPProFeedback, @@ -878,7 +877,7 @@ final class HelpSubMenu: NSMenu { final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { var subscriptionFeatureAvailability: SubscriptionFeatureAvailability - var accountManager: AccountManager + var subscriptionManager: SubscriptionManager var networkProtectionItem: NSMenuItem! var dataBrokerProtectionItem: NSMenuItem! @@ -887,10 +886,10 @@ final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { init(targeting target: AnyObject, subscriptionFeatureAvailability: SubscriptionFeatureAvailability, - accountManager: AccountManager) { + subscriptionManager: SubscriptionManager) { self.subscriptionFeatureAvailability = subscriptionFeatureAvailability - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager super.init(title: "") @@ -948,23 +947,14 @@ final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { } private func refreshAvailabilityBasedOnEntitlements() { - guard subscriptionFeatureAvailability.isFeatureAvailable, accountManager.isUserAuthenticated else { return } - - @Sendable func hasEntitlement(for productName: Entitlement.ProductName) async -> Bool { - switch await self.accountManager.hasEntitlement(forProductName: productName) { - case let .success(result): - return result - case .failure: - return false - } - } - + guard subscriptionFeatureAvailability.isFeatureAvailable, subscriptionManager.isUserAuthenticated else { return } + Task.detached(priority: .background) { [weak self] in guard let self else { return } - let isNetworkProtectionItemEnabled = await hasEntitlement(for: .networkProtection) - let isDataBrokerProtectionItemEnabled = await hasEntitlement(for: .dataBrokerProtection) - let isIdentityTheftRestorationItemEnabled = await hasEntitlement(for: .identityTheftRestoration) + let isNetworkProtectionItemEnabled = self.subscriptionManager.isEntitlementActive(.networkProtection) + let isDataBrokerProtectionItemEnabled = self.subscriptionManager.isEntitlementActive(.dataBrokerProtection) + let isIdentityTheftRestorationItemEnabled = self.subscriptionManager.isEntitlementActive(.identityTheftRestoration) Task { @MainActor in self.networkProtectionItem.isEnabled = isNetworkProtectionItemEnabled diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index b39e026096..dccf447e16 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -43,7 +43,9 @@ extension NetworkProtectionKeychainTokenStore { } convenience init(useAccessTokenProvider: Bool) { - let accessTokenProvider: () -> String? = { Application.appDelegate.subscriptionManager.accountManager.accessToken } + let accessTokenProvider: AccessTokenProvider = { + return try? await Application.appDelegate.subscriptionManager.getTokenContainer(policy: .localValid).accessToken + } self.init(keychainType: .default, errorEvents: .networkProtectionAppDebugEvents, useAccessTokenProvider: useAccessTokenProvider, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 9b32eb21ff..6400c131ee 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -264,7 +264,7 @@ final class NetworkProtectionDebugMenu: NSMenu { /// @objc func logFeedbackMetadataToConsole(_ sender: Any?) { Task { @MainActor in - let collector = DefaultVPNMetadataCollector(accountManager: Application.appDelegate.subscriptionManager.accountManager) + let collector = DefaultVPNMetadataCollector(subscriptionManager: Application.appDelegate.subscriptionManager) let metadata = await collector.collectMetadata() print(metadata.toPrettyPrintedJSON()!) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 8982657cf6..487f9ef386 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -68,7 +68,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr // MARK: - Subscriptions - private let accessTokenStorage: SubscriptionTokenKeychainStorage + private let subscriptionManager: any SubscriptionManager // MARK: - Debug Options Support @@ -158,7 +158,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr settings: VPNSettings, defaults: UserDefaults, notificationCenter: NotificationCenter = .default, - accessTokenStorage: SubscriptionTokenKeychainStorage) { + subscriptionManager: any SubscriptionManager) { self.featureFlagger = featureFlagger self.networkExtensionBundleID = networkExtensionBundleID @@ -166,7 +166,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr self.notificationCenter = notificationCenter self.settings = settings self.defaults = defaults - self.accessTokenStorage = accessTokenStorage + self.subscriptionManager = subscriptionManager subscribeToSettingsChanges() subscribeToStatusChanges() @@ -608,9 +608,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr var options = [String: NSObject]() options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString - guard let authToken = try fetchAuthToken() else { - throw StartError.noAuthToken - } + let authToken = try await fetchAuthToken() options[NetworkProtectionOptionKey.authToken] = authToken options[NetworkProtectionOptionKey.selectedEnvironment] = settings.selectedEnvironment.rawValue as NSString options[NetworkProtectionOptionKey.selectedServer] = settings.selectedServer.stringValue as? NSString @@ -793,17 +791,14 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } } - private func fetchAuthToken() throws -> NSString? { - if let accessToken = try? accessTokenStorage.getAccessToken() { + private func fetchAuthToken() async throws -> NSString { + do { + let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) Logger.networkProtection.log("🟢 TunnelController found token") - return Self.adaptAccessTokenForVPN(accessToken) as NSString? - } else { + return "ddg:\(tokenContainer.accessToken)" as NSString + } catch { Logger.networkProtection.error("TunnelController found no token") - return nil + throw StartError.noAuthToken } } - - private static func adaptAccessTokenForVPN(_ token: String) -> String { - "ddg:\(token)" - } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift index b7f2ee4afd..6406184908 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -100,7 +100,7 @@ extension NetworkProtectionIPCTunnelController: TunnelController { } do { - guard try await featureGatekeeper.canStartVPN() else { + guard featureGatekeeper.canStartVPN() else { throw RequestError.notAuthorizedToEnableLoginItem } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 127197a709..424ccc8d98 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -22,6 +22,7 @@ import Foundation import Subscription import NetworkProtection import NetworkProtectionUI +import Networking final class NetworkProtectionSubscriptionEventHandler { @@ -48,33 +49,19 @@ final class NetworkProtectionSubscriptionEventHandler { private func subscribeToEntitlementChanges() { Task { - switch await subscriptionManager.accountManager.hasEntitlement(forProductName: .networkProtection) { - case .success(let hasEntitlements): - Task { - await handleEntitlementsChange(hasEntitlements: hasEntitlements) - } - case .failure: - break - } + await handleEntitlementsChange(hasEntitlements: subscriptionManager.isEntitlementActive(.networkProtection)) NotificationCenter.default .publisher(for: .entitlementsDidChange) .receive(on: DispatchQueue.main) .sink { [weak self] notification in - guard let self else { - return - } - - guard let entitlements = notification.userInfo?[UserDefaultsCacheKey.subscriptionEntitlements] as? [Entitlement] else { - + guard let self else { return } + guard let entitlements = notification.userInfo?[UserDefaultsCacheKey.subscriptionEntitlements] as? [SubscriptionEntitlement] else { assertionFailure("Missing entitlements are truly unexpected") return } - let hasEntitlements = entitlements.contains { entitlement in - entitlement.product == .networkProtection - } - + let hasEntitlements = entitlements.contains(.networkProtection) Task { await self.handleEntitlementsChange(hasEntitlements: hasEntitlements) } @@ -98,10 +85,6 @@ final class NetworkProtectionSubscriptionEventHandler { } @objc private func handleAccountDidSignIn() { - guard subscriptionManager.accountManager.accessToken != nil else { - assertionFailure("[NetP Subscription] AccountManager signed in but token could not be retrieved") - return - } userDefaults.networkProtectionEntitlementsExpired = false } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 5978e58af7..cdbabbb2c7 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -430,9 +430,9 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { useAccessTokenProvider: false, accessTokenProvider: { nil } ) - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, - key: UserDefaultsCacheKey.subscriptionEntitlements, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) +// let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, +// key: UserDefaultsCacheKey.subscriptionEntitlements, +// settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) // Align Subscription environment to the VPN environment var subscriptionEnvironment = SubscriptionEnvironment.default switch settings.selectedEnvironment { @@ -442,15 +442,41 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { subscriptionEnvironment.serviceEnvironment = .staging } - let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = DefaultAccountManager(accessTokenStorage: tokenStore, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionEndpointService, - authEndpointService: authEndpointService) - - let entitlementsCheck = { - await accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) +// let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) +// let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) +// let accountManager = DefaultAccountManager(accessTokenStorage: tokenStore, +// entitlementsCache: entitlementsCache, +// subscriptionEndpointService: subscriptionEndpointService, +// authEndpointService: authEndpointService) + + let configuration = URLSessionConfiguration.default + configuration.httpCookieStorage = nil + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + let urlSession = URLSession(configuration: configuration, + delegate: SessionDelegate(), + delegateQueue: nil) + let apiService = DefaultAPIService(urlSession: urlSession) + let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging + let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) + + // keychain storage + let subscriptionAppGroup = MacPacketTunnelProvider.subscriptionsAppGroup! + let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(subscriptionAppGroup))) + let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) + + let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, + legacyTokenStorage: legacyAccountStorage, + authService: authService) + let entitlementsCheck: (() async -> Result) = { +// await accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + do { + let tokenContainer = try await authClient.getTokens(policy: .localForceRefresh) + let entitlements = tokenContainer.decodedAccessToken.subscriptionEntitlements + return .success(entitlements.contains(.networkProtection)) + } catch { + Logger.networkProtection.error("Failed to get token: \(error)") + return .failure(error) + } } let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) @@ -470,7 +496,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { entitlementCheck: entitlementsCheck) setupPixels() - accountManager.delegate = self observeServerChanges() observeStatusUpdateRequests() } @@ -676,10 +701,10 @@ final class DefaultWireGuardInterface: WireGuardInterface { } } -extension MacPacketTunnelProvider: AccountManagerKeychainAccessDelegate { - - public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { - PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), - frequency: .legacyDailyAndCount) - } -} +//extension MacPacketTunnelProvider: AccountManagerKeychainAccessDelegate { +// +// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { +// PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), +// frequency: .legacyDailyAndCount) +// } +//} diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index 195309d2d8..51bf39aa37 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -185,14 +185,11 @@ enum Preferences { }, restorePurchases: { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let subscriptionAppStoreRestorer = DefaultSubscriptionAppStoreRestorer( - subscriptionManager: subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow, - uiHandler: subscriptionUIHandler) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: subscriptionManager.storePurchaseManager()) + let subscriptionAppStoreRestorer = DefaultSubscriptionAppStoreRestorer(subscriptionManager: subscriptionManager, + appStoreRestoreFlow: appStoreRestoreFlow, + uiHandler: subscriptionUIHandler) await subscriptionAppStoreRestorer.restoreAppStoreSubscription() } } diff --git a/DuckDuckGo/RemoteMessaging/RemoteMessagingConfigMatcherProvider.swift b/DuckDuckGo/RemoteMessaging/RemoteMessagingConfigMatcherProvider.swift index 3d0e49a0c5..5821278c61 100644 --- a/DuckDuckGo/RemoteMessaging/RemoteMessagingConfigMatcherProvider.swift +++ b/DuckDuckGo/RemoteMessaging/RemoteMessagingConfigMatcherProvider.swift @@ -70,7 +70,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let subscriptionManager = await Application.appDelegate.subscriptionManager - let isPrivacyProSubscriber = subscriptionManager.accountManager.isUserAuthenticated + let isPrivacyProSubscriber = subscriptionManager.isUserAuthenticated let isPrivacyProEligibleUser = subscriptionManager.canPurchase let activationDateStore = DefaultWaitlistActivationDateStore(source: .netP) @@ -84,10 +84,8 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr var privacyProPurchasePlatform: String? let surveyActionMapper: RemoteMessagingSurveyActionMapping - if let accessToken = subscriptionManager.accountManager.accessToken { - let subscriptionResult = await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: accessToken) - - if case let .success(subscription) = subscriptionResult { + if isPrivacyProSubscriber { + if let subscription = try? await subscriptionManager.getSubscription(cachePolicy: .returnCacheDataElseLoad) { privacyProDaysSinceSubscribed = Calendar.current.numberOfDaysBetween(subscription.startedAt, and: Date()) ?? -1 privacyProDaysUntilExpiry = Calendar.current.numberOfDaysBetween(Date(), and: subscription.expiresOrRenewsAt) ?? -1 privacyProPurchasePlatform = subscription.platform.rawValue @@ -138,7 +136,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let deprecatedRemoteMessageStorage = DefaultSurveyRemoteMessagingStorage.surveys() let freemiumDBPUserStateManager = DefaultFreemiumDBPUserStateManager(userDefaults: .dbp) - let isCurrentFreemiumDBPUser = !subscriptionManager.accountManager.isUserAuthenticated && freemiumDBPUserStateManager.didActivate + let isCurrentFreemiumDBPUser = !isPrivacyProSubscriber && freemiumDBPUserStateManager.didActivate return RemoteMessagingConfigMatcher( appAttributeMatcher: AppAttributeMatcher(statisticsStore: statisticsStore, diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index ca14d18735..a6577accc4 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -132,7 +132,6 @@ extension DefaultSubscriptionManager { } } - let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) diff --git a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift index 1ed54b4e1e..cb9e5a6e72 100644 --- a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift @@ -23,6 +23,7 @@ import Foundation import WebKit import Subscription import UserScript +import os.log /// /// The user script that will be the broker for all subscription features diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift index 8fa9d63fdc..4a2d6a1535 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift @@ -46,7 +46,7 @@ final class UnifiedFeedbackFormViewController: NSViewController { source: UnifiedFeedbackSource = .default) { self.feedbackSender = feedbackSender self.viewModel = UnifiedFeedbackFormViewModel( - vpnMetadataCollector: DefaultVPNMetadataCollector(accountManager: Application.appDelegate.subscriptionManager.accountManager), + vpnMetadataCollector: DefaultVPNMetadataCollector(subscriptionManager: Application.appDelegate.subscriptionManager), feedbackSender: feedbackSender, source: source ) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift index 22e38142a2..9d374b3a33 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift @@ -40,7 +40,7 @@ final class VPNFeedbackFormViewController: NSViewController { private var cancellables = Set() init() { - self.viewModel = VPNFeedbackFormViewModel(metadataCollector: DefaultVPNMetadataCollector(accountManager: Application.appDelegate.subscriptionManager.accountManager)) + self.viewModel = VPNFeedbackFormViewModel(metadataCollector: DefaultVPNMetadataCollector(subscriptionManager: Application.appDelegate.subscriptionManager)) super.init(nibName: nil, bundle: nil) self.viewModel.delegate = self } diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index 1569d68f97..2a04ee4d92 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -29,7 +29,7 @@ import Subscription protocol VPNFeatureGatekeeper { var isInstalled: Bool { get } - func canStartVPN() async throws -> Bool + func canStartVPN() -> Bool func isVPNVisible() -> Bool func shouldUninstallAutomatically() -> Bool func disableIfUserHasNoAccess() async @@ -64,17 +64,11 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { /// For beta users this means they have an auth token. /// For subscription users this means they have entitlements. /// - func canStartVPN() async throws -> Bool { + func canStartVPN() -> Bool { guard subscriptionFeatureAvailability.isFeatureAvailable else { return false } - - switch await subscriptionManager.accountManager.hasEntitlement(forProductName: .networkProtection) { - case .success(let hasEntitlement): - return hasEntitlement - case .failure(let error): - throw error - } + return subscriptionManager.isEntitlementActive(.networkProtection) } /// Whether the user can see the VPN entry points in the UI. @@ -86,7 +80,7 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { guard subscriptionFeatureAvailability.isFeatureAvailable else { return false } - return subscriptionManager.accountManager.isUserAuthenticated + return subscriptionManager.isUserAuthenticated } /// We've had to add this method because accessing the singleton in app delegate is crashing the integration tests. @@ -98,7 +92,9 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { /// Returns whether the VPN should be uninstalled automatically. /// This is only true when the user is not an Easter Egg user, the waitlist test has ended, and the user is onboarded. func shouldUninstallAutomatically() -> Bool { - return subscriptionFeatureAvailability.isFeatureAvailable && !subscriptionManager.accountManager.isUserAuthenticated && LoginItem.vpnMenu.status.isInstalled + return subscriptionFeatureAvailability.isFeatureAvailable + && !subscriptionManager.isUserAuthenticated + && LoginItem.vpnMenu.status.isInstalled } /// Whether the user is fully onboarded diff --git a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift b/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift index 0fb44dfb1c..98d51439a7 100644 --- a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift +++ b/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift @@ -23,16 +23,16 @@ import Subscription final public class DataBrokerAuthenticationManagerBuilder { static func buildAuthenticationManager(redeemUseCase: RedeemUseCase = RedeemUseCase(), - subscriptionManager: SubscriptionManager) -> DataBrokerProtectionAuthenticationManager { - let subscriptionManager = DataBrokerProtectionSubscriptionManager(subscriptionManager: subscriptionManager) + subscriptionManager: any SubscriptionManager) -> DataBrokerProtectionAuthenticationManager { +// let subscriptionManager = DataBrokerProtectionSubscriptionManager(subscriptionManager: subscriptionManager) return DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) } } -extension DefaultAccountManager: DataBrokerProtectionAccountManaging { - public func hasEntitlement(for cachePolicy: APICachePolicy) async -> Result { - await hasEntitlement(forProductName: .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 6e45dcc1ac..df118fecf3 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -103,8 +103,7 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele authenticationRepository: KeychainAuthenticationData()) let authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - manager = DataBrokerProtectionAgentManagerProvider.agentManager(authenticationManager: authenticationManager, - accountManager: subscriptionManager.accountManager) + manager = DataBrokerProtectionAgentManagerProvider.agentManager(authenticationManager: authenticationManager) manager?.agentFinishedLaunching() setupStatusBarMenu() diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 6f8115af19..33c4a693b5 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -37,7 +37,7 @@ import os.log @objc(Application) final class DuckDuckGoVPNApplication: NSApplication { - public var accountManager: AccountManager + public var subscriptionManager: any SubscriptionManager private let _delegate: DuckDuckGoVPNAppDelegate override init() { @@ -50,38 +50,44 @@ final class DuckDuckGoVPNApplication: NSApplication { } // MARK: - Configure Subscription - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - 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 = DefaultAccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionEndpointService, - authEndpointService: authEndpointService) - - _delegate = DuckDuckGoVPNAppDelegate(accountManager: accountManager, - accessTokenStorage: accessTokenStorage, - subscriptionEnvironment: subscriptionEnvironment) + self.subscriptionManager = DefaultSubscriptionManager() + +// let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) +// let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! +// 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 = DefaultAccountManager(accessTokenStorage: accessTokenStorage, +// entitlementsCache: entitlementsCache, +// subscriptionEndpointService: subscriptionEndpointService, +// authEndpointService: authEndpointService) + + _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) super.init() setupPixelKit() self.delegate = _delegate - accountManager.delegate = _delegate #if DEBUG - if accountManager.accessToken != nil { - Logger.networkProtection.error("🟢 VPN Agent found token") - } else { - Logger.networkProtection.error("VPN Agent found no token") - } + checkTokenPresence() #endif } + func checkTokenPresence() { + Task { + do { + let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .local) + Logger.networkProtection.error("🟢 VPN Agent found token") + } catch { + Logger.networkProtection.error("VPN Agent found no token \(error.localizedDescription)") + } + } + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -128,8 +134,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private static let recentThreshold: TimeInterval = 5.0 private let appLauncher = AppLauncher() - private let accountManager: AccountManager - private let accessTokenStorage: SubscriptionTokenKeychainStorage + private let subscriptionManager: any SubscriptionManager private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager @@ -139,14 +144,10 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { internalUserDecider: privacyConfigurationManager.internalUserDecider, privacyConfigManager: privacyConfigurationManager) - public init(accountManager: AccountManager, - accessTokenStorage: SubscriptionTokenKeychainStorage, - subscriptionEnvironment: SubscriptionEnvironment) { - - self.accountManager = accountManager - self.accessTokenStorage = accessTokenStorage + public init(subscriptionManager: any SubscriptionManager) { + self.subscriptionManager = subscriptionManager self.tunnelSettings = VPNSettings(defaults: .netP) - self.tunnelSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) + self.tunnelSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) self.configurationManager = ConfigurationManager(privacyConfigManager: privacyConfigurationManager, store: configurationStore) } @@ -230,7 +231,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { featureFlagger: featureFlagger, settings: tunnelSettings, defaults: userDefaults, - accessTokenStorage: accessTokenStorage) + subscriptionManager: subscriptionManager) /// An IPC server that provides access to the tunnel controller. /// @@ -424,10 +425,18 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private lazy var entitlementMonitor = NetworkProtectionEntitlementMonitor() private func setUpSubscriptionMonitoring() { - guard accountManager.isUserAuthenticated else { return } - - let entitlementsCheck = { - await self.accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + guard subscriptionManager.isUserAuthenticated else { return } + + let entitlementsCheck: (() async -> Result) = { + //await self.accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + do { + let tokenContainer = try await self.subscriptionManager.getTokenContainer(policy: .localForceRefresh) + let entitlements = tokenContainer.decodedAccessToken.subscriptionEntitlements + return .success(entitlements.contains(.networkProtection)) + } catch { + Logger.networkProtection.error("Failed to get token: \(error)") + return .failure(error) + } } Task { @@ -455,10 +464,10 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { } -extension DuckDuckGoVPNAppDelegate: AccountManagerKeychainAccessDelegate { - - public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { - PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), - frequency: .legacyDailyAndCount) - } -} +//extension DuckDuckGoVPNAppDelegate: AccountManagerKeychainAccessDelegate { +// +// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { +// PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), +// frequency: .legacyDailyAndCount) +// } +//} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift index 0aea2ac3ef..be50df1852 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift @@ -17,6 +17,7 @@ // import Foundation +import Subscription public protocol DataBrokerProtectionAuthenticationManaging { var isUserAuthenticated: Bool { get } @@ -29,24 +30,24 @@ public protocol DataBrokerProtectionAuthenticationManaging { public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtectionAuthenticationManaging { private let redeemUseCase: DataBrokerProtectionRedeemUseCase - private let subscriptionManager: DataBrokerProtectionSubscriptionManaging + private let subscriptionManager: any SubscriptionManager public var isUserAuthenticated: Bool { subscriptionManager.isUserAuthenticated } public var accessToken: String? { - subscriptionManager.accessToken + subscriptionManager.getTokenContainerSynchronously(policy: .localValid)?.accessToken } public init(redeemUseCase: any DataBrokerProtectionRedeemUseCase, - subscriptionManager: any DataBrokerProtectionSubscriptionManaging) { + subscriptionManager: any SubscriptionManager) { self.redeemUseCase = redeemUseCase self.subscriptionManager = subscriptionManager } - public func hasValidEntitlement() async throws -> Bool { - try await subscriptionManager.hasValidEntitlement() + public func hasValidEntitlement() -> Bool { + subscriptionManager.isEntitlementActive(.dataBrokerProtection) } public func getAuthHeader() -> String? { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift index 793baac5bf..398e436fd9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift @@ -21,51 +21,45 @@ import Subscription import Common import AppKitExtensions -public protocol DataBrokerProtectionSubscriptionManaging { - var isUserAuthenticated: Bool { get } - var accessToken: String? { get } - func hasValidEntitlement() async throws -> Bool -} - -public final class DataBrokerProtectionSubscriptionManager: DataBrokerProtectionSubscriptionManaging { - - let subscriptionManager: SubscriptionManager - - public var isUserAuthenticated: Bool { - accessToken != nil - } - - public var accessToken: String? { - // We use a staging token for privacy pro supplied through a github secret/action - // for PIR end to end tests. This is also stored in bitwarden if you want to run - // the tests locally - let dbpSettings = DataBrokerProtectionSettings() - if dbpSettings.storedRunType == .integrationTests, - let token = ProcessInfo.processInfo.environment["PRIVACYPRO_STAGING_TOKEN"] { - return token - } - return subscriptionManager.accountManager.accessToken - } - - public init(subscriptionManager: SubscriptionManager) { - self.subscriptionManager = subscriptionManager - } - - public func hasValidEntitlement() async throws -> Bool { - switch await subscriptionManager.accountManager.hasEntitlement(forProductName: .dataBrokerProtection, - cachePolicy: .reloadIgnoringLocalCacheData) { - case let .success(result): - return result - case .failure(let error): - throw error - } - } -} - -// MARK: - Wrapper Protocols - -/// 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: APICachePolicy) async -> Result -} +//public protocol DataBrokerProtectionSubscriptionManaging { +// var isUserAuthenticated: Bool { get } +// var accessToken: String? { get } +// func hasValidEntitlement() async throws -> Bool +//} +// +//public final class DataBrokerProtectionSubscriptionManager: DataBrokerProtectionSubscriptionManaging { +// +// let subscriptionManager: SubscriptionManager +// +// public var isUserAuthenticated: Bool { +// accessToken != nil +// } +// +// public var accessToken: String? { +// // We use a staging token for privacy pro supplied through a github secret/action +// // for PIR end to end tests. This is also stored in bitwarden if you want to run +// // the tests locally +// let dbpSettings = DataBrokerProtectionSettings() +// if dbpSettings.storedRunType == .integrationTests, +// let token = ProcessInfo.processInfo.environment["PRIVACYPRO_STAGING_TOKEN"] { +// return token +// } +// return subscriptionManager.accountManager.accessToken +// } +// +// public init(subscriptionManager: SubscriptionManager) { +// self.subscriptionManager = subscriptionManager +// } +// +// public func hasValidEntitlement() async throws -> Bool { +// subscriptionManager.isEntitlementActive(.dataBrokerProtection) +// } +//} +// +//// MARK: - Wrapper Protocols +// +///// 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: APICachePolicy) async -> Result +//} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift index 4a14c3145f..bf4daabdee 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift @@ -31,8 +31,7 @@ import UserNotifications // This is to avoid exposing all the dependancies outside of the DBP package public class DataBrokerProtectionAgentManagerProvider { - public static func agentManager(authenticationManager: DataBrokerProtectionAuthenticationManaging, - accountManager: AccountManager) -> DataBrokerProtectionAgentManager { + public static func agentManager(authenticationManager: DataBrokerProtectionAuthenticationManaging) -> DataBrokerProtectionAgentManager { let pixelHandler = DataBrokerProtectionPixelsHandler() let dbpSettings = DataBrokerProtectionSettings() diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index f622fe79cc..e3c12c6b67 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -343,7 +343,7 @@ final class NetworkProtectionVisibilityMock: VPNFeatureGatekeeper { return !visible } - func canStartVPN() async throws -> Bool { + func canStartVPN() -> Bool { return false } From 0d333af070be934a24279938f8e828f39088e7cd Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 22 Nov 2024 11:21:50 +0000 Subject: [PATCH 03/59] token v1/v2 exchange fixed, token providing improved --- DuckDuckGo.xcodeproj/project.pbxproj | 6 -- DuckDuckGo/Application/AppDelegate.swift | 8 +- .../Configuration/ConfigurationManager.swift | 2 + DuckDuckGo/Menus/MainMenuActions.swift | 6 +- DuckDuckGo/NavigationBar/PinningManager.swift | 10 +-- .../View/NavigationBarViewController.swift | 27 ++++-- ...rkProtection+ConvenienceInitializers.swift | 21 +---- .../NetworkProtectionTunnelController.swift | 3 +- ...rkProtectionSubscriptionEventHandler.swift | 4 +- .../MacPacketTunnelProvider.swift | 72 ++++------------ ...ore+SubscriptionTokenKeychainStorage.swift | 43 ---------- .../Model/PreferencesSection.swift | 2 +- ...riptionManager+StandardConfiguration.swift | 85 ++----------------- ...scriptionPagesUseSubscriptionFeature.swift | 30 ++----- .../Waitlist/VPNFeatureGatekeeper.swift | 6 +- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 7 +- .../DuckDuckGoNotificationsAppDelegate.swift | 2 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 30 ++----- .../TunnelControllerViewModel.swift | 1 - .../DebugMenu/DebugPurchaseView.swift | 2 +- .../DebugMenu/SubscriptionDebugMenu.swift | 2 +- 21 files changed, 87 insertions(+), 282 deletions(-) delete mode 100644 DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c2dd12bfe1..9983afca61 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3031,8 +3031,6 @@ EE3424612BA0853900173B1B /* VPNUninstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE34245D2BA0853900173B1B /* VPNUninstaller.swift */; }; EE42CBCC2BC8004700AD411C /* PermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE42CBCB2BC8004700AD411C /* PermissionsTests.swift */; }; EE54F7B32BBFEA49006218DB /* BookmarksAndFavoritesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE54F7B22BBFEA48006218DB /* BookmarksAndFavoritesTests.swift */; }; - EE66418C2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */; }; - EE66418D2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */; }; EE66666F2B56EDE4001D898D /* VPNLocationsHostingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */; }; EE6666702B56EDE4001D898D /* VPNLocationsHostingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */; }; EE66F10C2C3431030071856E /* WebsiteAccount_isDuplicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66F10B2C3431030071856E /* WebsiteAccount_isDuplicateTests.swift */; }; @@ -4881,7 +4879,6 @@ EE34245D2BA0853900173B1B /* VPNUninstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUninstaller.swift; sourceTree = ""; }; EE42CBCB2BC8004700AD411C /* PermissionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionsTests.swift; sourceTree = ""; }; EE54F7B22BBFEA48006218DB /* BookmarksAndFavoritesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksAndFavoritesTests.swift; sourceTree = ""; }; - EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift"; sourceTree = ""; }; EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationsHostingViewController.swift; sourceTree = ""; }; EE66F10B2C3431030071856E /* WebsiteAccount_isDuplicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteAccount_isDuplicateTests.swift; sourceTree = ""; }; EE7F74902BB5D76600CD9456 /* BookmarksBarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarTests.swift; sourceTree = ""; }; @@ -6286,7 +6283,6 @@ 4B41ED9F2B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift */, EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */, 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */, - EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */, ); path = NetworkExtensionTargets; sourceTree = ""; @@ -12494,7 +12490,6 @@ 4B25377A2A11C01700610219 /* UserText+NetworkProtectionExtensions.swift in Sources */, B65DA5F42A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, 4B52354D2C854CB600AFAF64 /* DuckDuckGoUserAgent.swift in Sources */, - EE66418D2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */, EEBCA0C72BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, F1DA51932BF6081D00CF29FA /* AttributionPixelHandler.swift in Sources */, 7B2E52252A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift in Sources */, @@ -12622,7 +12617,6 @@ buildActionMask = 2147483647; files = ( 4B41EDA02B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift in Sources */, - EE66418C2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */, 7B7DFB202B7E736B009EA1A3 /* MacPacketTunnelProvider.swift in Sources */, F1DA51962BF6083700CF29FA /* PrivacyProPixel.swift in Sources */, EEBCA0C62BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 720226b46e..2761064c45 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -277,8 +277,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { onboardingStateMachine = ContextualOnboardingStateMachine() // MARK: - Subscription configuration - - subscriptionManager = DefaultSubscriptionManager() + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + userDefault: subscriptionUserDefaults, + environment: subscriptionEnvironment) subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { return WindowControllersManager.shared }) diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index f193bb836a..fb6bde177c 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -116,6 +116,8 @@ final class ConfigurationManager: DefaultConfigurationManager { do { try await task.value didFetchAnyTrackerBlockingDependencies = true + } catch APIRequest.Error.invalidStatusCode(304) { + tryAgainSoon() } catch { Logger.config.error( "Failed to complete configuration update to \(configuration.rawValue, privacy: .public): \(error.localizedDescription, privacy: .public)" diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index c563f3db8e..57d21bd8e7 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -897,8 +897,10 @@ extension MainViewController { /// Clears the PrivacyPro state to make testing easier. /// private func clearPrivacyProState() { - Application.appDelegate.subscriptionManager.accountManager.signOut() - UserDefaults.netP.networkProtectionEntitlementsExpired = false + Task { + await Application.appDelegate.subscriptionManager.signOut() + UserDefaults.netP.networkProtectionEntitlementsExpired = false + } } @objc func resetDailyPixels(_ sender: Any?) { diff --git a/DuckDuckGo/NavigationBar/PinningManager.swift b/DuckDuckGo/NavigationBar/PinningManager.swift index 01858412b0..29f3c9bf98 100644 --- a/DuckDuckGo/NavigationBar/PinningManager.swift +++ b/DuckDuckGo/NavigationBar/PinningManager.swift @@ -18,6 +18,7 @@ import Foundation import NetworkProtection +import Subscription enum PinnableView: String { case autofill @@ -40,7 +41,7 @@ protocol PinningManager { final class LocalPinningManager: PinningManager { - static let shared = LocalPinningManager(networkProtectionFeatureActivation: NetworkProtectionKeychainTokenStore()) + static let shared = LocalPinningManager() static let pinnedViewChangedNotificationViewTypeKey = "pinning.pinnedViewChanged.viewType" @@ -50,12 +51,6 @@ final class LocalPinningManager: PinningManager { @UserDefaultsWrapper(key: .manuallyToggledPinnedViews, defaultValue: []) private var manuallyToggledPinnedViewsStrings: [String] - private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation - - init(networkProtectionFeatureActivation: NetworkProtectionFeatureActivation) { - self.networkProtectionFeatureActivation = networkProtectionFeatureActivation - } - func togglePinning(for view: PinnableView) { flagAsManuallyToggled(view) @@ -72,7 +67,6 @@ final class LocalPinningManager: PinningManager { /// Do not call this for user-initiated toggling. This is only meant to be used for scenarios in which certain conditions /// may require a view to be pinned. - /// func pin(_ view: PinnableView) { guard !isPinned(view) else { return diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index bb32f3e5e8..340fe3c08a 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -121,11 +121,10 @@ final class NavigationBarViewController: NSViewController { static private let homeButtonLeftPosition = 0 private let networkProtectionButtonModel: NetworkProtectionNavBarButtonModel - private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation +// private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation static func create(tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, - networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), downloadListCoordinator: DownloadListCoordinator = .shared, dragDropManager: BookmarkDragDropManager = .shared, networkProtectionPopoverManager: NetPPopoverManager, @@ -134,18 +133,32 @@ final class NavigationBarViewController: NSViewController { aiChatMenuConfig: AIChatMenuVisibilityConfigurable, brokenSitePromptLimiter: BrokenSitePromptLimiter) -> NavigationBarViewController { NSStoryboard(name: "NavigationBar", bundle: nil).instantiateInitialController { coder in - self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner, networkProtectionFeatureActivation: networkProtectionFeatureActivation, downloadListCoordinator: downloadListCoordinator, dragDropManager: dragDropManager, networkProtectionPopoverManager: networkProtectionPopoverManager, networkProtectionStatusReporter: networkProtectionStatusReporter, autofillPopoverPresenter: autofillPopoverPresenter, aiChatMenuConfig: aiChatMenuConfig, brokenSitePromptLimiter: brokenSitePromptLimiter) + self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel, + isBurner: isBurner, + downloadListCoordinator: downloadListCoordinator, + dragDropManager: dragDropManager, + networkProtectionPopoverManager: networkProtectionPopoverManager, + networkProtectionStatusReporter: networkProtectionStatusReporter, + autofillPopoverPresenter: autofillPopoverPresenter, + aiChatMenuConfig: aiChatMenuConfig, + brokenSitePromptLimiter: brokenSitePromptLimiter) }! } - init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation, downloadListCoordinator: DownloadListCoordinator, dragDropManager: BookmarkDragDropManager, networkProtectionPopoverManager: NetPPopoverManager, networkProtectionStatusReporter: NetworkProtectionStatusReporter, autofillPopoverPresenter: AutofillPopoverPresenter, - aiChatMenuConfig: AIChatMenuVisibilityConfigurable, brokenSitePromptLimiter: BrokenSitePromptLimiter) { + init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, + isBurner: Bool, + downloadListCoordinator: DownloadListCoordinator, + dragDropManager: BookmarkDragDropManager, + networkProtectionPopoverManager: NetPPopoverManager, + networkProtectionStatusReporter: NetworkProtectionStatusReporter, + autofillPopoverPresenter: AutofillPopoverPresenter, + aiChatMenuConfig: AIChatMenuVisibilityConfigurable, + brokenSitePromptLimiter: BrokenSitePromptLimiter) { self.popovers = NavigationBarPopovers(networkProtectionPopoverManager: networkProtectionPopoverManager, autofillPopoverPresenter: autofillPopoverPresenter, isBurner: isBurner) self.tabCollectionViewModel = tabCollectionViewModel self.networkProtectionButtonModel = NetworkProtectionNavBarButtonModel(popoverManager: networkProtectionPopoverManager, statusReporter: networkProtectionStatusReporter) self.isBurner = isBurner - self.networkProtectionFeatureActivation = networkProtectionFeatureActivation self.downloadListCoordinator = downloadListCoordinator self.dragDropManager = dragDropManager self.aiChatMenuConfig = aiChatMenuConfig @@ -331,7 +344,7 @@ final class NavigationBarViewController: NSViewController { private func toggleNetworkProtectionPopover() { guard DefaultSubscriptionFeatureAvailability().isFeatureAvailable, - NetworkProtectionKeychainTokenStore().isFeatureActivated else { + subscriptionManager.isUserAuthenticated else { return } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index dccf447e16..f8f1502fc1 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -29,30 +29,13 @@ extension NetworkProtectionDeviceManager { static func create() -> NetworkProtectionDeviceManager { let settings = Application.appDelegate.vpnSettings let keyStore = NetworkProtectionKeychainKeyStore() - let tokenStore = NetworkProtectionKeychainTokenStore() return NetworkProtectionDeviceManager(environment: settings.selectedEnvironment, - tokenStore: tokenStore, + tokenProvider: Application.appDelegate.subscriptionManager, keyStore: keyStore, errorEvents: .networkProtectionAppDebugEvents) } } -extension NetworkProtectionKeychainTokenStore { - convenience init() { - self.init(useAccessTokenProvider: true) - } - - convenience init(useAccessTokenProvider: Bool) { - let accessTokenProvider: AccessTokenProvider = { - return try? await Application.appDelegate.subscriptionManager.getTokenContainer(policy: .localValid).accessToken - } - self.init(keychainType: .default, - errorEvents: .networkProtectionAppDebugEvents, - useAccessTokenProvider: useAccessTokenProvider, - accessTokenProvider: accessTokenProvider) - } -} - extension NetworkProtectionKeychainKeyStore { convenience init() { self.init(keychainType: .default, @@ -65,7 +48,7 @@ extension NetworkProtectionLocationListCompositeRepository { let settings = Application.appDelegate.vpnSettings self.init( environment: settings.selectedEnvironment, - tokenStore: NetworkProtectionKeychainTokenStore(), + tokenProvider: Application.appDelegate.subscriptionManager, errorEvents: .networkProtectionAppDebugEvents ) } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 487f9ef386..9f05906c78 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -608,8 +608,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr var options = [String: NSObject]() options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString - let authToken = try await fetchAuthToken() - options[NetworkProtectionOptionKey.authToken] = authToken + options[NetworkProtectionOptionKey.tokenContainer] = try await fetchAuthToken() options[NetworkProtectionOptionKey.selectedEnvironment] = settings.selectedEnvironment.rawValue as NSString options[NetworkProtectionOptionKey.selectedServer] = settings.selectedServer.stringValue as? NSString diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 424ccc8d98..65fc73927f 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -28,19 +28,17 @@ final class NetworkProtectionSubscriptionEventHandler { private let subscriptionManager: SubscriptionManager private let tunnelController: TunnelController - private let networkProtectionTokenStorage: NetworkProtectionTokenStore +// private let networkProtectionTokenStorage: NetworkProtectionTokenStore private let vpnUninstaller: VPNUninstalling private let userDefaults: UserDefaults private var cancellables = Set() init(subscriptionManager: SubscriptionManager, tunnelController: TunnelController, - networkProtectionTokenStorage: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), vpnUninstaller: VPNUninstalling, userDefaults: UserDefaults = .netP) { self.subscriptionManager = subscriptionManager self.tunnelController = tunnelController - self.networkProtectionTokenStorage = networkProtectionTokenStorage self.vpnUninstaller = vpnUninstaller self.userDefaults = userDefaults diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index cdbabbb2c7..5a7231ee2c 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -398,14 +398,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } } - static var tokenServiceName: String { -#if NETP_SYSTEM_EXTENSION - "\(Bundle.main.bundleIdentifier!).authToken" -#else - NetworkProtectionKeychainTokenStore.Defaults.tokenStoreService -#endif - } - // MARK: - Initialization @MainActor @objc public init() { @@ -424,15 +416,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter) let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore) - let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: Bundle.keychainType, - serviceName: Self.tokenServiceName, - errorEvents: debugEvents, - useAccessTokenProvider: false, - accessTokenProvider: { nil } - ) -// let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, -// key: UserDefaultsCacheKey.subscriptionEntitlements, -// settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) + // Align Subscription environment to the VPN environment var subscriptionEnvironment = SubscriptionEnvironment.default switch settings.selectedEnvironment { @@ -442,39 +426,21 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { subscriptionEnvironment.serviceEnvironment = .staging } -// let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) -// let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) -// let accountManager = DefaultAccountManager(accessTokenStorage: tokenStore, -// entitlementsCache: entitlementsCache, -// subscriptionEndpointService: subscriptionEndpointService, -// authEndpointService: authEndpointService) - - let configuration = URLSessionConfiguration.default - configuration.httpCookieStorage = nil - configuration.requestCachePolicy = .reloadIgnoringLocalCacheData - let urlSession = URLSession(configuration: configuration, - delegate: SessionDelegate(), - delegateQueue: nil) - let apiService = DefaultAPIService(urlSession: urlSession) - let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging - let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) - - // keychain storage + Logger.networkProtection.log("Subscription environment: \(subscriptionEnvironment.description, privacy: .public)") + let subscriptionAppGroup = MacPacketTunnelProvider.subscriptionsAppGroup! - let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) + let subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + userDefault: subscriptionUserDefaults, + environment: subscriptionEnvironment) - let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, - legacyTokenStorage: legacyAccountStorage, - authService: authService) let entitlementsCheck: (() async -> Result) = { -// await accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) do { - let tokenContainer = try await authClient.getTokens(policy: .localForceRefresh) - let entitlements = tokenContainer.decodedAccessToken.subscriptionEntitlements + Logger.networkProtection.log("Entitlements check...") + let entitlements = try await subscriptionManager.getEntitlements(forceRefresh: true) + Logger.networkProtection.log("Entitlements found: \(entitlements, privacy: .public)") return .success(entitlements.contains(.networkProtection)) } catch { - Logger.networkProtection.error("Failed to get token: \(error)") + Logger.networkProtection.error("Failed to get entitlements: \(error)") return .failure(error) } } @@ -488,7 +454,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { snoozeTimingStore: NetworkProtectionSnoozeTimingStore(userDefaults: .netP), wireGuardInterface: DefaultWireGuardInterface(), keychainType: Bundle.keychainType, - tokenStore: tokenStore, + tokenProvider: subscriptionManager, debugEvents: debugEvents, providerEvents: Self.packetTunnelProviderEvents, settings: settings, @@ -581,8 +547,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - NEPacketTunnelProvider - public override func load(options: StartupOptions) throws { - try super.load(options: options) + public override func load(options: StartupOptions) async throws { + try await super.load(options: options) #if NETP_SYSTEM_EXTENSION loadExcludeLocalNetworks(from: options) @@ -606,7 +572,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } public override func loadVendorOptions(from provider: NETunnelProviderProtocol?) throws { - try super.loadVendorOptions(from: provider) + try super.loadVendorOptions(from: provider) // empty guard let vendorOptions = provider?.providerConfiguration else { Logger.networkProtection.log("🔵 Provider is nil, or providerConfiguration is not set") @@ -625,7 +591,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { setupPixels(defaultHeaders: defaultPixelHeaders) } - // MARK: - Overrideable Connection Events + // MARK: - Override-able Connection Events override func prepareToConnect(using provider: NETunnelProviderProtocol?) { super.prepareToConnect(using: provider) @@ -700,11 +666,3 @@ final class DefaultWireGuardInterface: WireGuardInterface { wgSetLogger(context, logFunction) } } - -//extension MacPacketTunnelProvider: AccountManagerKeychainAccessDelegate { -// -// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { -// PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), -// frequency: .legacyDailyAndCount) -// } -//} diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift deleted file mode 100644 index 4716231a6f..0000000000 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.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 NetworkProtection -import Common -import os.log - -extension NetworkProtectionKeychainTokenStore: SubscriptionTokenStoring { - - public func store(accessToken: String) throws { - try store(accessToken) - } - - public func getAccessToken() throws -> String? { - guard var token = try fetchToken() else { return nil } - if token.hasPrefix("ddg:") { - token = token.replacingOccurrences(of: "ddg:", with: "") - } - Logger.networkProtection.log("🟢 Wrapper successfully fetched token \(token)") - return token - } - - public func removeAccessToken() throws { - try deleteToken() - } -} diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index a97027c40a..3c6de26132 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -70,7 +70,7 @@ struct PreferencesSection: Hashable, Identifiable { let platform = subscriptionManager.currentEnvironment.purchasePlatform var shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false - if subscriptionManager.accountManager.isUserAuthenticated { + if subscriptionManager.isUserAuthenticated { shouldHidePrivacyProDueToNoProducts = false } diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index a6577accc4..79af3d8545 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -26,77 +26,7 @@ import os.log extension DefaultSubscriptionManager { // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root - public convenience init() { -// -// -// // let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! -// let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) -// vpnSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) -// -// let configuration = URLSessionConfiguration.default -// configuration.httpCookieStorage = nil -// configuration.requestCachePolicy = .reloadIgnoringLocalCacheData -// let urlSession = URLSession(configuration: configuration, -// delegate: SessionDelegate(), -// delegateQueue: nil) -// let apiService = DefaultAPIService(urlSession: urlSession) -// let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging -// -// let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) -// -// // keychain storage -// let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) -// let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(subscriptionAppGroup))) -// let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) -// -// let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, -// legacyTokenStorage: legacyAccountStorage, -// authService: authService) -// -// -// apiService.authorizationRefresherCallback = { _ in -// guard let tokenContainer = tokenStorage.tokenContainer else { -// throw OAuthClientError.internalError("Missing refresh token") -// } -// -// if tokenContainer.decodedAccessToken.isExpired() { -// Logger.OAuth.debug("Refreshing tokens") -// let tokens = try await authClient.getTokens(policy: .localForceRefresh) -// return tokens.accessToken -// } else { -// Logger.general.debug("Trying to refresh valid token, using the old one") -// return tokenContainer.accessToken -// } -// } -// -// let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, -// baseURL: subscriptionEnvironment.serviceEnvironment.url) -// let pixelHandler: SubscriptionManager.PixelHandler = { type in -// switch type { -// case .deadToken: -// // TODO: add pixel -// // Pixel.fire(pixel: .privacyProDeadTokenDetected) -// break -// } -// } -// -// if #available(macOS 12.0, *) { -// let storePurchaseManager = DefaultStorePurchaseManager() -// subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, -// oAuthClient: authClient, -// subscriptionEndpointService: subscriptionEndpointService, -// subscriptionEnvironment: subscriptionEnvironment, -// pixelHandler: pixelHandler) -// } else { -// subscriptionManager = DefaultSubscriptionManager(oAuthClient: authClient, -// subscriptionEndpointService: subscriptionEndpointService, -// subscriptionEnvironment: subscriptionEnvironment, -// pixelHandler: pixelHandler) -// } - - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + public convenience init(appGroup: String, userDefault: UserDefaults, environment: SubscriptionEnvironment) { let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil @@ -105,13 +35,13 @@ extension DefaultSubscriptionManager { delegate: SessionDelegate(), delegateQueue: nil) let apiService = DefaultAPIService(urlSession: urlSession) - let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging + let authEnvironment: OAuthEnvironment = environment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) // keychain storage - let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) + let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(appGroup))) + let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(appGroup))) let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, legacyTokenStorage: legacyAccountStorage, @@ -133,8 +63,7 @@ extension DefaultSubscriptionManager { } let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: subscriptionEnvironment.serviceEnvironment.url) - + baseURL: environment.serviceEnvironment.url) let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { case .deadToken: @@ -148,12 +77,12 @@ extension DefaultSubscriptionManager { self.init(storePurchaseManager: DefaultStorePurchaseManager(), oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionEnvironment: subscriptionEnvironment, + subscriptionEnvironment: environment, pixelHandler: pixelHandler) } else { self.init(oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionEnvironment: subscriptionEnvironment, + subscriptionEnvironment: environment, pixelHandler: pixelHandler) } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index ff921dc14e..8f282d8293 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -95,6 +95,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { + Logger.subscription.debug("WebView handler: \(methodName)") + switch methodName { case Handlers.getSubscription: return getSubscription case Handlers.setSubscription: return setSubscription @@ -129,8 +131,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { -// let authToken = accountManager.authToken ?? "" -// return Subscription(token: authToken) + guard subscriptionManager.isUserAuthenticated else { return Subscription(token: "") } + do { let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken return Subscription(token: accessToken) @@ -140,27 +142,11 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } } -// func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { -// -// PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .legacyDailyAndCount) -// -// guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { -// assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") -// return nil -// } -// -// let authToken = subscriptionValues.token -// if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), -// case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { -// accountManager.storeAuthToken(token: authToken) -// accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) -// } -// -// return nil -// } func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { // Note: This is called by the web FE when a subscription is retrieved, `params` contains an auth token V1 that will need to be exchanged for a V2. This is a temporary workaround until the FE fully supports v2 auth. + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .legacyDailyAndCount) + guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { Logger.subscription.fault("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") @@ -181,10 +167,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { -// if let accessToken = accountManager.accessToken, -// case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { -// accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) -// } try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) DispatchQueue.main.async { [weak self] in self?.notificationCenter.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index 2a04ee4d92..b6c41f85bf 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -40,16 +40,14 @@ protocol VPNFeatureGatekeeper { struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { private static var subscriptionAuthTokenPrefix: String { "ddg:" } private let vpnUninstaller: VPNUninstalling - private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation +// private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let defaults: UserDefaults private let subscriptionManager: SubscriptionManager - init(networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), - vpnUninstaller: VPNUninstalling = VPNUninstaller(), + init(vpnUninstaller: VPNUninstalling = VPNUninstaller(), defaults: UserDefaults = .netP, subscriptionManager: SubscriptionManager) { - self.networkProtectionFeatureActivation = networkProtectionFeatureActivation self.vpnUninstaller = vpnUninstaller self.defaults = defaults self.subscriptionManager = subscriptionManager diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index df118fecf3..7a8ef05d91 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -69,7 +69,12 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { } // Configure Subscription - subscriptionManager = DefaultSubscriptionManager() + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + self.subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + userDefault: subscriptionUserDefaults, + environment: subscriptionEnvironment) _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) diff --git a/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift b/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift index cb2d280887..5f4ee6c632 100644 --- a/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift +++ b/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift @@ -29,7 +29,7 @@ final class DuckDuckGoNotificationsApplication: NSApplication { private let _delegate = DuckDuckGoNotificationsAppDelegate() override init() { - Logger.networkProtection.error("🟢 Notifications Agent starting: \(ProcessInfo.processInfo.processIdentifier, privacy: .public)") + Logger.networkProtection.log("🟢 Notifications Agent init: \(ProcessInfo.processInfo.processIdentifier, privacy: .public)") // prevent agent from running twice if let anotherInstance = NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.bundleIdentifier!).first(where: { $0 != .current }) { diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 33c4a693b5..4cdf8092b8 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -50,22 +50,12 @@ final class DuckDuckGoVPNApplication: NSApplication { } // MARK: - Configure Subscription - self.subscriptionManager = DefaultSubscriptionManager() - -// let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) -// let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! -// 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 = DefaultAccountManager(accessTokenStorage: accessTokenStorage, -// entitlementsCache: entitlementsCache, -// subscriptionEndpointService: subscriptionEndpointService, -// authEndpointService: authEndpointService) - + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + self.subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + userDefault: subscriptionUserDefaults, + environment: subscriptionEnvironment) _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) super.init() @@ -80,8 +70,8 @@ final class DuckDuckGoVPNApplication: NSApplication { func checkTokenPresence() { Task { do { - let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .local) - Logger.networkProtection.error("🟢 VPN Agent found token") + _ = try await subscriptionManager.getTokenContainer(policy: .local) + Logger.networkProtection.log("🟢 VPN Agent found token") } catch { Logger.networkProtection.error("VPN Agent found no token \(error.localizedDescription)") } @@ -428,10 +418,8 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { guard subscriptionManager.isUserAuthenticated else { return } let entitlementsCheck: (() async -> Result) = { - //await self.accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) do { - let tokenContainer = try await self.subscriptionManager.getTokenContainer(policy: .localForceRefresh) - let entitlements = tokenContainer.decodedAccessToken.subscriptionEntitlements + let entitlements = try await self.subscriptionManager.getEntitlements(forceRefresh: true) return .success(entitlements.contains(.networkProtection)) } catch { Logger.networkProtection.error("Failed to get token: \(error)") diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift index 0fa6932fff..bee4f89881 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift @@ -325,7 +325,6 @@ public final class TunnelControllerViewModel: ObservableObject { // MARK: - Connection Status: Toggle State - @frozen enum ToggleTransition: Equatable { case idle case switchingOn(locallyInitiated: Bool) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseView.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseView.swift index 07250f3c00..ccb76820cb 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseView.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseView.swift @@ -164,7 +164,7 @@ extension Product { } } -extension String: Identifiable { +extension String: @retroactive Identifiable { public typealias ID = Int public var id: Int { return hash diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 76c32bc211..b6f656791c 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -193,7 +193,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { @objc func checkEntitlements() { - let entitlements = subscriptionManager.entitlements + let entitlements = subscriptionManager.currentEntitlements let descriptions = entitlements.map({ entitlement in entitlement.rawValue }) showAlert(title: "Check Entitlements", message: descriptions.joined(separator: "\n")) } From b79e8eb14bd49834a343ef281bd6f06c7419b835 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 22 Nov 2024 14:16:24 +0000 Subject: [PATCH 04/59] new log --- .../BothAppTargets/NetworkProtectionTunnelController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 9f05906c78..559ba314d6 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -605,8 +605,10 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } private func start(_ tunnelManager: NETunnelProviderManager) async throws { - var options = [String: NSObject]() + Logger.networkProtection.debug("Starting NetworkProtectionTunnelController") + + var options = [String: NSObject]() options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString options[NetworkProtectionOptionKey.tokenContainer] = try await fetchAuthToken() options[NetworkProtectionOptionKey.selectedEnvironment] = settings.selectedEnvironment.rawValue as NSString From 18b8fe6a7e9538dfd9ea23f86f08538b5212940b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 22 Nov 2024 16:31:16 +0000 Subject: [PATCH 05/59] tokencontainer codable --- .../NetworkProtectionTunnelController.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 559ba314d6..38b8afb9ae 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -532,6 +532,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr /// Starts the VPN connection /// func start() async { + Logger.networkProtection.log("Start VPN") VPNOperationErrorRecorder().beginRecordingControllerStart() PixelKit.fire(NetworkProtectionPixelEvent.networkProtectionControllerStartAttempt, frequency: .legacyDailyAndCount) @@ -580,6 +581,8 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr frequency: .legacyDailyAndCount) } } catch { + Logger.networkProtection.error("Starting tunnel error: \(error, privacy: .public)") + VPNOperationErrorRecorder().recordControllerStartFailure(error) knownFailureStore.lastKnownFailure = KnownFailure(error) @@ -610,7 +613,10 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr var options = [String: NSObject]() options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString - options[NetworkProtectionOptionKey.tokenContainer] = try await fetchAuthToken() + + let tokenContainer = try await fetchTokenContainer() + options[NetworkProtectionOptionKey.tokenContainer] = tokenContainer.data + options[NetworkProtectionOptionKey.selectedEnvironment] = settings.selectedEnvironment.rawValue as NSString options[NetworkProtectionOptionKey.selectedServer] = settings.selectedServer.stringValue as? NSString @@ -792,13 +798,13 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } } - private func fetchAuthToken() async throws -> NSString { + private func fetchTokenContainer() async throws -> TokenContainer { do { let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) - Logger.networkProtection.log("🟢 TunnelController found token") - return "ddg:\(tokenContainer.accessToken)" as NSString + Logger.networkProtection.log("🟢 TunnelController found token container") + return tokenContainer } catch { - Logger.networkProtection.error("TunnelController found no token") + Logger.networkProtection.error("TunnelController found no token container") throw StartError.noAuthToken } } From e6cc6648b7a5a0ca131342ce793bd0a84e8c836c Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 25 Nov 2024 13:03:44 +0000 Subject: [PATCH 06/59] vpn token propagation --- .../NetworkProtectionTunnelController.swift | 5 ++--- .../MacPacketTunnelProvider.swift | 4 +--- .../SubscriptionManager+StandardConfiguration.swift | 12 +++++++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 38b8afb9ae..79edac2f02 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -608,9 +608,6 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } private func start(_ tunnelManager: NETunnelProviderManager) async throws { - - Logger.networkProtection.debug("Starting NetworkProtectionTunnelController") - var options = [String: NSObject]() options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString @@ -652,8 +649,10 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } do { + Logger.networkProtection.debug("Starting NetworkProtectionTunnelController, options: \(options, privacy: .public)") try tunnelManager.connection.startVPNTunnel(options: options) } catch { + Logger.networkProtection.fault("Failed to start VPN tunnel: \(error, privacy: .public)") throw StartError.startTunnelFailure(error) } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 5a7231ee2c..4e63b5eaf9 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -427,9 +427,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } Logger.networkProtection.log("Subscription environment: \(subscriptionEnvironment.description, privacy: .public)") - - let subscriptionAppGroup = MacPacketTunnelProvider.subscriptionsAppGroup! - let subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + let subscriptionManager = DefaultSubscriptionManager(appGroup: MacPacketTunnelProvider.subscriptionsAppGroup, userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index 79af3d8545..7bfac65f2d 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -26,7 +26,7 @@ import os.log extension DefaultSubscriptionManager { // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root - public convenience init(appGroup: String, userDefault: UserDefaults, environment: SubscriptionEnvironment) { + public convenience init(appGroup: String?, userDefault: UserDefaults, environment: SubscriptionEnvironment) { let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil @@ -40,8 +40,14 @@ extension DefaultSubscriptionManager { let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) // keychain storage - let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(.named(appGroup))) - let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(appGroup))) + let accessGroup: KeychainType.AccessGroup + if let appGroup = appGroup { + accessGroup = .named(appGroup) + } else { + accessGroup = .unspecified + } + let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(accessGroup)) + let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(accessGroup)) let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, legacyTokenStorage: legacyAccountStorage, From 00c4cbccac1ec6447a285f7bbe21d7ca3f30d54b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 25 Nov 2024 18:42:09 +0000 Subject: [PATCH 07/59] more logs --- .../MacPacketTunnelProvider.swift | 10 ++++++---- .../SubscriptionManager+StandardConfiguration.swift | 4 ++-- .../SubscriptionPagesUseSubscriptionFeature.swift | 5 ++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 4e63b5eaf9..bb3070a4b3 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -29,7 +29,7 @@ import WireGuard final class MacPacketTunnelProvider: PacketTunnelProvider { - static var isAppex: Bool { + static var isAppex: Bool { // IS APP STORE VERSION #if NETP_SYSTEM_EXTENSION false #else @@ -37,7 +37,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { #endif } - static var subscriptionsAppGroup: String? { + fileprivate static var subscriptionsAppGroup: String? { isAppex ? Bundle.main.appGroup(bundle: .subs) : nil } @@ -401,6 +401,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - Initialization @MainActor @objc public init() { + Logger.networkProtection.log("Initializing MacPacketTunnelProvider") #if NETP_SYSTEM_EXTENSION let defaults = UserDefaults.standard #else @@ -412,7 +413,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let settings = VPNSettings(defaults: defaults) // MARK: - Configure Subscription - let subscriptionUserDefaults = UserDefaults(suiteName: MacPacketTunnelProvider.subscriptionsAppGroup)! + let appGroup = MacPacketTunnelProvider.subscriptionsAppGroup + let subscriptionUserDefaults = UserDefaults(suiteName: appGroup)! let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter) let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore) @@ -427,7 +429,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } Logger.networkProtection.log("Subscription environment: \(subscriptionEnvironment.description, privacy: .public)") - let subscriptionManager = DefaultSubscriptionManager(appGroup: MacPacketTunnelProvider.subscriptionsAppGroup, + let subscriptionManager = DefaultSubscriptionManager(appGroup: appGroup, userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index 7bfac65f2d..1f9972c3d7 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -47,10 +47,10 @@ extension DefaultSubscriptionManager { accessGroup = .unspecified } let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(accessGroup)) - let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(accessGroup)) +// let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(accessGroup)) // TODO: re-enable let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, - legacyTokenStorage: legacyAccountStorage, + legacyTokenStorage: nil, // legacyAccountStorage, authService: authService) apiService.authorizationRefresherCallback = { _ in diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index 8f282d8293..3d8a9b58dd 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -443,7 +443,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { @MainActor func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) { - pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) + guard let webView = originalMessage.webView else { + return + } + pushAction(method: .onPurchaseUpdate, webView: webView, params: purchaseUpdate) } func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { From 4bcc28657612f27e224d4cc2dc6b73377ad1a705 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 25 Nov 2024 19:02:51 +0000 Subject: [PATCH 08/59] keychain type unification --- .../Bundle+VPN.swift | 1 + .../KeychainType+ClientDefault.swift | 1 + .../MacPacketTunnelProvider.swift | 2 ++ ...riptionManager+StandardConfiguration.swift | 19 +++++++------------ ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 1 + 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift index 169f0ceb50..40cb64c560 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift @@ -18,6 +18,7 @@ import Foundation import NetworkProtection +import Common extension Bundle { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/KeychainType+ClientDefault.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/KeychainType+ClientDefault.swift index 61f848178a..35d0fcfe1e 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/KeychainType+ClientDefault.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/KeychainType+ClientDefault.swift @@ -18,6 +18,7 @@ import Foundation import NetworkProtection +import Common /// Implements convenience default for the client apps making use of this. /// diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index bb3070a4b3..84d7622ff3 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -429,7 +429,9 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } Logger.networkProtection.log("Subscription environment: \(subscriptionEnvironment.description, privacy: .public)") + let subscriptionManager = DefaultSubscriptionManager(appGroup: appGroup, + keychainType: Bundle.keychainType, userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index 1f9972c3d7..cb473fbd3f 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -26,7 +26,10 @@ import os.log extension DefaultSubscriptionManager { // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root - public convenience init(appGroup: String?, userDefault: UserDefaults, environment: SubscriptionEnvironment) { + public convenience init(appGroup: String?, + keychainType: KeychainType, + userDefault: UserDefaults, + environment: SubscriptionEnvironment) { let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil @@ -38,19 +41,11 @@ extension DefaultSubscriptionManager { let authEnvironment: OAuthEnvironment = environment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) - - // keychain storage - let accessGroup: KeychainType.AccessGroup - if let appGroup = appGroup { - accessGroup = .named(appGroup) - } else { - accessGroup = .unspecified - } - let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: .dataProtection(accessGroup)) -// let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(accessGroup)) // TODO: re-enable + let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: keychainType) + let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: keychainType) let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, - legacyTokenStorage: nil, // legacyAccountStorage, + legacyTokenStorage: legacyAccountStorage, authService: authService) apiService.authorizationRefresherCallback = { _ in diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 7a8ef05d91..84738f6070 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -73,6 +73,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) self.subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + keychainType: .dataProtection(.named(subscriptionAppGroup)), userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) From 875e8800ac7565d45d4aa2ca5326ff0dd650202d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 25 Nov 2024 20:06:21 +0000 Subject: [PATCH 09/59] keychain fix --- DuckDuckGo/Application/AppDelegate.swift | 2 +- .../NetworkExtensionTargets/MacPacketTunnelProvider.swift | 6 ++---- .../SubscriptionManager+StandardConfiguration.swift | 3 +-- .../DuckDuckGoDBPBackgroundAgentAppDelegate.swift | 3 +-- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 7 ++++--- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 2761064c45..b8b785040a 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -280,7 +280,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 84d7622ff3..c3e25c067b 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -413,8 +413,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let settings = VPNSettings(defaults: defaults) // MARK: - Configure Subscription - let appGroup = MacPacketTunnelProvider.subscriptionsAppGroup - let subscriptionUserDefaults = UserDefaults(suiteName: appGroup)! + let subscriptionUserDefaults = UserDefaults(suiteName: MacPacketTunnelProvider.subscriptionsAppGroup)! let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter) let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore) @@ -430,8 +429,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { Logger.networkProtection.log("Subscription environment: \(subscriptionEnvironment.description, privacy: .public)") - let subscriptionManager = DefaultSubscriptionManager(appGroup: appGroup, - keychainType: Bundle.keychainType, + let subscriptionManager = DefaultSubscriptionManager(keychainType: Bundle.keychainType, // note: the old public static let tokenStoreService = "com.duckduckgo.networkprotection.authToken" was used as kSecAttrService in NetworkProtectionKeychainStore, now is different. the old token recovery will not work in the extension, yes in main app userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index cb473fbd3f..c257f8cd7c 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -26,8 +26,7 @@ import os.log extension DefaultSubscriptionManager { // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root - public convenience init(appGroup: String?, - keychainType: KeychainType, + public convenience init(keychainType: KeychainType, userDefault: UserDefaults, environment: SubscriptionEnvironment) { diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 84738f6070..6ab87965cd 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -72,8 +72,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - self.subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, - keychainType: .dataProtection(.named(subscriptionAppGroup)), + self.subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 4cdf8092b8..d17fa4f493 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -50,10 +50,11 @@ final class DuckDuckGoVPNApplication: NSApplication { } // MARK: - Configure Subscription - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let appGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: appGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - self.subscriptionManager = DefaultSubscriptionManager(appGroup: subscriptionAppGroup, + + self.subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(appGroup)), userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) From 92014b45ca1d14dfe05710592d08a89fd4f86f9c Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 26 Nov 2024 12:27:13 +0000 Subject: [PATCH 10/59] init improvements --- DuckDuckGo/Application/AppDelegate.swift | 1 - .../MacPacketTunnelProvider.swift | 9 ++++----- .../SubscriptionManager+StandardConfiguration.swift | 5 ++--- .../DuckDuckGoDBPBackgroundAgentAppDelegate.swift | 1 - DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 1 - 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index b8b785040a..3d05d296fd 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -281,7 +281,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), - userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { return WindowControllersManager.shared diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index c3e25c067b..2301a71194 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -412,25 +412,24 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { NetworkProtectionLastVersionRunStore(userDefaults: defaults).lastExtensionVersionRun = AppVersion.shared.versionAndBuildNumber let settings = VPNSettings(defaults: defaults) - // MARK: - Configure Subscription - let subscriptionUserDefaults = UserDefaults(suiteName: MacPacketTunnelProvider.subscriptionsAppGroup)! let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter) let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore) + // MARK: - Configure Subscription + // Align Subscription environment to the VPN environment var subscriptionEnvironment = SubscriptionEnvironment.default - switch settings.selectedEnvironment { + switch settings.selectedEnvironment { // we don't care about the purchasePlatform case .production: subscriptionEnvironment.serviceEnvironment = .production case .staging: subscriptionEnvironment.serviceEnvironment = .staging } - Logger.networkProtection.log("Subscription environment: \(subscriptionEnvironment.description, privacy: .public)") + Logger.networkProtection.debug("Subscription ServiceEnvironment: \(subscriptionEnvironment.serviceEnvironment.rawValue, privacy: .public)") let subscriptionManager = DefaultSubscriptionManager(keychainType: Bundle.keychainType, // note: the old public static let tokenStoreService = "com.duckduckgo.networkprotection.authToken" was used as kSecAttrService in NetworkProtectionKeychainStore, now is different. the old token recovery will not work in the extension, yes in main app - userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) let entitlementsCheck: (() async -> Result) = { diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index c257f8cd7c..2583e47bee 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -27,7 +27,6 @@ extension DefaultSubscriptionManager { // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root public convenience init(keychainType: KeychainType, - userDefault: UserDefaults, environment: SubscriptionEnvironment) { let configuration = URLSessionConfiguration.default @@ -41,10 +40,10 @@ extension DefaultSubscriptionManager { let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: keychainType) - let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: keychainType) +// let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: keychainType) let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, - legacyTokenStorage: legacyAccountStorage, +// legacyTokenStorage: legacyAccountStorage, authService: authService) apiService.authorizationRefresherCallback = { _ in diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 6ab87965cd..d1598a55a6 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -73,7 +73,6 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) self.subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), - userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index d17fa4f493..b4c8bcdc15 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -55,7 +55,6 @@ final class DuckDuckGoVPNApplication: NSApplication { let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) self.subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(appGroup)), - userDefault: subscriptionUserDefaults, environment: subscriptionEnvironment) _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) super.init() From 09eee507984012b79a9157006675299282d52af9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 26 Nov 2024 13:51:32 +0000 Subject: [PATCH 11/59] codablehelper --- .../NewTabPage/Favorites/NewTabPageFavoritesClient.swift | 8 ++++---- DuckDuckGo/NewTabPage/NewTabPageConfigurationClient.swift | 4 ++-- DuckDuckGo/NewTabPage/NewTabPageRMFClient.swift | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/NewTabPage/Favorites/NewTabPageFavoritesClient.swift b/DuckDuckGo/NewTabPage/Favorites/NewTabPageFavoritesClient.swift index c76f0ce9ad..caf9daeae0 100644 --- a/DuckDuckGo/NewTabPage/Favorites/NewTabPageFavoritesClient.swift +++ b/DuckDuckGo/NewTabPage/Favorites/NewTabPageFavoritesClient.swift @@ -83,7 +83,7 @@ final class NewTabPageFavoritesClient: NewTabPageScriptClient { @MainActor func setConfig(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let config: NewTabPageUserScript.WidgetConfig = DecodableHelper.decode(from: params) else { + guard let config: NewTabPageUserScript.WidgetConfig = CodableHelper.decode(from: params) else { return nil } favoritesModel.isViewExpanded = config.expansion == .expanded @@ -115,7 +115,7 @@ final class NewTabPageFavoritesClient: NewTabPageScriptClient { @MainActor func move(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let action: NewTabPageFavoritesClient.FavoritesMoveAction = DecodableHelper.decode(from: params) else { + guard let action: NewTabPageFavoritesClient.FavoritesMoveAction = CodableHelper.decode(from: params) else { return nil } favoritesModel.moveFavorite(withID: action.id, fromIndex: action.fromIndex, toIndex: action.targetIndex) @@ -124,7 +124,7 @@ final class NewTabPageFavoritesClient: NewTabPageScriptClient { @MainActor func open(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let action: NewTabPageFavoritesClient.FavoritesOpenAction = DecodableHelper.decode(from: params) else { + guard let action: NewTabPageFavoritesClient.FavoritesOpenAction = CodableHelper.decode(from: params) else { return nil } favoritesModel.openFavorite(withURL: action.url) @@ -133,7 +133,7 @@ final class NewTabPageFavoritesClient: NewTabPageScriptClient { @MainActor func openContextMenu(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let contextMenuAction: NewTabPageFavoritesClient.FavoritesContextMenuAction = DecodableHelper.decode(from: params) else { + guard let contextMenuAction: NewTabPageFavoritesClient.FavoritesContextMenuAction = CodableHelper.decode(from: params) else { return nil } favoritesModel.showContextMenu(for: contextMenuAction.id) diff --git a/DuckDuckGo/NewTabPage/NewTabPageConfigurationClient.swift b/DuckDuckGo/NewTabPage/NewTabPageConfigurationClient.swift index 8eb5c7b7d8..5d1168fb8f 100644 --- a/DuckDuckGo/NewTabPage/NewTabPageConfigurationClient.swift +++ b/DuckDuckGo/NewTabPage/NewTabPageConfigurationClient.swift @@ -82,7 +82,7 @@ final class NewTabPageConfigurationClient: NewTabPageScriptClient { @MainActor private func showContextMenu(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let params: NewTabPageUserScript.ContextMenuParams = DecodableHelper.decode(from: params) else { return nil } + guard let params: NewTabPageUserScript.ContextMenuParams = CodableHelper.decode(from: params) else { return nil } let menu = NSMenu() @@ -146,7 +146,7 @@ final class NewTabPageConfigurationClient: NewTabPageScriptClient { @MainActor private func widgetsSetConfig(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let widgetConfigs: [NewTabPageUserScript.NewTabPageConfiguration.WidgetConfig] = DecodableHelper.decode(from: params) else { + guard let widgetConfigs: [NewTabPageUserScript.NewTabPageConfiguration.WidgetConfig] = CodableHelper.decode(from: params) else { return nil } for widgetConfig in widgetConfigs { diff --git a/DuckDuckGo/NewTabPage/NewTabPageRMFClient.swift b/DuckDuckGo/NewTabPage/NewTabPageRMFClient.swift index b68373dca3..3be1fb4edd 100644 --- a/DuckDuckGo/NewTabPage/NewTabPageRMFClient.swift +++ b/DuckDuckGo/NewTabPage/NewTabPageRMFClient.swift @@ -80,7 +80,7 @@ final class NewTabPageRMFClient: NewTabPageScriptClient { } private func dismiss(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let remoteMessageParams: NewTabPageUserScript.RemoteMessageParams = DecodableHelper.decode(from: params), + guard let remoteMessageParams: NewTabPageUserScript.RemoteMessageParams = CodableHelper.decode(from: params), remoteMessageParams.id == remoteMessageProvider.remoteMessage?.id else { return nil @@ -91,7 +91,7 @@ final class NewTabPageRMFClient: NewTabPageScriptClient { } private func primaryAction(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let remoteMessageParams: NewTabPageUserScript.RemoteMessageParams = DecodableHelper.decode(from: params), + guard let remoteMessageParams: NewTabPageUserScript.RemoteMessageParams = CodableHelper.decode(from: params), remoteMessageParams.id == remoteMessageProvider.remoteMessage?.id else { return nil @@ -111,7 +111,7 @@ final class NewTabPageRMFClient: NewTabPageScriptClient { } private func secondaryAction(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let remoteMessageParams: NewTabPageUserScript.RemoteMessageParams = DecodableHelper.decode(from: params), + guard let remoteMessageParams: NewTabPageUserScript.RemoteMessageParams = CodableHelper.decode(from: params), remoteMessageParams.id == remoteMessageProvider.remoteMessage?.id else { return nil From ffb9ecd11dea6fc1e3c785198d0189dfbcabc6fe Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 26 Nov 2024 14:18:50 +0000 Subject: [PATCH 12/59] it works! --- .../NetworkExtensionTargets/MacPacketTunnelProvider.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 2301a71194..c02e1c6237 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -420,12 +420,13 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // Align Subscription environment to the VPN environment var subscriptionEnvironment = SubscriptionEnvironment.default - switch settings.selectedEnvironment { // we don't care about the purchasePlatform + switch settings.selectedEnvironment { case .production: subscriptionEnvironment.serviceEnvironment = .production case .staging: subscriptionEnvironment.serviceEnvironment = .staging } + subscriptionEnvironment.purchasePlatform = .stripe // we don't care about the purchasePlatform Logger.networkProtection.debug("Subscription ServiceEnvironment: \(subscriptionEnvironment.serviceEnvironment.rawValue, privacy: .public)") From dcccf44c95ace5a2d39b680ea95458cb5568fda3 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 27 Nov 2024 12:14:08 +0000 Subject: [PATCH 13/59] fixing unit tests --- .../xcshareddata/swiftpm/Package.resolved | 36 +-- ...scriptionPagesUseSubscriptionFeature.swift | 4 +- .../DataBrokerProtection/Package.swift | 2 + ...ProtectionAuthenticationManagerTests.swift | 79 +---- ...UseSubscriptionFeatureForStripeTests.swift | 87 +----- ...tionPagesUseSubscriptionFeatureTests.swift | 283 +++++------------- 6 files changed, 121 insertions(+), 370 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d9ae8c8be6..0c9dab4171 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "revision" : "deacf613553334f35053a2092d4e062e4775544c", + "version" : "211.1.2" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -63,15 +72,6 @@ "version" : "6.0.1" } }, - { - "identity" : "jwt-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/jwt-kit.git", - "state" : { - "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", - "version" : "4.13.4" - } - }, { "identity" : "lottie-spm", "kind" : "remoteSourceControl", @@ -135,24 +135,6 @@ "version" : "1.4.0" } }, - { - "identity" : "swift-asn1", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-asn1.git", - "state" : { - "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "06dc63c6d8da54ee11ceb268cde1fa68161afc96", - "version" : "3.9.1" - } - }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index 3d8a9b58dd..9dd1a72abb 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -167,11 +167,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) + _ = try? await subscriptionManager.getTokenContainer(policy: .localForceRefresh) DispatchQueue.main.async { [weak self] in self?.notificationCenter.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) } - return nil } @@ -230,7 +229,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { return nil } - let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, storePurchaseManager: subscriptionManager.storePurchaseManager()) diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index a7e31972da..8cee7d6b07 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -59,6 +59,8 @@ let package = Package( "DataBrokerProtection", "BrowserServicesKit", "Freemium", + .product(name: "TestUtils", package: "BrowserServicesKit"), + .product(name: "SubscriptionTestingUtilities", package: "BrowserServicesKit"), ], resources: [ .copy("Resources") diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift index b0603c8399..5677e286f6 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift @@ -18,15 +18,19 @@ import XCTest @testable import DataBrokerProtection +import Subscription +import SubscriptionTestingUtilities +import Networking +import TestUtils class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { var authenticationManager: DataBrokerProtectionAuthenticationManager! var redeemUseCase: DataBrokerProtectionRedeemUseCase! - var subscriptionManager: MockDataBrokerProtectionSubscriptionManaging! + var subscriptionManager: SubscriptionManagerMock! //MockDataBrokerProtectionSubscriptionManaging! override func setUp() async throws { redeemUseCase = MockRedeemUseCase() - subscriptionManager = MockDataBrokerProtectionSubscriptionManaging() + subscriptionManager = SubscriptionManagerMock() } override func tearDown() async throws { @@ -36,7 +40,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testUserNotAuthenticatedWhenSubscriptionManagerReturnsFalse() { - subscriptionManager.userAuthenticatedValue = false + subscriptionManager.isUserAuthenticated = false authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) @@ -45,7 +49,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testEmptyAccessTokenResultsInNilAuthHeader() { - subscriptionManager.accessTokenValue = nil + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) @@ -54,7 +58,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testUserAuthenticatedWhenSubscriptionManagerReturnsTrue() { - subscriptionManager.userAuthenticatedValue = true + subscriptionManager.isUserAuthenticated = true authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) @@ -63,8 +67,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testNonEmptyAccessTokenResultsInValidAuthHeader() { - let accessToken = "validAccessToken" - subscriptionManager.accessTokenValue = accessToken + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) @@ -73,69 +76,19 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testValidEntitlementCheckWithSuccess() async { - subscriptionManager.entitlementResultValue = true - + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - do { - let result = try await authenticationManager.hasValidEntitlement() - XCTAssertTrue(result, "Entitlement check should return true for valid entitlement") - } catch { - XCTFail("Entitlement check should not fail: \(error)") - } + let result = authenticationManager.hasValidEntitlement() + XCTAssertTrue(result, "Entitlement check should return true for valid entitlement") } func testValidEntitlementCheckWithSuccessFalse() async { - subscriptionManager.entitlementResultValue = false - + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - do { - let result = try await authenticationManager.hasValidEntitlement() - XCTAssertFalse(result, "Entitlement check should return false for valid entitlement") - } catch { - XCTFail("Entitlement check should not fail: \(error)") - } - } - - func testValidEntitlementCheckWithFailure() async { - let mockError = NSError(domain: "TestErrorDomain", code: 123, userInfo: nil) - subscriptionManager.entitlementError = mockError - - authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, - subscriptionManager: subscriptionManager) - - do { - _ = try await authenticationManager.hasValidEntitlement() - XCTFail("Entitlement check should fail") - } catch let error as NSError { - XCTAssertEqual(mockError.domain, error.domain) - XCTAssertEqual(mockError.code, error.code) - } - } -} - -final class MockDataBrokerProtectionSubscriptionManaging: DataBrokerProtectionSubscriptionManaging { - typealias EntitlementResult = Result - - var userAuthenticatedValue = false - var accessTokenValue: String? - var entitlementResultValue = false - var entitlementError: Error? - - var isUserAuthenticated: Bool { - userAuthenticatedValue - } - - var accessToken: String? { - accessTokenValue - } - - func hasValidEntitlement() async throws -> Bool { - if let error = entitlementError { - throw error - } - return entitlementResultValue + let result = authenticationManager.hasValidEntitlement() + XCTAssertFalse(result, "Entitlement check should return false for valid entitlement") } } diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index 197d222657..ffaa1180a6 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -85,29 +85,16 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { var uiHandler: SubscriptionUIHandlerMock! var pixelKit: PixelKit! - var accountStorage: AccountKeychainStorageMock! - var accessTokenStorage: SubscriptionTokenKeychainStorageMock! -// var entitlementsCache: UserDefaultsCache<[Entitlement]>! - - var subscriptionService: SubscriptionEndpointServiceMock! -// var authService: AuthEndpointServiceMock! + var subscriptionManager: SubscriptionManagerMock! var storePurchaseManager: StorePurchaseManagerMock! var subscriptionEnvironment: SubscriptionEnvironment! - var appStorePurchaseFlow: AppStorePurchaseFlow! var appStoreRestoreFlow: AppStoreRestoreFlow! -// var appStoreAccountManagementFlow: AppStoreAccountManagementFlow! var stripePurchaseFlow: StripePurchaseFlow! - var subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock! - -// var accountManager: AccountManager! - var subscriptionManager: SubscriptionManager! var mockFreemiumDBPExperimentManager: MockFreemiumDBPExperimentManager! - var feature: SubscriptionPagesUseSubscriptionFeature! - var pixelsFired: [String] = [] var uiEventsHappened: [SubscriptionUIHandlerMock.UIHandlerMockPerformedAction] = [] @@ -129,57 +116,22 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { self.uiEventsHappened.append(action) } - subscriptionService = SubscriptionEndpointServiceMock() - authService = AuthEndpointServiceMock() - storePurchaseManager = StorePurchaseManagerMock() subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .stripe) - accountStorage = AccountKeychainStorageMock() - accessTokenStorage = SubscriptionTokenKeychainStorageMock() - - entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: userDefaults, - key: UserDefaultsCacheKey.subscriptionEntitlements, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - - // Real AccountManager - accountManager = DefaultAccountManager(storage: accountStorage, - accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService) - - // Real Flows - appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: accountManager, - storePurchaseManager: storePurchaseManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService) - - appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionService, - storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: authService) - appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: authService, - storePurchaseManager: storePurchaseManager, - accountManager: accountManager) + subscriptionManager = SubscriptionManagerMock() - stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - accountManager: accountManager) + appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager) + appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager, + appStoreRestoreFlow: appStoreRestoreFlow) + stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionManager: subscriptionManager) subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock(isFeatureAvailable: true, isSubscriptionPurchaseAllowed: true, usesUnifiedFeedbackForm: false) - - // Real SubscriptionManager - subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - subscriptionEnvironment: subscriptionEnvironment) - mockFreemiumDBPExperimentManager = MockFreemiumDBPExperimentManager() feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, @@ -194,30 +146,13 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { userDefaults = nil pixelsFired.removeAll() uiEventsHappened.removeAll() - - subscriptionService = nil - authService = nil storePurchaseManager = nil subscriptionEnvironment = nil - - accountStorage = nil - accessTokenStorage = nil - - entitlementsCache.reset() - entitlementsCache = nil - - accountManager = nil - - // Real Flows appStorePurchaseFlow = nil appStoreRestoreFlow = nil - appStoreAccountManagementFlow = nil stripePurchaseFlow = nil - subscriptionFeatureAvailability = nil - subscriptionManager = nil - feature = nil } @@ -226,7 +161,7 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { func testGetSubscriptionOptionsSuccess() async throws { // Given XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - subscriptionService.getProductsResult = .success(Constants.productItems) + subscriptionManager.productsResponse = .success(Constants.productItems) // When let result = try await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) @@ -241,7 +176,7 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { func testGetSubscriptionOptionsReturnsEmptyOptionsWhenNoSubscriptionOptions() async throws { // Given XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - subscriptionService.getProductsResult = .success([]) + subscriptionManager.productsResponse = .success([]) storePurchaseManager.subscriptionOptionsResult = nil // When @@ -256,7 +191,7 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { func testGetSubscriptionOptionsReturnsEmptyOptionsWhenSubscriptionOptionsDidNotFetch() async throws { // Given XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - subscriptionService.getProductsResult = .failure(Constants.invalidTokenError) + subscriptionManager.productsResponse = .failure(Constants.invalidTokenError) storePurchaseManager.subscriptionOptionsResult = nil // When diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 65d57d5dd6..0e3277fa9d 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -27,25 +27,21 @@ import UserScript import PixelKitTestingUtilities import os.log import DataBrokerProtection +import Networking +import TestUtils @available(macOS 12.0, *) final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { private struct Constants { static let userDefaultsSuiteName = "SubscriptionPagesUseSubscriptionFeatureTests" - - static let authToken = UUID().uuidString - static let accessToken = UUID().uuidString static let externalID = UUID().uuidString - static let email = "dax@duck.com" - - static let entitlements = [Entitlement(product: .dataBrokerProtection), - Entitlement(product: .identityTheftRestoration), - Entitlement(product: .networkProtection)] + static let entitlements: [SubscriptionEntitlement] = [.dataBrokerProtection, + .identityTheftRestoration, + .networkProtection] static let mostRecentTransactionJWS = "dGhpcyBpcyBub3QgYSByZWFsIEFw(...)cCBTdG9yZSB0cmFuc2FjdGlvbiBKV1M=" - static let subscriptionOptions = SubscriptionOptions(platform: SubscriptionPlatformName.ios.rawValue, options: [ SubscriptionOption(id: "1", @@ -58,52 +54,30 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { SubscriptionFeature(name: "personal-information-removal"), SubscriptionFeature(name: "identity-theft-restoration") ]) - - static let validateTokenResponse = ValidateTokenResponse(account: ValidateTokenResponse.Account(email: Constants.email, - entitlements: Constants.entitlements, - externalID: Constants.externalID)) - static let mockParams: [String: String] = [:] @MainActor static let mockScriptMessage = MockWKScriptMessage(name: "", body: "", webView: WKWebView() ) - - static let invalidTokenError = APIServiceError.serverError(statusCode: 401, error: "invalid_token") } var userDefaults: UserDefaults! var broker: UserScriptMessageBroker = UserScriptMessageBroker(context: "testBroker") var uiHandler: SubscriptionUIHandlerMock! var pixelKit: PixelKit! - - var accountStorage: AccountKeychainStorageMock! - var accessTokenStorage: SubscriptionTokenKeychainStorageMock! - var entitlementsCache: UserDefaultsCache<[Entitlement]>! - - var subscriptionService: SubscriptionEndpointServiceMock! - var authService: AuthEndpointServiceMock! - var storePurchaseManager: StorePurchaseManagerMock! var subscriptionEnvironment: SubscriptionEnvironment! - var appStorePurchaseFlow: AppStorePurchaseFlow! var appStoreRestoreFlow: AppStoreRestoreFlow! - var appStoreAccountManagementFlow: AppStoreAccountManagementFlow! var stripePurchaseFlow: StripePurchaseFlow! - var subscriptionAttributionPixelHandler: SubscriptionAttributionPixelHandler! - var subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock! - - var accountManager: AccountManager! - var subscriptionManager: SubscriptionManager! + var subscriptionManager: SubscriptionManagerMock! var mockFreemiumDBPExperimentManager: MockFreemiumDBPExperimentManager! private var mockPixelHandler: MockFreemiumDBPExperimentPixelHandler! private var mockFreemiumDBPUserStateManager: MockFreemiumDBPUserStateManager! - - var feature: SubscriptionPagesUseSubscriptionFeature! - var pixelsFired: [String] = [] var uiEventsHappened: [SubscriptionUIHandlerMock.UIHandlerMockPerformedAction] = [] + var feature: SubscriptionPagesUseSubscriptionFeature! + @MainActor override func setUpWithError() throws { // Mocks userDefaults = UserDefaults(suiteName: Constants.userDefaultsSuiteName)! @@ -122,63 +96,22 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { self.uiEventsHappened.append(action) } - subscriptionService = SubscriptionEndpointServiceMock() - authService = AuthEndpointServiceMock() - storePurchaseManager = StorePurchaseManagerMock() subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) - accountStorage = AccountKeychainStorageMock() - accessTokenStorage = SubscriptionTokenKeychainStorageMock() - - entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: userDefaults, - key: UserDefaultsCacheKey.subscriptionEntitlements, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - - // Real AccountManager - accountManager = DefaultAccountManager(storage: accountStorage, - accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService) - - // Real Flows - appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: accountManager, - storePurchaseManager: storePurchaseManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService) - - appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionService, + appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager) + appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: authService) - - appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: authService, - storePurchaseManager: storePurchaseManager, - accountManager: accountManager) - - stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - accountManager: accountManager) - + appStoreRestoreFlow: appStoreRestoreFlow) + stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionManager: subscriptionManager) subscriptionAttributionPixelHandler = PrivacyProSubscriptionAttributionPixelHandler() - subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock(isFeatureAvailable: true, isSubscriptionPurchaseAllowed: true, usesUnifiedFeedbackForm: false) - - // Real SubscriptionManager - subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - subscriptionEnvironment: subscriptionEnvironment) - mockFreemiumDBPExperimentManager = MockFreemiumDBPExperimentManager() mockPixelHandler = MockFreemiumDBPExperimentPixelHandler() mockFreemiumDBPUserStateManager = MockFreemiumDBPUserStateManager() - feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, subscriptionSuccessPixelHandler: subscriptionAttributionPixelHandler, stripePurchaseFlow: stripePurchaseFlow, @@ -194,30 +127,13 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { userDefaults = nil pixelsFired.removeAll() uiEventsHappened.removeAll() - - subscriptionService = nil - authService = nil storePurchaseManager = nil subscriptionEnvironment = nil - - accountStorage = nil - accessTokenStorage = nil - - entitlementsCache.reset() - entitlementsCache = nil - - accountManager = nil - - // Real Flows appStorePurchaseFlow = nil appStoreRestoreFlow = nil - appStoreAccountManagementFlow = nil stripePurchaseFlow = nil - subscriptionFeatureAvailability = nil - subscriptionManager = nil - feature = nil } @@ -226,16 +142,14 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testGetSubscriptionSuccessWithoutRefreshingAuthToken() async throws { // Given ensureUserAuthenticatedState() - - authService.validateTokenResult = .success(Constants.validateTokenResponse) + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When let result = try await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) // Then let subscription = try XCTUnwrap(result as? SubscriptionPagesUseSubscriptionFeature.Subscription) - XCTAssertEqual(subscription.token, Constants.authToken) - XCTAssertEqual(accountManager.authToken, Constants.authToken) + XCTAssertEqual(subscription.token, subscriptionManager.resultTokenContainer?.accessToken) XCTAssertPrivacyPixelsFired([]) } @@ -243,7 +157,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() - authService.validateTokenResult = .failure(Constants.invalidTokenError) + subscriptionManager.resultTokenContainer = nil storePurchaseManager.mostRecentTransactionResult = nil // When @@ -252,7 +166,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Then let subscription = try XCTUnwrap(result as? SubscriptionPagesUseSubscriptionFeature.Subscription) XCTAssertEqual(subscription.token, "") - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertPrivacyPixelsFired([]) } @@ -261,19 +175,15 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testSetSubscriptionSuccess() async throws { // Given ensureUserUnauthenticatedState() - - authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) + subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When - let setSubscriptionParams = ["token": Constants.authToken] + let setSubscriptionParams = ["token": subscriptionManager.resultTokenContainer!.accessToken] let result = try await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) // Then - XCTAssertEqual(accountManager.authToken, Constants.authToken) - XCTAssertEqual(accountManager.accessToken, Constants.accessToken) - XCTAssertEqual(accountManager.email, Constants.email) - XCTAssertEqual(accountManager.externalID, Constants.externalID) + let tokens = try await subscriptionManager.getTokenContainer(policy: .local) + XCTAssertEqual(tokens, subscriptionManager.resultExchangeTokenContainer) XCTAssertNil(result) XCTAssertPrivacyPixelsFired([PrivacyProPixel.privacyProRestorePurchaseEmailSuccess.name + "_d", PrivacyProPixel.privacyProRestorePurchaseEmailSuccess.name + "_c"]) @@ -282,35 +192,16 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testSetSubscriptionErrorWhenFailedToExchangeToken() async throws { // Given ensureUserUnauthenticatedState() - - authService.getAccessTokenResult = .failure(Constants.invalidTokenError) - - // When - let setSubscriptionParams = ["token": Constants.authToken] - let result = try await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertNil(accountManager.authToken) - XCTAssertFalse(accountManager.isUserAuthenticated) - XCTAssertNil(result) - XCTAssertPrivacyPixelsFired([PrivacyProPixel.privacyProRestorePurchaseEmailSuccess.name + "_d", - PrivacyProPixel.privacyProRestorePurchaseEmailSuccess.name + "_c"]) - } - - func testSetSubscriptionErrorWhenFailedToFetchAccountDetails() async throws { - // Given - ensureUserUnauthenticatedState() - - authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) - authService.validateTokenResult = .failure(Constants.invalidTokenError) + subscriptionManager.resultExchangeTokenContainer = nil // When - let setSubscriptionParams = ["token": Constants.authToken] + let setSubscriptionParams = ["token": "sometoken"] let result = try await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) // Then - XCTAssertNil(accountManager.authToken) - XCTAssertFalse(accountManager.isUserAuthenticated) + let tokens = try? await subscriptionManager.getTokenContainer(policy: .local) + XCTAssertNil(tokens) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertNil(result) XCTAssertPrivacyPixelsFired([PrivacyProPixel.privacyProRestorePurchaseEmailSuccess.name + "_d", PrivacyProPixel.privacyProRestorePurchaseEmailSuccess.name + "_c"]) @@ -321,32 +212,25 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testBackToSettingsSuccess() async throws { // Given ensureUserAuthenticatedState() - accountStorage.email = nil - - XCTAssertNil(accountManager.email) + XCTAssertNil(subscriptionManager.userEmail) let notificationPostedExpectation = expectation(forNotification: .subscriptionPageCloseAndOpenPreferences, object: nil) - authService.validateTokenResult = .success(Constants.validateTokenResponse) - // When let result = try await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) // Then await fulfillment(of: [notificationPostedExpectation], timeout: 1) - XCTAssertEqual(accountManager.email, Constants.email) XCTAssertNil(result) XCTAssertPrivacyPixelsFired([]) } func testBackToSettingsErrorOnFetchingAccountDetails() async throws { // Given - ensureUserAuthenticatedState() + ensureUserUnauthenticatedState() let notificationPostedExpectation = expectation(forNotification: .subscriptionPageCloseAndOpenPreferences, object: nil) - authService.validateTokenResult = .failure(Constants.invalidTokenError) - // When let result = try await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) @@ -408,20 +292,19 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, - externalID: Constants.externalID, - status: "created")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) +// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, +// externalID: Constants.externalID, +// status: "created")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// subscription: SubscriptionMockFactory.subscription)) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -445,20 +328,21 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { mockFreemiumDBPExperimentManager.pixelParameters = ["daysEnrolled": "1"] ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, - externalID: Constants.externalID, - status: "created")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) - storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) + // TODO: re-mock everything +// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, +// externalID: Constants.externalID, +// status: "created")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) +// storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// entitlements: Constants.entitlements, +// subscription: SubscriptionMockFactory.subscription)) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -482,30 +366,31 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserAuthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertTrue(accountManager.isUserAuthenticated) + XCTAssertTrue(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) - authService.storeLoginResult = .success(StoreLoginResponse(authToken: Constants.authToken, - email: Constants.email, - externalID: Constants.externalID, - id: 1, - status: "authenticated")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) - storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) + // TODO: re-mock everything +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) +// authService.storeLoginResult = .success(StoreLoginResponse(authToken: Constants.authToken, +// email: Constants.email, +// externalID: Constants.externalID, +// id: 1, +// status: "authenticated")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) +// storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// entitlements: Constants.entitlements, +// subscription: SubscriptionMockFactory.subscription)) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] let result = try await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) // Then - XCTAssertFalse(authService.createAccountCalled) +// XCTAssertFalse(authService.createAccountCalled) XCTAssertEqual(uiEventsHappened, [.didPresentProgressViewController, .didUpdateProgressViewController, .didDismissProgressViewController]) @@ -522,21 +407,21 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserAuthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertTrue(accountManager.isUserAuthenticated) + XCTAssertTrue(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// entitlements: Constants.entitlements, +// subscription: SubscriptionMockFactory.subscription)) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] let result = try await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) // Then - XCTAssertFalse(authService.createAccountCalled) +// XCTAssertFalse(authService.createAccountCalled) XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) XCTAssertEqual(uiEventsHappened, [.didPresentProgressViewController, .didUpdateProgressViewController, @@ -584,14 +469,14 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = true await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS - authService.storeLoginResult = .success(StoreLoginResponse(authToken: Constants.authToken, - email: Constants.email, - externalID: Constants.externalID, - id: 1, - status: "authenticated")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) +// authService.storeLoginResult = .success(StoreLoginResponse(authToken: Constants.authToken, +// email: Constants.email, +// externalID: Constants.externalID, +// id: 1, +// status: "authenticated")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -622,7 +507,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .failure(Constants.invalidTokenError) +// authService.createAccountResult = .failure(Constants.invalidTokenError) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) // When @@ -651,7 +536,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseCancelledByUser) let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -672,7 +557,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.productNotFound) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) @@ -1169,15 +1054,11 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { extension SubscriptionPagesUseSubscriptionFeatureTests { func ensureUserAuthenticatedState() { - accountStorage.authToken = Constants.authToken - accountStorage.email = Constants.email - accountStorage.externalID = Constants.externalID - accessTokenStorage.accessToken = Constants.accessToken + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() } func ensureUserUnauthenticatedState() { - try? accessTokenStorage.removeAccessToken() - try? accountStorage.clearAuthenticationState() + subscriptionManager.resultTokenContainer = nil } public func XCTAssertPrivacyPixelsFired(_ pixels: [String], file: StaticString = #file, line: UInt = #line) { From 62ddb64e8490c68495bd83fc866de0d20b5ce1b8 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 28 Nov 2024 12:25:52 +0000 Subject: [PATCH 14/59] unit tests fixes --- DuckDuckGo.xcodeproj/project.pbxproj | 14 --- .../xcshareddata/swiftpm/Package.resolved | 36 ++++++-- ...ataBrokerProtectionFeatureGatekeeper.swift | 2 +- .../UnifiedFeedbackFormViewController.swift | 2 + .../UnifiedFeedbackFormViewModel.swift | 8 +- ...ProtectionAuthenticationManagerTests.swift | 4 +- .../DBP/Mocks/DataBrokerProtectionMocks.swift | 79 ----------------- ...okerProtectionFeatureGatekeeperTests.swift | 33 +++---- ...emiumDBPPixelExperimentManagingTests.swift | 20 ++--- .../DBP/FreemiumDBPFeatureTests.swift | 52 ++--------- ...iumDBPFirstProfileSavedNotifierTests.swift | 13 ++- UnitTests/Menus/MoreOptionsMenuTests.swift | 27 ++---- .../LocalPinningManagerTests.swift | 11 +-- .../SubscriptionAppStoreRestorerTests.swift | 31 +------ ...UseSubscriptionFeatureForStripeTests.swift | 37 ++++---- ...tionPagesUseSubscriptionFeatureTests.swift | 86 +++++++++---------- .../UnifiedFeedbackFormViewModelTests.swift | 52 ++++++++--- 17 files changed, 180 insertions(+), 327 deletions(-) delete mode 100644 UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1f6a774213..4a071dea71 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2980,8 +2980,6 @@ C1CE846A2C887CF60068913B /* FreemiumDBPScanResultPolling.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CE84682C887CF60068913B /* FreemiumDBPScanResultPolling.swift */; }; C1CE846C2C88868D0068913B /* FreemiumDBPScanResultPollingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CE846B2C88868D0068913B /* FreemiumDBPScanResultPollingTests.swift */; }; C1CE846D2C88868D0068913B /* FreemiumDBPScanResultPollingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CE846B2C88868D0068913B /* FreemiumDBPScanResultPollingTests.swift */; }; - C1D8BE452C1739E70057E426 /* DataBrokerProtectionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D8BE442C1739E70057E426 /* DataBrokerProtectionMocks.swift */; }; - C1D8BE462C1739EC0057E426 /* DataBrokerProtectionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D8BE442C1739E70057E426 /* DataBrokerProtectionMocks.swift */; }; C1DAF3B52B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */; }; C1DAF3B62B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */; }; C1E961EB2B879E79001760E1 /* MockAutofillActionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E961E72B879E4D001760E1 /* MockAutofillActionPresenter.swift */; }; @@ -4882,7 +4880,6 @@ C1C405862C7F80E50089DE8A /* PromotionView+FreemiumDBP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PromotionView+FreemiumDBP.swift"; sourceTree = ""; }; C1CE84682C887CF60068913B /* FreemiumDBPScanResultPolling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreemiumDBPScanResultPolling.swift; sourceTree = ""; }; C1CE846B2C88868D0068913B /* FreemiumDBPScanResultPollingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreemiumDBPScanResultPollingTests.swift; sourceTree = ""; }; - C1D8BE442C1739E70057E426 /* DataBrokerProtectionMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionMocks.swift; sourceTree = ""; }; C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPopoverPresenter.swift; sourceTree = ""; }; C1E961E72B879E4D001760E1 /* MockAutofillActionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutofillActionPresenter.swift; sourceTree = ""; }; C1E961EC2B879ED9001760E1 /* MockAutofillActionExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutofillActionExecutor.swift; sourceTree = ""; }; @@ -5620,7 +5617,6 @@ isa = PBXGroup; children = ( C1D8BE432C1739BF0057E426 /* Tests */, - C1D8BE422C1739BA0057E426 /* Mocks */, ); path = DBP; sourceTree = ""; @@ -9627,14 +9623,6 @@ path = Promotion; sourceTree = ""; }; - C1D8BE422C1739BA0057E426 /* Mocks */ = { - isa = PBXGroup; - children = ( - C1D8BE442C1739E70057E426 /* DataBrokerProtectionMocks.swift */, - ); - path = Mocks; - sourceTree = ""; - }; C1D8BE432C1739BF0057E426 /* Tests */ = { isa = PBXGroup; children = ( @@ -12313,7 +12301,6 @@ B60C6F8229B1B4AD007BFAA8 /* TestRunHelper.swift in Sources */, 567DA94029E8045D008AC5EE /* MockEmailStorage.swift in Sources */, 9FBB0C0A2CBD3B800006B6A6 /* ViewHighlighterTests.swift in Sources */, - C1D8BE462C1739EC0057E426 /* DataBrokerProtectionMocks.swift in Sources */, 317295D32AF058D3002C3206 /* MockWaitlistTermsAndConditionsActionHandler.swift in Sources */, 9F0FFFB52BCCAE37007C87DD /* BookmarkAllTabsDialogCoordinatorViewModelTests.swift in Sources */, 3706FE34293F661700E42796 /* PermissionStoreTests.swift in Sources */, @@ -14038,7 +14025,6 @@ BDA7648D2BC4E4EF00D0400C /* DefaultVPNLocationFormatterTests.swift in Sources */, 85F487B5276A8F2E003CE668 /* OnboardingTests.swift in Sources */, B626A7642992506A00053070 /* SerpHeadersNavigationResponderTests.swift in Sources */, - C1D8BE452C1739E70057E426 /* DataBrokerProtectionMocks.swift in Sources */, 5677A93D2C98414900DA7B0A /* ContextualOnboardingStateMachineTests.swift in Sources */, AA652CCE25DD9071009059CC /* BookmarkListTests.swift in Sources */, 859E7D6D274548F2009C2B69 /* BookmarksExporterTests.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ea1c934f56..1e88f8d86f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,15 +27,6 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", - "state" : { - "revision" : "f83b1f5ebd328bc2447d1a3793149bb21037d685", - "version" : "211.1.3" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -72,6 +63,15 @@ "version" : "6.0.1" } }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", + "version" : "4.13.4" + } + }, { "identity" : "lottie-spm", "kind" : "remoteSourceControl", @@ -135,6 +135,24 @@ "version" : "1.4.0" } }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "ff0f781cf7c6a22d52957e50b104f5768b50c779", + "version" : "3.10.0" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index 281f997ede..189150cc13 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -80,7 +80,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature /// Checks DBP prerequisites /// - /// Prerequisites are satisified if either: + /// Prerequisites are satisfied if either: /// 1. The user is an active freemium user (e.g has activated freemium and is not authenticated) /// 2. The user has a subscription with valid entitlements /// diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift index 314aaad54a..8639325fcb 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift @@ -48,6 +48,8 @@ final class UnifiedFeedbackFormViewController: NSViewController { source: UnifiedFeedbackSource = .default) { self.feedbackSender = feedbackSender self.viewModel = UnifiedFeedbackFormViewModel( + subscriptionTokenProvider: Application.appDelegate.subscriptionManager, + apiService: DefaultAPIService(), vpnMetadataCollector: DefaultVPNMetadataCollector(subscriptionManager: Application.appDelegate.subscriptionManager), feedbackSender: feedbackSender, source: source diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift index 963a5403a6..c47d8dc513 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift @@ -150,7 +150,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { weak var delegate: UnifiedFeedbackFormViewModelDelegate? - private let accountManager: any AccountManager + private let subscriptionTokenProvider: any SubscriptionTokenProvider private let apiService: any Networking.APIService private let vpnMetadataCollector: any UnifiedMetadataCollector private let defaultMetadataCollector: any UnifiedMetadataCollector @@ -158,7 +158,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { let source: UnifiedFeedbackSource - init(accountManager: any AccountManager, + init(subscriptionTokenProvider: any SubscriptionTokenProvider, apiService: any Networking.APIService, vpnMetadataCollector: any UnifiedMetadataCollector, defaultMetadataCollector: any UnifiedMetadataCollector = EmptyMetadataCollector(), @@ -166,7 +166,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { source: UnifiedFeedbackSource = .default) { self.viewState = .feedbackPending - self.accountManager = accountManager + self.subscriptionTokenProvider = subscriptionTokenProvider self.apiService = apiService self.vpnMetadataCollector = vpnMetadataCollector self.defaultMetadataCollector = defaultMetadataCollector @@ -267,7 +267,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { private func submitIssue(metadata: UnifiedFeedbackMetadata?) async throws { guard !userEmail.isEmpty else { return } - guard let accessToken = accountManager.accessToken else { + guard let accessToken = try? await subscriptionTokenProvider.getTokenContainer(policy: .localValid) else { throw Error.missingAccessToken } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift index 5677e286f6..082a1dbc8b 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift @@ -40,7 +40,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testUserNotAuthenticatedWhenSubscriptionManagerReturnsFalse() { - subscriptionManager.isUserAuthenticated = false + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) @@ -58,7 +58,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testUserAuthenticatedWhenSubscriptionManagerReturnsTrue() { - subscriptionManager.isUserAuthenticated = true + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) diff --git a/UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift b/UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift deleted file mode 100644 index bbcd473a1f..0000000000 --- a/UnitTests/DBP/Mocks/DataBrokerProtectionMocks.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// DataBrokerProtectionMocks.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 -@testable import DuckDuckGo_Privacy_Browser - -final class MockAccountManager: AccountManager { - var hasEntitlementResult: Result = .success(true) - - var delegate: AccountManagerKeychainAccessDelegate? - - var isUserAuthenticated = false - - var accessToken: String? = "" - - var authToken: String? - - var email: String? - - var externalID: String? - - func storeAuthToken(token: String) { - } - - func storeAccount(token: String, email: String?, externalID: String?) { - } - - func signOut(skipNotification: Bool) { - } - - func signOut() { - } - - func migrateAccessTokenToNewStore() throws { - } - - func hasEntitlement(forProductName productName: Entitlement.ProductName, cachePolicy: APICachePolicy) async -> Result { - hasEntitlementResult - } - - func hasEntitlement(forProductName productName: Entitlement.ProductName) async -> Result { - hasEntitlementResult - } - - func updateCache(with entitlements: [Entitlement]) { - } - - func fetchEntitlements(cachePolicy: APICachePolicy) async -> Result<[Entitlement], any Error> { - .success([]) - } - - func exchangeAuthTokenToAccessToken(_ authToken: String) async -> Result { - .success("") - } - - func fetchAccountDetails(with accessToken: String) async -> Result { - .success(AccountDetails(email: "", externalID: "")) - } - - func checkForEntitlements(wait waitTime: Double, retry retryCount: Int) async -> Bool { - true - } -} diff --git a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift index f82095119b..6652f9303d 100644 --- a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift +++ b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift @@ -19,6 +19,8 @@ import XCTest import BrowserServicesKit import Subscription +import TestUtils +import SubscriptionTestingUtilities @testable import DuckDuckGo_Privacy_Browser @@ -27,7 +29,7 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { private var sut: DefaultDataBrokerProtectionFeatureGatekeeper! private var mockFeatureDisabler: MockFeatureDisabler! private var mockFeatureAvailability: MockFeatureAvailability! - private var mockAccountManager: MockAccountManager! + private var mockSubscriptionManager: SubscriptionManagerMock! private var mockFreemiumDBPUserStateManager: MockFreemiumDBPUserStateManager! private func userDefaults() -> UserDefaults { @@ -37,19 +39,17 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { override func setUpWithError() throws { mockFeatureDisabler = MockFeatureDisabler() mockFeatureAvailability = MockFeatureAvailability() - mockAccountManager = MockAccountManager() + mockSubscriptionManager = SubscriptionManagerMock() mockFreemiumDBPUserStateManager = MockFreemiumDBPUserStateManager() mockFreemiumDBPUserStateManager.didActivate = false } func testWhenNoAccessTokenIsFound_butEntitlementIs_andIsNotActiveFreemiumUser_thenFeatureIsDisabled() async { // Given - mockAccountManager.accessToken = nil - mockAccountManager.hasEntitlementResult = .success(true) sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), subscriptionAvailability: mockFeatureAvailability, - accountManager: mockAccountManager, + subscriptionManager: mockSubscriptionManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) // When @@ -61,13 +61,12 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { func testWhenAccessTokenIsFound_butNoEntitlementIs_andIsNotActiveFreemiumUser_thenFeatureIsDisabled() async { // Given - mockAccountManager.accessToken = "token" - mockAccountManager.hasEntitlementResult = .failure(MockError.someError) + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() mockFreemiumDBPUserStateManager.didActivate = false sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), subscriptionAvailability: mockFeatureAvailability, - accountManager: mockAccountManager, + subscriptionManager: mockSubscriptionManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) // When @@ -79,13 +78,12 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { func testWhenAccessTokenIsFound_butNoEntitlementIs_andIsActiveFreemiumUser_thenFeatureIsDisabled() async { // Given - mockAccountManager.accessToken = "token" - mockAccountManager.hasEntitlementResult = .failure(MockError.someError) + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() mockFreemiumDBPUserStateManager.didActivate = true sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), subscriptionAvailability: mockFeatureAvailability, - accountManager: mockAccountManager, + subscriptionManager: mockSubscriptionManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) // When @@ -97,13 +95,11 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { func testWhenAccessTokenAndEntitlementAreNotFound_andIsNotActiveFreemiumUser_thenFeatureIsDisabled() async { // Given - mockAccountManager.accessToken = nil - mockAccountManager.hasEntitlementResult = .failure(MockError.someError) mockFreemiumDBPUserStateManager.didActivate = false sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), subscriptionAvailability: mockFeatureAvailability, - accountManager: mockAccountManager, + subscriptionManager: mockSubscriptionManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) // When @@ -115,13 +111,12 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { func testWhenAccessTokenAndEntitlementAreFound_andIsNotActiveFreemiumUser_thenFeatureIsEnabled() async { // Given - mockAccountManager.accessToken = "token" - mockAccountManager.hasEntitlementResult = .success(true) + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() mockFreemiumDBPUserStateManager.didActivate = false sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), subscriptionAvailability: mockFeatureAvailability, - accountManager: mockAccountManager, + subscriptionManager: mockSubscriptionManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) // When @@ -133,13 +128,11 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { func testWhenAccessTokenAndEntitlementAreNotFound_andIsActiveFreemiumUser_thenFeatureIsEnabled() async { // Given - mockAccountManager.accessToken = nil - mockAccountManager.hasEntitlementResult = .failure(MockError.someError) mockFreemiumDBPUserStateManager.didActivate = true sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), subscriptionAvailability: mockFeatureAvailability, - accountManager: mockAccountManager, + subscriptionManager: mockSubscriptionManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) // When diff --git a/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift b/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift index a0f6038443..f65e2697a7 100644 --- a/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift +++ b/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift @@ -20,30 +20,23 @@ import XCTest import SubscriptionTestingUtilities import Subscription @testable import DuckDuckGo_Privacy_Browser +import TestUtils +import SubscriptionTestingUtilities final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { private var sut: FreemiumDBPPixelExperimentManaging! - private var mockAccountManager: MockAccountManager! private var mockSubscriptionManager: SubscriptionManagerMock! private var mockUserDefaults: MockUserDefaults! override func setUp() { super.setUp() - mockAccountManager = MockAccountManager() - let mockSubscriptionService = SubscriptionEndpointServiceMock() - let mockAuthService = AuthEndpointServiceMock() + mockSubscriptionManager = SubscriptionManagerMock() let mockStorePurchaseManager = StorePurchaseManagerMock() - let currentEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) - mockSubscriptionManager = SubscriptionManagerMock(accountManager: mockAccountManager, - subscriptionEndpointService: mockSubscriptionService, - authEndpointService: mockAuthService, - storePurchaseManager: mockStorePurchaseManager, - currentEnvironment: currentEnvironment, - canPurchase: false) + mockSubscriptionManager = SubscriptionManagerMock() mockUserDefaults = MockUserDefaults() let testLocale = Locale(identifier: "en_US") sut = FreemiumDBPPixelExperimentManager(subscriptionManager: mockSubscriptionManager, userDefaults: mockUserDefaults, locale: testLocale) @@ -61,7 +54,6 @@ final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { func testAssignUserToCohort_whenUserEligibleAndNotEnrolled_assignsToCohort() { // Given mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockUserDefaults.removeObject(forKey: MockUserDefaults.Keys.experimentCohort) mockUserDefaults.removeObject(forKey: MockUserDefaults.Keys.enrollmentDate) @@ -85,7 +77,6 @@ final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { mockUserDefaults.set(existingCohort.rawValue, forKey: MockUserDefaults.Keys.experimentCohort) mockUserDefaults.set(existingDate, forKey: MockUserDefaults.Keys.enrollmentDate) mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil // When sut.assignUserToCohort() @@ -102,7 +93,7 @@ final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { func testAssignUserToCohort_whenUserNotEligible_dueToSubscription_doesNotAssign() { // Given mockSubscriptionManager.canPurchase = false - mockAccountManager.accessToken = "some_token" + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() mockUserDefaults.removeObject(forKey: MockUserDefaults.Keys.experimentCohort) mockUserDefaults.removeObject(forKey: MockUserDefaults.Keys.enrollmentDate) @@ -122,7 +113,6 @@ final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { let nonUSLocale = Locale(identifier: "en_GB") sut = FreemiumDBPPixelExperimentManager(subscriptionManager: mockSubscriptionManager, userDefaults: mockUserDefaults, locale: nonUSLocale) mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockUserDefaults.removeObject(forKey: MockUserDefaults.Keys.experimentCohort) mockUserDefaults.removeObject(forKey: MockUserDefaults.Keys.enrollmentDate) diff --git a/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift b/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift index 04ace9f251..7f0703a43a 100644 --- a/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift +++ b/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift @@ -23,13 +23,14 @@ import BrowserServicesKit import SubscriptionTestingUtilities import Freemium import Combine +import TestUtils +import SubscriptionTestingUtilities final class FreemiumDBPFeatureTests: XCTestCase { private var sut: FreemiumDBPFeature! private var mockPrivacyConfigurationManager: MockPrivacyConfigurationManaging! private var mockFreemiumDBPExperimentManager: MockFreemiumDBPExperimentManager! - private var mockAccountManager: MockAccountManager! private var mockSubscriptionManager: SubscriptionManagerMock! private var mockFreemiumDBPUserStateManagerManager: MockFreemiumDBPUserStateManager! private var mockFeatureDisabler: MockFeatureDisabler! @@ -40,20 +41,13 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockPrivacyConfigurationManager = MockPrivacyConfigurationManaging() mockFreemiumDBPExperimentManager = MockFreemiumDBPExperimentManager() - mockAccountManager = MockAccountManager() let mockSubscriptionService = SubscriptionEndpointServiceMock() - let mockAuthService = AuthEndpointServiceMock() let mockStorePurchaseManager = StorePurchaseManagerMock() let currentEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) - mockSubscriptionManager = SubscriptionManagerMock(accountManager: mockAccountManager, - subscriptionEndpointService: mockSubscriptionService, - authEndpointService: mockAuthService, - storePurchaseManager: mockStorePurchaseManager, - currentEnvironment: currentEnvironment, - canPurchase: false) + mockSubscriptionManager = SubscriptionManagerMock() mockFreemiumDBPUserStateManagerManager = MockFreemiumDBPUserStateManager() mockFeatureDisabler = MockFeatureDisabler() @@ -64,11 +58,9 @@ final class FreemiumDBPFeatureTests: XCTestCase { // Given mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in false } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) // When @@ -82,12 +74,10 @@ final class FreemiumDBPFeatureTests: XCTestCase { // Given mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockFreemiumDBPExperimentManager.isTreatment = true sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) // When @@ -101,11 +91,9 @@ final class FreemiumDBPFeatureTests: XCTestCase { // Given mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = false - mockAccountManager.accessToken = nil sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) // When @@ -119,11 +107,10 @@ final class FreemiumDBPFeatureTests: XCTestCase { // Given mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in false } mockSubscriptionManager.canPurchase = false - mockAccountManager.accessToken = "some_token" + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) // When @@ -137,11 +124,10 @@ final class FreemiumDBPFeatureTests: XCTestCase { // Given mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = "some_token" + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) // When @@ -155,12 +141,10 @@ final class FreemiumDBPFeatureTests: XCTestCase { // Given mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockFreemiumDBPExperimentManager.isTreatment = true sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) // When @@ -174,12 +158,10 @@ final class FreemiumDBPFeatureTests: XCTestCase { // Given mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockFreemiumDBPExperimentManager.isTreatment = false sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) // When @@ -194,13 +176,11 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = false mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in false } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil // When sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -213,12 +193,10 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = true mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in false } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -236,12 +214,11 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = true mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in false } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = "some_token" + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -259,13 +236,11 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = true mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil // When sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -279,13 +254,11 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = true mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = false - mockAccountManager.accessToken = nil // When sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -299,14 +272,12 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = false mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in false } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockFreemiumDBPExperimentManager.isTreatment = true let expectation = XCTestExpectation(description: "isAvailablePublisher emits values") sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -335,14 +306,12 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = true mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockFreemiumDBPExperimentManager.isTreatment = true let expectation = XCTestExpectation(description: "isAvailablePublisher emits values") sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -371,14 +340,12 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = true mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = nil mockFreemiumDBPExperimentManager.isTreatment = true let expectation = XCTestExpectation(description: "isAvailablePublisher emits values") sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -394,7 +361,7 @@ final class FreemiumDBPFeatureTests: XCTestCase { // When sut.subscribeToDependencyUpdates() - mockAccountManager.accessToken = "some_token" + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() NotificationCenter.default.post(name: .subscriptionDidChange, object: nil) // Then @@ -407,14 +374,13 @@ final class FreemiumDBPFeatureTests: XCTestCase { mockFreemiumDBPUserStateManagerManager.didActivate = true mockPrivacyConfigurationManager.mockConfig.isSubfeatureKeyEnabled = { _, _ in true } mockSubscriptionManager.canPurchase = true - mockAccountManager.accessToken = "some_token" + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() mockFreemiumDBPExperimentManager.isTreatment = true let expectation = XCTestExpectation(description: "isAvailablePublisher emits values") sut = DefaultFreemiumDBPFeature(privacyConfigurationManager: mockPrivacyConfigurationManager, experimentManager: mockFreemiumDBPExperimentManager, subscriptionManager: mockSubscriptionManager, - accountManager: mockAccountManager, freemiumDBPUserStateManager: mockFreemiumDBPUserStateManagerManager, featureDisabler: mockFeatureDisabler) @@ -430,7 +396,7 @@ final class FreemiumDBPFeatureTests: XCTestCase { // When sut.subscribeToDependencyUpdates() - mockAccountManager.accessToken = nil + mockSubscriptionManager.resultTokenContainer = nil NotificationCenter.default.post(name: .subscriptionDidChange, object: nil) // Then diff --git a/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift b/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift index 1056485bb2..3c1b2db0a8 100644 --- a/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift +++ b/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift @@ -18,26 +18,26 @@ import XCTest @testable import DuckDuckGo_Privacy_Browser +import TestUtils +import SubscriptionTestingUtilities final class FreemiumDBPFirstProfileSavedNotifierTests: XCTestCase { private var mockFreemiumDBPUserStateManager: MockFreemiumDBPUserStateManager! - private var mockAccountManager: MockAccountManager! private var mockNotificationCenter: MockNotificationCenter! + private var mockSubscriptionManager: SubscriptionManagerMock! private var sut: FreemiumDBPFirstProfileSavedNotifier! override func setUpWithError() throws { mockFreemiumDBPUserStateManager = MockFreemiumDBPUserStateManager() - mockAccountManager = MockAccountManager() mockNotificationCenter = MockNotificationCenter() sut = FreemiumDBPFirstProfileSavedNotifier(freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager, - accountManager: mockAccountManager, + subscriptionManager: mockSubscriptionManager, notificationCenter: mockNotificationCenter) } func testWhenAllCriteriaSatisfied_thenNotificationShouldBePosted() { // Given - mockAccountManager.accessToken = nil mockFreemiumDBPUserStateManager.didActivate = true mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = false @@ -52,7 +52,7 @@ final class FreemiumDBPFirstProfileSavedNotifierTests: XCTestCase { func testWhenUserIsAuthenticated_thenNotificationShouldNotBePosted() { // Given - mockAccountManager.accessToken = "some_token" + mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() mockFreemiumDBPUserStateManager.didActivate = true mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = false @@ -65,7 +65,6 @@ final class FreemiumDBPFirstProfileSavedNotifierTests: XCTestCase { func testWhenUserHasNotActivated_thenNotificationShouldNotBePosted() { // Given - mockAccountManager.accessToken = nil mockFreemiumDBPUserStateManager.didActivate = false mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = false @@ -78,7 +77,6 @@ final class FreemiumDBPFirstProfileSavedNotifierTests: XCTestCase { func testWhenNotificationAlreadyPosted_thenShouldNotPostAgain() { // Given - mockAccountManager.accessToken = nil mockFreemiumDBPUserStateManager.didActivate = true mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = true @@ -91,7 +89,6 @@ final class FreemiumDBPFirstProfileSavedNotifierTests: XCTestCase { func testWhenNotificationIsPosted_thenStateShouldBeUpdated() { // Given - mockAccountManager.accessToken = nil mockFreemiumDBPUserStateManager.didActivate = true mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = false diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index e3c12c6b67..503b6d8360 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -58,19 +58,9 @@ final class MoreOptionsMenuTests: XCTestCase { internalUserDecider = InternalUserDeciderMock() defaultBrowserProvider = DefaultBrowserProviderMock() defaultBrowserProvider.isDefault = true - storePurchaseManager = StorePurchaseManagerMock() - - subscriptionManager = SubscriptionManagerMock(accountManager: AccountManagerMock(), - subscriptionEndpointService: SubscriptionEndpointServiceMock(), - authEndpointService: AuthEndpointServiceMock(), - storePurchaseManager: storePurchaseManager, - currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, - purchasePlatform: .appStore), - canPurchase: false) - + subscriptionManager = SubscriptionManagerMock() mockFreemiumDBPFeature = MockFreemiumDBPFeature() - mockNotificationCenter = MockNotificationCenter() mockPixelHandler = MockFreemiumDBPExperimentPixelHandler() mockFreemiumDBPUserStateManager = MockFreemiumDBPUserStateManager() @@ -109,11 +99,6 @@ final class MoreOptionsMenuTests: XCTestCase { // MARK: - Subscription & Freemium - private func mockAuthentication() { - subscriptionManager.accountManager.storeAuthToken(token: "") - subscriptionManager.accountManager.storeAccount(token: "", email: "", externalID: "") - } - @MainActor func testThatPrivacyProIsNotPresentWhenUnauthenticatedAndPurchaseNotAllowedOnAppStore () { subscriptionManager.canPurchase = false @@ -121,7 +106,7 @@ final class MoreOptionsMenuTests: XCTestCase { setupMoreOptionsMenu() - XCTAssertFalse(subscriptionManager.accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertFalse(moreOptionsMenu.items.map { $0.title }.contains(UserText.subscriptionOptionsMenuItem)) } @@ -132,7 +117,7 @@ final class MoreOptionsMenuTests: XCTestCase { setupMoreOptionsMenu() - XCTAssertFalse(subscriptionManager.accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertTrue(moreOptionsMenu.items.map { $0.title }.contains(UserText.subscriptionOptionsMenuItem)) } @@ -143,7 +128,7 @@ final class MoreOptionsMenuTests: XCTestCase { setupMoreOptionsMenu() - XCTAssertFalse(subscriptionManager.accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertTrue(moreOptionsMenu.items.map { $0.title }.contains(UserText.subscriptionOptionsMenuItem)) } @@ -155,7 +140,7 @@ final class MoreOptionsMenuTests: XCTestCase { setupMoreOptionsMenu() - XCTAssertFalse(subscriptionManager.accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertTrue(subscriptionManager.canPurchase) XCTAssertEqual(moreOptionsMenu.items[0].title, UserText.sendFeedback) @@ -187,7 +172,7 @@ final class MoreOptionsMenuTests: XCTestCase { setupMoreOptionsMenu() - XCTAssertFalse(subscriptionManager.accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertTrue(subscriptionManager.canPurchase) XCTAssertEqual(moreOptionsMenu.items[0].title, UserText.sendFeedback) diff --git a/UnitTests/NavigationBar/LocalPinningManagerTests.swift b/UnitTests/NavigationBar/LocalPinningManagerTests.swift index 5cf5cf7962..254ab6c8ac 100644 --- a/UnitTests/NavigationBar/LocalPinningManagerTests.swift +++ b/UnitTests/NavigationBar/LocalPinningManagerTests.swift @@ -21,15 +21,6 @@ import NetworkProtection @testable import DuckDuckGo_Privacy_Browser -private struct NetworkProtectionFeatureActivationMock: NetworkProtectionFeatureActivation { - - let activated: Bool = true - - var isFeatureActivated: Bool { - activated - } -} - final class LocalPinningManagerTests: XCTestCase { override func setUp() { @@ -43,7 +34,7 @@ final class LocalPinningManagerTests: XCTestCase { } private func createManager() -> LocalPinningManager { - return LocalPinningManager(networkProtectionFeatureActivation: NetworkProtectionFeatureActivationMock()) + return LocalPinningManager() } func testWhenTogglingPinningForAView_AndViewIsNotPinned_ThenViewBecomesPinned() { diff --git a/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift b/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift index f0efe70f31..a2be140437 100644 --- a/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift +++ b/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift @@ -40,10 +40,6 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { var userDefaults: UserDefaults! var pixelKit: PixelKit! var uiHandler: SubscriptionUIHandlerMock! - - var accountManager: AccountManagerMock! - var subscriptionService: SubscriptionEndpointServiceMock! - var authService: AuthEndpointServiceMock! var storePurchaseManager: StorePurchaseManagerMock! var subscriptionEnvironment: SubscriptionEnvironment! @@ -71,19 +67,11 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { self.uiEventsHappened.append(action) }) - accountManager = AccountManagerMock() - subscriptionService = SubscriptionEndpointServiceMock() - authService = AuthEndpointServiceMock() storePurchaseManager = StorePurchaseManagerMock() subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) - subscriptionManager = SubscriptionManagerMock(accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - storePurchaseManager: storePurchaseManager, - currentEnvironment: subscriptionEnvironment, - canPurchase: true) + subscriptionManager = SubscriptionManagerMock() appStoreRestoreFlow = AppStoreRestoreFlowMock() subscriptionAppStoreRestorer = DefaultSubscriptionAppStoreRestorer(subscriptionManager: subscriptionManager, @@ -93,23 +81,15 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { override func tearDown() async throws { userDefaults = nil - PixelKit.tearDown() pixelKit.clearFrequencyHistoryForAllPixels() - pixelsFired.removeAll() uiEventsHappened.removeAll() - - accountManager = nil - subscriptionService = nil - authService = nil storePurchaseManager = nil subscriptionEnvironment = nil - subscriptionManager = nil appStoreRestoreFlow = nil uiHandler = nil - subscriptionAppStoreRestorer = nil } @@ -117,7 +97,7 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { func testRestoreAppStoreSubscriptionSuccess() async throws { // Given - appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .success(()) + appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .success("") // When await subscriptionAppStoreRestorer.restoreAppStoreSubscription() @@ -151,7 +131,7 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { // Given storePurchaseManager.syncAppleIDAccountResultError = StoreKitError.unknown await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) - appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .success(()) + appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .success("") // When await subscriptionAppStoreRestorer.restoreAppStoreSubscription() @@ -286,10 +266,7 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { func testRestoreAppStoreSubscriptionWhenRestoreFailsDueToSubscriptionBeingExpired() async throws { // Given - appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .failure(.subscriptionExpired(accountDetails: .init(authToken: Constants.authToken, - accessToken: Constants.accessToken, - externalID: Constants.externalID, - email: Constants.email)) ) + appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .failure(AppStoreRestoreFlowError.subscriptionExpired) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) // When diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index ffaa1180a6..4f1ef056a6 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -27,6 +27,7 @@ import UserScript import PixelKitTestingUtilities import os.log import Networking +import TestUtils @available(macOS 12.0, *) final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { @@ -191,7 +192,7 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { func testGetSubscriptionOptionsReturnsEmptyOptionsWhenSubscriptionOptionsDidNotFetch() async throws { // Given XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - subscriptionManager.productsResponse = .failure(Constants.invalidTokenError) +// subscriptionManager.productsResponse = .failure(Constants.invalidTokenError) storePurchaseManager.subscriptionOptionsResult = nil // When @@ -209,11 +210,13 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { // Given ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) - authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, - externalID: Constants.externalID, - status: "created")) +// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, +// externalID: Constants.externalID, +// status: "created")) +// subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription +// subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -230,16 +233,18 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { // Given ensureUserAuthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - XCTAssertTrue(accountManager.isUserAuthenticated) + XCTAssertTrue(subscriptionManager.isUserAuthenticated) - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredSubscription +// subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When let subscriptionSelectedParams = ["id": "some-subscription-id"] let result = try await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) // Then - XCTAssertFalse(authService.createAccountCalled) +// XCTAssertFalse(authService.createAccountCalled) XCTAssertEqual(uiEventsHappened, [.didDismissProgressViewController]) XCTAssertNil(result) XCTAssertPrivacyPixelsFired([PrivacyProPixel.privacyProPurchaseAttempt.name + "_d", @@ -251,7 +256,7 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - authService.createAccountResult = .failure(Constants.invalidTokenError) +// authService.createAccountResult = .failure(Constants.invalidTokenError) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) // When @@ -280,8 +285,10 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { // Given ensureUserAuthenticatedState() - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) +// subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription +// subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When let result = try await feature.completeStripePayment(params: Constants.mockParams, original: Constants.mockScriptMessage) @@ -302,15 +309,11 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { extension SubscriptionPagesUseSubscriptionFeatureForStripeTests { func ensureUserAuthenticatedState() { - accountStorage.authToken = Constants.authToken - accountStorage.email = Constants.email - accountStorage.externalID = Constants.externalID - accessTokenStorage.accessToken = Constants.accessToken + subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() } func ensureUserUnauthenticatedState() { - try? accessTokenStorage.removeAccessToken() - try? accountStorage.clearAuthenticationState() + subscriptionManager.resultExchangeTokenContainer = nil } public func XCTAssertPrivacyPixelsFired(_ pixels: [String], file: StaticString = #file, line: UInt = #line) { diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 0e3277fa9d..02d197c231 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -587,7 +587,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.externalIDisNotAValidUUID) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) @@ -617,7 +617,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseFailed) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) @@ -647,7 +647,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionCannotBeVerified) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) @@ -677,7 +677,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionPendingAuthentication) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) @@ -707,7 +707,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.unknownError) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) @@ -900,14 +900,14 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Then let tokenResponse = try XCTUnwrap(result as? [String: String]) - XCTAssertEqual(tokenResponse["token"], Constants.accessToken) + XCTAssertEqual(tokenResponse["token"], subscriptionManager.resultTokenContainer!.accessToken) XCTAssertPrivacyPixelsFired([]) } func testGetAccessTokenEmptyOnMissingToken() async throws { // Given ensureUserUnauthenticatedState() - XCTAssertNil(accountManager.accessToken) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) // When let result = try await feature.getAccessToken(params: Constants.mockParams, original: Constants.mockScriptMessage) @@ -922,20 +922,20 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, - externalID: Constants.externalID, - status: "created")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) +// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, +// externalID: Constants.externalID, +// status: "created")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// entitlements: Constants.entitlements, +// subscription: SubscriptionMockFactory.subscription)) mockFreemiumDBPUserStateManager.didActivate = true feature.with(broker: broker) @@ -955,20 +955,20 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, - externalID: Constants.externalID, - status: "created")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) +// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, +// externalID: Constants.externalID, +// status: "created")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// entitlements: Constants.entitlements, +// subscription: SubscriptionMockFactory.subscription)) mockFreemiumDBPUserStateManager.didActivate = false feature.with(broker: broker) @@ -989,20 +989,20 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, - externalID: Constants.externalID, - status: "created")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) +// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, +// externalID: Constants.externalID, +// status: "created")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// entitlements: Constants.entitlements, +// subscription: SubscriptionMockFactory.subscription)) mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = true feature.with(broker: broker) @@ -1021,20 +1021,20 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, - externalID: Constants.externalID, - status: "created")) - authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) +// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, +// externalID: Constants.externalID, +// status: "created")) +// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) - subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, - entitlements: Constants.entitlements, - subscription: SubscriptionMockFactory.subscription)) +// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, +// entitlements: Constants.entitlements, +// subscription: SubscriptionMockFactory.subscription)) mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = false feature.with(broker: broker) diff --git a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift index 5c31d96b52..1f4acce957 100644 --- a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift +++ b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift @@ -20,8 +20,23 @@ import XCTest @testable import DuckDuckGo_Privacy_Browser @testable import TestUtils @testable import Networking +import SubscriptionTestingUtilities final class UnifiedFeedbackFormViewModelTests: XCTestCase { + + var subscriptionTokenProvider: SubscriptionManagerMock! + var apiService: MockAPIService! + + override func setUpWithError() throws { + subscriptionTokenProvider = SubscriptionManagerMock() + apiService = MockAPIService() + } + + override func tearDownWithError() throws { + subscriptionTokenProvider = nil + apiService = nil + } + enum Error: String, Swift.Error { case generic } @@ -29,8 +44,9 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testWhenCreatingViewModel_ThenInitialStateIsFeedbackPending() throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - let viewModel = UnifiedFeedbackFormViewModel(accountManager: MockAccountManager(), - apiService: MockAPIService(apiResponse: .failure(Error.generic)), + // apiService.set(response: .failure(Error.generic), forRequest: <#T##APIRequestV2#>) + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, + apiService: apiService, vpnMetadataCollector: collector, feedbackSender: sender) @@ -40,8 +56,9 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testGivenNoEmail_WhenSendingFeedbackSucceeds_ThenFeedbackIsSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - let viewModel = UnifiedFeedbackFormViewModel(accountManager: MockAccountManager(), - apiService: MockAPIService(apiResponse: .failure(Error.generic)), + // apiService.set(response: .failure(Error.generic), forRequest: <#T##APIRequestV2#>) + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, + apiService: apiService, vpnMetadataCollector: collector, feedbackSender: sender) viewModel.selectedReportType = UnifiedFeedbackReportType.reportIssue.rawValue @@ -59,8 +76,9 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { let sender = MockVPNFeedbackSender() let payload = UnifiedFeedbackFormViewModel.Response(message: "something", error: nil) let response = APIResponseV2(data: try! JSONEncoder().encode(payload), httpResponse: HTTPURLResponse()) - let viewModel = UnifiedFeedbackFormViewModel(accountManager: MockAccountManager(), - apiService: MockAPIService(apiResponse: .success(response)), + // apiService.set(response: , forRequest: <#T##APIRequestV2#>) + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, + apiService: apiService, vpnMetadataCollector: collector, feedbackSender: sender) viewModel.selectedReportType = UnifiedFeedbackReportType.reportIssue.rawValue @@ -77,8 +95,9 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testWhenSendingFeedbackFails_ThenFeedbackIsNotSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - let viewModel = UnifiedFeedbackFormViewModel(accountManager: MockAccountManager(), - apiService: MockAPIService(apiResponse: .failure(Error.generic)), + // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, + apiService: apiService, vpnMetadataCollector: collector, feedbackSender: sender) viewModel.selectedReportType = UnifiedFeedbackReportType.reportIssue.rawValue @@ -95,8 +114,10 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testGivenInvalidEmail_WhenSendingFeedbackFails_ThenFeedbackIsNotSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - let viewModel = UnifiedFeedbackFormViewModel(accountManager: MockAccountManager(), - apiService: MockAPIService(apiResponse: .failure(Error.generic)), + // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, + apiService: apiService, + vpnMetadataCollector: collector, feedbackSender: sender) viewModel.selectedReportType = UnifiedFeedbackReportType.reportIssue.rawValue @@ -114,8 +135,9 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testGivenValidEmail_WhenSendingFeedbackFails_ThenFeedbackIsNotSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - let viewModel = UnifiedFeedbackFormViewModel(accountManager: MockAccountManager(), - apiService: MockAPIService(apiResponse: .failure(Error.generic)), + // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, + apiService: apiService, vpnMetadataCollector: collector, feedbackSender: sender) viewModel.selectedReportType = UnifiedFeedbackReportType.reportIssue.rawValue @@ -134,8 +156,10 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() let delegate = MockVPNFeedbackFormViewModelDelegate() - let viewModel = UnifiedFeedbackFormViewModel(accountManager: MockAccountManager(), - apiService: MockAPIService(apiResponse: .failure(Error.generic)), + // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, + apiService: apiService, + vpnMetadataCollector: collector, feedbackSender: sender) viewModel.delegate = delegate From a121c068bf876e193327392f28d8694cef94e6fe Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 29 Nov 2024 12:52:42 +0000 Subject: [PATCH 15/59] unit tests fixed --- DuckDuckGo.xcodeproj/project.pbxproj | 42 ++++-- .../NavigationBar/View/MoreOptionsMenu.swift | 2 +- .../VPNRedditSessionWorkaround.swift | 2 +- ...riptionManager+StandardConfiguration.swift | 12 +- .../Subscription/SubscriptionPixels.swift | 36 +++++ ...scriptionPagesUseSubscriptionFeature.swift | 8 +- .../UnifiedFeedbackFormViewModel.swift | 2 +- ...taBrokerAuthenticationManagerBuilder.swift | 4 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 4 +- ...BrokerProtectionSubscriptionManaging.swift | 65 --------- ...ProtectionAuthenticationManagerTests.swift | 9 +- .../PreferencesSubscriptionModel.swift | 1 + .../AppStateChangePublisherTests.swift | 0 ...wserTabViewControllerOnboardingTests.swift | 0 .../WindowManagerStateRestorationTests.swift | 0 ...emiumDBPPixelExperimentManagingTests.swift | 4 +- .../DBP/FreemiumDBPFeatureTests.swift | 3 +- ...iumDBPFirstProfileSavedNotifierTests.swift | 1 + .../SubscriptionAppStoreRestorerTests.swift | 5 + ...UseSubscriptionFeatureForStripeTests.swift | 47 +++---- ...tionPagesUseSubscriptionFeatureTests.swift | 132 ++++++------------ .../UnifiedFeedbackFormViewModelTests.swift | 12 +- 22 files changed, 161 insertions(+), 230 deletions(-) create mode 100644 DuckDuckGo/Subscription/SubscriptionPixels.swift delete mode 100644 LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift rename {UnitTests/App => UITests}/AppStateChangePublisherTests.swift (100%) rename {UnitTests/Onboarding/ContextualOnboarding => UITests}/BrowserTabViewControllerOnboardingTests.swift (100%) rename {UnitTests/App => UITests}/WindowManagerStateRestorationTests.swift (100%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b5ff540455..01475b8b0c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -905,7 +905,6 @@ 3706FE1B293F661700E42796 /* PermissionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9F26A7BE0B0013B453 /* PermissionManagerTests.swift */; }; 3706FE1C293F661700E42796 /* ConnectBitwardenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59CC8B290083240058F2F6 /* ConnectBitwardenViewModelTests.swift */; }; 3706FE1E293F661700E42796 /* GeolocationProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BB426A809E60013B453 /* GeolocationProviderTests.swift */; }; - 3706FE1F293F661700E42796 /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; 3706FE20293F661700E42796 /* CLLocationManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63ED0E226B3E7FA00A9DAD1 /* CLLocationManagerMock.swift */; }; 3706FE21293F661700E42796 /* DownloadsPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54BA27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift */; }; 3706FE22293F661700E42796 /* FireproofDomainsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B02199925E063DE00ED7DEA /* FireproofDomainsTests.swift */; }; @@ -951,7 +950,6 @@ 3706FE4D293F661700E42796 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F487B4276A8F2E003CE668 /* OnboardingTests.swift */; }; 3706FE4E293F661700E42796 /* BookmarkListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CCD25DD9071009059CC /* BookmarkListTests.swift */; }; 3706FE4F293F661700E42796 /* BookmarksExporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859E7D6C274548F2009C2B69 /* BookmarksExporterTests.swift */; }; - 3706FE50293F661700E42796 /* WindowManagerStateRestorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */; }; 3706FE51293F661700E42796 /* SafariBookmarksReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99D0E26FE1A84001E4761 /* SafariBookmarksReaderTests.swift */; }; 3706FE52293F661700E42796 /* FileSystemDSLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */; }; 3706FE53293F661700E42796 /* CoreDataEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B11060925903EAC0039B979 /* CoreDataEncryptionTests.swift */; }; @@ -1695,8 +1693,6 @@ 567A23DC2C88980B0010F66C /* ContextualDaxDialogsFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23DA2C8894CD0010F66C /* ContextualDaxDialogsFactoryTests.swift */; }; 567A23DE2C89980A0010F66C /* OnboardingNavigationDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23DD2C89980A0010F66C /* OnboardingNavigationDelegateTests.swift */; }; 567A23DF2C89980A0010F66C /* OnboardingNavigationDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23DD2C89980A0010F66C /* OnboardingNavigationDelegateTests.swift */; }; - 567A23E12C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */; }; - 567A23E22C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */; }; 567DA93F29E8045D008AC5EE /* MockEmailStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DA93E29E8045D008AC5EE /* MockEmailStorage.swift */; }; 567DA94029E8045D008AC5EE /* MockEmailStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DA93E29E8045D008AC5EE /* MockEmailStorage.swift */; }; 567DA94529E95C3F008AC5EE /* YoutubeOverlayUserScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DA94429E95C3F008AC5EE /* YoutubeOverlayUserScriptTests.swift */; }; @@ -2674,8 +2670,6 @@ B6A5A27125B9377300AA7ADA /* StatePersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A27025B9377300AA7ADA /* StatePersistenceService.swift */; }; B6A5A27925B93FFF00AA7ADA /* StateRestorationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A27825B93FFE00AA7ADA /* StateRestorationManagerTests.swift */; }; B6A5A27E25B9403E00AA7ADA /* FileStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A27D25B9403E00AA7ADA /* FileStoreMock.swift */; }; - B6A5A2A025B96E8300AA7ADA /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; - B6A5A2A825BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */; }; B6A924D92664C72E001A28CA /* WebKitDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */; }; B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */; }; B6AA64732994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; @@ -3141,6 +3135,17 @@ F18826912BC0105800D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; F18826922BC0105900D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; F18826932BC0105900D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F18E51022CF8C5650020D129 /* BrowserTabViewControllerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */; }; + F18E51032CF8DAFB0020D129 /* WindowManagerStateRestorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */; }; + F18E51042CF8DB2B0020D129 /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; + F18E51062CF9DC970020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; + F18E51072CF9DC970020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; + F18E51082CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; + F18E510A2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; + F18E510C2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; + F18E510D2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; + F18E510E2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; + F18E510F2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; F198C7122BD18A28000BF24D /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = F198C7112BD18A28000BF24D /* PixelKit */; }; F198C7142BD18A30000BF24D /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = F198C7132BD18A30000BF24D /* PixelKit */; }; F198C7162BD18A44000BF24D /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = F198C7152BD18A44000BF24D /* PixelKit */; }; @@ -4956,6 +4961,7 @@ F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; + F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPixels.swift; sourceTree = ""; }; F1AFDBD12C231B7A00710F2C /* SubscriptionAppStoreRestorerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorerTests.swift; sourceTree = ""; }; F1AFDBD32C231B9700710F2C /* SubscriptionErrorReporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporterTests.swift; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; @@ -7021,7 +7027,6 @@ 56CE77602C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift */, 567A23DA2C8894CD0010F66C /* ContextualDaxDialogsFactoryTests.swift */, 567A23DD2C89980A0010F66C /* OnboardingNavigationDelegateTests.swift */, - 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */, 5677A9392C983FF100DA7B0A /* ContextualOnboardingStateMachineTests.swift */, 56A214AE2CB583BF00E5BC0E /* TrackerMessageProviderTests.swift */, 9FBB0C082CBD39B70006B6A6 /* ViewHighlighterTests.swift */, @@ -7073,6 +7078,9 @@ 7B4CE8DB26F02108009134B1 /* UITests */ = { isa = PBXGroup; children = ( + B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */, + B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */, + 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */, BB731F302CDBA6320023D2E4 /* FireWindowTests.swift */, 376E708D2BD686260082B7EB /* UI Tests.xctestplan */, EEBCE6802BA444FA00B9DF00 /* Common */, @@ -9254,8 +9262,6 @@ B6A5A28C25B962CB00AA7ADA /* App */ = { isa = PBXGroup; children = ( - B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */, - B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */, 1D7693FE2BE3A1AA0016A22B /* DockCustomizerMock.swift */, 1DA860712BE3AE950027B813 /* DockPositionProviderTests.swift */, ); @@ -9806,6 +9812,7 @@ F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */, F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, 1E25A4FD2CC937120080EFD4 /* SubscriptionCookieManageEventPixelMapping.swift */, + F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */, ); path = Subscription; sourceTree = ""; @@ -11292,6 +11299,7 @@ 3706FAD2293F65D500E42796 /* Atb.swift in Sources */, 3706FAD3293F65D500E42796 /* DownloadsViewController.swift in Sources */, 3706FAD4293F65D500E42796 /* DataExtension.swift in Sources */, + F18E51062CF9DC970020D129 /* SubscriptionPixels.swift in Sources */, 3706FAD6293F65D500E42796 /* ConfigurationStore.swift in Sources */, EEDFA38B2CD148DE00D1C558 /* SyncDiagnosisHelper.swift in Sources */, 3706FAD7293F65D500E42796 /* Feedback.swift in Sources */, @@ -12242,7 +12250,6 @@ 9FAD623B2BCFDB32007F3A65 /* WebsiteInfoHelpers.swift in Sources */, 56A054312C2043C8007D8FAB /* OnboardingTabExtensionTests.swift in Sources */, 56A0542E2C201DAA007D8FAB /* MockContentBlocking.swift in Sources */, - 3706FE1F293F661700E42796 /* AppStateChangePublisherTests.swift in Sources */, 9FA5A0B12BC9039300153786 /* BookmarkFolderStoreMock.swift in Sources */, BBBEE1C02C4FF63600035ABA /* SortBookmarksViewModelTests.swift in Sources */, 9F0660792BECC81C00B8EEF1 /* PixelCapturedParameters.swift in Sources */, @@ -12277,7 +12284,6 @@ 567A23CF2C80CF4B0010F66C /* ErrorPageTabExtensionTest.swift in Sources */, 37D046A22C7DA9A200AEAA50 /* UserBackgroundImagesManagerTests.swift in Sources */, 37DB56F02C3B31CD0093D4DC /* MockRemoteMessagingAvailabilityProvider.swift in Sources */, - 567A23E22C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift in Sources */, 1D8C2FEB2B70F5A7005E4BBD /* MockWebViewSnapshotRenderer.swift in Sources */, 378D62572CEF80200056BBD8 /* NewTabPageFavoritesModelTests.swift in Sources */, 3767880F2CECD5A800F59D83 /* NewTabPageUserScriptTests.swift in Sources */, @@ -12359,7 +12365,6 @@ 3706FE4F293F661700E42796 /* BookmarksExporterTests.swift in Sources */, 566B196629CDB829007E38F4 /* CapturingOptionsButtonMenuDelegate.swift in Sources */, 3767880C2CECCB7200F59D83 /* NewTabPageActionsManagerTests.swift in Sources */, - 3706FE50293F661700E42796 /* WindowManagerStateRestorationTests.swift in Sources */, 3706FE51293F661700E42796 /* SafariBookmarksReaderTests.swift in Sources */, 3706FE52293F661700E42796 /* FileSystemDSLTests.swift in Sources */, 3706FE53293F661700E42796 /* CoreDataEncryptionTests.swift in Sources */, @@ -12569,6 +12574,7 @@ buildActionMask = 2147483647; files = ( 4B25377A2A11C01700610219 /* UserText+NetworkProtectionExtensions.swift in Sources */, + F18E510A2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, B65DA5F42A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, 4B52354D2C854CB600AFAF64 /* DuckDuckGoUserAgent.swift in Sources */, EEBCA0C72BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, @@ -12617,6 +12623,7 @@ 7B4D8A232BDA857300852966 /* VPNOperationErrorRecorder.swift in Sources */, 7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */, 7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, + F18E510C2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, F1DA51982BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */, F1C70D802BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, @@ -12660,6 +12667,7 @@ 4BF0E5152AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, F1DA51992BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */, F1C70D812BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, + F18E510D2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, BDA764852BC49E4000D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, @@ -12713,6 +12721,7 @@ 4B4D60A52A0B2EC000BCD287 /* UserText+NetworkProtectionExtensions.swift in Sources */, 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, + F18E51082CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, F1DA51882BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, F1FDC93A2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, ); @@ -12736,6 +12745,7 @@ EE7F74912BB5D76600CD9456 /* BookmarksBarTests.swift in Sources */, EE02D41C2BB460A600DBE6B3 /* BrowsingHistoryTests.swift in Sources */, EE02D41A2BB4609900DBE6B3 /* UITests.swift in Sources */, + F18E51032CF8DAFB0020D129 /* WindowManagerStateRestorationTests.swift in Sources */, EE0429E02BA31D2F009EB20F /* FindInPageTests.swift in Sources */, BBCD467A2C8643EC004DB483 /* XCUIApplicationExtension.swift in Sources */, BBBB65402C77BB9400E69AC6 /* BookmarkSearchTests.swift in Sources */, @@ -12744,12 +12754,14 @@ EEC7BE2E2BC6C09500F86835 /* AddressBarKeyboardShortcutsTests.swift in Sources */, EE54F7B32BBFEA49006218DB /* BookmarksAndFavoritesTests.swift in Sources */, EE02D4222BB4611A00DBE6B3 /* TestsURLExtension.swift in Sources */, + F18E51042CF8DB2B0020D129 /* AppStateChangePublisherTests.swift in Sources */, BB731F312CDBA6360023D2E4 /* FireWindowTests.swift in Sources */, EE42CBCC2BC8004700AD411C /* PermissionsTests.swift in Sources */, 7B4CE8E726F02135009134B1 /* TabBarTests.swift in Sources */, EEBCE6832BA463DD00B9DF00 /* NSImageExtensions.swift in Sources */, EEBCE6822BA444FA00B9DF00 /* XCUIElementExtension.swift in Sources */, EED735362BB46B6000F173D6 /* AutocompleteTests.swift in Sources */, + F18E51022CF8C5650020D129 /* BrowserTabViewControllerOnboardingTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12792,6 +12804,7 @@ buildActionMask = 2147483647; files = ( F17E7DDC2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift in Sources */, + F18E510E2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, 31A83FB72BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, F1C70D822BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, @@ -12809,6 +12822,7 @@ buildActionMask = 2147483647; files = ( F10C99422C7E20A1005568B4 /* Logger+DBPBackgroundAgent.swift in Sources */, + F18E510F2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, 31A83FB82BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, F1C70D832BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, @@ -13308,6 +13322,7 @@ B6BE9FAA293F7955006363C6 /* ModalSheetCancellable.swift in Sources */, B6830963274CDEC7004B46BB /* FireproofDomainsStore.swift in Sources */, F188268D2BBF01C300D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, + F18E51072CF9DC970020D129 /* SubscriptionPixels.swift in Sources */, 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 1E7E2E942902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift in Sources */, AA9FF95F24A1FB690039E328 /* TabCollectionViewModel.swift in Sources */, @@ -13923,7 +13938,6 @@ B6106BB526A809E60013B453 /* GeolocationProviderTests.swift in Sources */, BDA764912BC4E57200D0400C /* MockVPNLocationFormatter.swift in Sources */, B6E6BA232BA2EDDE008AA7E1 /* FileReadResult.swift in Sources */, - B6A5A2A025B96E8300AA7ADA /* AppStateChangePublisherTests.swift in Sources */, B63ED0E326B3E7FA00A9DAD1 /* CLLocationManagerMock.swift in Sources */, 37CD54BB27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift in Sources */, 4BE344EE2B2376DF003FC223 /* UnifiedFeedbackFormViewModelTests.swift in Sources */, @@ -13960,7 +13974,6 @@ 9F0660732BECC71200B8EEF1 /* SubscriptionAttributionPixelHandlerTests.swift in Sources */, 56A054302C2043C8007D8FAB /* OnboardingTabExtensionTests.swift in Sources */, BDCB66D82C7CE1A600E8ABC9 /* VPNFeedbackFormViewModelTests.swift in Sources */, - 567A23E12C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift in Sources */, 986189E62A7CFB3E001B4519 /* LocalBookmarkStoreSavingTests.swift in Sources */, AA652CD325DDA6E9009059CC /* LocalBookmarkManagerTests.swift in Sources */, CBDD5DE329A67F2700832877 /* MockConfigurationStore.swift in Sources */, @@ -14028,7 +14041,6 @@ 5677A93D2C98414900DA7B0A /* ContextualOnboardingStateMachineTests.swift in Sources */, AA652CCE25DD9071009059CC /* BookmarkListTests.swift in Sources */, 859E7D6D274548F2009C2B69 /* BookmarksExporterTests.swift in Sources */, - B6A5A2A825BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift in Sources */, B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */, 4BB99D1126FE1A84001E4761 /* SafariBookmarksReaderTests.swift in Sources */, BBC063E82C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift in Sources */, diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index e1847d95cd..f3bd2d2fc6 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -948,7 +948,7 @@ final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { private func refreshAvailabilityBasedOnEntitlements() { guard subscriptionFeatureAvailability.isFeatureAvailable, subscriptionManager.isUserAuthenticated else { return } - + Task.detached(priority: .background) { [weak self] in guard let self else { return } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift index 1f80aea936..a4c557695a 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift @@ -25,7 +25,7 @@ import Common import os.log final class VPNRedditSessionWorkaround { - + private let subscriptionManager: any SubscriptionManager private let ipcClient: VPNControllerXPCClient private let statusReporter: NetworkProtectionStatusReporter diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index 2583e47bee..7cd436feae 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -66,9 +66,7 @@ extension DefaultSubscriptionManager { let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { case .deadToken: - // TODO: add pixel - // Pixel.fire(pixel: .privacyProDeadTokenDetected) - break + PixelKit.fire(SubscriptionPixels.privacyProDeadTokenDetected) } } @@ -86,11 +84,3 @@ extension DefaultSubscriptionManager { } } } - -//extension DefaultSubscriptionManager: AccountManagerKeychainAccessDelegate { -// -// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { -// PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), -// frequency: .legacyDailyAndCount) -// } -//} diff --git a/DuckDuckGo/Subscription/SubscriptionPixels.swift b/DuckDuckGo/Subscription/SubscriptionPixels.swift new file mode 100644 index 0000000000..8d0631d556 --- /dev/null +++ b/DuckDuckGo/Subscription/SubscriptionPixels.swift @@ -0,0 +1,36 @@ +// +// SubscriptionPixels.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 PixelKit + +enum SubscriptionPixels: PixelKitEventV2 { + + case privacyProDeadTokenDetected + + var error: (any Error)? { nil } + + var name: String { + switch self { + case .privacyProDeadTokenDetected: + return "m_privacy-pro_dead_token_detected" + } + } + + var parameters: [String : String]? { nil } +} diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index 9dd1a72abb..e5968ecb8b 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -156,9 +156,13 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // Clear subscription Cache await subscriptionManager.signOut() - let authToken = subscriptionValues.token + guard !subscriptionValues.token.isEmpty else { + Logger.subscription.fault("Empty token provided, Failed to exchange v1 token for v2") + return nil + } + do { - _ = try await subscriptionManager.exchange(tokenV1: authToken) + _ = try await subscriptionManager.exchange(tokenV1: subscriptionValues.token) Logger.subscription.log("v1 token exchanged for v2") } catch { Logger.subscription.error("Failed to exchange v1 token for v2") diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift index c47d8dc513..f85d4c868f 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift @@ -28,7 +28,7 @@ protocol UnifiedFeedbackFormViewModelDelegate: AnyObject { } final class UnifiedFeedbackFormViewModel: ObservableObject { - private static let feedbackEndpoint = URL(string: "https://subscriptions.duckduckgo.com/api/feedback")! + static let feedbackEndpoint = URL(string: "https://subscriptions.duckduckgo.com/api/feedback")! private static let platform = "macos" enum ViewState { diff --git a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift b/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift index 98d51439a7..2effdbfcb9 100644 --- a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift +++ b/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift @@ -31,8 +31,8 @@ final public class DataBrokerAuthenticationManagerBuilder { } } -//extension DefaultAccountManager: DataBrokerProtectionAccountManaging { +// extension DefaultAccountManager: DataBrokerProtectionAccountManaging { // public func hasEntitlement(for cachePolicy: APICachePolicy) async -> Result { // await hasEntitlement(forProductName: .dataBrokerProtection, cachePolicy: .reloadIgnoringLocalCacheData) // } -//} +// } diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index b4c8bcdc15..1fbc36b454 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -452,10 +452,10 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { } -//extension DuckDuckGoVPNAppDelegate: AccountManagerKeychainAccessDelegate { +// extension DuckDuckGoVPNAppDelegate: AccountManagerKeychainAccessDelegate { // // public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { // PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), // frequency: .legacyDailyAndCount) // } -//} +// } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift deleted file mode 100644 index 398e436fd9..0000000000 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// DataBrokerProtectionSubscriptionManaging.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 Common -import AppKitExtensions - -//public protocol DataBrokerProtectionSubscriptionManaging { -// var isUserAuthenticated: Bool { get } -// var accessToken: String? { get } -// func hasValidEntitlement() async throws -> Bool -//} -// -//public final class DataBrokerProtectionSubscriptionManager: DataBrokerProtectionSubscriptionManaging { -// -// let subscriptionManager: SubscriptionManager -// -// public var isUserAuthenticated: Bool { -// accessToken != nil -// } -// -// public var accessToken: String? { -// // We use a staging token for privacy pro supplied through a github secret/action -// // for PIR end to end tests. This is also stored in bitwarden if you want to run -// // the tests locally -// let dbpSettings = DataBrokerProtectionSettings() -// if dbpSettings.storedRunType == .integrationTests, -// let token = ProcessInfo.processInfo.environment["PRIVACYPRO_STAGING_TOKEN"] { -// return token -// } -// return subscriptionManager.accountManager.accessToken -// } -// -// public init(subscriptionManager: SubscriptionManager) { -// self.subscriptionManager = subscriptionManager -// } -// -// public func hasValidEntitlement() async throws -> Bool { -// subscriptionManager.isEntitlementActive(.dataBrokerProtection) -// } -//} -// -//// MARK: - Wrapper Protocols -// -///// 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: APICachePolicy) async -> Result -//} diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift index 082a1dbc8b..c5014a3143 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift @@ -26,7 +26,7 @@ import TestUtils class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { var authenticationManager: DataBrokerProtectionAuthenticationManager! var redeemUseCase: DataBrokerProtectionRedeemUseCase! - var subscriptionManager: SubscriptionManagerMock! //MockDataBrokerProtectionSubscriptionManaging! + var subscriptionManager: SubscriptionManagerMock! override func setUp() async throws { redeemUseCase = MockRedeemUseCase() @@ -40,26 +40,19 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { } func testUserNotAuthenticatedWhenSubscriptionManagerReturnsFalse() { - subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() - authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - XCTAssertEqual(authenticationManager.isUserAuthenticated, false) } func testEmptyAccessTokenResultsInNilAuthHeader() { - subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() - authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - XCTAssertNil(authenticationManager.getAuthHeader()) } func testUserAuthenticatedWhenSubscriptionManagerReturnsTrue() { subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainer() - authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index a3b9480146..073a7d4cfd 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -261,6 +261,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { // await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() // } // } // TODO: Double check but I don't think this makes sense in this context + try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) Task { @MainActor in userEventHandler(eventType) diff --git a/UnitTests/App/AppStateChangePublisherTests.swift b/UITests/AppStateChangePublisherTests.swift similarity index 100% rename from UnitTests/App/AppStateChangePublisherTests.swift rename to UITests/AppStateChangePublisherTests.swift diff --git a/UnitTests/Onboarding/ContextualOnboarding/BrowserTabViewControllerOnboardingTests.swift b/UITests/BrowserTabViewControllerOnboardingTests.swift similarity index 100% rename from UnitTests/Onboarding/ContextualOnboarding/BrowserTabViewControllerOnboardingTests.swift rename to UITests/BrowserTabViewControllerOnboardingTests.swift diff --git a/UnitTests/App/WindowManagerStateRestorationTests.swift b/UITests/WindowManagerStateRestorationTests.swift similarity index 100% rename from UnitTests/App/WindowManagerStateRestorationTests.swift rename to UITests/WindowManagerStateRestorationTests.swift diff --git a/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift b/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift index f65e2697a7..32286f588e 100644 --- a/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift +++ b/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift @@ -21,8 +21,6 @@ import SubscriptionTestingUtilities import Subscription @testable import DuckDuckGo_Privacy_Browser import TestUtils -import SubscriptionTestingUtilities - final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { private var sut: FreemiumDBPPixelExperimentManaging! @@ -35,7 +33,7 @@ final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { let mockStorePurchaseManager = StorePurchaseManagerMock() let currentEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) - + mockSubscriptionManager.currentEnvironment = currentEnvironment mockSubscriptionManager = SubscriptionManagerMock() mockUserDefaults = MockUserDefaults() let testLocale = Locale(identifier: "en_US") diff --git a/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift b/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift index 7f0703a43a..803f6eae11 100644 --- a/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift +++ b/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift @@ -24,8 +24,6 @@ import SubscriptionTestingUtilities import Freemium import Combine import TestUtils -import SubscriptionTestingUtilities - final class FreemiumDBPFeatureTests: XCTestCase { private var sut: FreemiumDBPFeature! @@ -48,6 +46,7 @@ final class FreemiumDBPFeatureTests: XCTestCase { purchasePlatform: .appStore) mockSubscriptionManager = SubscriptionManagerMock() + mockSubscriptionManager.currentEnvironment = currentEnvironment mockFreemiumDBPUserStateManagerManager = MockFreemiumDBPUserStateManager() mockFeatureDisabler = MockFeatureDisabler() diff --git a/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift b/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift index 3c1b2db0a8..7bfe5d52b6 100644 --- a/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift +++ b/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift @@ -31,6 +31,7 @@ final class FreemiumDBPFirstProfileSavedNotifierTests: XCTestCase { override func setUpWithError() throws { mockFreemiumDBPUserStateManager = MockFreemiumDBPUserStateManager() mockNotificationCenter = MockNotificationCenter() + mockSubscriptionManager = SubscriptionManagerMock() sut = FreemiumDBPFirstProfileSavedNotifier(freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager, subscriptionManager: mockSubscriptionManager, notificationCenter: mockNotificationCenter) diff --git a/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift b/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift index a2be140437..c500f66968 100644 --- a/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift +++ b/UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift @@ -68,10 +68,13 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { }) storePurchaseManager = StorePurchaseManagerMock() + subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) subscriptionManager = SubscriptionManagerMock() + subscriptionManager.currentEnvironment = subscriptionEnvironment + subscriptionManager.resultStorePurchaseManager = storePurchaseManager appStoreRestoreFlow = AppStoreRestoreFlowMock() subscriptionAppStoreRestorer = DefaultSubscriptionAppStoreRestorer(subscriptionManager: subscriptionManager, @@ -156,6 +159,7 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { // Given appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .failure(.missingAccountOrTransactions) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) + subscriptionManager.resultURL = URL(string: "https://www.duckduckgo.com") // When await subscriptionAppStoreRestorer.restoreAppStoreSubscription() @@ -268,6 +272,7 @@ final class SubscriptionAppStoreRestorerTests: XCTestCase { // Given appStoreRestoreFlow.restoreAccountFromPastPurchaseResult = .failure(AppStoreRestoreFlowError.subscriptionExpired) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) + subscriptionManager.resultURL = URL(string: "https://www.duckduckgo.com") // When await subscriptionAppStoreRestorer.restoreAppStoreSubscription() diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index 4f1ef056a6..d49983ca1c 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -116,13 +116,13 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { uiHandler = SubscriptionUIHandlerMock { action in self.uiEventsHappened.append(action) } - + subscriptionManager = SubscriptionManagerMock() + subscriptionManager.resultURL = URL(string: "https://example.com") storePurchaseManager = StorePurchaseManagerMock() - subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, + subscriptionManager.resultStorePurchaseManager = storePurchaseManager + subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .staging, purchasePlatform: .stripe) - - subscriptionManager = SubscriptionManagerMock() - + subscriptionManager.currentEnvironment = subscriptionEnvironment appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, storePurchaseManager: storePurchaseManager) @@ -192,7 +192,7 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { func testGetSubscriptionOptionsReturnsEmptyOptionsWhenSubscriptionOptionsDidNotFetch() async throws { // Given XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) -// subscriptionManager.productsResponse = .failure(Constants.invalidTokenError) + subscriptionManager.productsResponse = .failure(Subscription.SubscriptionManagerError.tokenUnavailable(error: nil)) storePurchaseManager.subscriptionOptionsResult = nil // When @@ -211,15 +211,14 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) XCTAssertFalse(subscriptionManager.isUserAuthenticated) - -// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, -// externalID: Constants.externalID, -// status: "created")) -// subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription -// subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + let subscriptionID = "some-subscription-id" + subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription + storePurchaseManager.purchaseSubscriptionResult = .success(subscriptionID) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] + let subscriptionSelectedParams = ["id": subscriptionID] let result = try await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) // Then @@ -234,10 +233,8 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { ensureUserAuthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) XCTAssertTrue(subscriptionManager.isUserAuthenticated) - -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) + await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredSubscription -// subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -255,8 +252,9 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { // Given ensureUserUnauthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .stripe) - -// authService.createAccountResult = .failure(Constants.invalidTokenError) + subscriptionManager.resultCreateAccountTokenContainer = nil + storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) // When @@ -265,10 +263,11 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { // Then XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) - XCTAssertEqual(uiEventsHappened, [.didDismissProgressViewController, - .didShowAlert(.somethingWentWrong), - .didShowTab(.subscription(subscriptionManager.url(for: .purchase))), - .didDismissProgressViewController]) + XCTAssertTrue(uiEventsHappened.count == 4) + XCTAssertTrue(uiEventsHappened.contains(.didDismissProgressViewController)) + XCTAssertTrue(uiEventsHappened.contains(.didShowAlert(.somethingWentWrong))) + XCTAssertTrue(uiEventsHappened.contains(.didShowTab(.subscription(subscriptionManager.url(for: .purchase))))) + XCTAssertTrue(uiEventsHappened.contains(.didDismissProgressViewController)) XCTAssertNil(result) XCTAssertPrivacyPixelsFired([PrivacyProPixel.privacyProPurchaseAttempt.name + "_d", PrivacyProPixel.privacyProPurchaseAttempt.name + "_c", @@ -309,11 +308,11 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { extension SubscriptionPagesUseSubscriptionFeatureForStripeTests { func ensureUserAuthenticatedState() { - subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() } func ensureUserUnauthenticatedState() { - subscriptionManager.resultExchangeTokenContainer = nil + subscriptionManager.resultTokenContainer = nil } public func XCTAssertPrivacyPixelsFired(_ pixels: [String], file: StaticString = #file, line: UInt = #line) { diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 02d197c231..9a4fc6b293 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -97,8 +97,11 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { } storePurchaseManager = StorePurchaseManagerMock() - subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, - purchasePlatform: .appStore) + subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) + subscriptionManager = SubscriptionManagerMock() + subscriptionManager.resultStorePurchaseManager = storePurchaseManager + subscriptionManager.resultURL = URL(string: "https://example.com") + subscriptionManager.currentEnvironment = subscriptionEnvironment appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, storePurchaseManager: storePurchaseManager) appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, @@ -178,7 +181,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When - let setSubscriptionParams = ["token": subscriptionManager.resultTokenContainer!.accessToken] + let setSubscriptionParams = ["token": subscriptionManager.resultExchangeTokenContainer!.accessToken] let result = try await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) // Then @@ -297,14 +300,9 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil -// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, -// externalID: Constants.externalID, -// status: "created")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// subscription: SubscriptionMockFactory.subscription)) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -333,16 +331,12 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - // TODO: re-mock everything -// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, -// externalID: Constants.externalID, -// status: "created")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) -// storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// entitlements: Constants.entitlements, -// subscription: SubscriptionMockFactory.subscription)) + await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) + + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription + storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -366,24 +360,15 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserAuthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertTrue(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS - - // TODO: re-mock everything -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) -// authService.storeLoginResult = .success(StoreLoginResponse(authToken: Constants.authToken, -// email: Constants.email, -// externalID: Constants.externalID, -// id: 1, -// status: "authenticated")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) -// storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// entitlements: Constants.entitlements, -// subscription: SubscriptionMockFactory.subscription)) + await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) + storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredSubscription + let newSub = SubscriptionMockFactory.subscription + subscriptionManager.confirmPurchaseResponse = .success(newSub) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -407,21 +392,19 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserAuthenticatedState() XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) - XCTAssertTrue(subscriptionManager.isUserAuthenticated) - + await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// entitlements: Constants.entitlements, -// subscription: SubscriptionMockFactory.subscription)) + await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredSubscription + storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When let subscriptionSelectedParams = ["id": "some-subscription-id"] let result = try await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) // Then -// XCTAssertFalse(authService.createAccountCalled) XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) XCTAssertEqual(uiEventsHappened, [.didPresentProgressViewController, .didUpdateProgressViewController, @@ -469,14 +452,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = true await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS -// authService.storeLoginResult = .success(StoreLoginResponse(authToken: Constants.authToken, -// email: Constants.email, -// externalID: Constants.externalID, -// id: 1, -// status: "authenticated")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -536,9 +512,11 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseCancelledByUser) + await uiHandler.setAlertResponse(alertResponse: .abort) + let subscriptionSelectedParams = ["id": "some-subscription-id"] let result = try await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) @@ -557,7 +535,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.productNotFound) await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) @@ -924,18 +902,12 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) XCTAssertFalse(subscriptionManager.isUserAuthenticated) + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - -// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, -// externalID: Constants.externalID, -// status: "created")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// entitlements: Constants.entitlements, -// subscription: SubscriptionMockFactory.subscription)) mockFreemiumDBPUserStateManager.didActivate = true feature.with(broker: broker) @@ -960,15 +932,9 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil -// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, -// externalID: Constants.externalID, -// status: "created")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// entitlements: Constants.entitlements, -// subscription: SubscriptionMockFactory.subscription)) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) mockFreemiumDBPUserStateManager.didActivate = false feature.with(broker: broker) @@ -994,15 +960,12 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil -// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, -// externalID: Constants.externalID, -// status: "created")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) + await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) + + subscriptionManager.resultCreateAccountTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) + storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// entitlements: Constants.entitlements, -// subscription: SubscriptionMockFactory.subscription)) mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = true feature.with(broker: broker) @@ -1023,18 +986,13 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { XCTAssertEqual(subscriptionEnvironment.purchasePlatform, .appStore) XCTAssertFalse(subscriptionManager.isUserAuthenticated) + await uiHandler.setAlertResponse(alertResponse: .alertFirstButtonReturn) storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil -// authService.createAccountResult = .success(CreateAccountResponse(authToken: Constants.authToken, -// externalID: Constants.externalID, -// status: "created")) -// authService.getAccessTokenResult = .success(AccessTokenResponse(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() storePurchaseManager.purchaseSubscriptionResult = .success(Constants.mostRecentTransactionJWS) -// subscriptionService.confirmPurchaseResult = .success(ConfirmPurchaseResponse(email: Constants.email, -// entitlements: Constants.entitlements, -// subscription: SubscriptionMockFactory.subscription)) + subscriptionManager.confirmPurchaseResponse = .success(SubscriptionMockFactory.subscription) mockFreemiumDBPUserStateManager.didPostFirstProfileSavedNotification = false feature.with(broker: broker) @@ -1055,10 +1013,12 @@ extension SubscriptionPagesUseSubscriptionFeatureTests { func ensureUserAuthenticatedState() { subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + XCTAssertTrue(subscriptionManager.isUserAuthenticated) } func ensureUserUnauthenticatedState() { subscriptionManager.resultTokenContainer = nil + XCTAssertFalse(subscriptionManager.isUserAuthenticated) } public func XCTAssertPrivacyPixelsFired(_ pixels: [String], file: StaticString = #file, line: UInt = #line) { diff --git a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift index 1f4acce957..ee536f15ab 100644 --- a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift +++ b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift @@ -44,7 +44,6 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testWhenCreatingViewModel_ThenInitialStateIsFeedbackPending() throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - // apiService.set(response: .failure(Error.generic), forRequest: <#T##APIRequestV2#>) let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, apiService: apiService, vpnMetadataCollector: collector, @@ -56,7 +55,6 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testGivenNoEmail_WhenSendingFeedbackSucceeds_ThenFeedbackIsSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - // apiService.set(response: .failure(Error.generic), forRequest: <#T##APIRequestV2#>) let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, apiService: apiService, vpnMetadataCollector: collector, @@ -74,9 +72,13 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testGivenEmail_WhenSendingFeedbackSucceeds_ThenFeedbackIsSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() + + // API request to mock let payload = UnifiedFeedbackFormViewModel.Response(message: "something", error: nil) let response = APIResponseV2(data: try! JSONEncoder().encode(payload), httpResponse: HTTPURLResponse()) - // apiService.set(response: , forRequest: <#T##APIRequestV2#>) + apiService.set(response: response, forRequestURL: UnifiedFeedbackFormViewModel.feedbackEndpoint) + + subscriptionTokenProvider.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, apiService: apiService, vpnMetadataCollector: collector, @@ -95,7 +97,6 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testWhenSendingFeedbackFails_ThenFeedbackIsNotSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, apiService: apiService, vpnMetadataCollector: collector, @@ -114,7 +115,6 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testGivenInvalidEmail_WhenSendingFeedbackFails_ThenFeedbackIsNotSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, apiService: apiService, @@ -135,7 +135,6 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { func testGivenValidEmail_WhenSendingFeedbackFails_ThenFeedbackIsNotSent() async throws { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, apiService: apiService, vpnMetadataCollector: collector, @@ -156,7 +155,6 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() let delegate = MockVPNFeedbackFormViewModelDelegate() - // apiService.set(response: , forRequest: <#T##APIRequestV2#>) // error let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: subscriptionTokenProvider, apiService: apiService, From d3e657c391d7bac37e529d2681ddd684fefd939e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 29 Nov 2024 13:22:49 +0000 Subject: [PATCH 16/59] BSK > branch --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++---- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 01475b8b0c..0ce92405e9 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -4952,7 +4952,6 @@ EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; - F1449EB42CE61461002536E4 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; @@ -7818,7 +7817,6 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( - F1449EB42CE61461002536E4 /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, @@ -15198,8 +15196,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 212.0.0; + branch = fcappelli/subscription_oauth_api_v2; + kind = branch; }; }; 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 1e88f8d86f..83ee92a59f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "branch" : "fcappelli/subscription_oauth_api_v2", + "revision" : "71155a226c3bce220a9675ad09587a300d77fe7d" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 08bf3f01da752f5e7359d1aab47ddf6a5cb241e6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 29 Nov 2024 13:33:32 +0000 Subject: [PATCH 17/59] lint --- .../xcshareddata/swiftpm/Package.resolved | 6 +-- .../Subscription/SubscriptionPixels.swift | 2 +- .../PreferencesSubscriptionModel.swift | 38 ++++--------------- 3 files changed, 12 insertions(+), 34 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 83ee92a59f..31680add20 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "71155a226c3bce220a9675ad09587a300d77fe7d" + "revision" : "97ddc9c243337cb028c979aa488e3037f7e268c3" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "757bbbae1e2afbb421caee9bfca04ee5c56c3af8", - "version" : "7.2.0" + "revision" : "49db79829dcb166b3524afdbc1c680890452ce1c", + "version" : "7.2.1" } }, { diff --git a/DuckDuckGo/Subscription/SubscriptionPixels.swift b/DuckDuckGo/Subscription/SubscriptionPixels.swift index 8d0631d556..45331c07d0 100644 --- a/DuckDuckGo/Subscription/SubscriptionPixels.swift +++ b/DuckDuckGo/Subscription/SubscriptionPixels.swift @@ -32,5 +32,5 @@ enum SubscriptionPixels: PixelKitEventV2 { } } - var parameters: [String : String]? { nil } + var parameters: [String: String]? { nil } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 073a7d4cfd..c799f9a8b6 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -159,13 +159,9 @@ public final class PreferencesSubscriptionModel: ObservableObject { switch subscriptionPlatform { case .apple: -// if await confirmIfSignedInToSameAccount() { // TODO: what is this for? - return .navigateToManageSubscription { [weak self] in - self?.changePlanOrBilling(for: .appStore) - } -// } else { -// return .presentSheet(.apple) -// } + return .navigateToManageSubscription { [weak self] in + self?.changePlanOrBilling(for: .appStore) + } case .google: return .presentSheet(.google) case .stripe: @@ -192,20 +188,6 @@ 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.authEndpointService.storeLogin(signature: lastTransactionJWSRepresentation) { -// case .success(let response): -// return response.externalID == accountManager.externalID -// case .failure: -// return false -// } -// } -// -// return false -// } - @MainActor func openVPN() { userEventHandler(.openVPN) @@ -253,15 +235,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { } Task { -// if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { -// if #available(macOS 12.0, iOS 15.0, *) { -// let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, -// storePurchaseManager: subscriptionManager.storePurchaseManager(), -// accountManager: subscriptionManager.accountManager) -// await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() -// } -// } // TODO: Double check but I don't think this makes sense in this context - try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) + if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { + if #available(macOS 12.0, iOS 15.0, *) { + try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) + } + } Task { @MainActor in userEventHandler(eventType) From b5a3f437af092e07a74623675f88fa0686fc129e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 2 Dec 2024 16:36:02 +0000 Subject: [PATCH 18/59] unit tests improvments --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +++----- DuckDuckGo/Application/AppDelegate.swift | 2 +- .../BookmarksBarViewControllerTests.swift | 0 ...nPagesUseSubscriptionFeatureForStripeTests.swift | 13 +------------ ...bscriptionPagesUseSubscriptionFeatureTests.swift | 12 ++++-------- .../UnifiedFeedbackFormViewModelTests.swift | 6 ++---- 6 files changed, 11 insertions(+), 30 deletions(-) rename {UnitTests/BookmarksBar => UITests}/BookmarksBarViewControllerTests.swift (100%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2de991d581..f0e5ae4af9 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1763,8 +1763,6 @@ 56A054532C2592CE007D8FAB /* OnboardingUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A054522C2592CE007D8FAB /* OnboardingUITests.swift */; }; 56A214AF2CB583BF00E5BC0E /* TrackerMessageProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A214AE2CB583BF00E5BC0E /* TrackerMessageProviderTests.swift */; }; 56A214B02CB583BF00E5BC0E /* TrackerMessageProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A214AE2CB583BF00E5BC0E /* TrackerMessageProviderTests.swift */; }; - 56AC09C72C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; - 56AC09C82C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; 56B234BF2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */; }; 56B234C02A84EFD800F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */; }; 56BA1E752BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */; }; @@ -3156,6 +3154,7 @@ F118EA862BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; F1476FC02C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; F1476FC12C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; + F14E5D532CFE18BC00B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; F17114822C7C98FB009836C1 /* Logger+Favicons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114812C7C98FB009836C1 /* Logger+Favicons.swift */; }; F17114832C7C98FB009836C1 /* Logger+Favicons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114812C7C98FB009836C1 /* Logger+Favicons.swift */; }; F17114852C7C9D28009836C1 /* Logger+Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114842C7C9D28009836C1 /* Logger+Fire.swift */; }; @@ -6299,7 +6298,6 @@ children = ( 4B43468E285ED6CB00177407 /* ViewModel */, 9F3344612BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift */, - 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */, ); path = BookmarksBar; sourceTree = ""; @@ -7165,6 +7163,7 @@ 7B4CE8DB26F02108009134B1 /* UITests */ = { isa = PBXGroup; children = ( + 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */, B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */, B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */, 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */, @@ -12499,7 +12498,6 @@ 3706FE58293F661700E42796 /* HistoryStoreTests.swift in Sources */, 3706FE59293F661700E42796 /* EncryptionKeyGeneratorTests.swift in Sources */, 3706FE5A293F661700E42796 /* GeolocationServiceMock.swift in Sources */, - 56AC09C82C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift in Sources */, 3706FE5B293F661700E42796 /* FirefoxLoginReaderTests.swift in Sources */, 1D1C36E429FAE8DA001FA40C /* FaviconManagerTests.swift in Sources */, 31F25EFF2CC3CA02002F9084 /* AIChatMenuConfigurationTests.swift in Sources */, @@ -12869,6 +12867,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F14E5D532CFE18BC00B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */, BB4339DB2C7F9606005D7ED7 /* PinnedTabsTests.swift in Sources */, EEBCE6842BA4643200B9DF00 /* NSSizeExtension.swift in Sources */, BB5F46A32C8751F6005F72DF /* BookmarkSortTests.swift in Sources */, @@ -14021,7 +14020,6 @@ 028904202A7B25380028369C /* AppConfigurationURLProviderTests.swift in Sources */, B65349AA265CF45000DCC645 /* DispatchQueueExtensionsTests.swift in Sources */, 858A798A26A9B35E00A75A42 /* PasswordManagementItemModelTests.swift in Sources */, - 56AC09C72C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift in Sources */, 1D3B1AB92934062B006F4388 /* PasswordManagerCoordinatingMock.swift in Sources */, 1D77921828FDC54C00BE0210 /* FaviconReferenceCacheTests.swift in Sources */, FD23FD2B28816606007F6985 /* AutoconsentMessageProtocolTests.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 3ed62a616c..d7cdb28b9d 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -295,7 +295,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, userDefaults: subscriptionUserDefaults) -//featureFlagger: featureFlagger +// featureFlagger: featureFlagger subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { return WindowControllersManager.shared }) diff --git a/UnitTests/BookmarksBar/BookmarksBarViewControllerTests.swift b/UITests/BookmarksBarViewControllerTests.swift similarity index 100% rename from UnitTests/BookmarksBar/BookmarksBarViewControllerTests.swift rename to UITests/BookmarksBarViewControllerTests.swift diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index 97d24bab1d..39a7cfe112 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -65,20 +65,9 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { SubscriptionOption(id: "2", cost: SubscriptionOptionCost(displayPrice: "$99.00", recurrence: "yearly")) ], - features: [ - SubscriptionFeature(name: .networkProtection), - SubscriptionFeature(name: .dataBrokerProtection), - SubscriptionFeature(name: .identityTheftRestoration) - ]) - -// static let validateTokenResponse = ValidateTokenResponse(account: ValidateTokenResponse.Account(email: Constants.email, -// entitlements: Constants.entitlements, -// externalID: Constants.externalID)) - + features: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) static let mockParams: [String: String] = [:] @MainActor static let mockScriptMessage = MockWKScriptMessage(name: "", body: "", webView: WKWebView() ) - -// static let invalidTokenError = APIServiceError.serverError(statusCode: 401, error: "invalid_token") } var userDefaults: UserDefaults! diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index b002c6399b..9a340d9d76 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -49,11 +49,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { SubscriptionOption(id: "2", cost: SubscriptionOptionCost(displayPrice: "99 USD", recurrence: "yearly")) ], - features: [ - SubscriptionFeature(name: .networkProtection), - SubscriptionFeature(name: .dataBrokerProtection), - SubscriptionFeature(name: .identityTheftRestoration) - ]) + features: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) static let mockParams: [String: String] = [:] @MainActor static let mockScriptMessage = MockWKScriptMessage(name: "", body: "", webView: WKWebView() ) } @@ -736,7 +732,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testFeatureSelectedSuccessForNetworkProtection() async throws { // Given ensureUserAuthenticatedState() - let selectedFeature = Entitlement.ProductName.networkProtection + let selectedFeature = SubscriptionEntitlement.networkProtection let notificationPostedExpectation = expectation(forNotification: .ToggleNetworkProtectionInMainWindow, object: nil) @@ -753,7 +749,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testFeatureSelectedSuccessForPersonalInformationRemoval() async throws { // Given ensureUserAuthenticatedState() - let selectedFeature = Entitlement.ProductName.dataBrokerProtection + let selectedFeature = SubscriptionEntitlement.dataBrokerProtection let notificationPostedExpectation = expectation(forNotification: .openPersonalInformationRemoval, object: nil) let uiHandlerCalledExpectation = expectation(description: "uiHandlerCalled") @@ -777,7 +773,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testFeatureSelectedSuccessForIdentityTheftRestoration() async throws { // Given ensureUserAuthenticatedState() - let selectedFeature = Entitlement.ProductName.identityTheftRestoration + let selectedFeature = SubscriptionEntitlement.identityTheftRestoration let uiHandlerCalledExpectation = expectation(description: "uiHandlerCalled") diff --git a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift index 4c80546060..677d75f0b7 100644 --- a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift +++ b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift @@ -22,8 +22,6 @@ import SubscriptionTestingUtilities @testable import DuckDuckGo_Privacy_Browser @testable import TestUtils @testable import Networking -import SubscriptionTestingUtilities - final class UnifiedFeedbackFormViewModelTests: XCTestCase { var subscriptionManager: SubscriptionManagerMock! @@ -47,7 +45,7 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { let collector = MockVPNMetadataCollector() let sender = MockVPNFeedbackSender() - let viewModel = UnifiedFeedbackFormViewModel(subscriptionManager: subscriptionTokenProvider, + let viewModel = UnifiedFeedbackFormViewModel(subscriptionManager: subscriptionManager, apiService: apiService, vpnMetadataCollector: collector, feedbackSender: sender) @@ -82,7 +80,7 @@ final class UnifiedFeedbackFormViewModelTests: XCTestCase { apiService.set(response: response, forRequestURL: UnifiedFeedbackFormViewModel.feedbackEndpoint) - subscriptionTokenProvider.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() let viewModel = UnifiedFeedbackFormViewModel(subscriptionManager: subscriptionManager, apiService: apiService, vpnMetadataCollector: collector, From 6dc2e1f32320cfb70a6ad374c01a2ea1fdfca89d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 3 Dec 2024 12:28:39 +0000 Subject: [PATCH 19/59] varius --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +++-- DuckDuckGo/Application/AppDelegate.swift | 5 +-- .../SubscriptionErrorReporter.swift | 35 ++++++++++++++++++- .../PreferencesSubscriptionModel.swift | 8 +++-- .../BookmarksBarViewControllerTests.swift | 0 5 files changed, 45 insertions(+), 11 deletions(-) rename {UITests => UnitTests/BookmarksBar/ViewModel}/BookmarksBarViewControllerTests.swift (100%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f0e5ae4af9..33de213b90 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3154,7 +3154,8 @@ F118EA862BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; F1476FC02C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; F1476FC12C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; - F14E5D532CFE18BC00B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; + F14E5D562CFE1BE200B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; + F14E5D572CFE1BE200B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; F17114822C7C98FB009836C1 /* Logger+Favicons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114812C7C98FB009836C1 /* Logger+Favicons.swift */; }; F17114832C7C98FB009836C1 /* Logger+Favicons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114812C7C98FB009836C1 /* Logger+Favicons.swift */; }; F17114852C7C9D28009836C1 /* Logger+Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114842C7C9D28009836C1 /* Logger+Fire.swift */; }; @@ -6305,6 +6306,7 @@ 4B43468E285ED6CB00177407 /* ViewModel */ = { isa = PBXGroup; children = ( + 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */, 4B43468F285ED7A100177407 /* BookmarksBarViewModelTests.swift */, ); path = ViewModel; @@ -7163,7 +7165,6 @@ 7B4CE8DB26F02108009134B1 /* UITests */ = { isa = PBXGroup; children = ( - 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */, B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */, B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */, 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */, @@ -12402,6 +12403,7 @@ 37F1E3302CEF4A2C00130142 /* NewTabPageFavoritesClientTests.swift in Sources */, 567A23CF2C80CF4B0010F66C /* ErrorPageTabExtensionTest.swift in Sources */, 37D046A22C7DA9A200AEAA50 /* UserBackgroundImagesManagerTests.swift in Sources */, + F14E5D572CFE1BE200B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */, 37DB56F02C3B31CD0093D4DC /* MockRemoteMessagingAvailabilityProvider.swift in Sources */, 1D8C2FEB2B70F5A7005E4BBD /* MockWebViewSnapshotRenderer.swift in Sources */, 378D62572CEF80200056BBD8 /* NewTabPageFavoritesModelTests.swift in Sources */, @@ -12867,7 +12869,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F14E5D532CFE18BC00B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */, BB4339DB2C7F9606005D7ED7 /* PinnedTabsTests.swift in Sources */, EEBCE6842BA4643200B9DF00 /* NSSizeExtension.swift in Sources */, BB5F46A32C8751F6005F72DF /* BookmarkSortTests.swift in Sources */, @@ -14227,6 +14228,7 @@ 37E260922C8A3EB4006EE07F /* MockHomePageSettingsModelNavigator.swift in Sources */, 4BF4EA5027C71F26004E57C4 /* PasswordManagementListSectionTests.swift in Sources */, AA7E9176286DB05D00AB6B62 /* RecentlyClosedCoordinatorMock.swift in Sources */, + F14E5D562CFE1BE200B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */, 31E163BD293A579E00963C10 /* PrivacyReferenceTestHelper.swift in Sources */, B693955D26F19CD70015B914 /* DownloadListStoreTests.swift in Sources */, B610F2EB27AA8E4500FCEBE9 /* ContentBlockingUpdatingTests.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index d7cdb28b9d..66b8badca2 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -546,10 +546,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { DataBrokerProtectionAppEvents(featureGatekeeper: pirGatekeeper).applicationDidBecomeActive() Task { - guard let subscription = try? await subscriptionManager.currentSubscription(refresh: true) else { - return - } - + guard let subscription = try? await subscriptionManager.currentSubscription(refresh: false) else { return } if subscription.isActive { PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift index ae370e03f4..4279dbadb3 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift @@ -21,7 +21,7 @@ import Common import PixelKit import os.log -enum SubscriptionError: Error { +enum SubscriptionError: LocalizedError { case purchaseFailed, missingEntitlements, failedToGetSubscriptionOptions, @@ -36,6 +36,39 @@ enum SubscriptionError: Error { accountCreationFailed, activeSubscriptionAlreadyPresent, generalError + + var localizedDescription: String { + switch self { + case .purchaseFailed: + return "Purchase process failed. Please try again." + case .missingEntitlements: + return "Required entitlements are missing." + case .failedToGetSubscriptionOptions: + return "Unable to retrieve subscription options." + case .failedToSetSubscription: + return "Failed to set the subscription." + case .failedToRestoreFromEmail: + return "Email restore process failed." + case .failedToRestoreFromEmailSubscriptionInactive: + return "Cannot restore; email subscription is inactive." + case .failedToRestorePastPurchase: + return "Failed to restore your past purchase." + case .subscriptionNotFound: + return "No subscription could be found." + case .subscriptionExpired: + return "Your subscription has expired." + case .hasActiveSubscription: + return "You already have an active subscription." + case .cancelledByUser: + return "Action was cancelled by the user." + case .accountCreationFailed: + return "Account creation failed. Please try again." + case .activeSubscriptionAlreadyPresent: + return "There is already an active subscription present." + case .generalError: + return "A general error has occurred." + } + } } protocol SubscriptionErrorReporter { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index d66033ab48..f28e3563fd 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -344,9 +344,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func updateAvailableSubscriptionFeatures() { let features = currentSubscriptionFeatures() - shouldShowVPN = features.contains(.networkProtection) - shouldShowDBP = features.contains(.dataBrokerProtection) - shouldShowITR = features.contains(.identityTheftRestoration) || features.contains(.identityTheftRestorationGlobal) + Task { @MainActor in + shouldShowVPN = features.contains(.networkProtection) + shouldShowDBP = features.contains(.dataBrokerProtection) + shouldShowITR = features.contains(.identityTheftRestoration) || features.contains(.identityTheftRestorationGlobal) + } } private func currentSubscriptionFeatures() -> [SubscriptionEntitlement] { diff --git a/UITests/BookmarksBarViewControllerTests.swift b/UnitTests/BookmarksBar/ViewModel/BookmarksBarViewControllerTests.swift similarity index 100% rename from UITests/BookmarksBarViewControllerTests.swift rename to UnitTests/BookmarksBar/ViewModel/BookmarksBarViewControllerTests.swift From 89b338a5b6e4cc48e5aeeb4a2042ff7d8bc92e63 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 5 Dec 2024 11:57:09 +0000 Subject: [PATCH 20/59] work-ish --- .../DuckDuckGo Privacy Browser.xcscheme | 1 + DuckDuckGo/Application/AppDelegate.swift | 10 +- ...ataBrokerProtectionFeatureGatekeeper.swift | 3 +- .../NavigationBar/View/MoreOptionsMenu.swift | 44 +++-- ...NetworkProtectionIPCTunnelController.swift | 2 +- ...rkProtectionSubscriptionEventHandler.swift | 3 +- .../MacPacketTunnelProvider.swift | 13 +- ...scriptionPagesUseSubscriptionFeature.swift | 23 ++- .../UnifiedFeedbackFormViewModel.swift | 27 ++-- .../VPNMetadataCollector.swift | 2 +- .../Waitlist/VPNFeatureGatekeeper.swift | 7 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 9 +- ...okerProtectionAuthenticationManaging.swift | 6 +- .../DebugMenu/SubscriptionDebugMenu.swift | 10 +- .../PreferencesSubscriptionModel.swift | 151 +++++++++--------- 15 files changed, 164 insertions(+), 147 deletions(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index b272e5bb34..c6589b37f9 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -277,6 +277,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "it" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 66b8badca2..6093002c91 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -546,9 +546,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { DataBrokerProtectionAppEvents(featureGatekeeper: pirGatekeeper).applicationDidBecomeActive() Task { - guard let subscription = try? await subscriptionManager.currentSubscription(refresh: false) else { return } - if subscription.isActive { - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) + do { + let subscription = try await subscriptionManager.getSubscription(cachePolicy: .returnCacheDataDontLoad) + if subscription.isActive { + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) + } + } catch { + Logger.general.log("Subscription not active") } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index 189150cc13..8dbd63dab3 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -89,7 +89,8 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature let isAuthenticated = subscriptionManager.isUserAuthenticated if !isAuthenticated && freemiumDBPUserStateManager.didActivate { return true } - var hasEntitlements = subscriptionManager.isEntitlementActive(.dataBrokerProtection) + + var hasEntitlements = await subscriptionManager.isFeatureActive(.dataBrokerProtection) firePrerequisitePixelsAndLogIfNecessary(hasEntitlements: hasEntitlements, isAuthenticatedResult: isAuthenticated) return hasEntitlements && isAuthenticated } diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index b09b0680ae..88384e3c74 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -899,23 +899,29 @@ final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { self.subscriptionSettingsItem = makeSubscriptionSettingsItem(target: target) delegate = self - addMenuItems() + Task { + await addMenuItems() + } } required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - private func addMenuItems() { - let features = subscriptionManager.currentEntitlements + private func addMenuItems() async { + let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false) + let vpnFeature = features.first { $0.entitlement == .networkProtection } + let dbpFeature = features.first { $0.entitlement == .dataBrokerProtection } + let itrFeature = features.first { $0.entitlement == .identityTheftRestoration } + let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal } - if features.contains(.networkProtection) { + if vpnFeature != nil { addItem(networkProtectionItem) } - if features.contains(.dataBrokerProtection) { + if dbpFeature != nil { addItem(dataBrokerProtectionItem) } - if features.contains(.identityTheftRestoration) || features.contains(.identityTheftRestorationGlobal) { + if itrFeature != nil || itrgFeature != nil { addItem(identityTheftRestorationItem) } addItem(NSMenuItem.separator()) @@ -953,23 +959,27 @@ final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { .targetting(target) } - private func refreshAvailabilityBasedOnEntitlements() { - guard subscriptionFeatureAvailability.isFeatureAvailable, subscriptionManager.isUserAuthenticated else { return } - let isNetworkProtectionItemEnabled = subscriptionManager.isEntitlementActive(.networkProtection) - let isDataBrokerProtectionItemEnabled = subscriptionManager.isEntitlementActive(.dataBrokerProtection) - let hasIdentityTheftRestoration = subscriptionManager.isEntitlementActive(.identityTheftRestoration) - let hasIdentityTheftRestorationGlobal = subscriptionManager.isEntitlementActive(.identityTheftRestorationGlobal) - let isIdentityTheftRestorationItemEnabled = hasIdentityTheftRestoration || hasIdentityTheftRestorationGlobal + private func refreshAvailabilityBasedOnEntitlements() async { +// guard subscriptionFeatureAvailability.isFeatureAvailable, subscriptionManager.isUserAuthenticated else { return } + let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false) + let vpnFeature = features.first { $0.entitlement == .networkProtection } + let dbpFeature = features.first { $0.entitlement == .dataBrokerProtection } + let itrFeature = features.first { $0.entitlement == .identityTheftRestoration } + let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal } Task { @MainActor in - self.networkProtectionItem.isEnabled = isNetworkProtectionItemEnabled - self.dataBrokerProtectionItem.isEnabled = isDataBrokerProtectionItemEnabled - self.identityTheftRestorationItem.isEnabled = isIdentityTheftRestorationItemEnabled + self.networkProtectionItem.isEnabled = vpnFeature?.enabled ?? false + self.dataBrokerProtectionItem.isEnabled = dbpFeature?.enabled ?? false + let hasIdentityTheftRestoration = itrFeature?.enabled ?? false + let hasIdentityTheftRestorationGlobal = itrgFeature?.enabled ?? false + self.identityTheftRestorationItem.isEnabled = hasIdentityTheftRestoration || hasIdentityTheftRestorationGlobal } } public func menuWillOpen(_ menu: NSMenu) { - refreshAvailabilityBasedOnEntitlements() + Task { + await refreshAvailabilityBasedOnEntitlements() + } } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift index 6406184908..7aa4dc281b 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -100,7 +100,7 @@ extension NetworkProtectionIPCTunnelController: TunnelController { } do { - guard featureGatekeeper.canStartVPN() else { + guard await featureGatekeeper.canStartVPN() else { throw RequestError.notAuthorizedToEnableLoginItem } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 65fc73927f..b55b5248d1 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -47,7 +47,8 @@ final class NetworkProtectionSubscriptionEventHandler { private func subscribeToEntitlementChanges() { Task { - await handleEntitlementsChange(hasEntitlements: subscriptionManager.isEntitlementActive(.networkProtection)) + let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) + await handleEntitlementsChange(hasEntitlements: isNetworkProtectionEnabled) NotificationCenter.default .publisher(for: .entitlementsDidChange) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 4ca9cf486d..e92b92c443 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -430,15 +430,10 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { userDefaults: defaults) let entitlementsCheck: (() async -> Result) = { - do { - Logger.networkProtection.log("Entitlements check...") - let entitlements = try await subscriptionManager.getEntitlements(forceRefresh: true) - Logger.networkProtection.log("Entitlements found: \(entitlements, privacy: .public)") - return .success(entitlements.contains(.networkProtection)) - } catch { - Logger.networkProtection.error("Failed to get entitlements: \(error)") - return .failure(error) - } + Logger.networkProtection.log("Entitlements check...") + let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) + Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled" , privacy: .public)") + return .success(isNetworkProtectionEnabled) } let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index bf75d07550..e74b83413b 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -38,14 +38,11 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { ]) let subscriptionManager: SubscriptionManager var subscriptionPlatform: SubscriptionEnvironment.PurchasePlatform { subscriptionManager.currentEnvironment.purchasePlatform } - - let stripePurchaseFlow: StripePurchaseFlow + let stripePurchaseFlow: any StripePurchaseFlow let subscriptionErrorReporter = DefaultSubscriptionErrorReporter() let subscriptionSuccessPixelHandler: SubscriptionAttributionPixelHandler let uiHandler: SubscriptionUIHandling - let subscriptionFeatureAvailability: SubscriptionFeatureAvailability - private var freemiumDBPUserStateManager: FreemiumDBPUserStateManager private let freemiumDBPPixelExperimentManager: FreemiumDBPPixelExperimentManaging private let notificationCenter: NotificationCenter @@ -222,14 +219,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { return nil } - Logger.subscription.info("[Purchase] Starting purchase for: \(subscriptionSelection.id, privacy: .public)") + Logger.subscription.log("[Purchase] Starting purchase for: \(subscriptionSelection.id, privacy: .public)") await uiHandler.presentProgressViewController(withTitle: UserText.purchasingSubscriptionTitle) // Check for active subscriptions if await subscriptionManager.storePurchaseManager().hasActiveSubscription() { PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) - Logger.subscription.info("[Purchase] Found active subscription during purchase") + Logger.subscription.log("[Purchase] Found active subscription during purchase") subscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) await showSubscriptionFoundAlert(originalMessage: message) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) @@ -243,8 +240,9 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { storePurchaseManager: subscriptionManager.storePurchaseManager(), appStoreRestoreFlow: appStoreRestoreFlow) - Logger.subscription.info("[Purchase] Purchasing") - switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id) { + Logger.subscription.log("[Purchase] Purchasing") + let purchaseResult = await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id) + switch purchaseResult { case .success(let transactionJWS): purchaseTransactionJWS = transactionJWS case .failure(let error): @@ -278,11 +276,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await uiHandler.updateProgressViewController(title: UserText.completingPurchaseTitle) - Logger.subscription.info("[Purchase] Completing purchase") let completePurchaseResult = await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) switch completePurchaseResult { case .success(let purchaseUpdate): - Logger.subscription.info("[Purchase] Purchase complete") + Logger.subscription.log("[Purchase] Purchase completed") PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .legacyDailyAndCount) sendFreemiumSubscriptionPixelIfFreemiumActivated() saveSubscriptionUpgradeTimestampIfFreemiumActivated() @@ -480,8 +477,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { storePurchaseManager: subscriptionManager.storePurchaseManager()) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { - case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .legacyDailyAndCount) - case .failure: break + case .success: + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .legacyDailyAndCount) + case .failure(let error): + Logger.subscription.error("Failed to restore account from past purchase: \(error, privacy: .public)") } Task { @MainActor in originalMessage.webView?.reload() diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift index e0ea0ca04e..4e9cab092e 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift @@ -173,15 +173,24 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { self.source = source self.subscriptionManager = subscriptionManager - let features = subscriptionManager.currentEntitlements - if features.contains(.networkProtection) { - availableCategories.append(.vpn) - } - if features.contains(.dataBrokerProtection) { - availableCategories.append(.pir) - } - if features.contains(.identityTheftRestoration) || features.contains(.identityTheftRestorationGlobal) { - availableCategories.append(.itr) + Task { + let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false) + let vpnFeature = features.first { $0.entitlement == .networkProtection } + let dbpFeature = features.first { $0.entitlement == .dataBrokerProtection } + let itrFeature = features.first { $0.entitlement == .identityTheftRestoration } + let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal } + + if vpnFeature?.enabled ?? false { + availableCategories.append(.vpn) + } + if dbpFeature?.enabled ?? false { + availableCategories.append(.pir) + } + let idpEnabled = itrFeature?.enabled ?? false + let idpgEnabled = itrgFeature?.enabled ?? false + if idpEnabled || idpgEnabled { + availableCategories.append(.itr) + } } } diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index 1f1238d272..c6b00200ba 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -323,7 +323,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { return .init( hasPrivacyProAccount: subscriptionManager.isUserAuthenticated, - hasVPNEntitlement: subscriptionManager.isEntitlementActive(.networkProtection) + hasVPNEntitlement: await subscriptionManager.isFeatureActive(.networkProtection) ) } diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index b6c41f85bf..f51519429a 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -29,7 +29,7 @@ import Subscription protocol VPNFeatureGatekeeper { var isInstalled: Bool { get } - func canStartVPN() -> Bool + func canStartVPN() async -> Bool func isVPNVisible() -> Bool func shouldUninstallAutomatically() -> Bool func disableIfUserHasNoAccess() async @@ -62,11 +62,12 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { /// For beta users this means they have an auth token. /// For subscription users this means they have entitlements. /// - func canStartVPN() -> Bool { + func canStartVPN() async -> Bool { guard subscriptionFeatureAvailability.isFeatureAvailable else { return false } - return subscriptionManager.isEntitlementActive(.networkProtection) + let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) + return isNetworkProtectionEnabled } /// Whether the user can see the VPN entry points in the UI. diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index be2842bf45..af9b09e98c 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -424,13 +424,8 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { guard subscriptionManager.isUserAuthenticated else { return } let entitlementsCheck: (() async -> Result) = { - do { - let entitlements = try await self.subscriptionManager.getEntitlements(forceRefresh: true) - return .success(entitlements.contains(.networkProtection)) - } catch { - Logger.networkProtection.error("Failed to get token: \(error)") - return .failure(error) - } + let isNetworkProtectionEnabled = await self.subscriptionManager.isFeatureActive(.networkProtection) + return .success(isNetworkProtectionEnabled) } Task { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift index be50df1852..69953fa2f5 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift @@ -22,7 +22,7 @@ import Subscription public protocol DataBrokerProtectionAuthenticationManaging { var isUserAuthenticated: Bool { get } var accessToken: String? { get } - func hasValidEntitlement() async throws -> Bool + func hasValidEntitlement() async -> Bool func shouldAskForInviteCode() -> Bool func redeem(inviteCode: String) async throws func getAuthHeader() -> String? @@ -46,8 +46,8 @@ public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtecti self.subscriptionManager = subscriptionManager } - public func hasValidEntitlement() -> Bool { - subscriptionManager.isEntitlementActive(.dataBrokerProtection) + public func hasValidEntitlement() async -> Bool { + await subscriptionManager.isFeatureActive(.dataBrokerProtection) //isEntitlementActive(.dataBrokerProtection) } public func getAuthHeader() -> String? { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 6a628ba821..a7cff8e11e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -229,9 +229,13 @@ public final class SubscriptionDebugMenu: NSMenuItem { @objc func checkEntitlements() { - let entitlements = subscriptionManager.currentEntitlements - let descriptions = entitlements.map({ entitlement in entitlement.rawValue }) - showAlert(title: "Check Entitlements", message: descriptions.joined(separator: "\n")) + Task { + let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: true) + let descriptions = features.map({ feature in + "\(feature.entitlement.rawValue): Enabled: \(feature.enabled)" + }) + showAlert(title: "Check Entitlements", message: descriptions.joined(separator: "\n")) + } } @objc diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index f28e3563fd..4547e639a3 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -23,12 +23,13 @@ import enum Combine.Publishers import Networking import FeatureFlags import BrowserServicesKit +import os.log public final class PreferencesSubscriptionModel: ObservableObject { @Published var isUserAuthenticated: Bool = false @Published var subscriptionDetails: String? - @Published var subscriptionStatus: PrivacyProSubscription.Status? + @Published var subscriptionStatus: PrivacyProSubscription.Status = .unknown @Published var subscriptionStorefrontRegion: SubscriptionRegion = .usa @@ -43,11 +44,13 @@ public final class PreferencesSubscriptionModel: ObservableObject { @Published var email: String? var hasEmail: Bool { !(email?.isEmpty ?? true) } - private var subscriptionPlatform: PrivacyProSubscription.Platform? let featureFlagger: FeatureFlagger var isROWLaunched: Bool = false - lazy var sheetModel = SubscriptionAccessViewModel(actionHandlers: sheetActionHandler, + private var subscriptionPlatform: PrivacyProSubscription.Platform? + + lazy var sheetModel = SubscriptionAccessViewModel( + actionHandlers: sheetActionHandler, purchasePlatform: subscriptionManager.currentEnvironment.purchasePlatform) private let subscriptionManager: SubscriptionManager @@ -55,7 +58,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { public let userEventHandler: (UserEvent) -> Void private let sheetActionHandler: SubscriptionAccessActionHandlers -// private var fetchSubscriptionDetailsTask: Task<(), Never>? + private var fetchSubscriptionDetailsTask: Task<(), Never>? private var signInObserver: Any? private var signOutObserver: Any? @@ -77,9 +80,10 @@ public final class PreferencesSubscriptionModel: ObservableObject { } lazy var statePublisher: AnyPublisher = { - let isSubscriptionActivePublisher: AnyPublisher = $subscriptionStatus.map { - guard let status = $0 else { return nil} - return status != .expired && status != .inactive + let isSubscriptionActivePublisher: AnyPublisher = $subscriptionStatus.map { +// guard let status = $0 else { return false} + let status = $0 + return status != .expired && status != .inactive && status != .unknown }.eraseToAnyPublisher() let hasAnyEntitlementPublisher = Publishers.CombineLatest3($hasAccessToVPN, $hasAccessToDBP, $hasAccessToITR).map { @@ -90,10 +94,10 @@ public final class PreferencesSubscriptionModel: ObservableObject { .map { isUserAuthenticated, isSubscriptionActive, hasAnyEntitlement in switch (isUserAuthenticated, isSubscriptionActive, hasAnyEntitlement) { case (false, _, _): return PreferencesSubscriptionState.noSubscription - case (true, .some(false), _): return PreferencesSubscriptionState.subscriptionExpired - case (true, nil, _): return PreferencesSubscriptionState.subscriptionPendingActivation - case (true, .some(true), false): return PreferencesSubscriptionState.subscriptionPendingActivation - case (true, .some(true), true): return PreferencesSubscriptionState.subscriptionActive + case (true, false, _): return PreferencesSubscriptionState.subscriptionExpired +// case (true, nil, _): return PreferencesSubscriptionState.subscriptionPendingActivation + case (true, true, false): return PreferencesSubscriptionState.subscriptionPendingActivation + case (true, true, true): return PreferencesSubscriptionState.subscriptionActive } } .removeDuplicates() @@ -114,10 +118,9 @@ public final class PreferencesSubscriptionModel: ObservableObject { self.isUserAuthenticated = subscriptionManager.isUserAuthenticated - if subscriptionManager.isUserAuthenticated { + if self.isUserAuthenticated { Task { await self.updateSubscription(cachePolicy: .returnCacheDataElseLoad) - await self.loadEntitlements(policy: .local) await self.updateAvailableSubscriptionFeatures() } @@ -125,16 +128,17 @@ public final class PreferencesSubscriptionModel: ObservableObject { } signInObserver = NotificationCenter.default.addObserver(forName: .accountDidSignIn, object: nil, queue: .main) { [weak self] _ in - self?.updateUserAuthenticatedState(true) + self?.updateUserAuthenticatedState() } signOutObserver = NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { [weak self] _ in - self?.updateUserAuthenticatedState(false) + self?.updateUserAuthenticatedState() } subscriptionChangeObserver = NotificationCenter.default.addObserver(forName: .subscriptionDidChange, object: nil, queue: .main) { _ in Task { [weak self] in - await self?.updateSubscription(cachePolicy: .returnCacheDataDontLoad) + Logger.general.debug("SubscriptionDidChange notification received") + await self?.updateSubscription(cachePolicy: .returnCacheDataElseLoad) await self?.updateAvailableSubscriptionFeatures() } } @@ -165,9 +169,9 @@ public final class PreferencesSubscriptionModel: ObservableObject { isROWLaunched = featureFlagger.isFeatureOn(.isPrivacyProLaunchedROW) || featureFlagger.isFeatureOn(.isPrivacyProLaunchedROWOverride) } - private func updateUserAuthenticatedState(_ isUserAuthenticated: Bool) { - self.isUserAuthenticated = isUserAuthenticated - self.email = subscriptionManager.userEmail + private func updateUserAuthenticatedState() { + isUserAuthenticated = subscriptionManager.isUserAuthenticated + email = subscriptionManager.userEmail } @MainActor @@ -206,10 +210,12 @@ public final class PreferencesSubscriptionModel: ObservableObject { NSWorkspace.shared.open(subscriptionManager.url(for: .manageSubscriptionsInAppStore)) case .stripe: Task { - guard let customerPortalURL = try? await subscriptionManager.getCustomerPortalURL() else { - return + do { + let customerPortalURL = try await subscriptionManager.getCustomerPortalURL() + openURLHandler(customerPortalURL) + } catch { + Logger.general.log("Error getting customer portal URL: \(error, privacy: .public)") } - openURLHandler(customerPortalURL) } } } @@ -263,7 +269,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { Task { if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, iOS 15.0, *) { - try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) + try await subscriptionManager.getTokenContainer(policy: .localValid) } } @@ -309,22 +315,18 @@ public final class PreferencesSubscriptionModel: ObservableObject { } @MainActor - func fetchAndUpdateSubscriptionDetails() { - self.isUserAuthenticated = subscriptionManager.isUserAuthenticated - Task { [weak self] in - await self?.fetchEmailAndRemoteEntitlements() + private func fetchAndUpdateSubscriptionDetails() { + updateUserAuthenticatedState() + + guard fetchSubscriptionDetailsTask == nil else { return } + + fetchSubscriptionDetailsTask = Task { [weak self] in + defer { + self?.fetchSubscriptionDetailsTask = nil + } + await self?.fetchEmail() await self?.updateSubscription(cachePolicy: .reloadIgnoringLocalCacheData) } -// guard fetchSubscriptionDetailsTask == nil else { return } -// -// fetchSubscriptionDetailsTask = Task { [weak self] in -// defer { -// self?.fetchSubscriptionDetailsTask = nil -// } -// -// await self?.fetchEmailAndRemoteEntitlements() -// await self?.updateSubscription(cachePolicy: .reloadIgnoringLocalCacheData) -// } } private func currentStorefrontRegion() -> SubscriptionRegion { @@ -342,54 +344,46 @@ public final class PreferencesSubscriptionModel: ObservableObject { return region ?? .usa } - private func updateAvailableSubscriptionFeatures() { - let features = currentSubscriptionFeatures() - Task { @MainActor in - shouldShowVPN = features.contains(.networkProtection) - shouldShowDBP = features.contains(.dataBrokerProtection) - shouldShowITR = features.contains(.identityTheftRestoration) || features.contains(.identityTheftRestorationGlobal) - } - } - - private func currentSubscriptionFeatures() -> [SubscriptionEntitlement] { - if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { - return subscriptionManager.currentEntitlements - } else { - return [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - } - } + private func updateAvailableSubscriptionFeatures() async { + let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false) + let vpnFeature = features.first { $0.entitlement == .networkProtection } + let dbpFeature = features.first { $0.entitlement == .dataBrokerProtection } + let itrFeature = features.first { $0.entitlement == .identityTheftRestoration } + let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal } - @MainActor - private func loadEntitlements(policy: TokensCachePolicy) async { - guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: policy) else { - hasAccessToVPN = false - hasAccessToDBP = false - hasAccessToITR = false - return + Task { @MainActor in + // Should show + shouldShowVPN = vpnFeature != nil + shouldShowDBP = dbpFeature != nil + shouldShowITR = itrFeature != nil || itrgFeature != nil + + // is active/enabled + hasAccessToVPN = vpnFeature?.enabled ?? false + hasAccessToDBP = dbpFeature?.enabled ?? false + hasAccessToITR = itrFeature?.enabled ?? false || itrgFeature?.enabled ?? false } - - hasAccessToVPN = tokenContainer.decodedAccessToken.hasEntitlement(.networkProtection) - hasAccessToDBP = tokenContainer.decodedAccessToken.hasEntitlement(.dataBrokerProtection) - - var hasITR = tokenContainer.decodedAccessToken.hasEntitlement(.identityTheftRestoration) - var hasITRGlobal = tokenContainer.decodedAccessToken.hasEntitlement(.identityTheftRestorationGlobal) - hasAccessToITR = hasITR || hasITRGlobal } - @MainActor func fetchEmailAndRemoteEntitlements() async { - await loadEntitlements(policy: .localForceRefresh) - guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local) else { return } - email = tokenContainer.decodedAccessToken.email + @MainActor func fetchEmail() async { + let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local) + email = tokenContainer?.decodedAccessToken.email } @MainActor private func updateSubscription(cachePolicy: SubscriptionCachePolicy) async { - guard let subscription = try? await subscriptionManager.getSubscription(cachePolicy: cachePolicy) else { - return + updateUserAuthenticatedState() + + guard isUserAuthenticated else { return } + + do { + let subscription = try await subscriptionManager.getSubscription(cachePolicy: cachePolicy) + updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) + subscriptionPlatform = subscription.platform + subscriptionStatus = subscription.status + } catch { + subscriptionPlatform = .unknown + subscriptionStatus = .unknown } - updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) - subscriptionPlatform = subscription.platform - subscriptionStatus = subscription.status } @MainActor @@ -418,8 +412,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { private var dateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long +#if DEBUG + dateFormatter.timeStyle = .medium +#else dateFormatter.timeStyle = .none - +#endif return dateFormatter }() } From eda12c92f9ecd137c436965cbe6458b55dcb2adf Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 5 Dec 2024 19:04:35 +0000 Subject: [PATCH 21/59] asd --- DuckDuckGo.xcodeproj/project.pbxproj | 38 ++++++----- .../NetworkProtectionTunnelController.swift | 7 +- .../MacPacketTunnelProvider.swift | 65 +++++++++++++++++-- ...ProtectionKeychainStore+TokenStoring.swift | 42 ++++++++++++ ...iptionManager+StandardConfiguration.swift} | 13 ++-- fastlane/README.md | 10 ++- 6 files changed, 141 insertions(+), 34 deletions(-) create mode 100644 DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift rename DuckDuckGo/Subscription/{SubscriptionManager+StandardConfiguration.swift => DefaultSubscriptionManager+StandardConfiguration.swift} (95%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 33de213b90..4816a87a70 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3198,6 +3198,8 @@ F1AFDBD42C231B9700710F2C /* SubscriptionErrorReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AFDBD32C231B9700710F2C /* SubscriptionErrorReporterTests.swift */; }; F1AFDBD72C23221700710F2C /* SubscriptionErrorReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AFDBD32C231B9700710F2C /* SubscriptionErrorReporterTests.swift */; }; F1AFDBD92C23221700710F2C /* SubscriptionAppStoreRestorerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AFDBD12C231B7A00710F2C /* SubscriptionAppStoreRestorerTests.swift */; }; + F1B09DA82D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B09DA72D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift */; }; + F1B09DA92D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B09DA72D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift */; }; F1B33DF22BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; @@ -3223,14 +3225,12 @@ F1D042902BFB9FA300A31506 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F1D0428F2BFB9FA300A31506 /* Subscription */; }; F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; - F1D042992BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; - F1D0429A2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; - F1D0429B2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; - F1D0429C2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; - F1D0429D2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; - F1D0429E2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; - F1D0429F2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; - F1D042A02BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D042992BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */; }; + F1D0429A2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */; }; + F1D0429D2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */; }; + F1D0429E2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */; }; + F1D0429F2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */; }; + F1D042A02BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */; }; F1D042A12BFBB4DD00A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; F1D042A22BFBB4DD00A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; F1D43AEE2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */; }; @@ -5016,6 +5016,7 @@ F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPixels.swift; sourceTree = ""; }; F1AFDBD12C231B7A00710F2C /* SubscriptionAppStoreRestorerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorerTests.swift; sourceTree = ""; }; F1AFDBD32C231B9700710F2C /* SubscriptionErrorReporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporterTests.swift; sourceTree = ""; }; + F1B09DA72D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtectionKeychainStore+TokenStoring.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 = ""; }; @@ -5024,7 +5025,7 @@ F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; F1CA67052C7DCA2300264E6A /* Logger+BitWarden.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+BitWarden.swift"; sourceTree = ""; }; F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataBrokerProtectionSettings+Environment.swift"; sourceTree = ""; }; - F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionManager+StandardConfiguration.swift"; sourceTree = ""; }; + F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionManager+StandardConfiguration.swift"; sourceTree = ""; }; 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 = ""; }; @@ -6453,6 +6454,7 @@ EEBCA0C12BD7CDDA004DF19C /* Pixels */, 4B41ED9F2B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift */, EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */, + F1B09DA72D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift */, 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */, ); path = NetworkExtensionTargets; @@ -9910,7 +9912,7 @@ F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */, F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */, - F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */, + F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */, F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */, F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, 1E25A4FD2CC937120080EFD4 /* SubscriptionCookieManageEventPixelMapping.swift */, @@ -11522,7 +11524,7 @@ 85774B042A71CDD000DE0561 /* BlockMenuItem.swift in Sources */, 3706FB19293F65D500E42796 /* FireViewController.swift in Sources */, B6E3E55C2BC0041A00A41922 /* DownloadListStoreMock.swift in Sources */, - F1D0429A2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, + F1D0429A2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 4B4D60D42A0C84F700BCD287 /* UserText+NetworkProtection.swift in Sources */, 3707C71F294B5D2900682A9F /* WKUserContentControllerExtension.swift in Sources */, 3706FB1A293F65D500E42796 /* OutlineSeparatorViewCell.swift in Sources */, @@ -12699,6 +12701,7 @@ 4B52354D2C854CB600AFAF64 /* DuckDuckGoUserAgent.swift in Sources */, EEBCA0C72BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, F1DA51932BF6081D00CF29FA /* AttributionPixelHandler.swift in Sources */, + F1B09DA92D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift in Sources */, 7B2E52252A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift in Sources */, F1DA51892BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, B602E8232A1E260E006D261F /* Bundle+NetworkProtectionExtensions.swift in Sources */, @@ -12713,7 +12716,6 @@ F1C70D7F2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */, 4B2D06292A11C0C900DE1F49 /* Bundle+VPN.swift in Sources */, - F1D0429C2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, F1DA51972BF6083A00CF29FA /* PrivacyProPixel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -12734,7 +12736,7 @@ 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, 02FDA6592C764B970024CD8B /* ConfigurationManager.swift in Sources */, 7BA7CC4C2AD11EC70042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */, - F1D0429D2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, + F1D0429D2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 7B8DB31A2B504D7500EC16DA /* VPNAppEventsHandler.swift in Sources */, 02FDA65F2C764E220024CD8B /* VPNPrivacyConfigurationManager.swift in Sources */, 7BA7CC532AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */, @@ -12780,7 +12782,7 @@ 7B22D8702CCFD7B7006A76E1 /* TipKitController.swift in Sources */, F1DA51952BF6081E00CF29FA /* AttributionPixelHandler.swift in Sources */, 4BA7C4D92B3F61FB00AFE511 /* BundleExtension.swift in Sources */, - F1D0429E2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, + F1D0429E2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 02FDA65C2C764CB00024CD8B /* ConfigurationManager.swift in Sources */, 7BA7CC3F2AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, 4B0EF7292B5780EB009D6481 /* VPNAppEventsHandler.swift in Sources */, @@ -12840,9 +12842,9 @@ F1DA51962BF6083700CF29FA /* PrivacyProPixel.swift in Sources */, EEBCA0C62BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, 4B4D60A12A0B2D6100BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */, - F1D0429B2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, B602E8182A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, B65DA5F52A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, + F1B09DA82D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift in Sources */, 4B52354E2C854CB700AFAF64 /* DuckDuckGoUserAgent.swift in Sources */, 4B4D60892A0B2A1C00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */, F1C70D7E2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, @@ -12942,7 +12944,7 @@ 9D9AE91D2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, - F1D0429F2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, + F1D0429F2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0E2BED317300AE679F /* BundleExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -12960,7 +12962,7 @@ 9D9AE91E2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, - F1D042A02BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, + F1D042A02BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0F2BED317300AE679F /* BundleExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -12977,7 +12979,7 @@ 4B9DB0412A983B24000927DB /* WaitlistDialogView.swift in Sources */, 37D2377A287EB8CA00BCE03B /* TabIndex.swift in Sources */, B60C6F8D29B200AB007BFAA8 /* SavePanelAccessoryView.swift in Sources */, - F1D042992BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, + F1D042992BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 37534CA3281132CB002621E7 /* TabLazyLoaderDataSource.swift in Sources */, 84537A042C998C28008723BC /* FireWindowSession.swift in Sources */, 3158B15C2B0BF76D00AF130C /* DataBrokerProtectionAppEvents.swift in Sources */, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index bdbcf7b74a..56f3218346 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -573,7 +573,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr // It's important to note that we've seen instances where the above call to start() // doesn't throw any errors, yet the tunnel fails to start. In any case this pixel - // should be interpreted as "the controller successfully requrested the tunnel to be + // should be interpreted as "the controller successfully requested the tunnel to be // started". Meaning there's no error caught in this start attempt. There are pixels // in the packet tunnel provider side that can be used to debug additional logic. // @@ -652,7 +652,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr Logger.networkProtection.debug("Starting NetworkProtectionTunnelController, options: \(options, privacy: .public)") try tunnelManager.connection.startVPNTunnel(options: options) } catch { - Logger.networkProtection.fault("Failed to start VPN tunnel: \(error, privacy: .public)") + Logger.networkProtection.error("Failed to start VPN tunnel: \(error, privacy: .public)") throw StartError.startTunnelFailure(error) } @@ -669,6 +669,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr /// @MainActor func stop() async { + Logger.networkProtection.log("Stop VPN") await stop(disableOnDemand: true) } @@ -802,7 +803,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr Logger.networkProtection.log("🟢 TunnelController found token container") return tokenContainer } catch { - Logger.networkProtection.error("TunnelController found no token container") + Logger.networkProtection.fault("TunnelController found no token container") throw StartError.noAuthToken } } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index e92b92c443..103e051977 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -395,6 +395,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - Initialization + let subscriptionManager: any SubscriptionManager + @MainActor @objc public init() { Logger.networkProtection.log("Initializing MacPacketTunnelProvider") #if NETP_SYSTEM_EXTENSION @@ -424,21 +426,76 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { subscriptionEnvironment.purchasePlatform = .stripe // we don't care about the purchasePlatform Logger.networkProtection.debug("Subscription ServiceEnvironment: \(subscriptionEnvironment.serviceEnvironment.rawValue, privacy: .public)") + let configuration = URLSessionConfiguration.default + configuration.httpCookieStorage = nil + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + let urlSession = URLSession(configuration: configuration, delegate: SessionDelegate(), delegateQueue: nil) + let apiService = DefaultAPIService(urlSession: urlSession) + let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging + + let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) +#if NETP_SYSTEM_EXTENSION + let tokenServiceName = "\(Bundle.main.bundleIdentifier!).authToken" +#else + let tokenServiceName = "com.duckduckgo.networkprotection.authToken" +#endif + + let tokenStorage = NetworkProtectionKeychainStore(label: "DuckDuckGo Network Protection Auth Token", + serviceName: tokenServiceName, + keychainType: Bundle.keychainType) + + // TEST +// final class TemporaryTokenStorage: TokenStoring { +// var tokenContainer: Networking.TokenContainer? +// } +// let tokenStorage = TemporaryTokenStorage() + + let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, authService: authService) + apiService.authorizationRefresherCallback = { _ in + guard let tokenContainer = tokenStorage.tokenContainer else { + throw OAuthClientError.internalError("Missing refresh token") + } + if tokenContainer.decodedAccessToken.isExpired() { + Logger.networkProtection.debug("Refreshing tokens") + let tokens = try await authClient.getTokens(policy: .localForceRefresh) + return tokens.accessToken + } else { + Logger.networkProtection.error("Trying to refresh valid token, using the old one") + return tokenContainer.accessToken + } + } + let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, + baseURL: subscriptionEnvironment.serviceEnvironment.url) + let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, + userDefaults: defaults) + let pixelHandler: SubscriptionManager.PixelHandler = { type in + switch type { + case .deadToken: + PixelKit.fire(SubscriptionPixels.privacyProDeadTokenDetected) + } + } + + let subscriptionManager = DefaultSubscriptionManager(oAuthClient: authClient, + subscriptionEndpointService: subscriptionEndpointService, + subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, + subscriptionEnvironment: subscriptionEnvironment, + subscriptionFeatureFlagger: nil, + pixelHandler: pixelHandler) - let subscriptionManager = DefaultSubscriptionManager(keychainType: Bundle.keychainType, // note: the old public static let tokenStoreService = "com.duckduckgo.networkprotection.authToken" was used as kSecAttrService in NetworkProtectionKeychainStore, now is different. the old token recovery will not work in the extension, yes in main app - environment: subscriptionEnvironment, - userDefaults: defaults) + // MARK: - let entitlementsCheck: (() async -> Result) = { Logger.networkProtection.log("Entitlements check...") let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) - Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled" , privacy: .public)") + Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)") return .success(isNetworkProtectionEnabled) } let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) let notificationsPresenter = NetworkProtectionNotificationsPresenterFactory().make(settings: settings, defaults: defaults) + self.subscriptionManager = subscriptionManager + super.init(notificationsPresenter: notificationsPresenter, tunnelHealthStore: tunnelHealthStore, controllerErrorStore: controllerErrorStore, diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift new file mode 100644 index 0000000000..8f3a9ce15a --- /dev/null +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift @@ -0,0 +1,42 @@ +// +// NetworkProtectionKeychainStore+TokenStoring.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 NetworkProtection +import Networking + +extension NetworkProtectionKeychainStore: @retroactive TokenStoring { + static var name = "com.duckduckgo.networkprotection.token" + + public var tokenContainer: Networking.TokenContainer? { + get { + if let data = try? readData(named: Self.name) as? NSData { + return try? TokenContainer(with: data) + } + return nil + } + set(newValue) { + if newValue == nil { + try? deleteAll() + } else if let data = newValue?.data as? Data { + try? writeData(data, named: Self.name) + } + } + } +} diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift similarity index 95% rename from DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift rename to DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index 0ecdf18769..3ddd6a6a31 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -1,5 +1,5 @@ // -// SubscriptionManager+StandardConfiguration.swift +// DefaultSubscriptionManager+StandardConfiguration.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // @@ -24,9 +24,9 @@ import Networking import os.log import BrowserServicesKit import FeatureFlags +import NetworkProtection extension DefaultSubscriptionManager { - // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root public convenience init(keychainType: KeychainType, environment: SubscriptionEnvironment, @@ -41,15 +41,12 @@ extension DefaultSubscriptionManager { delegateQueue: nil) let apiService = DefaultAPIService(urlSession: urlSession) let authEnvironment: OAuthEnvironment = environment.serviceEnvironment == .production ? .production : .staging - let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: keychainType) -// let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: keychainType) - + let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: keychainType) let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, -// legacyTokenStorage: legacyAccountStorage, + legacyTokenStorage: legacyAccountStorage, authService: authService) - apiService.authorizationRefresherCallback = { _ in guard let tokenContainer = tokenStorage.tokenContainer else { throw OAuthClientError.internalError("Missing refresh token") @@ -67,7 +64,7 @@ extension DefaultSubscriptionManager { let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: environment.serviceEnvironment.url) -// let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! +// let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! // main app let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, userDefaults: userDefaults) let subscriptionFeatureFlagger: FeatureFlaggerMapping = FeatureFlaggerMapping { feature in diff --git a/fastlane/README.md b/fastlane/README.md index 95cba385c1..1d4f0ea94c 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -69,7 +69,7 @@ Makes App Store release build and uploads it to TestFlight [bundle exec] fastlane mac promote_latest_testflight_to_appstore ``` -Promotes the latest testflight build to appstore without submitting for review +Promotes the latest TestFlight build to App Store without submitting for review ### mac release_appstore @@ -135,6 +135,14 @@ Updates embedded files and pushes to remote. Executes the release preparation work in the repository +### mac create_keychain_ui_tests + +```sh +[bundle exec] fastlane mac create_keychain_ui_tests +``` + +Creates a new Kechain to use on UI tests + ---- This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. From b1a42fc4732ec63bd6ffa297763c2439a80f9d5e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 5 Dec 2024 20:13:44 +0000 Subject: [PATCH 22/59] reverting minor changes --- .../MacPacketTunnelProvider.swift | 49 +++++++------------ ...ProtectionKeychainStore+TokenStoring.swift | 2 +- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 103e051977..9f4d8a56b4 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -29,18 +29,6 @@ import WireGuard final class MacPacketTunnelProvider: PacketTunnelProvider { - static var isAppex: Bool { // IS APP STORE VERSION -#if NETP_SYSTEM_EXTENSION - false -#else - true -#endif - } - - fileprivate static var subscriptionsAppGroup: String? { - isAppex ? Bundle.main.appGroup(bundle: .subs) : nil - } - // MARK: - Additional Status Info /// Holds the date when the status was last changed so we can send it out as additional information @@ -393,12 +381,20 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } } + static var tokenServiceName: String { +#if NETP_SYSTEM_EXTENSION + "\(Bundle.main.bundleIdentifier!).authToken" +#else + "com.duckduckgo.networkprotection.authToken" +#endif + } + // MARK: - Initialization let subscriptionManager: any SubscriptionManager @MainActor @objc public init() { - Logger.networkProtection.log("Initializing MacPacketTunnelProvider") + Logger.networkProtection.log("[+] MacPacketTunnelProvider") #if NETP_SYSTEM_EXTENSION let defaults = UserDefaults.standard #else @@ -434,22 +430,10 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) -#if NETP_SYSTEM_EXTENSION - let tokenServiceName = "\(Bundle.main.bundleIdentifier!).authToken" -#else - let tokenServiceName = "com.duckduckgo.networkprotection.authToken" -#endif let tokenStorage = NetworkProtectionKeychainStore(label: "DuckDuckGo Network Protection Auth Token", - serviceName: tokenServiceName, + serviceName: Self.tokenServiceName, keychainType: Bundle.keychainType) - - // TEST -// final class TemporaryTokenStorage: TokenStoring { -// var tokenContainer: Networking.TokenContainer? -// } -// let tokenStorage = TemporaryTokenStorage() - let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, authService: authService) apiService.authorizationRefresherCallback = { _ in guard let tokenContainer = tokenStorage.tokenContainer else { @@ -458,10 +442,10 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { if tokenContainer.decodedAccessToken.isExpired() { Logger.networkProtection.debug("Refreshing tokens") let tokens = try await authClient.getTokens(policy: .localForceRefresh) - return tokens.accessToken + return VPNAuthTokenBuilder.getVPNAuthToken(from: tokens.accessToken) } else { Logger.networkProtection.error("Trying to refresh valid token, using the old one") - return tokenContainer.accessToken + return VPNAuthTokenBuilder.getVPNAuthToken(from: tokenContainer.accessToken) } } let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, @@ -485,10 +469,11 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - let entitlementsCheck: (() async -> Result) = { - Logger.networkProtection.log("Entitlements check...") - let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) - Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)") - return .success(isNetworkProtectionEnabled) +// Logger.networkProtection.log("Entitlements check...") +// let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) +// Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)") +// return .success(isNetworkProtectionEnabled) + return .success(true) } let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift index 8f3a9ce15a..e6c331f9fb 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionKeychainStore+TokenStoring.swift @@ -22,7 +22,7 @@ import NetworkProtection import Networking extension NetworkProtectionKeychainStore: @retroactive TokenStoring { - static var name = "com.duckduckgo.networkprotection.token" + static var name = "com.duckduckgo.networkprotection.tokenContainer" public var tokenContainer: Networking.TokenContainer? { get { From 97d16815ed7dae9cbeb1e4d5d4524743239fb2a9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 6 Dec 2024 14:28:58 +0000 Subject: [PATCH 23/59] subscription purchase notification fixed --- DuckDuckGo.xcodeproj/project.pbxproj | 16 ++++++ DuckDuckGo/Application/AppDelegate.swift | 4 +- .../MacPacketTunnelProvider.swift | 6 +-- ...vice+SubscriptionFeatureMappingCache.swift | 35 +++++++++++++ ...riptionManager+StandardConfiguration.swift | 10 ++-- ...scriptionPagesUseSubscriptionFeature.swift | 1 + .../PreferencesSubscriptionModel.swift | 49 ++++++++++++------- 7 files changed, 94 insertions(+), 27 deletions(-) create mode 100644 DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c2a95970cd..ef8b932c2c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3160,6 +3160,13 @@ F118EA7E2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; F118EA852BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; F118EA862BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; + F11B3CA82D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; + F11B3CA92D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; + F11B3CAA2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; + F11B3CAB2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; + F11B3CAC2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; + F11B3CAD2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; + F11B3CAE2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; F1476FC02C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; F1476FC12C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; F14E5D562CFE1BE200B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; @@ -5014,6 +5021,7 @@ EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; + F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift"; sourceTree = ""; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; F14E5D522CFE024D00B91BE6 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; @@ -9938,6 +9946,7 @@ F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */, F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */, F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */, + F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */, F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, 1E25A4FD2CC937120080EFD4 /* SubscriptionCookieManageEventPixelMapping.swift */, F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */, @@ -11562,6 +11571,7 @@ 319FCFF22CC81D54004F9288 /* AIChatRemoteSettings.swift in Sources */, 3706FB22293F65D500E42796 /* NSTextViewExtension.swift in Sources */, 3706FB23293F65D500E42796 /* DownloadsCellView.swift in Sources */, + F11B3CA92D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 3706FB25293F65D500E42796 /* PublishedAfter.swift in Sources */, 9FBB0C062CBD223D0006B6A6 /* ViewHighlighter.swift in Sources */, BBB881892C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift in Sources */, @@ -12753,6 +12763,7 @@ 7BD7B0032C19D3830039D20A /* VPNIPCResources.swift in Sources */, 7B22D86F2CCFD7B7006A76E1 /* TipKitController.swift in Sources */, F1DA51942BF6081E00CF29FA /* AttributionPixelHandler.swift in Sources */, + F11B3CAB2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, 02FDA6592C764B970024CD8B /* ConfigurationManager.swift in Sources */, @@ -12802,6 +12813,7 @@ 7BD7B0042C19D3830039D20A /* VPNIPCResources.swift in Sources */, 7B22D8702CCFD7B7006A76E1 /* TipKitController.swift in Sources */, F1DA51952BF6081E00CF29FA /* AttributionPixelHandler.swift in Sources */, + F11B3CAC2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 4BA7C4D92B3F61FB00AFE511 /* BundleExtension.swift in Sources */, F1D0429E2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 02FDA65C2C764CB00024CD8B /* ConfigurationManager.swift in Sources */, @@ -12845,6 +12857,7 @@ files = ( 4B4BEC3D2A11B56B001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift in Sources */, 4B4BEC412A11B5BD001D9AC5 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */, + F11B3CAA2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 4B4BEC432A11B5C7001D9AC5 /* Bundle+VPN.swift in Sources */, 4B4BEC452A11B5EE001D9AC5 /* UserText+NetworkProtectionExtensions.swift in Sources */, 4B4BEC422A11B5C7001D9AC5 /* NetworkProtectionOptionKeyExtension.swift in Sources */, @@ -12968,6 +12981,7 @@ 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, F1D0429F2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0E2BED317300AE679F /* BundleExtension.swift in Sources */, + F11B3CAD2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12986,6 +13000,7 @@ 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, F1D042A02BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0F2BED317300AE679F /* BundleExtension.swift in Sources */, + F11B3CAE2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -13169,6 +13184,7 @@ 85589E8727BBB8F20038AD11 /* HomePageFavoritesModel.swift in Sources */, 4BB88B4A25B7B690006F6B06 /* SequenceExtensions.swift in Sources */, BDBA85932C5D255200BC54F5 /* VPNFeedbackCategory.swift in Sources */, + F11B3CA82D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, B602E7CF2A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */, 4B59024026B35F3600489384 /* ChromiumDataImporter.swift in Sources */, B62B48562ADE730D000DECE5 /* FileImportView.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 1b27387b9c..4bff5607c5 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -376,6 +376,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { didFinishLaunching = true } + subscriptionManager.loadInitialData() + HistoryCoordinator.shared.loadHistory { HistoryCoordinator.shared.migrateModelV5toV6IfNeeded() } @@ -410,8 +412,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { startupSync() - subscriptionManager.loadInitialData() - let privacyConfigurationManager = ContentBlocking.shared.privacyConfigurationManager // Enable subscriptionCookieManager if feature flag is present diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 9f4d8a56b4..1f2e577d88 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -450,8 +450,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) - let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, - userDefaults: defaults) +// let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, +// userDefaults: defaults) let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { case .deadToken: @@ -461,7 +461,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let subscriptionManager = DefaultSubscriptionManager(oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, +// subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: subscriptionEnvironment, subscriptionFeatureFlagger: nil, pixelHandler: pixelHandler) diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift b/DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift new file mode 100644 index 0000000000..b530684053 --- /dev/null +++ b/DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift @@ -0,0 +1,35 @@ +// +// DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.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 Networking +import os.log + +extension DefaultSubscriptionEndpointService: @retroactive SubscriptionFeatureMappingCache { + + public func subscriptionFeatures(for subscriptionIdentifier: String) async -> [Networking.SubscriptionEntitlement] { + do { + let response = try await getSubscriptionFeatures(for: subscriptionIdentifier) + return response.features + } catch { + Logger.subscription.error("Failed to get subscription features: \(error)") + return [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] + } + } +} diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index 3ddd6a6a31..f3472116bf 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -65,8 +65,8 @@ extension DefaultSubscriptionManager { let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: environment.serviceEnvironment.url) // let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! // main app - let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, - userDefaults: userDefaults) +// let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, +// userDefaults: userDefaults) let subscriptionFeatureFlagger: FeatureFlaggerMapping = FeatureFlaggerMapping { feature in guard let featureFlagger else { // With no featureFlagger provided there is no gating of features @@ -96,18 +96,18 @@ extension DefaultSubscriptionManager { } if #available(macOS 12.0, *) { - self.init(storePurchaseManager: DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, + self.init(storePurchaseManager: DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionEndpointService, subscriptionFeatureFlagger: subscriptionFeatureFlagger), oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, +// subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: environment, subscriptionFeatureFlagger: subscriptionFeatureFlagger, pixelHandler: pixelHandler) } else { self.init(oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, +// subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: environment, subscriptionFeatureFlagger: subscriptionFeatureFlagger, pixelHandler: pixelHandler) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index d859a8a1ce..4d4d4c1520 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -225,6 +225,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // Check for active subscriptions if await subscriptionManager.storePurchaseManager().hasActiveSubscription() { + // Sandbox note: Looks like our BE is not receiving updates when a subscription transitions from grace period to expired, so during testing we can end up with a subscription in grace period and we will not be able to purchase a new one, only restore it because Transaction.currentEntitlements will not return the subscription to restore. PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) Logger.subscription.log("[Purchase] Found active subscription during purchase") subscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 4547e639a3..4d35b647cb 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -62,6 +62,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { private var signInObserver: Any? private var signOutObserver: Any? + private var entitlementsObserver: Any? private var subscriptionChangeObserver: Any? public enum UserEvent { @@ -119,9 +120,8 @@ public final class PreferencesSubscriptionModel: ObservableObject { self.isUserAuthenticated = subscriptionManager.isUserAuthenticated if self.isUserAuthenticated { - Task { - await self.updateSubscription(cachePolicy: .returnCacheDataElseLoad) - await self.updateAvailableSubscriptionFeatures() + Task { [weak self] in + await self?.updateSubscription(cachePolicy: .returnCacheDataElseLoad) } self.email = subscriptionManager.userEmail @@ -138,7 +138,12 @@ public final class PreferencesSubscriptionModel: ObservableObject { subscriptionChangeObserver = NotificationCenter.default.addObserver(forName: .subscriptionDidChange, object: nil, queue: .main) { _ in Task { [weak self] in Logger.general.debug("SubscriptionDidChange notification received") - await self?.updateSubscription(cachePolicy: .returnCacheDataElseLoad) + await self?.updateSubscription(cachePolicy: .returnCacheDataDontLoad) + } + } + + entitlementsObserver = NotificationCenter.default.addObserver(forName: .entitlementsDidChange, object: nil, queue: .main) { [weak self] _ in + Task { [weak self] in await self?.updateAvailableSubscriptionFeatures() } } @@ -156,6 +161,10 @@ public final class PreferencesSubscriptionModel: ObservableObject { if let subscriptionChangeObserver { NotificationCenter.default.removeObserver(subscriptionChangeObserver) } + + if let entitlementsObserver { + NotificationCenter.default.removeObserver(entitlementsObserver) + } } @MainActor @@ -170,8 +179,10 @@ public final class PreferencesSubscriptionModel: ObservableObject { } private func updateUserAuthenticatedState() { - isUserAuthenticated = subscriptionManager.isUserAuthenticated - email = subscriptionManager.userEmail + Task { @MainActor in + isUserAuthenticated = subscriptionManager.isUserAuthenticated + email = subscriptionManager.userEmail + } } @MainActor @@ -369,20 +380,24 @@ public final class PreferencesSubscriptionModel: ObservableObject { email = tokenContainer?.decodedAccessToken.email } - @MainActor private func updateSubscription(cachePolicy: SubscriptionCachePolicy) async { updateUserAuthenticatedState() - guard isUserAuthenticated else { return } - - do { - let subscription = try await subscriptionManager.getSubscription(cachePolicy: cachePolicy) - updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) - subscriptionPlatform = subscription.platform - subscriptionStatus = subscription.status - } catch { - subscriptionPlatform = .unknown - subscriptionStatus = .unknown + if isUserAuthenticated { + do { + let subscription = try await subscriptionManager.getSubscription(cachePolicy: cachePolicy) + Task { @MainActor in + updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) + subscriptionPlatform = subscription.platform + subscriptionStatus = subscription.status + } + } catch { + Task { @MainActor in + subscriptionPlatform = .unknown + subscriptionStatus = .unknown + } + } + await self.updateAvailableSubscriptionFeatures() } } From d313d3278e093a1c22d51dd22469d0adb7463f88 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 6 Dec 2024 16:16:20 +0000 Subject: [PATCH 24/59] unit tests fixes --- ...BrokerProtectionAuthenticationManaging.swift | 2 +- ...erProtectionAuthenticationManagerTests.swift | 4 ++-- .../Tests/DataBrokerProtectionTests/Mocks.swift | 5 +---- .../MaliciousSiteProtectionTests.swift | 2 +- ...esUseSubscriptionFeatureForStripeTests.swift | 15 +++++++-------- ...iptionPagesUseSubscriptionFeatureTests.swift | 17 ++++++++--------- 6 files changed, 20 insertions(+), 25 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift index 69953fa2f5..c8b26c1964 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift @@ -47,7 +47,7 @@ public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtecti } public func hasValidEntitlement() async -> Bool { - await subscriptionManager.isFeatureActive(.dataBrokerProtection) //isEntitlementActive(.dataBrokerProtection) + await subscriptionManager.isFeatureActive(.dataBrokerProtection) } public func getAuthHeader() -> String? { diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift index c5014a3143..35dcc757bc 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift @@ -72,7 +72,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - let result = authenticationManager.hasValidEntitlement() + let result = await authenticationManager.hasValidEntitlement() XCTAssertTrue(result, "Entitlement check should return true for valid entitlement") } @@ -81,7 +81,7 @@ class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { authenticationManager = DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - let result = authenticationManager.hasValidEntitlement() + let result = await authenticationManager.hasValidEntitlement() XCTAssertFalse(result, "Entitlement check should return false for valid entitlement") } } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index 941f469659..e048d0b7bf 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -1657,10 +1657,7 @@ final class MockAuthenticationManager: DataBrokerProtectionAuthenticationManagin var accessToken: String? { accessTokenValue } - func hasValidEntitlement() async throws -> Bool { - if shouldThrowEntitlementError { - throw NSError(domain: "duck.com", code: 0, userInfo: [NSLocalizedDescriptionKey: "Error"]) - } + func hasValidEntitlement() async -> Bool { return hasValidEntitlementValue } diff --git a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift index 2618d6e8d1..68bd0df609 100644 --- a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift +++ b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift @@ -35,7 +35,7 @@ final class MaliciousSiteProtectionTests: XCTestCase { var dataManager: MaliciousSiteProtection.DataManager! override func setUp() async throws { - apiService = MockAPIService(apiResponse: .failure(CancellationError())) + apiService = MockAPIService()//apiResponse: .failure(CancellationError())) let mockFileStore = MockMaliciousSiteFileStore() mockDataProvider = MockMaliciousSiteDataProvider() dataManager = MaliciousSiteProtection.DataManager(fileStore: mockFileStore, embeddedDataProvider: mockDataProvider, fileNameProvider: { _ in "file.json" }) diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index 39a7cfe112..0c44fb5625 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -58,14 +58,13 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { price: "99", currency: "USD")] - static let subscriptionOptions = SubscriptionOptions(platform: SubscriptionPlatformName.stripe, - options: [ - SubscriptionOption(id: "1", - cost: SubscriptionOptionCost(displayPrice: "$9.00", recurrence: "monthly")), - SubscriptionOption(id: "2", - cost: SubscriptionOptionCost(displayPrice: "$99.00", recurrence: "yearly")) - ], - features: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) + static let subscriptionOptions = SubscriptionOptions( + platform: SubscriptionPlatformName.stripe, + options: [ + SubscriptionOption(id: "1", cost: SubscriptionOptionCost(displayPrice: "$9.00", recurrence: "monthly")), + SubscriptionOption(id: "2", cost: SubscriptionOptionCost(displayPrice: "$99.00", recurrence: "yearly")) + ], + availableEntitlements: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) static let mockParams: [String: String] = [:] @MainActor static let mockScriptMessage = MockWKScriptMessage(name: "", body: "", webView: WKWebView() ) } diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 9a340d9d76..3b25fc9de8 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -40,16 +40,15 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { static let entitlements: [SubscriptionEntitlement] = [.dataBrokerProtection, .identityTheftRestoration, .networkProtection] - + static let mostRecentTransactionJWS = "dGhpcyBpcyBub3QgYSByZWFsIEFw(...)cCBTdG9yZSB0cmFuc2FjdGlvbiBKV1M=" - static let subscriptionOptions = SubscriptionOptions(platform: SubscriptionPlatformName.macos, - options: [ - SubscriptionOption(id: "1", - cost: SubscriptionOptionCost(displayPrice: "9 USD", recurrence: "monthly")), - SubscriptionOption(id: "2", - cost: SubscriptionOptionCost(displayPrice: "99 USD", recurrence: "yearly")) - ], - features: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) + static let subscriptionOptions = SubscriptionOptions( + platform: SubscriptionPlatformName.macos, + options: [ + SubscriptionOption(id: "1", cost: SubscriptionOptionCost(displayPrice: "9 USD", recurrence: "monthly")), + SubscriptionOption(id: "2", cost: SubscriptionOptionCost(displayPrice: "99 USD", recurrence: "yearly")) + ], + availableEntitlements: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) static let mockParams: [String: String] = [:] @MainActor static let mockScriptMessage = MockWKScriptMessage(name: "", body: "", webView: WKWebView() ) } From 7bad6bddd3ca3e048664361a1fcb522ec3164d03 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 6 Dec 2024 16:58:58 +0000 Subject: [PATCH 25/59] unit tests --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../xcshareddata/swiftpm/Package.resolved | 36 +++++-------------- ...okerProtectionFeatureGatekeeperTests.swift | 5 +-- 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ef8b932c2c..44ab6c7150 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -5023,7 +5023,6 @@ F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift"; sourceTree = ""; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; - F14E5D522CFE024D00B91BE6 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DBPBackgroundAgent.swift"; sourceTree = ""; }; @@ -7951,7 +7950,6 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( - F14E5D522CFE024D00B91BE6 /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1acda16177..2c9bcdc0b6 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "branch" : "fcappelli/subscription_oauth_api_v2", + "revision" : "2a2fb68f1a1431702689ddfe389e1200099fdb5e" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -144,24 +153,6 @@ "version" : "1.3.0" } }, - { - "identity" : "swift-clocks", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-clocks.git", - "state" : { - "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53", - "version" : "1.0.5" - } - }, - { - "identity" : "swift-concurrency-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", - "state" : { - "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f", - "version" : "1.3.0" - } - }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -224,15 +215,6 @@ "revision" : "13fd026384b1af11048451061cc1b21434990668", "version" : "1.1.3" } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1", - "version" : "1.4.3" - } } ], "version" : 2 diff --git a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift index 6652f9303d..ea0f3c1c50 100644 --- a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift +++ b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift @@ -17,8 +17,8 @@ // import XCTest -import BrowserServicesKit -import Subscription +@testable import BrowserServicesKit +@testable import Subscription import TestUtils import SubscriptionTestingUtilities @@ -112,6 +112,7 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { func testWhenAccessTokenAndEntitlementAreFound_andIsNotActiveFreemiumUser_thenFeatureIsEnabled() async { // Given mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + mockSubscriptionManager.resultFeatures = [ SubscriptionFeature(entitlement: .dataBrokerProtection, enabled: true) ] mockFreemiumDBPUserStateManager.didActivate = false sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), From 828fd5f37d331f32511ada5e4e5d06989daf10cb Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 6 Dec 2024 18:42:06 +0000 Subject: [PATCH 26/59] lint and bsk update --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../MaliciousSiteProtection/MaliciousSiteProtectionTests.swift | 2 +- .../SubscriptionPagesUseSubscriptionFeatureTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2c9bcdc0b6..2d4dc63844 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "2a2fb68f1a1431702689ddfe389e1200099fdb5e" + "revision" : "07e8b56fe52f08c673a41896882aa31fab25fc34" } }, { diff --git a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift index 68bd0df609..2a123c1146 100644 --- a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift +++ b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift @@ -35,7 +35,7 @@ final class MaliciousSiteProtectionTests: XCTestCase { var dataManager: MaliciousSiteProtection.DataManager! override func setUp() async throws { - apiService = MockAPIService()//apiResponse: .failure(CancellationError())) + apiService = MockAPIService() let mockFileStore = MockMaliciousSiteFileStore() mockDataProvider = MockMaliciousSiteDataProvider() dataManager = MaliciousSiteProtection.DataManager(fileStore: mockFileStore, embeddedDataProvider: mockDataProvider, fileNameProvider: { _ in "file.json" }) diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 3b25fc9de8..bf822856e4 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -40,7 +40,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { static let entitlements: [SubscriptionEntitlement] = [.dataBrokerProtection, .identityTheftRestoration, .networkProtection] - + static let mostRecentTransactionJWS = "dGhpcyBpcyBub3QgYSByZWFsIEFw(...)cCBTdG9yZSB0cmFuc2FjdGlvbiBKV1M=" static let subscriptionOptions = SubscriptionOptions( platform: SubscriptionPlatformName.macos, From 22d929035bb7afe820de41d8c85115e5e9c9672f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 6 Dec 2024 18:51:23 +0000 Subject: [PATCH 27/59] unit test fix --- .../MaliciousSiteProtection/MaliciousSiteProtectionTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift index 2a123c1146..c0b8b09601 100644 --- a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift +++ b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift @@ -35,7 +35,7 @@ final class MaliciousSiteProtectionTests: XCTestCase { var dataManager: MaliciousSiteProtection.DataManager! override func setUp() async throws { - apiService = MockAPIService() + apiService = MockAPIService(requestHandler: { request in .failure(CancellationError()) }) let mockFileStore = MockMaliciousSiteFileStore() mockDataProvider = MockMaliciousSiteDataProvider() dataManager = MaliciousSiteProtection.DataManager(fileStore: mockFileStore, embeddedDataProvider: mockDataProvider, fileNameProvider: { _ in "file.json" }) From b21b30d5608cd0ea7b267e9a654607764ae591d9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 12 Dec 2024 12:59:42 +0000 Subject: [PATCH 28/59] bsk update --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../xcshareddata/swiftpm/Package.resolved | 36 +++++-------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8046c91ff8..3243b43dcd 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -4979,7 +4979,6 @@ F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift"; sourceTree = ""; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; - F169CDE82D0AF4890075AFBF /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DBPBackgroundAgent.swift"; sourceTree = ""; }; @@ -7879,7 +7878,6 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( - F169CDE82D0AF4890075AFBF /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 623df4192b..ee70e575ab 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "branch" : "fcappelli/subscription_oauth_api_v2", + "revision" : "2cd4babf45be6c7cfdd879a38712327a994eda8c" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -144,24 +153,6 @@ "version" : "1.3.0" } }, - { - "identity" : "swift-clocks", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-clocks.git", - "state" : { - "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53", - "version" : "1.0.5" - } - }, - { - "identity" : "swift-concurrency-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", - "state" : { - "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f", - "version" : "1.3.0" - } - }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -224,15 +215,6 @@ "revision" : "13fd026384b1af11048451061cc1b21434990668", "version" : "1.1.3" } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1", - "version" : "1.4.3" - } } ], "version" : 2 From 8002ec0daec2798404da40cbb6525daa278b7f47 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 12 Dec 2024 14:35:20 +0000 Subject: [PATCH 29/59] bsk update --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../MacPacketTunnelProvider.swift | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ee70e575ab..c82dd6d1a0 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "2cd4babf45be6c7cfdd879a38712327a994eda8c" + "revision" : "50d4d64cba660ee779d987585b6c866d5b710057" } }, { diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 1f2e577d88..5d13537d59 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -450,8 +450,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) -// let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, -// userDefaults: defaults) let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { case .deadToken: @@ -461,7 +459,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let subscriptionManager = DefaultSubscriptionManager(oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, -// subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: subscriptionEnvironment, subscriptionFeatureFlagger: nil, pixelHandler: pixelHandler) @@ -469,11 +466,10 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - let entitlementsCheck: (() async -> Result) = { -// Logger.networkProtection.log("Entitlements check...") -// let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) -// Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)") -// return .success(isNetworkProtectionEnabled) - return .success(true) + Logger.networkProtection.log("Entitlements check...") + let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) + Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)") + return .success(isNetworkProtectionEnabled) } let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) From 14ee67e76d7c7edecdd4d2b882a9ea9467d17bcc Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 12 Dec 2024 15:43:06 +0000 Subject: [PATCH 30/59] code cleanup --- .../DefaultSubscriptionManager+StandardConfiguration.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index f3472116bf..f6256d02aa 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -64,9 +64,6 @@ extension DefaultSubscriptionManager { let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: environment.serviceEnvironment.url) -// let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! // main app -// let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, -// userDefaults: userDefaults) let subscriptionFeatureFlagger: FeatureFlaggerMapping = FeatureFlaggerMapping { feature in guard let featureFlagger else { // With no featureFlagger provided there is no gating of features @@ -100,14 +97,12 @@ extension DefaultSubscriptionManager { subscriptionFeatureFlagger: subscriptionFeatureFlagger), oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, -// subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: environment, subscriptionFeatureFlagger: subscriptionFeatureFlagger, pixelHandler: pixelHandler) } else { self.init(oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, -// subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: environment, subscriptionFeatureFlagger: subscriptionFeatureFlagger, pixelHandler: pixelHandler) From 9f6d6f42bd13c31825cd28835103fac63418f83f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 12 Dec 2024 17:12:02 +0000 Subject: [PATCH 31/59] schema language reverted --- .../xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme | 1 - 1 file changed, 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index ca004007c5..8a90105f09 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -287,7 +287,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "it" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" From ad293c9aed0c0088946a005fb19e840fe433876a Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 13 Dec 2024 16:12:16 +0000 Subject: [PATCH 32/59] background token refresh --- DuckDuckGo.xcodeproj/project.pbxproj | 8 ++++ .../xcshareddata/swiftpm/Package.resolved | 36 ++++++++++++---- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 27 ++++++------ .../SubscriptionTokenContainerRefresher.swift | 41 +++++++++++++++++++ 4 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3243b43dcd..f9cb03f31e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3256,6 +3256,8 @@ F1FDC93B2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC93C2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC93D2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; + F1FEB7A72D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */; }; + F1FEB7A82D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */; }; F41D174125CB131900472416 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; F44C130225C2DA0400426E3E /* NSAppearanceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */; }; F4A6198C283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */; }; @@ -5005,6 +5007,8 @@ F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandlerMock.swift; sourceTree = ""; }; F1FD5B662C2B0AAA0040FA0D /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VPNSettings+Environment.swift"; sourceTree = ""; }; + F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionTokenContainerRefresher.swift; sourceTree = ""; }; + F1FEB7A92D0C377500BE2E39 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F41D174025CB131900472416 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = ""; }; F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearanceExtension.swift; sourceTree = ""; }; F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScopeFeatureFlagging.swift; sourceTree = ""; }; @@ -7189,6 +7193,7 @@ 02FDA6612C765D0F0024CD8B /* VPNAgentConfigurationURLProvider.swift */, 7B60AFF92C511B65008E32A3 /* VPNUIActionHandler.swift */, 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */, + F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */, 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */, 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */, 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */, @@ -7878,6 +7883,7 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( + F1FEB7A92D0C377500BE2E39 /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, @@ -12699,6 +12705,7 @@ F1DA51982BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */, F1C70D802BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, + F1FEB7A82D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */, 02FDA65B2C764C200024CD8B /* ConfigurationStore.swift in Sources */, 7BDBAD202CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */, 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */, @@ -12749,6 +12756,7 @@ 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, BDA764852BC49E4000D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, + F1FEB7A72D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */, 02FDA65D2C764CB30024CD8B /* ConfigurationStore.swift in Sources */, 7BDBAD212CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */, 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c82dd6d1a0..623df4192b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,15 +27,6 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", - "state" : { - "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "50d4d64cba660ee779d987585b6c866d5b710057" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -153,6 +144,24 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks.git", + "state" : { + "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53", + "version" : "1.0.5" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f", + "version" : "1.3.0" + } + }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -215,6 +224,15 @@ "revision" : "13fd026384b1af11048451061cc1b21434990668", "version" : "1.1.3" } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1", + "version" : "1.4.3" + } } ], "version" : 2 diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index af9b09e98c..239a94fd55 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -54,7 +54,7 @@ final class DuckDuckGoVPNApplication: NSApplication { let subscriptionUserDefaults = UserDefaults(suiteName: appGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - self.subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(appGroup)), + subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(appGroup)), environment: subscriptionEnvironment, userDefaults: subscriptionUserDefaults) _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) @@ -126,6 +126,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private let appLauncher = AppLauncher() private let subscriptionManager: any SubscriptionManager + private let tokenRefresher: SubscriptionTokenContainerRefresher private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager @@ -138,9 +139,10 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { public init(subscriptionManager: any SubscriptionManager) { self.subscriptionManager = subscriptionManager - self.tunnelSettings = VPNSettings(defaults: .netP) - self.tunnelSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) - self.configurationManager = ConfigurationManager(privacyConfigManager: privacyConfigurationManager, store: configurationStore) + tunnelSettings = VPNSettings(defaults: .netP) + tunnelSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) + configurationManager = ConfigurationManager(privacyConfigManager: privacyConfigurationManager, store: configurationStore) + tokenRefresher = SubscriptionTokenContainerRefresher(subscriptionManager: subscriptionManager) } private var cancellables = Set() @@ -397,10 +399,19 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { } } } + + Task { + await tokenRefresher.refreshIfNeeded() + } } @MainActor private func setupMenuVisibility() { + + Task { + await tokenRefresher.refreshIfNeeded() + } + if tunnelSettings.showInMenuBar { networkProtectionMenu.show() } else { @@ -452,11 +463,3 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { } } - -// extension DuckDuckGoVPNAppDelegate: AccountManagerKeychainAccessDelegate { -// -// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { -// PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), -// frequency: .legacyDailyAndCount) -// } -// } diff --git a/DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift b/DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift new file mode 100644 index 0000000000..ed7fd0d553 --- /dev/null +++ b/DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift @@ -0,0 +1,41 @@ +// +// SubscriptionTokenContainerRefresher.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 Networking +import os.log + +struct SubscriptionTokenContainerRefresher { + + let subscriptionManager: SubscriptionManager + + func refreshIfNeeded() async { + guard subscriptionManager.isUserAuthenticated else { return } + + if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local), + tokenContainer.decodedAccessToken.expirationDate.daysSinceNow() < 15 { + do { + try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) + Logger.subscription.log("Successfully refreshed subscription token container") + } catch { + Logger.subscription.error("Failed to refresh subscription token container: \(error)") + } + } + } +} From d89f4a81586c95399474e1b4a9f489f2d4e9e6d2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 13 Dec 2024 16:13:47 +0000 Subject: [PATCH 33/59] BSK update --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../xcshareddata/swiftpm/Package.resolved | 36 +++++-------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f9cb03f31e..a018e1e13e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -5008,7 +5008,6 @@ F1FD5B662C2B0AAA0040FA0D /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VPNSettings+Environment.swift"; sourceTree = ""; }; F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionTokenContainerRefresher.swift; sourceTree = ""; }; - F1FEB7A92D0C377500BE2E39 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F41D174025CB131900472416 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = ""; }; F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearanceExtension.swift; sourceTree = ""; }; F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScopeFeatureFlagging.swift; sourceTree = ""; }; @@ -7883,7 +7882,6 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( - F1FEB7A92D0C377500BE2E39 /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 623df4192b..d7540fee23 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "branch" : "fcappelli/subscription_oauth_api_v2", + "revision" : "8fec94fb58b2c5b02de1a277633e56319238674c" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -144,24 +153,6 @@ "version" : "1.3.0" } }, - { - "identity" : "swift-clocks", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-clocks.git", - "state" : { - "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53", - "version" : "1.0.5" - } - }, - { - "identity" : "swift-concurrency-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", - "state" : { - "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f", - "version" : "1.3.0" - } - }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -224,15 +215,6 @@ "revision" : "13fd026384b1af11048451061cc1b21434990668", "version" : "1.1.3" } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1", - "version" : "1.4.3" - } } ], "version" : 2 From e89f9a8d09537b13b8f2679cca5eeaf63fc331d9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 16 Dec 2024 14:41:13 +0100 Subject: [PATCH 34/59] DBP cleanup --- DuckDuckGo.xcodeproj/project.pbxproj | 12 +----- .../xcshareddata/swiftpm/Package.resolved | 36 +++++++++++++----- DuckDuckGo/Application/AppDelegate.swift | 7 ++-- .../DBP/DataBrokerProtectionDebugMenu.swift | 3 +- ...ataBrokerProtectionFeatureGatekeeper.swift | 2 +- .../DBP/DataBrokerProtectionManager.swift | 3 +- ...erProtectionSubscriptionEventHandler.swift | 24 +++--------- ...taBrokerAuthenticationManagerBuilder.swift | 38 ------------------- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 15 ++++---- ...okerProtectionAuthenticationManaging.swift | 15 ++++---- .../DataBrokerProtectionAgentManager.swift | 4 +- .../Services/CaptchaService.swift | 4 +- .../Services/EmailService.swift | 4 +- 13 files changed, 67 insertions(+), 100 deletions(-) delete mode 100644 DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a018e1e13e..fd5ce30e86 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -336,10 +336,6 @@ 31E3FD732CE39C4600A392C8 /* AIChatDebugURLSettingsRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E3FD702CE39C4600A392C8 /* AIChatDebugURLSettingsRepresentable.swift */; }; 31ECDA0E2BED317300AE679F /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; 31ECDA0F2BED317300AE679F /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; - 31ECDA112BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */; }; - 31ECDA122BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */; }; - 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */; }; - 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */; }; 31EF1E802B63FFA800E6DB17 /* DBPHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */; }; 31EF1E812B63FFB800E6DB17 /* DataBrokerProtectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */; }; 31EF1E832B63FFCA00E6DB17 /* LoginItem+DataBrokerProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */; }; @@ -3614,7 +3610,6 @@ 31E163BC293A579E00963C10 /* PrivacyReferenceTestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyReferenceTestHelper.swift; sourceTree = ""; }; 31E163BF293A581900963C10 /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "Submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; 31E3FD702CE39C4600A392C8 /* AIChatDebugURLSettingsRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatDebugURLSettingsRepresentable.swift; sourceTree = ""; }; - 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerAuthenticationManagerBuilder.swift; sourceTree = ""; }; 31F25EFE2CC3CA00002F9084 /* AIChatMenuConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatMenuConfigurationTests.swift; sourceTree = ""; }; 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubePlayerUserScript.swift; sourceTree = ""; }; 31F28C4E28C8EEC500119F70 /* YoutubeOverlayUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubeOverlayUserScript.swift; sourceTree = ""; }; @@ -4981,6 +4976,7 @@ F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift"; sourceTree = ""; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; + F152A8C62D1062DD006AC5DE /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DBPBackgroundAgent.swift"; sourceTree = ""; }; @@ -7664,7 +7660,6 @@ 9D9AE9152AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift */, F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */, 9D9AE9172AAA3B450026E7DC /* UserText.swift */, - 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */, 9D9AE9162AAA3B450026E7DC /* Assets.xcassets */, 9D9AE91A2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgent.entitlements */, 9D9AE9192AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppStore.entitlements */, @@ -7882,6 +7877,7 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( + F152A8C62D1062DD006AC5DE /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, @@ -12191,7 +12187,6 @@ B602E8172A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, 3706FCA1293F65D500E42796 /* AdjacentItemEnumerator.swift in Sources */, 9F9C49FA2BC7BC970099738D /* BookmarkAllTabsDialogView.swift in Sources */, - 31ECDA122BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, 3706FCA2293F65D500E42796 /* ChromiumKeychainPrompt.swift in Sources */, 3707C71E294B5D2900682A9F /* URLRequestExtension.swift in Sources */, 3706FCA3293F65D500E42796 /* WKProcessPool+GeolocationProvider.swift in Sources */, @@ -12901,7 +12896,6 @@ 1EFA1A072C7C7F0E0099F508 /* PrivacyProPixel.swift in Sources */, 9D9AE91D2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, - 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, F1D0429F2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0E2BED317300AE679F /* BundleExtension.swift in Sources */, F11B3CAD2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, @@ -12920,7 +12914,6 @@ 1EFA1A082C7C7F0F0099F508 /* PrivacyProPixel.swift in Sources */, 9D9AE91E2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, - 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, F1D042A02BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0F2BED317300AE679F /* BundleExtension.swift in Sources */, F11B3CAE2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, @@ -13286,7 +13279,6 @@ B60293E62BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */, B6B3E0E12657EA7A0040E0A2 /* NSScreenExtension.swift in Sources */, B65E6BA026D9F10600095F96 /* NSBezierPathExtension.swift in Sources */, - 31ECDA112BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, 4B4D60E02A0C875F00BCD287 /* Bundle+VPN.swift in Sources */, AA6820E425502F19005ED0D5 /* WebsiteDataStore.swift in Sources */, 3199AF732C80734A003AEBDC /* DuckPlayerOnboardingModalManager.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d7540fee23..623df4192b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,15 +27,6 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", - "state" : { - "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "8fec94fb58b2c5b02de1a277633e56319238674c" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -153,6 +144,24 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks.git", + "state" : { + "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53", + "version" : "1.0.5" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f", + "version" : "1.3.0" + } + }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -215,6 +224,15 @@ "revision" : "13fd026384b1af11048451061cc1b21434990668", "version" : "1.1.3" } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1", + "version" : "1.4.3" + } } ], "version" : 2 diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index e25ac54e63..a10a66d46f 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -106,7 +106,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let remoteMessagingClient: RemoteMessagingClient! let onboardingStateMachine: ContextualOnboardingStateMachine & ContextualOnboardingStateUpdater - public let subscriptionManager: SubscriptionManager + public let subscriptionManager: any SubscriptionManager public let subscriptionUIHandler: SubscriptionUIHandling private let subscriptionCookieManager: SubscriptionCookieManaging private var subscriptionCookieManagerFeatureFlagCancellable: AnyCancellable? @@ -132,9 +132,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { // MARK: - DBP private lazy var dataBrokerProtectionSubscriptionEventHandler: DataBrokerProtectionSubscriptionEventHandler = { - let authManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(subscriptionManager: subscriptionManager) + let authenticationManager = DataBrokerProtectionAuthenticationManager( + subscriptionManager: subscriptionManager) return DataBrokerProtectionSubscriptionEventHandler(featureDisabler: DataBrokerProtectionFeatureDisabler(), - authenticationManager: authManager, + authenticationManager: authenticationManager, pixelHandler: DataBrokerProtectionPixelsHandler()) }() diff --git a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift index c04080fddb..a4e053b9d4 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift @@ -249,7 +249,8 @@ final class DataBrokerProtectionDebugMenu: NSMenu { } @objc private func runCustomJSON() { - let authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(subscriptionManager: Application.appDelegate.subscriptionManager) + let authenticationManager = DataBrokerProtectionAuthenticationManager( + subscriptionManager: Application.appDelegate.subscriptionManager) let viewController = DataBrokerRunCustomJSONViewController(authenticationManager: authenticationManager) let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 500, height: 400), styleMask: [.titled, .closable, .miniaturizable, .resizable], diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index 8dbd63dab3..8f547c15d6 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -90,7 +90,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature let isAuthenticated = subscriptionManager.isUserAuthenticated if !isAuthenticated && freemiumDBPUserStateManager.didActivate { return true } - var hasEntitlements = await subscriptionManager.isFeatureActive(.dataBrokerProtection) + let hasEntitlements = await subscriptionManager.isFeatureActive(.dataBrokerProtection) firePrerequisitePixelsAndLogIfNecessary(hasEntitlements: hasEntitlements, isAuthenticatedResult: isAuthenticated) return hasEntitlements && isAuthenticated } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift index 0809c80925..2eed43121a 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift @@ -55,7 +55,8 @@ public final class DataBrokerProtectionManager { }() private init() { - self.authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(subscriptionManager: Application.appDelegate.subscriptionManager) + self.authenticationManager = DataBrokerProtectionAuthenticationManager( + subscriptionManager: Application.appDelegate.subscriptionManager) } public func isUserAuthenticated() -> Bool { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index 24cc81d548..be004140b5 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -59,25 +59,13 @@ final class DataBrokerProtectionSubscriptionEventHandler { } private func entitlementsDidChange(_ notification: Notification) { - - guard let entitlements = notification.userInfo?[UserDefaultsCacheKey.subscriptionEntitlements] as? [SubscriptionEntitlement] else { - assertionFailure("Missing entitlements are truly unexpected") - return - } - - let hasEntitlements = entitlements.contains(.dataBrokerProtection) Task { - await entitlementsDidChange(hasEntitlements: hasEntitlements) - } - } - - @MainActor - private func entitlementsDidChange(hasEntitlements: Bool) async { - if hasEntitlements { - pixelHandler.fire(.entitlementCheckValid) - } else { - pixelHandler.fire(.entitlementCheckInvalid) - featureDisabler.disableAndDelete() + if await authenticationManager.hasValidEntitlement() { + pixelHandler.fire(.entitlementCheckValid) + } else { + pixelHandler.fire(.entitlementCheckInvalid) + featureDisabler.disableAndDelete() + } } } } diff --git a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift b/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift deleted file mode 100644 index 2effdbfcb9..0000000000 --- a/DuckDuckGoDBPBackgroundAgent/DataBrokerAuthenticationManagerBuilder.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// DataBrokerAuthenticationManagerBuilder.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 DataBrokerProtection -import Subscription - -final public class DataBrokerAuthenticationManagerBuilder { - - static func buildAuthenticationManager(redeemUseCase: RedeemUseCase = RedeemUseCase(), - subscriptionManager: any SubscriptionManager) -> DataBrokerProtectionAuthenticationManager { -// let subscriptionManager = DataBrokerProtectionSubscriptionManager(subscriptionManager: subscriptionManager) - return DataBrokerProtectionAuthenticationManager(redeemUseCase: redeemUseCase, - subscriptionManager: subscriptionManager) - - } -} - -// 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 6fcbb3cae0..a1b9b55c1f 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -72,10 +72,10 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - self.subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), - environment: subscriptionEnvironment, - userDefaults: subscriptionUserDefaults) - + subscriptionManager = DefaultSubscriptionManager( + keychainType: .dataProtection(.named(subscriptionAppGroup)), + environment: subscriptionEnvironment, + userDefaults: subscriptionUserDefaults) _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) super.init() @@ -101,12 +101,13 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele @MainActor func applicationDidFinishLaunching(_ aNotification: Notification) { - Logger.dbpBackgroundAgent.info("DuckDuckGoAgent started") + Logger.dbpBackgroundAgent.log("DuckDuckGoAgent started") let redeemUseCase = RedeemUseCase(authenticationService: AuthenticationService(), authenticationRepository: KeychainAuthenticationData()) - let authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(redeemUseCase: redeemUseCase, - subscriptionManager: subscriptionManager) + let authenticationManager = DataBrokerProtectionAuthenticationManager( + redeemUseCase: redeemUseCase, + subscriptionManager: subscriptionManager) manager = DataBrokerProtectionAgentManagerProvider.agentManager(authenticationManager: authenticationManager) manager?.agentFinishedLaunching() diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift index c8b26c1964..74d51d862d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift @@ -21,11 +21,11 @@ import Subscription public protocol DataBrokerProtectionAuthenticationManaging { var isUserAuthenticated: Bool { get } - var accessToken: String? { get } + func accessToken() async -> String? func hasValidEntitlement() async -> Bool func shouldAskForInviteCode() -> Bool func redeem(inviteCode: String) async throws - func getAuthHeader() -> String? + func getAuthHeader() async -> String? } public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtectionAuthenticationManaging { @@ -36,11 +36,11 @@ public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtecti subscriptionManager.isUserAuthenticated } - public var accessToken: String? { - subscriptionManager.getTokenContainerSynchronously(policy: .localValid)?.accessToken + public func accessToken() async -> String? { + try? await subscriptionManager.getTokenContainer(policy: .localForceRefresh).accessToken } - public init(redeemUseCase: any DataBrokerProtectionRedeemUseCase, + public init(redeemUseCase: any DataBrokerProtectionRedeemUseCase = RedeemUseCase(), subscriptionManager: any SubscriptionManager) { self.redeemUseCase = redeemUseCase self.subscriptionManager = subscriptionManager @@ -50,8 +50,9 @@ public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtecti await subscriptionManager.isFeatureActive(.dataBrokerProtection) } - public func getAuthHeader() -> String? { - ServicesAuthHeaderBuilder().getAuthHeader(accessToken) + public func getAuthHeader() async -> String? { + guard let token = await accessToken() else { return nil } + return ServicesAuthHeaderBuilder().getAuthHeader(token) } // MARK: - Redeem code flow diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift index b02e14640e..90738c320c 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift @@ -38,7 +38,9 @@ public class DataBrokerProtectionAgentManagerProvider { let executionConfig = DataBrokerExecutionConfig(mode: dbpSettings.storedRunType == .integrationTests ? .fastForIntegrationTests : .normal) let activityScheduler = DefaultDataBrokerProtectionBackgroundActivityScheduler(config: executionConfig) - let notificationService = DefaultDataBrokerProtectionUserNotificationService(pixelHandler: pixelHandler, userNotificationCenter: UNUserNotificationCenter.current(), authenticationManager: authenticationManager) + let notificationService = DefaultDataBrokerProtectionUserNotificationService( + pixelHandler: pixelHandler, userNotificationCenter: UNUserNotificationCenter.current(), + authenticationManager: authenticationManager) Configuration.setURLProvider(DBPAgentConfigurationURLProvider()) let configStore = ConfigurationStore() let privacyConfigurationManager = DBPPrivacyConfigurationManager() diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift index 762afd13b7..f88358ed35 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift @@ -187,7 +187,7 @@ struct CaptchaService: CaptchaServiceProtocol { Logger.service.debug("Submitting captcha request ...") var request = URLRequest(url: url) - guard let authHeader = authenticationManager.getAuthHeader() else { + guard let authHeader = await authenticationManager.getAuthHeader() else { servicePixel.fireEmptyAccessToken(callSite: .submitCaptchaInformationRequest) throw AuthenticationError.noAuthToken } @@ -273,7 +273,7 @@ struct CaptchaService: CaptchaServiceProtocol { } var request = URLRequest(url: url) - guard let authHeader = authenticationManager.getAuthHeader() else { + guard let authHeader = await authenticationManager.getAuthHeader() else { servicePixel.fireEmptyAccessToken(callSite: .submitCaptchaToBeResolvedRequest) throw AuthenticationError.noAuthToken } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift index 22486fea1d..a675ff431e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift @@ -80,7 +80,7 @@ struct EmailService: EmailServiceProtocol { } var request = URLRequest(url: url) - guard let authHeader = authenticationManager.getAuthHeader() else { + guard let authHeader = await authenticationManager.getAuthHeader() else { servicePixel.fireEmptyAccessToken(callSite: .getEmail) throw AuthenticationError.noAuthToken } @@ -163,7 +163,7 @@ struct EmailService: EmailServiceProtocol { var request = URLRequest(url: url) - guard let authHeader = authenticationManager.getAuthHeader() else { + guard let authHeader = await authenticationManager.getAuthHeader() else { servicePixel.fireEmptyAccessToken(callSite: .extractEmailLink) throw AuthenticationError.noAuthToken } From 651d070cadda5a2d4a0bf5d25c1bcdfb358cce74 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 16 Dec 2024 15:00:41 +0100 Subject: [PATCH 35/59] DBP logger added --- DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift index f5f5c4d091..36c49c139e 100644 --- a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -20,6 +20,7 @@ import Foundation import Combine import DataBrokerProtection import LoginItems +import os.log enum DataBrokerPrerequisitesStatus { case invalidDirectory @@ -40,10 +41,13 @@ final class DefaultDataBrokerPrerequisitesStatusVerifier: DataBrokerPrerequisite func checkStatus() -> DataBrokerPrerequisitesStatus { if !statusChecker.doesHaveNecessaryPermissions() { + Logger.dataBrokerProtection.log("Invalid system permissions") return .invalidSystemPermission } else if !statusChecker.isInCorrectDirectory() { + Logger.dataBrokerProtection.log("Invalid directory") return .invalidDirectory } else { + Logger.dataBrokerProtection.log("Valid system permissions") return .valid } } From 3a6fec1b7919e6a5be2c1055e2700beeef26f9da Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 16 Dec 2024 16:59:42 +0100 Subject: [PATCH 36/59] DBP fix --- .../DBP/DataBrokerProtectionAppEvents.swift | 2 ++ ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 25 ++++++++++--------- ...okerProtectionAuthenticationManaging.swift | 6 +++-- .../DataBrokerProtectionAgentStopper.swift | 3 ++- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift index f0b173a42a..e30abff430 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift @@ -20,6 +20,7 @@ import Foundation import LoginItems import Common import DataBrokerProtection +import os.log struct DataBrokerProtectionAppEvents { @@ -52,6 +53,7 @@ struct DataBrokerProtectionAppEvents { // In this case, let's disable the agent and delete any left-over data because there's nothing for it to do if let profileQueriesCount = try? DataBrokerProtectionManager.shared.dataManager.profileQueriesCount(), profileQueriesCount > 0 { + Logger.dataBrokerProtection.log("Found \(profileQueriesCount) profile queries in DB. Disabling agent.") restartBackgroundAgent(loginItemsManager: loginItemsManager) // Wait to make sure the agent has had time to restart before attempting to call a method on it diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index a1b9b55c1f..6a47bf6bd7 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -33,7 +33,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { private let subscriptionManager: SubscriptionManager override init() { - Logger.dbpBackgroundAgent.info("🟢 Starting: \(NSRunningApplication.current.processIdentifier, privacy: .public)") + Logger.dbpBackgroundAgent.debug("🟢 Starting: \(NSRunningApplication.current.processIdentifier, privacy: .public)") let dryRun: Bool #if DEBUG @@ -80,6 +80,8 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { super.init() self.delegate = _delegate + + Logger.dbpBackgroundAgent.debug("🟢 Started") } required init?(coder: NSCoder) { @@ -92,29 +94,28 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele private let settings = DataBrokerProtectionSettings() private var cancellables = Set() private var statusBarMenu: StatusBarMenu? - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: any SubscriptionManager private var manager: DataBrokerProtectionAgentManager? init(subscriptionManager: SubscriptionManager) { self.subscriptionManager = subscriptionManager - } - - @MainActor - func applicationDidFinishLaunching(_ aNotification: Notification) { - Logger.dbpBackgroundAgent.log("DuckDuckGoAgent started") + // Aligning the environment with the Subscription one + settings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) let redeemUseCase = RedeemUseCase(authenticationService: AuthenticationService(), authenticationRepository: KeychainAuthenticationData()) let authenticationManager = DataBrokerProtectionAuthenticationManager( redeemUseCase: redeemUseCase, subscriptionManager: subscriptionManager) - manager = DataBrokerProtectionAgentManagerProvider.agentManager(authenticationManager: authenticationManager) - manager?.agentFinishedLaunching() + self.manager = DataBrokerProtectionAgentManagerProvider.agentManager(authenticationManager: authenticationManager) + } + @MainActor + func applicationDidFinishLaunching(_ aNotification: Notification) { + Logger.dbpBackgroundAgent.debug("DuckDuckGo DBP Agent launched") + subscriptionManager.loadInitialData() + manager?.agentFinishedLaunching() setupStatusBarMenu() - - // Aligning the environment with the Subscription one - settings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) } @MainActor diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift index 74d51d862d..1b8feb7875 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift @@ -37,7 +37,7 @@ public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtecti } public func accessToken() async -> String? { - try? await subscriptionManager.getTokenContainer(policy: .localForceRefresh).accessToken + try? await subscriptionManager.getTokenContainer(policy: .localValid).accessToken } public init(redeemUseCase: any DataBrokerProtectionRedeemUseCase = RedeemUseCase(), @@ -47,7 +47,9 @@ public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtecti } public func hasValidEntitlement() async -> Bool { - await subscriptionManager.isFeatureActive(.dataBrokerProtection) +// await subscriptionManager.isFeatureActive(.dataBrokerProtection) + let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid) + return tokenContainer?.decodedAccessToken.subscriptionEntitlements.contains(.dataBrokerProtection) ?? false } public func getAuthHeader() async -> String? { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift index e22df1fb56..35c37d960e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift @@ -75,7 +75,8 @@ struct DefaultDataBrokerProtectionAgentStopper: DataBrokerProtectionAgentStopper return } - let hasValidEntitlement = try await authenticationManager.hasValidEntitlement() + let hasValidEntitlement = await authenticationManager.hasValidEntitlement() + Logger.dataBrokerProtection.debug("Entitlements are \(hasValidEntitlement ? "valid" : "invalid")") stopAgentBasedOnEntitlementCheckResult(hasValidEntitlement ? .enabled : .disabled) } catch { From 5e6667415fa40b2e85120f8e63304926bafa8509 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 16 Dec 2024 18:11:21 +0100 Subject: [PATCH 37/59] BSK update --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../xcshareddata/swiftpm/Package.resolved | 36 +++++-------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fd5ce30e86..f0ef3944d3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -4976,7 +4976,6 @@ F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift"; sourceTree = ""; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; - F152A8C62D1062DD006AC5DE /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DBPBackgroundAgent.swift"; sourceTree = ""; }; @@ -7877,7 +7876,6 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( - F152A8C62D1062DD006AC5DE /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 623df4192b..95713dba32 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "branch" : "fcappelli/subscription_oauth_api_v2", + "revision" : "2f8291151d4277f6ab7499ae8012f84a5a08f989" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -144,24 +153,6 @@ "version" : "1.3.0" } }, - { - "identity" : "swift-clocks", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-clocks.git", - "state" : { - "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53", - "version" : "1.0.5" - } - }, - { - "identity" : "swift-concurrency-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", - "state" : { - "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f", - "version" : "1.3.0" - } - }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -224,15 +215,6 @@ "revision" : "13fd026384b1af11048451061cc1b21434990668", "version" : "1.1.3" } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1", - "version" : "1.4.3" - } } ], "version" : 2 From 39adf1700c0b92181607ae10c33bdba9c7ba36b7 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 7 Jan 2025 16:39:55 +0000 Subject: [PATCH 38/59] TestUtils removed from BSK --- DuckDuckGo.xcodeproj/project.pbxproj | 46 ++----------- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../xcschemes/Unit Tests.xcscheme | 64 +++++++++++++++++++ .../DataBrokerProtection/Package.swift | 2 +- ...ProtectionAuthenticationManagerTests.swift | 2 +- LocalPackages/NewTabPage/Package.swift | 2 +- .../NewTabPageFavoritesClientTests.swift | 2 +- .../NewTabPageFavoritesModelTests.swift | 2 +- .../NewTabPageNextStepsCardsClientTests.swift | 2 +- .../NewTabPagePrivacyStatsClientTests.swift | 2 +- .../NewTabPagePrivacyStatsModelTests.swift | 2 +- ...okerProtectionFeatureGatekeeperTests.swift | 3 +- ...emiumDBPPixelExperimentManagingTests.swift | 4 +- .../DBP/FreemiumDBPFeatureTests.swift | 4 +- ...iumDBPFirstProfileSavedNotifierTests.swift | 3 +- .../MaliciousSiteProtectionTests.swift | 3 +- ...UseSubscriptionFeatureForStripeTests.swift | 2 +- ...tionPagesUseSubscriptionFeatureTests.swift | 3 +- UnitTests/Sync/Mocks/MockDDGSyncing.swift | 2 +- UnitTests/Sync/SyncPreferencesTests.swift | 2 +- .../UnifiedFeedbackFormViewModelTests.swift | 4 +- 21 files changed, 100 insertions(+), 58 deletions(-) create mode 100644 DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2a828a2798..0620679f20 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1107,8 +1107,6 @@ 37219B3B2CBFD4F300C9D7A8 /* NewTabPageSearchBoxExperiment+Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37219B392CBFD4F300C9D7A8 /* NewTabPageSearchBoxExperiment+Logger.swift */; }; 37219B3D2CC27DB700C9D7A8 /* NewTabPageSearchBoxExperimentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37219B3C2CC27DB300C9D7A8 /* NewTabPageSearchBoxExperimentTests.swift */; }; 37219B3E2CC27DB700C9D7A8 /* NewTabPageSearchBoxExperimentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37219B3C2CC27DB300C9D7A8 /* NewTabPageSearchBoxExperimentTests.swift */; }; - 372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 3722177F2B3337FE00B8E9C2 /* TestUtils */; }; - 372217822B33380700B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 372217812B33380700B8E9C2 /* TestUtils */; }; 37269EFB2B332F9E005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFA2B332F9E005E8E46 /* Common */; }; 37269EFD2B332FAC005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFC2B332FAC005E8E46 /* Common */; }; 37269EFF2B332FBB005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFE2B332FBB005E8E46 /* Common */; }; @@ -1142,7 +1140,6 @@ 374EF08429B7575B003D2E87 /* RecentlyClosedCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374EF08229B751FC003D2E87 /* RecentlyClosedCoordinatorTests.swift */; }; 374EFDEB2D01A1D800B30939 /* Utilities in Frameworks */ = {isa = PBXBuildFile; productRef = 374EFDEA2D01A1D800B30939 /* Utilities */; }; 374EFDED2D01A1DE00B30939 /* Utilities in Frameworks */ = {isa = PBXBuildFile; productRef = 374EFDEC2D01A1DE00B30939 /* Utilities */; }; - 374EFDEF2D01C70300B30939 /* Utilities in Frameworks */ = {isa = PBXBuildFile; productRef = 374EFDEE2D01C70300B30939 /* Utilities */; }; 374EFDF12D01C70A00B30939 /* Utilities in Frameworks */ = {isa = PBXBuildFile; productRef = 374EFDF02D01C70A00B30939 /* Utilities */; }; 374EFDF32D01C99E00B30939 /* ActiveRemoteMessageModel+NewTabPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374EFDF22D01C99700B30939 /* ActiveRemoteMessageModel+NewTabPage.swift */; }; 374EFDF42D01C99E00B30939 /* ActiveRemoteMessageModel+NewTabPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374EFDF22D01C99700B30939 /* ActiveRemoteMessageModel+NewTabPage.swift */; }; @@ -1953,8 +1950,6 @@ 84B479092CCA7A3E00F40329 /* Logger+UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B479072CCA7A3900F40329 /* Logger+UnitTests.swift */; }; 84B49F0D2CB10F0900FF08BB /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 84B49F0C2CB10F0900FF08BB /* OHHTTPStubs */; }; 84B49F0F2CB10F0900FF08BB /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 84B49F0E2CB10F0900FF08BB /* OHHTTPStubsSwift */; }; - 84BBC7FF2CFA0D2F00BAE57A /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 84BBC7FE2CFA0D2F00BAE57A /* TestUtils */; }; - 84BBC8012CFA0D3800BAE57A /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 84BBC8002CFA0D3800BAE57A /* TestUtils */; }; 84C96E462CF9BB6400A80A01 /* malwareFilterSet.json in Resources */ = {isa = PBXBuildFile; fileRef = 84C96E442CF9BB6400A80A01 /* malwareFilterSet.json */; }; 84C96E472CF9BB6400A80A01 /* malwareHashPrefixes.json in Resources */ = {isa = PBXBuildFile; fileRef = 84C96E452CF9BB6400A80A01 /* malwareHashPrefixes.json */; }; 84C96E482CF9BB6400A80A01 /* malwareFilterSet.json in Resources */ = {isa = PBXBuildFile; fileRef = 84C96E442CF9BB6400A80A01 /* malwareFilterSet.json */; }; @@ -3211,6 +3206,7 @@ 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 */; }; + F1C074232D2D779D00999B02 /* Utilities in Frameworks */ = {isa = PBXBuildFile; productRef = F1C074222D2D779D00999B02 /* Utilities */; }; 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 */; }; @@ -5080,7 +5076,6 @@ 4BCBE45A2BA7E17800FC75A1 /* Subscription in Frameworks */, B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */, 567A23C52C7F75BB0010F66C /* SpecialErrorPages in Frameworks */, - 372217822B33380700B8E9C2 /* TestUtils in Frameworks */, 3706FCAA293F65D500E42796 /* UserScript in Frameworks */, 5641734D2CFE169400F4B716 /* PixelExperimentKit in Frameworks */, 85E2BBD02B8F534A00DBEC7A /* History in Frameworks */, @@ -5103,7 +5098,6 @@ files = ( 3706FE88293F661700E42796 /* OHHTTPStubs in Frameworks */, F116A7C72BD1925500F3FCF7 /* PixelKitTestingUtilities in Frameworks */, - 84BBC7FF2CFA0D2F00BAE57A /* TestUtils in Frameworks */, B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */, 9DC5FACD2C6B8E620011F068 /* AppKitExtensions in Frameworks */, 374EFDF12D01C70A00B30939 /* Utilities in Frameworks */, @@ -5360,7 +5354,6 @@ 37BA812D29B3CD690053F1A3 /* SyncUI in Frameworks */, 9D9DE5732C63AA0700D20B15 /* AppKitExtensions in Frameworks */, F1DF95E72BD188B60045E591 /* LoginItems in Frameworks */, - 372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */, CBECDB8A2CDBD616005B8B87 /* PageRefreshMonitor in Frameworks */, 7BA076BB2B65D61400D7FB72 /* NetworkProtectionProxy in Frameworks */, 4B4D60B12A0C83B900BCD287 /* NetworkProtectionUI in Frameworks */, @@ -5377,10 +5370,9 @@ 46066CBC2D1330A100AB683B /* Persistence in Frameworks */, B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */, F116A7C32BD1924B00F3FCF7 /* PixelKitTestingUtilities in Frameworks */, - 84BBC8012CFA0D3800BAE57A /* TestUtils in Frameworks */, + F1C074232D2D779D00999B02 /* Utilities in Frameworks */, B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */, 9DC5FACB2C6B8E050011F068 /* AppKitExtensions in Frameworks */, - 374EFDEF2D01C70300B30939 /* Utilities in Frameworks */, F1DA51A92BF6114C00CF29FA /* SubscriptionTestingUtilities in Frameworks */, B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */, ); @@ -9979,7 +9971,6 @@ 37DF000629F9C061002B7D3E /* SyncDataProviders */, 9DC70B192AA1FA5B005A844B /* LoginItems */, 37269EFC2B332FAC005E8E46 /* Common */, - 372217812B33380700B8E9C2 /* TestUtils */, 4BF97AD02B43C43F00EB4240 /* NetworkProtectionIPC */, 4BF97AD22B43C43F00EB4240 /* NetworkProtectionUI */, 4BF97AD42B43C43F00EB4240 /* NetworkProtection */, @@ -10038,7 +10029,6 @@ F116A7C62BD1925500F3FCF7 /* PixelKitTestingUtilities */, F1DA51A42BF6114200CF29FA /* SubscriptionTestingUtilities */, 9DC5FACC2C6B8E620011F068 /* AppKitExtensions */, - 84BBC7FE2CFA0D2F00BAE57A /* TestUtils */, 374EFDF02D01C70A00B30939 /* Utilities */, ); productName = DuckDuckGoTests; @@ -10470,7 +10460,6 @@ 7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */, 31A3A4E22B0C115F0021063C /* DataBrokerProtection */, 37269EFA2B332F9E005E8E46 /* Common */, - 3722177F2B3337FE00B8E9C2 /* TestUtils */, 7BA076BA2B65D61400D7FB72 /* NetworkProtectionProxy */, 85E2BBCD2B8F534000DBEC7A /* History */, 1EA7B8D22B7E078C000330A4 /* SubscriptionUI */, @@ -10525,9 +10514,8 @@ F116A7C22BD1924B00F3FCF7 /* PixelKitTestingUtilities */, F1DA51A82BF6114C00CF29FA /* SubscriptionTestingUtilities */, 9DC5FACA2C6B8E050011F068 /* AppKitExtensions */, - 84BBC8002CFA0D3800BAE57A /* TestUtils */, - 374EFDEE2D01C70300B30939 /* Utilities */, 46066CBB2D1330A100AB683B /* Persistence */, + F1C074222D2D779D00999B02 /* Utilities */, ); productName = DuckDuckGoTests; productReference = AA585D90248FD31400E9A3E2 /* Unit Tests.xctest */; @@ -15619,16 +15607,6 @@ package = 371D00DF29D8509400EC8598 /* XCRemoteSwiftPackageReference "OpenSSL-XCFramework" */; productName = OpenSSL; }; - 3722177F2B3337FE00B8E9C2 /* TestUtils */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = TestUtils; - }; - 372217812B33380700B8E9C2 /* TestUtils */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = TestUtils; - }; 37269EFA2B332F9E005E8E46 /* Common */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -15676,10 +15654,6 @@ isa = XCSwiftPackageProductDependency; productName = Utilities; }; - 374EFDEE2D01C70300B30939 /* Utilities */ = { - isa = XCSwiftPackageProductDependency; - productName = Utilities; - }; 374EFDF02D01C70A00B30939 /* Utilities */ = { isa = XCSwiftPackageProductDependency; productName = Utilities; @@ -16003,16 +15977,6 @@ package = B6DA44152616C13800DD1EC2 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; productName = OHHTTPStubsSwift; }; - 84BBC7FE2CFA0D2F00BAE57A /* TestUtils */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = TestUtils; - }; - 84BBC8002CFA0D3800BAE57A /* TestUtils */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = TestUtils; - }; 85D44B852BA08D29001B4AB5 /* Suggestions */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -16395,6 +16359,10 @@ package = B6F997B92B8F352500476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = SwiftLintTool; }; + F1C074222D2D779D00999B02 /* Utilities */ = { + isa = XCSwiftPackageProductDependency; + productName = Utilities; + }; F1D0428D2BFB9F9C00A31506 /* Subscription */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 57c74476fc..c0ca2f5bfb 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "de7f10b090a04f273bbad8d38aae99dd1506afc4" + "revision" : "de310ea9360d8bd1c1a0243ad6e6e39b1470a4c8" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme new file mode 100644 index 0000000000..609dd378bd --- /dev/null +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 49f12a1601..c60da923ba 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -59,7 +59,7 @@ let package = Package( "DataBrokerProtection", "BrowserServicesKit", "Freemium", - .product(name: "TestUtils", package: "BrowserServicesKit"), + .product(name: "PersistenceTestingUtils", package: "BrowserServicesKit"), .product(name: "SubscriptionTestingUtilities", package: "BrowserServicesKit"), ], resources: [ diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift index 39cd204de3..9c99c1cd34 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAuthenticationManagerTests.swift @@ -21,7 +21,7 @@ import XCTest import Subscription import SubscriptionTestingUtilities import Networking -import TestUtils +import NetworkingTestingUtils class DataBrokerProtectionAuthenticationManagerTests: XCTestCase { var authenticationManager: DataBrokerProtectionAuthenticationManager! diff --git a/LocalPackages/NewTabPage/Package.swift b/LocalPackages/NewTabPage/Package.swift index b1e912c146..1ea428644d 100644 --- a/LocalPackages/NewTabPage/Package.swift +++ b/LocalPackages/NewTabPage/Package.swift @@ -45,7 +45,7 @@ let package = Package( .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), .product(name: "PrivacyStats", package: "BrowserServicesKit"), .product(name: "RemoteMessaging", package: "BrowserServicesKit"), - .product(name: "TestUtils", package: "BrowserServicesKit"), + .product(name: "PersistenceTestingUtils", package: "BrowserServicesKit"), .product(name: "WebKitExtensions", package: "WebKitExtensions"), ], swiftSettings: [ diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift index d3cdba7267..63c16c7922 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift @@ -18,9 +18,9 @@ import Combine import RemoteMessaging -import TestUtils import XCTest @testable import NewTabPage +import PersistenceTestingUtils final class NewTabPageFavoritesClientTests: XCTestCase { typealias NewTabPageFavoritesClientUnderTest = NewTabPageFavoritesClient diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesModelTests.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesModelTests.swift index 7785c1928d..55f040e4ce 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesModelTests.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesModelTests.swift @@ -17,7 +17,7 @@ // import Combine -import TestUtils +import PersistenceTestingUtils import XCTest @testable import NewTabPage diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageNextStepsCardsClientTests.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageNextStepsCardsClientTests.swift index 82acec3fc9..ee6a43255b 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageNextStepsCardsClientTests.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageNextStepsCardsClientTests.swift @@ -17,7 +17,7 @@ // import Combine -import TestUtils +import PersistenceTestingUtils import XCTest @testable import NewTabPage diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsClientTests.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsClientTests.swift index 3202f76922..4bd8cc92de 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsClientTests.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsClientTests.swift @@ -18,7 +18,7 @@ import Combine import PrivacyStats -import TestUtils +import PersistenceTestingUtils import TrackerRadarKit import XCTest @testable import NewTabPage diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsModelTests.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsModelTests.swift index fc4d46e4db..08f2cedf6e 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsModelTests.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPagePrivacyStatsModelTests.swift @@ -18,7 +18,7 @@ import Combine import PrivacyStats -import TestUtils +import PersistenceTestingUtils import TrackerRadarKit import XCTest @testable import NewTabPage diff --git a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift index ea0f3c1c50..de88f54a26 100644 --- a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift +++ b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift @@ -19,7 +19,8 @@ import XCTest @testable import BrowserServicesKit @testable import Subscription -import TestUtils +import Networking +import NetworkingTestingUtils import SubscriptionTestingUtilities @testable import DuckDuckGo_Privacy_Browser diff --git a/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift b/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift index 224726739a..dc2cd81837 100644 --- a/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift +++ b/UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift @@ -20,7 +20,9 @@ import XCTest import SubscriptionTestingUtilities import Subscription @testable import DuckDuckGo_Privacy_Browser -import TestUtils +import Networking +import NetworkingTestingUtils + final class FreemiumDBPPixelExperimentManagingTests: XCTestCase { private var sut: FreemiumDBPPixelExperimentManaging! diff --git a/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift b/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift index 7acd5601ab..03a27896e5 100644 --- a/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift +++ b/UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift @@ -23,7 +23,9 @@ import BrowserServicesKit import SubscriptionTestingUtilities import Freemium import Combine -import TestUtils +import Networking +import NetworkingTestingUtils + final class FreemiumDBPFeatureTests: XCTestCase { private var sut: FreemiumDBPFeature! diff --git a/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift b/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift index 7bfe5d52b6..74077e12f3 100644 --- a/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift +++ b/UnitTests/Freemium/DBP/FreemiumDBPFirstProfileSavedNotifierTests.swift @@ -18,7 +18,8 @@ import XCTest @testable import DuckDuckGo_Privacy_Browser -import TestUtils +import Networking +import NetworkingTestingUtils import SubscriptionTestingUtilities final class FreemiumDBPFirstProfileSavedNotifierTests: XCTestCase { diff --git a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift index 222c295d29..e0996324ca 100644 --- a/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift +++ b/UnitTests/MaliciousSiteProtection/MaliciousSiteProtectionTests.swift @@ -20,7 +20,8 @@ import BrowserServicesKit import Combine import Foundation import MaliciousSiteProtection -import TestUtils +import Networking +import NetworkingTestingUtils import XCTest @testable import DuckDuckGo_Privacy_Browser diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index 0c44fb5625..037177e059 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -27,7 +27,7 @@ import UserScript import PixelKitTestingUtilities import os.log import Networking -import TestUtils +import NetworkingTestingUtils @available(macOS 12.0, *) final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index bf822856e4..e418b3695b 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -28,7 +28,8 @@ import PixelKitTestingUtilities import os.log import DataBrokerProtection import Networking -import TestUtils +import Networking +import NetworkingTestingUtils @available(macOS 12.0, *) final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { diff --git a/UnitTests/Sync/Mocks/MockDDGSyncing.swift b/UnitTests/Sync/Mocks/MockDDGSyncing.swift index e400ec9a8a..a1f23043e8 100644 --- a/UnitTests/Sync/Mocks/MockDDGSyncing.swift +++ b/UnitTests/Sync/Mocks/MockDDGSyncing.swift @@ -18,7 +18,7 @@ import Foundation import Combine -import TestUtils +import PersistenceTestingUtils @testable import DuckDuckGo_Privacy_Browser @testable import DDGSync diff --git a/UnitTests/Sync/SyncPreferencesTests.swift b/UnitTests/Sync/SyncPreferencesTests.swift index f1b1d40fa5..54400565dd 100644 --- a/UnitTests/Sync/SyncPreferencesTests.swift +++ b/UnitTests/Sync/SyncPreferencesTests.swift @@ -21,7 +21,7 @@ import Combine import Persistence import SyncUI import XCTest -import TestUtils +import PersistenceTestingUtils @testable import BrowserServicesKit @testable import DDGSync @testable import DuckDuckGo_Privacy_Browser diff --git a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift index 677d75f0b7..3d71623387 100644 --- a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift +++ b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift @@ -20,8 +20,10 @@ import XCTest import Subscription import SubscriptionTestingUtilities @testable import DuckDuckGo_Privacy_Browser -@testable import TestUtils +@testable import PersistenceTestingUtils @testable import Networking +import NetworkingTestingUtils + final class UnifiedFeedbackFormViewModelTests: XCTestCase { var subscriptionManager: SubscriptionManagerMock! From a0d5a7a1c3e94219c8f18ebcac020465dc470548 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 7 Jan 2025 17:02:06 +0000 Subject: [PATCH 39/59] BSK update and lint --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../SubscriptionPagesUseSubscriptionFeatureTests.swift | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c0ca2f5bfb..99bc882cf5 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "de310ea9360d8bd1c1a0243ad6e6e39b1470a4c8" + "revision" : "d1998cae838ac33584b24c99662dd898e2eca0f0" } }, { diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index e418b3695b..869be86581 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -28,7 +28,6 @@ import PixelKitTestingUtilities import os.log import DataBrokerProtection import Networking -import Networking import NetworkingTestingUtils @available(macOS 12.0, *) From 107fe1e18d83153f0fa526852b5816755dfa8509 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 9 Jan 2025 14:35:40 +0000 Subject: [PATCH 40/59] restore sub final web page display fixed --- .../Subscription/SubscriptionPagesUseSubscriptionFeature.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index b680d7f17b..56bb12c812 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -152,7 +152,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } // Clear subscription Cache - await subscriptionManager.signOut() + subscriptionManager.clearSubscriptionCache() guard !subscriptionValues.token.isEmpty else { Logger.subscription.fault("Empty token provided, Failed to exchange v1 token for v2") From 4924945255065b090984249d8d3c1efb3b6d592e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 Jan 2025 10:32:03 +0000 Subject: [PATCH 41/59] first stage of fixing the VPN --- DuckDuckGo.xcodeproj/project.pbxproj | 16 --------- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Application/AppDelegate.swift | 2 +- .../NetworkProtectionTunnelController.swift | 6 ++-- .../MacPacketTunnelProvider.swift | 10 +++--- ...vice+SubscriptionFeatureMappingCache.swift | 35 ------------------- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 7 ++-- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 4 +-- 8 files changed, 16 insertions(+), 66 deletions(-) delete mode 100644 DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0620679f20..ba0c1731c0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3145,13 +3145,6 @@ F118EA7E2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; F118EA852BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; F118EA862BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; - F11B3CA82D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; - F11B3CA92D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; - F11B3CAA2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; - F11B3CAB2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; - F11B3CAC2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; - F11B3CAD2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; - F11B3CAE2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */; }; F1476FC02C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; F1476FC12C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; F14E5D562CFE1BE200B91BE6 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; @@ -5002,7 +4995,6 @@ EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; - F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift"; sourceTree = ""; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; @@ -9903,7 +9895,6 @@ F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */, F1D042982BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift */, F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */, - F11B3CA72D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift */, F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, 1E25A4FD2CC937120080EFD4 /* SubscriptionCookieManageEventPixelMapping.swift */, F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */, @@ -11553,7 +11544,6 @@ 319FCFF22CC81D54004F9288 /* AIChatRemoteSettings.swift in Sources */, 3706FB22293F65D500E42796 /* NSTextViewExtension.swift in Sources */, 3706FB23293F65D500E42796 /* DownloadsCellView.swift in Sources */, - F11B3CA92D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 3706FB25293F65D500E42796 /* PublishedAfter.swift in Sources */, 9FBB0C062CBD223D0006B6A6 /* ViewHighlighter.swift in Sources */, BBB881892C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift in Sources */, @@ -12729,7 +12719,6 @@ 7BD7B0032C19D3830039D20A /* VPNIPCResources.swift in Sources */, 7B22D86F2CCFD7B7006A76E1 /* TipKitController.swift in Sources */, F1DA51942BF6081E00CF29FA /* AttributionPixelHandler.swift in Sources */, - F11B3CAB2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, 02FDA6592C764B970024CD8B /* ConfigurationManager.swift in Sources */, @@ -12781,7 +12770,6 @@ 7BD7B0042C19D3830039D20A /* VPNIPCResources.swift in Sources */, 7B22D8702CCFD7B7006A76E1 /* TipKitController.swift in Sources */, F1DA51952BF6081E00CF29FA /* AttributionPixelHandler.swift in Sources */, - F11B3CAC2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 4BA7C4D92B3F61FB00AFE511 /* BundleExtension.swift in Sources */, F1D0429E2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 02FDA65C2C764CB00024CD8B /* ConfigurationManager.swift in Sources */, @@ -12826,7 +12814,6 @@ files = ( 4B4BEC3D2A11B56B001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift in Sources */, 4B4BEC412A11B5BD001D9AC5 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */, - F11B3CAA2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, 4B4BEC432A11B5C7001D9AC5 /* Bundle+VPN.swift in Sources */, 4B4BEC452A11B5EE001D9AC5 /* UserText+NetworkProtectionExtensions.swift in Sources */, 4B4BEC422A11B5C7001D9AC5 /* NetworkProtectionOptionKeyExtension.swift in Sources */, @@ -12949,7 +12936,6 @@ 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, F1D0429F2BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0E2BED317300AE679F /* BundleExtension.swift in Sources */, - F11B3CAD2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12967,7 +12953,6 @@ 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, F1D042A02BFBABA100A31506 /* DefaultSubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0F2BED317300AE679F /* BundleExtension.swift in Sources */, - F11B3CAE2D032A3000879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -13152,7 +13137,6 @@ 85589E8727BBB8F20038AD11 /* HomePageFavoritesModel.swift in Sources */, 4BB88B4A25B7B690006F6B06 /* SequenceExtensions.swift in Sources */, BDBA85932C5D255200BC54F5 /* VPNFeedbackCategory.swift in Sources */, - F11B3CA82D03287800879E51 /* DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift in Sources */, B602E7CF2A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */, 4B59024026B35F3600489384 /* ChromiumDataImporter.swift in Sources */, B62B48562ADE730D000DECE5 /* FileImportView.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1bd3805c74..9f55bd981a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "351dc80d2364cce863cc10c1ed753a55bbcca7a2" + "revision" : "5a448547b9aaefcee6326186f5edf9c9e41cbc04" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 4e3383842e..70fea50dbc 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -300,7 +300,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, userDefaults: subscriptionUserDefaults) -// featureFlagger: featureFlagger + subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { return WindowControllersManager.shared }) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index d7f181ab50..a339860c61 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -649,10 +649,10 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } do { - Logger.networkProtection.debug("Starting NetworkProtectionTunnelController, options: \(options, privacy: .public)") + Logger.networkProtection.log("Starting NetworkProtectionTunnelController, options: \(options, privacy: .public)") try tunnelManager.connection.startVPNTunnel(options: options) } catch { - Logger.networkProtection.error("Failed to start VPN tunnel: \(error, privacy: .public)") + Logger.networkProtection.fault("Failed to start VPN tunnel: \(error, privacy: .public)") throw StartError.startTunnelFailure(error) } @@ -803,7 +803,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr Logger.networkProtection.log("🟢 TunnelController found token container") return tokenContainer } catch { - Logger.networkProtection.fault("TunnelController found no token container") + Logger.networkProtection.fault("🔴 TunnelController found no token container") throw StartError.noAuthToken } } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 93c7c44e5b..9341be36f2 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -430,11 +430,12 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) - let tokenStorage = NetworkProtectionKeychainStore(label: "DuckDuckGo Network Protection Auth Token", serviceName: Self.tokenServiceName, keychainType: Bundle.keychainType) - let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, authService: authService) + let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, + legacyTokenStorage: nil, // Note: The VPN SysExt will stop at the first transition from auth v1 to v2, is up to the user to re-enable it + authService: authService) apiService.authorizationRefresherCallback = { _ in guard let tokenContainer = tokenStorage.tokenContainer else { throw OAuthClientError.internalError("Missing refresh token") @@ -465,9 +466,9 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - let entitlementsCheck: (() async -> Result) = { - Logger.networkProtection.log("Entitlements check...") + Logger.networkProtection.log("Subscription Entitlements check...") let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) - Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)") + Logger.networkProtection.log("Network protection is \( isNetworkProtectionEnabled ? "🟢 Enabled" : "⚫️ Disabled", privacy: .public)") return .success(isNetworkProtectionEnabled) } @@ -576,6 +577,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - NEPacketTunnelProvider public override func load(options: StartupOptions) async throws { + Logger.networkProtection.log("Loading startup options...") try await super.load(options: options) #if NETP_SYSTEM_EXTENSION diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift b/DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift deleted file mode 100644 index b530684053..0000000000 --- a/DuckDuckGo/Subscription/DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// DefaultSubscriptionEndpointService+SubscriptionFeatureMappingCache.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 Networking -import os.log - -extension DefaultSubscriptionEndpointService: @retroactive SubscriptionFeatureMappingCache { - - public func subscriptionFeatures(for subscriptionIdentifier: String) async -> [Networking.SubscriptionEntitlement] { - do { - let response = try await getSubscriptionFeatures(for: subscriptionIdentifier) - return response.features - } catch { - Logger.subscription.error("Failed to get subscription features: \(error)") - return [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - } - } -} diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index acc5d3468c..a407d2ac3d 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -71,10 +71,9 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - subscriptionManager = DefaultSubscriptionManager( - keychainType: .dataProtection(.named(subscriptionAppGroup)), - environment: subscriptionEnvironment, - userDefaults: subscriptionUserDefaults) + subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), + environment: subscriptionEnvironment, + userDefaults: subscriptionUserDefaults) _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) super.init() diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 239a94fd55..a69ffc7e27 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -55,8 +55,8 @@ final class DuckDuckGoVPNApplication: NSApplication { let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(appGroup)), - environment: subscriptionEnvironment, - userDefaults: subscriptionUserDefaults) + environment: subscriptionEnvironment, + userDefaults: subscriptionUserDefaults) _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) super.init() From 1518416f8649d94ffc59124c16580338c00e6dd7 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 Jan 2025 11:28:08 +0000 Subject: [PATCH 42/59] subscription signout notification parametrised --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift | 2 +- .../Preferences/PreferencesSubscriptionModel.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9f55bd981a..674315fa8d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "5a448547b9aaefcee6326186f5edf9c9e41cbc04" + "revision" : "68a3adafdc9dca257acd0e67c22613f4b5e1acaa" } }, { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 0fdfb4fd83..cf746bcfca 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -200,7 +200,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { @objc func signOut() { Task { - await subscriptionManager.signOut() + await subscriptionManager.signOut(notifyUI: true) } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index c9c5953f70..5d9bef27b3 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -293,7 +293,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { func removeFromThisDeviceAction() { userEventHandler(.removeSubscriptionClick) Task { - await subscriptionManager.signOut() + await subscriptionManager.signOut(notifyUI: true) } } From 01038cf40eff2c57f38a0b2bb4bd38a41431afce Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 Jan 2025 16:25:56 +0000 Subject: [PATCH 43/59] debug crash temp fixed --- DuckDuckGo/Common/Extensions/WKWebViewExtension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift b/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift index d744a8fc80..835eb3f21c 100644 --- a/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift +++ b/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift @@ -403,7 +403,7 @@ extension WKWebView { // prevent exception if private API keys go missing open override func value(forUndefinedKey key: String) -> Any? { - assertionFailure("valueForUndefinedKey: \(key)") + // assertionFailure("valueForUndefinedKey: \(key)") // Temporary disabled https://app.asana.com/0/1199230911884351/1209123691403049/f return nil } From 0098e5ccc74eb9f46054348f5e4ebb37acb48f1f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Sun, 12 Jan 2025 15:08:19 +0000 Subject: [PATCH 44/59] subscription manager initial data load improved, pixels added --- DuckDuckGo.xcodeproj/project.pbxproj | 24 ----------- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Application/AppDelegate.swift | 19 +++------ ...ataBrokerProtectionFeatureGatekeeper.swift | 2 +- ...rkProtectionSubscriptionEventHandler.swift | 4 +- .../MacPacketTunnelProvider.swift | 11 +++-- DuckDuckGo/Statistics/PrivacyProPixel.swift | 7 ++++ ...riptionManager+StandardConfiguration.swift | 27 +++++++++--- .../Subscription/SubscriptionPixels.swift | 36 ---------------- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 11 ++++- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 27 ++++++------ .../SubscriptionTokenContainerRefresher.swift | 41 ------------------- ...ataBrokerProtectionAgentStopperTests.swift | 39 ------------------ .../DataBrokerProtectionTests/Mocks.swift | 2 - ...UseSubscriptionFeatureForStripeTests.swift | 3 +- ...tionPagesUseSubscriptionFeatureTests.swift | 3 +- 16 files changed, 67 insertions(+), 191 deletions(-) delete mode 100644 DuckDuckGo/Subscription/SubscriptionPixels.swift delete mode 100644 DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ba0c1731c0..67d2536d4a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3171,14 +3171,6 @@ F18E51022CF8C5650020D129 /* BrowserTabViewControllerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23E02C89B1EE0010F66C /* BrowserTabViewControllerOnboardingTests.swift */; }; F18E51032CF8DAFB0020D129 /* WindowManagerStateRestorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */; }; F18E51042CF8DB2B0020D129 /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; - F18E51062CF9DC970020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; - F18E51072CF9DC970020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; - F18E51082CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; - F18E510A2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; - F18E510C2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; - F18E510D2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; - F18E510E2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; - F18E510F2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */; }; F198C7122BD18A28000BF24D /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = F198C7112BD18A28000BF24D /* PixelKit */; }; F198C7142BD18A30000BF24D /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = F198C7132BD18A30000BF24D /* PixelKit */; }; F198C7162BD18A44000BF24D /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = F198C7152BD18A44000BF24D /* PixelKit */; }; @@ -3263,8 +3255,6 @@ F1FDC93B2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC93C2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC93D2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; - F1FEB7A72D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */; }; - F1FEB7A82D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */; }; F41D174125CB131900472416 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; F44C130225C2DA0400426E3E /* NSAppearanceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */; }; F4A6198C283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */; }; @@ -5003,7 +4993,6 @@ F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; - F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPixels.swift; sourceTree = ""; }; F1AFDBD12C231B7A00710F2C /* SubscriptionAppStoreRestorerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorerTests.swift; sourceTree = ""; }; F1AFDBD32C231B9700710F2C /* SubscriptionErrorReporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporterTests.swift; sourceTree = ""; }; F1B09DA72D020F7A0045AD44 /* NetworkProtectionKeychainStore+TokenStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtectionKeychainStore+TokenStoring.swift"; sourceTree = ""; }; @@ -5022,7 +5011,6 @@ F1F861142C1B25D4005DB446 /* SubscriptionUIHandlerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandlerMock.swift; sourceTree = ""; }; F1FD5B662C2B0AAA0040FA0D /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VPNSettings+Environment.swift"; sourceTree = ""; }; - F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionTokenContainerRefresher.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 = ""; }; F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScopeFeatureFlagging.swift; sourceTree = ""; }; @@ -7207,7 +7195,6 @@ 02FDA6612C765D0F0024CD8B /* VPNAgentConfigurationURLProvider.swift */, 7B60AFF92C511B65008E32A3 /* VPNUIActionHandler.swift */, 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */, - F1FEB7A62D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift */, 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */, 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */, 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */, @@ -9897,7 +9884,6 @@ F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */, F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, 1E25A4FD2CC937120080EFD4 /* SubscriptionCookieManageEventPixelMapping.swift */, - F18E51052CF9DC970020D129 /* SubscriptionPixels.swift */, ); path = Subscription; sourceTree = ""; @@ -11416,7 +11402,6 @@ 3706FAD2293F65D500E42796 /* Atb.swift in Sources */, 3706FAD3293F65D500E42796 /* DownloadsViewController.swift in Sources */, 3706FAD4293F65D500E42796 /* DataExtension.swift in Sources */, - F18E51062CF9DC970020D129 /* SubscriptionPixels.swift in Sources */, 3706FAD6293F65D500E42796 /* ConfigurationStore.swift in Sources */, EEDFA38B2CD148DE00D1C558 /* SyncDiagnosisHelper.swift in Sources */, 3706FAD7293F65D500E42796 /* Feedback.swift in Sources */, @@ -12684,7 +12669,6 @@ buildActionMask = 2147483647; files = ( 4B25377A2A11C01700610219 /* UserText+NetworkProtectionExtensions.swift in Sources */, - F18E510A2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, B65DA5F42A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, 4B52354D2C854CB600AFAF64 /* DuckDuckGoUserAgent.swift in Sources */, EEBCA0C72BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, @@ -12733,11 +12717,9 @@ 7B4D8A232BDA857300852966 /* VPNOperationErrorRecorder.swift in Sources */, 7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */, 7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, - F18E510C2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, F1DA51982BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */, F1C70D802BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, - F1FEB7A82D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */, 02FDA65B2C764C200024CD8B /* ConfigurationStore.swift in Sources */, 7BDBAD202CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */, 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */, @@ -12783,11 +12765,9 @@ 4BF0E5152AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, F1DA51992BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */, F1C70D812BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, - F18E510D2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, BDA764852BC49E4000D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, - F1FEB7A72D0C356B00BE2E39 /* SubscriptionTokenContainerRefresher.swift in Sources */, 02FDA65D2C764CB30024CD8B /* ConfigurationStore.swift in Sources */, 7BDBAD212CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */, 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, @@ -12843,7 +12823,6 @@ 4B4D60A52A0B2EC000BCD287 /* UserText+NetworkProtectionExtensions.swift in Sources */, 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, - F18E51082CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, F1DA51882BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, F1FDC93A2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, ); @@ -12927,7 +12906,6 @@ buildActionMask = 2147483647; files = ( F17E7DDC2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift in Sources */, - F18E510E2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, 31A83FB72BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, F1C70D822BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, @@ -12944,7 +12922,6 @@ buildActionMask = 2147483647; files = ( F10C99422C7E20A1005568B4 /* Logger+DBPBackgroundAgent.swift in Sources */, - F18E510F2CF9DCB40020D129 /* SubscriptionPixels.swift in Sources */, 31A83FB82BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, F1C70D832BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, @@ -13451,7 +13428,6 @@ B6BE9FAA293F7955006363C6 /* ModalSheetCancellable.swift in Sources */, B6830963274CDEC7004B46BB /* FireproofDomainsStore.swift in Sources */, F188268D2BBF01C300D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, - F18E51072CF9DC970020D129 /* SubscriptionPixels.swift in Sources */, 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 1E7E2E942902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift in Sources */, AA9FF95F24A1FB690039E328 /* TabCollectionViewModel.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 674315fa8d..5851c82b5a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "68a3adafdc9dca257acd0e67c22613f4b5e1acaa" + "revision" : "9fd4aeaef05e212b8e93cd5e025c0ae38f013453" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 70fea50dbc..af7733f707 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -299,7 +299,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, - userDefaults: subscriptionUserDefaults) + userDefaults: subscriptionUserDefaults, + handleMigration: true, + handlePixels: true) subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { return WindowControllersManager.shared @@ -380,8 +382,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { didFinishLaunching = true } - subscriptionManager.loadInitialData() - HistoryCoordinator.shared.loadHistory { HistoryCoordinator.shared.migrateModelV5toV6IfNeeded() } @@ -551,22 +551,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { DataBrokerProtectionAppEvents(featureGatekeeper: pirGatekeeper).applicationDidBecomeActive() + // Subscription initial tasks Task { - do { - let subscription = try await subscriptionManager.getSubscription(cachePolicy: .returnCacheDataDontLoad) - if subscription.isActive { - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) - } - } catch { - Logger.general.log("Subscription not active") - } + await subscriptionManager.loadInitialData() } Task { @MainActor in await vpnRedditSessionWorkaround.installRedditSessionWorkaround() - } - - Task { @MainActor in await subscriptionCookieManager.refreshSubscriptionCookie() } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index 9856737f39..a2314edc8f 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -104,7 +104,7 @@ private extension DefaultDataBrokerProtectionFeatureGatekeeper { func firePrerequisitePixelsAndLogIfNecessary(hasEntitlements: Bool, isAuthenticatedResult: Bool) { if !hasEntitlements { - Logger.dataBrokerProtection.error("DBP feature Gatekeeper: Entitlement check failed") + Logger.dataBrokerProtection.log("DBP feature Gatekeeper: No Entitlements available") } if !isAuthenticatedResult { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index b55b5248d1..f4bc352c97 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -47,8 +47,8 @@ final class NetworkProtectionSubscriptionEventHandler { private func subscribeToEntitlementChanges() { Task { - let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) - await handleEntitlementsChange(hasEntitlements: isNetworkProtectionEnabled) +// let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) +// await handleEntitlementsChange(hasEntitlements: isNetworkProtectionEnabled) NotificationCenter.default .publisher(for: .entitlementsDidChange) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 9341be36f2..a7e8b28ca3 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -391,8 +391,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - Initialization - let subscriptionManager: any SubscriptionManager - @MainActor @objc public init() { Logger.networkProtection.log("[+] MacPacketTunnelProvider") #if NETP_SYSTEM_EXTENSION @@ -452,9 +450,12 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) let pixelHandler: SubscriptionManager.PixelHandler = { type in + // The SysExt handles only dead token pixels switch type { case .deadToken: - PixelKit.fire(SubscriptionPixels.privacyProDeadTokenDetected) + PixelKit.fire(PrivacyProPixel.privacyProDeadTokenDetected) + case .subscriptionIsActive, .v1MigrationFailed, .v1MigrationSuccessful: // handled by the main app only + break } } @@ -475,15 +476,13 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) let notificationsPresenter = NetworkProtectionNotificationsPresenterFactory().make(settings: settings, defaults: defaults) - self.subscriptionManager = subscriptionManager - super.init(notificationsPresenter: notificationsPresenter, tunnelHealthStore: tunnelHealthStore, controllerErrorStore: controllerErrorStore, snoozeTimingStore: NetworkProtectionSnoozeTimingStore(userDefaults: .netP), wireGuardInterface: DefaultWireGuardInterface(), keychainType: Bundle.keychainType, - tokenProvider: subscriptionManager, + subscriptionManager: subscriptionManager, debugEvents: debugEvents, providerEvents: Self.packetTunnelProviderEvents, settings: settings, diff --git a/DuckDuckGo/Statistics/PrivacyProPixel.swift b/DuckDuckGo/Statistics/PrivacyProPixel.swift index c4a88a6b52..2de5147f16 100644 --- a/DuckDuckGo/Statistics/PrivacyProPixel.swift +++ b/DuckDuckGo/Statistics/PrivacyProPixel.swift @@ -68,6 +68,9 @@ enum PrivacyProPixel: PixelKitEventV2 { case privacyProOfferYearlyPriceClick case privacyProAddEmailSuccess case privacyProWelcomeFAQClick + case privacyProDeadTokenDetected + case authV1MigrationFailed + case authV1MigrationSucceeded var name: String { switch self { @@ -109,6 +112,10 @@ enum PrivacyProPixel: PixelKitEventV2 { case .privacyProOfferYearlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_yearly-price_click" case .privacyProAddEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_add-email_success_u" case .privacyProWelcomeFAQClick: return "m_mac_\(appDistribution)_privacy-pro_welcome_faq_click_u" + // Auth v2 + case .privacyProDeadTokenDetected: return "m_privacy-pro_dead_token_detected" + case .authV1MigrationFailed: return "m_privacy-pro_v1migration_failed" + case .authV1MigrationSucceeded: return "m_privacy-pro_v1migration_succeeded" } } diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index 39ffc73cc8..deff58e2a2 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -31,7 +31,9 @@ extension DefaultSubscriptionManager { public convenience init(keychainType: KeychainType, environment: SubscriptionEnvironment, featureFlagger: FeatureFlagger? = nil, - userDefaults: UserDefaults) { + userDefaults: UserDefaults, + handleMigration: Bool, + handlePixels: Bool) { let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil @@ -43,7 +45,7 @@ extension DefaultSubscriptionManager { let authEnvironment: OAuthEnvironment = environment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: keychainType) - let legacyAccountStorage = SubscriptionTokenKeychainStorage(keychainType: keychainType) + let legacyAccountStorage = handleMigration == true ? SubscriptionTokenKeychainStorage(keychainType: keychainType) : nil let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, legacyTokenStorage: legacyAccountStorage, authService: authService) @@ -81,11 +83,24 @@ extension DefaultSubscriptionManager { userDefaults.storefrontRegionOverride == .restOfWorld) } } - let pixelHandler: SubscriptionManager.PixelHandler = { type in - switch type { - case .deadToken: - PixelKit.fire(SubscriptionPixels.privacyProDeadTokenDetected) + + // Pixel handler configuration + let pixelHandler: SubscriptionManager.PixelHandler + if handlePixels { + pixelHandler = { type in + switch type { + case .deadToken: + PixelKit.fire(PrivacyProPixel.privacyProDeadTokenDetected) + case .subscriptionIsActive: + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) + case .v1MigrationFailed: + PixelKit.fire(PrivacyProPixel.authV1MigrationFailed) + case .v1MigrationSuccessful: + PixelKit.fire(PrivacyProPixel.authV1MigrationSucceeded) + } } + } else { + pixelHandler = { _ in } } if #available(macOS 12.0, *) { diff --git a/DuckDuckGo/Subscription/SubscriptionPixels.swift b/DuckDuckGo/Subscription/SubscriptionPixels.swift deleted file mode 100644 index 45331c07d0..0000000000 --- a/DuckDuckGo/Subscription/SubscriptionPixels.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// SubscriptionPixels.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 PixelKit - -enum SubscriptionPixels: PixelKitEventV2 { - - case privacyProDeadTokenDetected - - var error: (any Error)? { nil } - - var name: String { - switch self { - case .privacyProDeadTokenDetected: - return "m_privacy-pro_dead_token_detected" - } - } - - var parameters: [String: String]? { nil } -} diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index a407d2ac3d..1b159e93b7 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -73,7 +73,9 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, - userDefaults: subscriptionUserDefaults) + userDefaults: subscriptionUserDefaults, + handleMigration: false, + handlePixels: false) _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) super.init() @@ -111,7 +113,12 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele @MainActor func applicationDidFinishLaunching(_ aNotification: Notification) { Logger.dbpBackgroundAgent.log("DuckDuckGo DBP Agent launched") - subscriptionManager.loadInitialData() + + // Subscription initial tasks + Task { + await subscriptionManager.loadInitialData() + } + manager?.agentFinishedLaunching() setupStatusBarMenu() } diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index a69ffc7e27..8792ae366b 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -56,7 +56,9 @@ final class DuckDuckGoVPNApplication: NSApplication { subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(appGroup)), environment: subscriptionEnvironment, - userDefaults: subscriptionUserDefaults) + userDefaults: subscriptionUserDefaults, + handleMigration: false, + handlePixels: false) _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) super.init() @@ -126,7 +128,6 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private let appLauncher = AppLauncher() private let subscriptionManager: any SubscriptionManager - private let tokenRefresher: SubscriptionTokenContainerRefresher private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager @@ -139,10 +140,9 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { public init(subscriptionManager: any SubscriptionManager) { self.subscriptionManager = subscriptionManager - tunnelSettings = VPNSettings(defaults: .netP) - tunnelSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) - configurationManager = ConfigurationManager(privacyConfigManager: privacyConfigurationManager, store: configurationStore) - tokenRefresher = SubscriptionTokenContainerRefresher(subscriptionManager: subscriptionManager) + self.tunnelSettings = VPNSettings(defaults: .netP) + self.tunnelSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) + self.configurationManager = ConfigurationManager(privacyConfigManager: privacyConfigurationManager, store: configurationStore) } private var cancellables = Set() @@ -376,6 +376,11 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { setupMenuVisibility() + // Subscription initial tasks + Task { + await subscriptionManager.loadInitialData() + } + Task { @MainActor in // Initialize lazy properties _ = tunnelControllerIPCService @@ -399,18 +404,14 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { } } } - - Task { - await tokenRefresher.refreshIfNeeded() - } } @MainActor private func setupMenuVisibility() { - Task { - await tokenRefresher.refreshIfNeeded() - } +// Task { +// await tokenRefresher.refreshIfNeeded() +// } if tunnelSettings.showInMenuBar { networkProtectionMenu.show() diff --git a/DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift b/DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift deleted file mode 100644 index bc78788a9d..0000000000 --- a/DuckDuckGoVPN/SubscriptionTokenContainerRefresher.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// SubscriptionTokenContainerRefresher.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 Networking -import os.log - -struct SubscriptionTokenContainerRefresher { - - let subscriptionManager: SubscriptionManager - - func refreshIfNeeded() async { - guard subscriptionManager.isUserAuthenticated else { return } - - if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local), - tokenContainer.decodedAccessToken.expirationDate.daysSinceNow() < 15 { - do { - try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) - Logger.subscription.log("Successfully refreshed subscription token container") - } catch { - Logger.subscription.error("Failed to refresh subscription token container: \(error)") - } - } - } -} diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAgentStopperTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAgentStopperTests.swift index d18285fc69..26d006cf8e 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAgentStopperTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAgentStopperTests.swift @@ -229,22 +229,6 @@ final class DataBrokerProtectionAgentStopperTests: XCTestCase { XCTAssertFalse(mockStopAction.wasStopCalled) } - func testErrorEntitlement_thenStopAgentIsNotCalled() async { - mockAuthenticationManager.isUserAuthenticatedValue = true - mockAuthenticationManager.shouldThrowEntitlementError = true - mockDataManager.profileToReturn = fakeProfile - - let stopper = DefaultDataBrokerProtectionAgentStopper(dataManager: mockDataManager, - entitlementMonitor: mockEntitlementMonitor, - authenticationManager: mockAuthenticationManager, - pixelHandler: mockPixelHandler, - stopAction: mockStopAction, - freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) - await stopper.validateRunPrerequisitesAndStopAgentIfNecessary() - - XCTAssertFalse(mockStopAction.wasStopCalled) - } - func testValidEntitlement_andUserIsNotFreemium_thenStopAgentIsNotCalled() async { mockAuthenticationManager.isUserAuthenticatedValue = true mockAuthenticationManager.hasValidEntitlementValue = true @@ -374,27 +358,4 @@ final class DataBrokerProtectionAgentStopperTests: XCTestCase { wait(for: [expectation], timeout: 3) } - - func testEntitlementMonitorWithErrorResult_thenStopAgentIsNotCalled() { - mockAuthenticationManager.isUserAuthenticatedValue = true - mockAuthenticationManager.shouldThrowEntitlementError = true - mockDataManager.profileToReturn = fakeProfile - - let stopper = DefaultDataBrokerProtectionAgentStopper(dataManager: mockDataManager, - entitlementMonitor: mockEntitlementMonitor, - authenticationManager: mockAuthenticationManager, - pixelHandler: mockPixelHandler, - stopAction: mockStopAction, - freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager) - - let expectation = XCTestExpectation(description: "Wait for monitor") - stopper.monitorEntitlementAndStopAgentIfEntitlementIsInvalidAndUserIsNotFreemium(interval: 0.1) - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [self] in - XCTAssertFalse(mockStopAction.wasStopCalled) - expectation.fulfill() - } - - wait(for: [expectation], timeout: 3) - } } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index f4f03905b8..37c4947b0c 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -1652,7 +1652,6 @@ final class MockAuthenticationManager: DataBrokerProtectionAuthenticationManagin var redeemCodeCalled = false var authHeaderValue: String? = "fake auth header" var hasValidEntitlementValue = false - var shouldThrowEntitlementError = false var isUserAuthenticated: Bool { isUserAuthenticatedValue } @@ -1679,7 +1678,6 @@ final class MockAuthenticationManager: DataBrokerProtectionAuthenticationManagin redeemCodeCalled = false authHeaderValue = "fake auth header" hasValidEntitlementValue = false - shouldThrowEntitlementError = false } } diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift index 037177e059..c44b2ec129 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift @@ -118,8 +118,7 @@ final class SubscriptionPagesUseSubscriptionFeatureForStripeTests: XCTestCase { storePurchaseManager: storePurchaseManager, appStoreRestoreFlow: appStoreRestoreFlow) stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionManager: subscriptionManager) - subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock(isFeatureAvailable: true, - isSubscriptionPurchaseAllowed: true, + subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock(isSubscriptionPurchaseAllowed: true, usesUnifiedFeedbackForm: false) mockFreemiumDBPExperimentManager = MockFreemiumDBPExperimentManager() diff --git a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 869be86581..dbc762d070 100644 --- a/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -104,8 +104,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { appStoreRestoreFlow: appStoreRestoreFlow) stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionManager: subscriptionManager) subscriptionAttributionPixelHandler = PrivacyProSubscriptionAttributionPixelHandler() - subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock(isFeatureAvailable: true, - isSubscriptionPurchaseAllowed: true, + subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock(isSubscriptionPurchaseAllowed: true, usesUnifiedFeedbackForm: false) mockFreemiumDBPExperimentManager = MockFreemiumDBPExperimentManager() mockPixelHandler = MockFreemiumDBPExperimentPixelHandler() From 358ea037f47158d82e6ebe0a5883f97e0cff7018 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Sun, 12 Jan 2025 15:10:02 +0000 Subject: [PATCH 45/59] lint --- DuckDuckGo/Tab/UserScripts/UserScripts.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index 3d77e08fed..5dc1e376f7 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -124,7 +124,7 @@ final class UserScripts: UserScriptsProvider { } userScripts.append(specialPages) } - + let subscriptionManager = Application.appDelegate.subscriptionManager let stripePurchaseFlow = DefaultStripePurchaseFlow(subscriptionManager: subscriptionManager) let freemiumDBPPixelExperimentManager = FreemiumDBPPixelExperimentManager(subscriptionManager: subscriptionManager) @@ -134,7 +134,7 @@ final class UserScripts: UserScriptsProvider { freemiumDBPPixelExperimentManager: freemiumDBPPixelExperimentManager) subscriptionPagesUserScript.registerSubfeature(delegate: delegate) userScripts.append(subscriptionPagesUserScript) - + let identityTheftRestorationPagesFeature = IdentityTheftRestorationPagesFeature(subscriptionManager: subscriptionManager) identityTheftRestorationPagesUserScript.registerSubfeature(delegate: identityTheftRestorationPagesFeature) userScripts.append(identityTheftRestorationPagesUserScript) From 9eaf062c797b639142772bb58ab1d14f3250f4c9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 13 Jan 2025 15:27:19 +0000 Subject: [PATCH 46/59] subscription cache bug fix --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../MacPacketTunnelProvider.swift | 12 +++++++++++- ...SubscriptionManager+StandardConfiguration.swift | 11 ++++++++++- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 14 +++++++------- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5851c82b5a..6308a44f47 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "9fd4aeaef05e212b8e93cd5e025c0ae38f013453" + "revision" : "092c3344c3b186d41effb13ac798f1cef8d7c70f" } }, { diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index a7e8b28ca3..7c32fe3c58 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -447,8 +447,18 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { return VPNAuthTokenBuilder.getVPNAuthToken(from: tokenContainer.accessToken) } } + +#if DEBUG + let cacheExpiration: Int = 1 +#else + let cacheExpiration: Int = 120 +#endif + let subscriptionCache = UserDefaultsCache(userDefaults: defaults, + key: UserDefaultsCacheKey.subscription, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: subscriptionEnvironment.serviceEnvironment.url) + baseURL: subscriptionEnvironment.serviceEnvironment.url, + subscriptionCache: subscriptionCache) let pixelHandler: SubscriptionManager.PixelHandler = { type in // The SysExt handles only dead token pixels switch type { diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index deff58e2a2..3010fc9a97 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -64,8 +64,17 @@ extension DefaultSubscriptionManager { } } +#if DEBUG + let cacheExpiration: Int = 1 +#else + let cacheExpiration: Int = 120 +#endif + let subscriptionCache = UserDefaultsCache(userDefaults: userDefaults, + key: UserDefaultsCacheKey.subscription, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: environment.serviceEnvironment.url) + baseURL: environment.serviceEnvironment.url, + subscriptionCache: subscriptionCache) let subscriptionFeatureFlagger: FeatureFlaggerMapping = FeatureFlaggerMapping { feature in guard let featureFlagger else { // With no featureFlagger provided there is no gating of features diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 8792ae366b..1014f1482b 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -50,11 +50,11 @@ final class DuckDuckGoVPNApplication: NSApplication { } // MARK: - Configure Subscription - let appGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: appGroup)! + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(appGroup)), + subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, userDefaults: subscriptionUserDefaults, handleMigration: false, @@ -376,12 +376,10 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { setupMenuVisibility() - // Subscription initial tasks - Task { + Task { @MainActor in + // Subscription initial tasks await subscriptionManager.loadInitialData() - } - Task { @MainActor in // Initialize lazy properties _ = tunnelControllerIPCService _ = vpnProxyLauncher @@ -436,7 +434,9 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { guard subscriptionManager.isUserAuthenticated else { return } let entitlementsCheck: (() async -> Result) = { + Logger.networkProtection.log("Subscription Entitlements check...") let isNetworkProtectionEnabled = await self.subscriptionManager.isFeatureActive(.networkProtection) + Logger.networkProtection.log("Network protection is \( isNetworkProtectionEnabled ? "🟢 Enabled" : "⚫️ Disabled", privacy: .public)") return .success(isNetworkProtectionEnabled) } From c77e374a9cc231a896f3496a39da77c92dd92a39 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 Jan 2025 14:53:55 +0000 Subject: [PATCH 47/59] bug fixing --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../DataBrokerProtectionFeatureGatekeeper.swift | 2 +- ...rokerProtectionSubscriptionEventHandler.swift | 1 + .../NavigationBar/View/MoreOptionsMenu.swift | 12 ++++-------- .../View/NavigationBarViewController.swift | 1 - .../MacPacketTunnelProvider.swift | 16 ++++------------ ...bscriptionManager+StandardConfiguration.swift | 16 +++++----------- .../Subscription/VPNSettings+Environment.swift | 4 +--- DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift | 1 - ...aBrokerProtectionAuthenticationManaging.swift | 1 - .../PreferencesSubscriptionModel.swift | 14 ++++++++++++++ 11 files changed, 31 insertions(+), 39 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6308a44f47..efb55e6d0f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "092c3344c3b186d41effb13ac798f1cef8d7c70f" + "revision" : "c02f441e03c985a4ab3550116f786d7e85ab24a1" } }, { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index a2314edc8f..0c1977ed50 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -108,7 +108,7 @@ private extension DefaultDataBrokerProtectionFeatureGatekeeper { } if !isAuthenticatedResult { - Logger.dataBrokerProtection.error("DBP feature Gatekeeper: Authentication check failed") + Logger.dataBrokerProtection.log("DBP feature Gatekeeper: Authentication check failed") } } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index be004140b5..a5210d08e9 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -58,6 +58,7 @@ final class DataBrokerProtectionSubscriptionEventHandler { featureDisabler.disableAndDelete() } + @MainActor private func entitlementsDidChange(_ notification: Notification) { Task { if await authenticationManager.hasValidEntitlement() { diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index d02dea1927..7c04339402 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -916,18 +916,14 @@ final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { private func addMenuItems() async { let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false) - let vpnFeature = features.first { $0.entitlement == .networkProtection } - let dbpFeature = features.first { $0.entitlement == .dataBrokerProtection } - let itrFeature = features.first { $0.entitlement == .identityTheftRestoration } - let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal } - - if vpnFeature != nil { + let entitlements = features.map { $0.entitlement } + if entitlements.contains(.networkProtection) { addItem(networkProtectionItem) } - if dbpFeature != nil { + if entitlements.contains(.dataBrokerProtection) { addItem(dataBrokerProtectionItem) } - if itrFeature != nil || itrgFeature != nil { + if entitlements.contains(.identityTheftRestoration) || entitlements.contains(.identityTheftRestorationGlobal) { addItem(identityTheftRestorationItem) } addItem(NSMenuItem.separator()) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 14e2ebc92f..3a32e1fb9b 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -121,7 +121,6 @@ final class NavigationBarViewController: NSViewController { static private let homeButtonLeftPosition = 0 private let networkProtectionButtonModel: NetworkProtectionNavBarButtonModel -// private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation static func create(tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 7c32fe3c58..d6bf3596f7 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -420,6 +420,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { subscriptionEnvironment.purchasePlatform = .stripe // we don't care about the purchasePlatform Logger.networkProtection.debug("Subscription ServiceEnvironment: \(subscriptionEnvironment.serviceEnvironment.rawValue, privacy: .public)") + let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil configuration.requestCachePolicy = .reloadIgnoringLocalCacheData @@ -448,17 +449,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } } -#if DEBUG - let cacheExpiration: Int = 1 -#else - let cacheExpiration: Int = 120 -#endif - let subscriptionCache = UserDefaultsCache(userDefaults: defaults, - key: UserDefaultsCacheKey.subscription, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: subscriptionEnvironment.serviceEnvironment.url, - subscriptionCache: subscriptionCache) + baseURL: subscriptionEnvironment.serviceEnvironment.url) let pixelHandler: SubscriptionManager.PixelHandler = { type in // The SysExt handles only dead token pixels switch type { @@ -611,7 +603,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } public override func loadVendorOptions(from provider: NETunnelProviderProtocol?) throws { - try super.loadVendorOptions(from: provider) // empty + try super.loadVendorOptions(from: provider) guard let vendorOptions = provider?.providerConfiguration else { Logger.networkProtection.log("🔵 Provider is nil, or providerConfiguration is not set") @@ -630,7 +622,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { setupPixels(defaultHeaders: defaultPixelHeaders) } - // MARK: - Override-able Connection Events + // MARK: - Overrideable Connection Events override func prepareToConnect(using provider: NETunnelProviderProtocol?) { super.prepareToConnect(using: provider) diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index 3010fc9a97..5152ffeec7 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -44,7 +44,10 @@ extension DefaultSubscriptionManager { let apiService = DefaultAPIService(urlSession: urlSession) let authEnvironment: OAuthEnvironment = environment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) - let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: keychainType) + let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: keychainType) { keychainType, error in + PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: keychainType, accessError: error), + frequency: .legacyDailyAndCount) + } let legacyAccountStorage = handleMigration == true ? SubscriptionTokenKeychainStorage(keychainType: keychainType) : nil let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, legacyTokenStorage: legacyAccountStorage, @@ -64,17 +67,8 @@ extension DefaultSubscriptionManager { } } -#if DEBUG - let cacheExpiration: Int = 1 -#else - let cacheExpiration: Int = 120 -#endif - let subscriptionCache = UserDefaultsCache(userDefaults: userDefaults, - key: UserDefaultsCacheKey.subscription, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: environment.serviceEnvironment.url, - subscriptionCache: subscriptionCache) + baseURL: environment.serviceEnvironment.url) let subscriptionFeatureFlagger: FeatureFlaggerMapping = FeatureFlaggerMapping { feature in guard let featureFlagger else { // With no featureFlagger provided there is no gating of features diff --git a/DuckDuckGo/Subscription/VPNSettings+Environment.swift b/DuckDuckGo/Subscription/VPNSettings+Environment.swift index cffa7251a6..4e046637ed 100644 --- a/DuckDuckGo/Subscription/VPNSettings+Environment.swift +++ b/DuckDuckGo/Subscription/VPNSettings+Environment.swift @@ -26,10 +26,8 @@ public extension VPNSettings { func alignTo(subscriptionEnvironment: SubscriptionEnvironment) { switch subscriptionEnvironment.serviceEnvironment { case .production: - // Do nothing for a production subscription, as it can be used for both VPN environments. - break + self.selectedEnvironment = .production case .staging: - // If using a staging subscription, force the staging VPN environment as it is not compatible with anything else. self.selectedEnvironment = .staging } } diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index 0ee219244b..bd2ce2f61e 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -40,7 +40,6 @@ protocol VPNFeatureGatekeeper { struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { private static var subscriptionAuthTokenPrefix: String { "ddg:" } private let vpnUninstaller: VPNUninstalling -// private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let defaults: UserDefaults private let subscriptionManager: SubscriptionManager diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift index 1b8feb7875..3f9ddc1a4d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionAuthenticationManaging.swift @@ -47,7 +47,6 @@ public final class DataBrokerProtectionAuthenticationManager: DataBrokerProtecti } public func hasValidEntitlement() async -> Bool { -// await subscriptionManager.isFeatureActive(.dataBrokerProtection) let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid) return tokenContainer?.decodedAccessToken.subscriptionEntitlements.contains(.dataBrokerProtection) ?? false } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 5d9bef27b3..b90b5487c1 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -229,6 +229,20 @@ 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.authEndpointService.storeLogin(signature: lastTransactionJWSRepresentation) { +// case .success(let response): +// return response.externalID == accountManager.externalID +// case .failure: +// return false +// } +// } +// +// return false +// } + @MainActor func openVPN() { userEventHandler(.openVPN) From d32146995646622fec3a68d3f4087cee82ca1d0c Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 Jan 2025 15:49:43 +0000 Subject: [PATCH 48/59] some changes requested in the PR --- DuckDuckGo/Application/AppDelegate.swift | 4 ++-- .../DataBrokerProtectionSubscriptionEventHandler.swift | 3 +-- .../NetworkProtectionSubscriptionEventHandler.swift | 4 ---- ...DefaultSubscriptionManager+StandardConfiguration.swift | 8 ++++---- .../UnifiedFeedbackFormViewModel.swift | 4 ++-- DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift | 4 ++-- .../DuckDuckGoDBPBackgroundAgentAppDelegate.swift | 4 ++-- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 4 ++-- 8 files changed, 15 insertions(+), 20 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index af7733f707..7f2cfc7300 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -300,8 +300,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, userDefaults: subscriptionUserDefaults, - handleMigration: true, - handlePixels: true) + canPerformAuthMigration: true, + canHandlePixels: true) subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: { return WindowControllersManager.shared diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index a5210d08e9..4c32d79bbc 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -58,9 +58,8 @@ final class DataBrokerProtectionSubscriptionEventHandler { featureDisabler.disableAndDelete() } - @MainActor private func entitlementsDidChange(_ notification: Notification) { - Task { + Task { @MainActor in if await authenticationManager.hasValidEntitlement() { pixelHandler.fire(.entitlementCheckValid) } else { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index f4bc352c97..4eb32b98d8 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -28,7 +28,6 @@ final class NetworkProtectionSubscriptionEventHandler { private let subscriptionManager: SubscriptionManager private let tunnelController: TunnelController -// private let networkProtectionTokenStorage: NetworkProtectionTokenStore private let vpnUninstaller: VPNUninstalling private let userDefaults: UserDefaults private var cancellables = Set() @@ -47,9 +46,6 @@ final class NetworkProtectionSubscriptionEventHandler { private func subscribeToEntitlementChanges() { Task { -// let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) -// await handleEntitlementsChange(hasEntitlements: isNetworkProtectionEnabled) - NotificationCenter.default .publisher(for: .entitlementsDidChange) .receive(on: DispatchQueue.main) diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index 5152ffeec7..b3254df023 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -32,8 +32,8 @@ extension DefaultSubscriptionManager { environment: SubscriptionEnvironment, featureFlagger: FeatureFlagger? = nil, userDefaults: UserDefaults, - handleMigration: Bool, - handlePixels: Bool) { + canPerformAuthMigration: Bool, + canHandlePixels: Bool) { let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil @@ -48,7 +48,7 @@ extension DefaultSubscriptionManager { PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: keychainType, accessError: error), frequency: .legacyDailyAndCount) } - let legacyAccountStorage = handleMigration == true ? SubscriptionTokenKeychainStorage(keychainType: keychainType) : nil + let legacyAccountStorage = canPerformAuthMigration == true ? SubscriptionTokenKeychainStorage(keychainType: keychainType) : nil let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, legacyTokenStorage: legacyAccountStorage, authService: authService) @@ -89,7 +89,7 @@ extension DefaultSubscriptionManager { // Pixel handler configuration let pixelHandler: SubscriptionManager.PixelHandler - if handlePixels { + if canHandlePixels { pixelHandler = { type in switch type { case .deadToken: diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift index 882a5015c6..3794c1a182 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift @@ -287,7 +287,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { private func submitIssue(metadata: UnifiedFeedbackMetadata?) async throws { guard !userEmail.isEmpty else { return } - guard let accessToken = try? await subscriptionManager.getTokenContainer(policy: .localValid) else { + guard let tokenContainer: TokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid) else { throw Error.missingAccessToken } @@ -298,7 +298,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { feedbackText: feedbackFormText, problemSubCategory: selectedSubcategory, customMetadata: metadata?.toString() ?? "") - let headers = APIRequestV2.HeadersV2(additionalHeaders: [HTTPHeaderKey.authorization: "Bearer \(accessToken)"]) + let headers = APIRequestV2.HeadersV2(additionalHeaders: [HTTPHeaderKey.authorization: "Bearer \(tokenContainer.accessToken)"]) guard let request = APIRequestV2(url: Self.feedbackEndpoint, method: .post, headers: headers, body: payload.toData()) else { throw Error.invalidRequest } diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index bd2ce2f61e..c6ceab718d 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -72,13 +72,13 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { /// func isVPNVisible() -> Bool { return subscriptionManager.isUserAuthenticated + // Validate approach if we should use: return await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false).map { $0.entitlement}.contains(.networkProtection) } /// Returns whether the VPN should be uninstalled automatically. /// This is only true when the user is not an Easter Egg user, the waitlist test has ended, and the user is onboarded. func shouldUninstallAutomatically() -> Bool { - !subscriptionManager.isUserAuthenticated && - LoginItem.vpnMenu.status.isInstalled + !subscriptionManager.isUserAuthenticated && LoginItem.vpnMenu.status.isInstalled } /// Whether the user is fully onboarded diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 1b159e93b7..f0f962b6aa 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -74,8 +74,8 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, userDefaults: subscriptionUserDefaults, - handleMigration: false, - handlePixels: false) + canPerformAuthMigration: false, + canHandlePixels: false) _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) super.init() diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 1014f1482b..6ab41929d2 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -57,8 +57,8 @@ final class DuckDuckGoVPNApplication: NSApplication { subscriptionManager = DefaultSubscriptionManager(keychainType: .dataProtection(.named(subscriptionAppGroup)), environment: subscriptionEnvironment, userDefaults: subscriptionUserDefaults, - handleMigration: false, - handlePixels: false) + canPerformAuthMigration: false, + canHandlePixels: false) _delegate = DuckDuckGoVPNAppDelegate(subscriptionManager: subscriptionManager) super.init() From a2470eb3c0c729e30c06224b61a53bbfd8aec193 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 Jan 2025 16:22:19 +0000 Subject: [PATCH 49/59] pr changes --- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- .../NetworkExtensionTargets/MacPacketTunnelProvider.swift | 4 +--- .../DefaultSubscriptionManager+StandardConfiguration.swift | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index efb55e6d0f..ef714382df 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "c02f441e03c985a4ab3550116f786d7e85ab24a1" + "revision" : "f365a42528ec48448715820bc337d2f079fa2a52" } }, { @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-asn1.git", "state" : { - "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", - "version" : "1.3.0" + "revision" : "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", + "version" : "1.3.1" } }, { diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index d6bf3596f7..fd39bb4d98 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -426,9 +426,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { configuration.requestCachePolicy = .reloadIgnoringLocalCacheData let urlSession = URLSession(configuration: configuration, delegate: SessionDelegate(), delegateQueue: nil) let apiService = DefaultAPIService(urlSession: urlSession) - let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging - - let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) + let authService = DefaultOAuthService(baseURL: subscriptionEnvironment.authEnvironment.url, apiService: apiService) let tokenStorage = NetworkProtectionKeychainStore(label: "DuckDuckGo Network Protection Auth Token", serviceName: Self.tokenServiceName, keychainType: Bundle.keychainType) diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift index b3254df023..14b5e01e72 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+StandardConfiguration.swift @@ -42,8 +42,7 @@ extension DefaultSubscriptionManager { delegate: SessionDelegate(), delegateQueue: nil) let apiService = DefaultAPIService(urlSession: urlSession) - let authEnvironment: OAuthEnvironment = environment.serviceEnvironment == .production ? .production : .staging - let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) + let authService = DefaultOAuthService(baseURL: environment.authEnvironment.url, apiService: apiService) let tokenStorage = SubscriptionTokenKeychainStorageV2(keychainType: keychainType) { keychainType, error in PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: keychainType, accessError: error), frequency: .legacyDailyAndCount) From d0ca705b56ec055900bd818bb6da8f4ac59425c1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 Jan 2025 16:32:02 +0000 Subject: [PATCH 50/59] pixels added --- DuckDuckGo/Statistics/PrivacyProPixel.swift | 3 +++ .../Subscription/SubscriptionPagesUseSubscriptionFeature.swift | 2 ++ 2 files changed, 5 insertions(+) diff --git a/DuckDuckGo/Statistics/PrivacyProPixel.swift b/DuckDuckGo/Statistics/PrivacyProPixel.swift index 2de5147f16..345a9f68d4 100644 --- a/DuckDuckGo/Statistics/PrivacyProPixel.swift +++ b/DuckDuckGo/Statistics/PrivacyProPixel.swift @@ -68,9 +68,11 @@ enum PrivacyProPixel: PixelKitEventV2 { case privacyProOfferYearlyPriceClick case privacyProAddEmailSuccess case privacyProWelcomeFAQClick + // Auth v2 case privacyProDeadTokenDetected case authV1MigrationFailed case authV1MigrationSucceeded + case setSubscriptionInvalidSubscriptionValues var name: String { switch self { @@ -116,6 +118,7 @@ enum PrivacyProPixel: PixelKitEventV2 { case .privacyProDeadTokenDetected: return "m_privacy-pro_dead_token_detected" case .authV1MigrationFailed: return "m_privacy-pro_v1migration_failed" case .authV1MigrationSucceeded: return "m_privacy-pro_v1migration_succeeded" + case .setSubscriptionInvalidSubscriptionValues: return "m_privacy-pro_invalid_subscriptionvalues" } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index 56bb12c812..a47dc13f57 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -147,6 +147,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { Logger.subscription.fault("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + PixelKit.fire(PrivacyProPixel.setSubscriptionInvalidSubscriptionValues) assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") return nil } @@ -156,6 +157,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { guard !subscriptionValues.token.isEmpty else { Logger.subscription.fault("Empty token provided, Failed to exchange v1 token for v2") + PixelKit.fire(PrivacyProPixel.setSubscriptionInvalidSubscriptionValues) return nil } From 39b30fc69823ea4acfa34c473b60a7d32964294d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 Jan 2025 16:35:47 +0000 Subject: [PATCH 51/59] cleanup --- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 6ab41929d2..9f152664e8 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -407,10 +407,6 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { @MainActor private func setupMenuVisibility() { -// Task { -// await tokenRefresher.refreshIfNeeded() -// } - if tunnelSettings.showInMenuBar { networkProtectionMenu.show() } else { From de72420dd2466d9c2ec298e6ea9a9904957ffaa1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 Jan 2025 17:29:24 +0000 Subject: [PATCH 52/59] settings subscription status logic improved --- .../Preferences/PreferencesSubscriptionModel.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index b90b5487c1..7c3b488e17 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -81,7 +81,6 @@ public final class PreferencesSubscriptionModel: ObservableObject { lazy var statePublisher: AnyPublisher = { let isSubscriptionActivePublisher: AnyPublisher = $subscriptionStatus.map { -// guard let status = $0 else { return false} let status = $0 return status != .expired && status != .inactive && status != .unknown }.eraseToAnyPublisher() @@ -94,8 +93,13 @@ public final class PreferencesSubscriptionModel: ObservableObject { .map { isUserAuthenticated, isSubscriptionActive, hasAnyEntitlement in switch (isUserAuthenticated, isSubscriptionActive, hasAnyEntitlement) { case (false, _, _): return PreferencesSubscriptionState.noSubscription - case (true, false, _): return PreferencesSubscriptionState.subscriptionExpired -// case (true, nil, _): return PreferencesSubscriptionState.subscriptionPendingActivation + case (true, false, _): + switch self.subscriptionStatus { + case .expired, .inactive: + return PreferencesSubscriptionState.subscriptionExpired + default: + return PreferencesSubscriptionState.subscriptionPendingActivation + } case (true, true, false): return PreferencesSubscriptionState.subscriptionPendingActivation case (true, true, true): return PreferencesSubscriptionState.subscriptionActive } From c29989b02098ee381220951e11f8af0edc4f8906 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 Jan 2025 10:43:19 +0000 Subject: [PATCH 53/59] legacy token support implemented in VPN, migration improved --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../MacPacketTunnelProvider.swift | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ef714382df..7c9716a317 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "f365a42528ec48448715820bc337d2f079fa2a52" + "revision" : "f60b81b0e433eb33e0c7d69eb3167ad3d21b6e0c" } }, { diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index fd39bb4d98..bc841bde2d 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -381,13 +381,11 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } } - static var tokenServiceName: String { #if NETP_SYSTEM_EXTENSION - "\(Bundle.main.bundleIdentifier!).authToken" + static let tokenServiceName = "\(Bundle.main.bundleIdentifier!).authToken" #else - "com.duckduckgo.networkprotection.authToken" + static let tokenServiceName = "com.duckduckgo.networkprotection.authToken" #endif - } // MARK: - Initialization @@ -417,7 +415,10 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .staging: subscriptionEnvironment.serviceEnvironment = .staging } - subscriptionEnvironment.purchasePlatform = .stripe // we don't care about the purchasePlatform + + // The SysExt doesn't care about the purchase platform because the only operations executed here are about the Auth token. No purchase or + // platforms-related operations are performed. + subscriptionEnvironment.purchasePlatform = .stripe Logger.networkProtection.debug("Subscription ServiceEnvironment: \(subscriptionEnvironment.serviceEnvironment.rawValue, privacy: .public)") @@ -430,8 +431,13 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let tokenStorage = NetworkProtectionKeychainStore(label: "DuckDuckGo Network Protection Auth Token", serviceName: Self.tokenServiceName, keychainType: Bundle.keychainType) + let legacyTokenStore = NetworkProtectionKeychainTokenStore(keychainType: Bundle.keychainType, + serviceName: Self.tokenServiceName, + errorEvents: debugEvents, + useAccessTokenProvider: false, + accessTokenProvider: { nil }) let authClient = DefaultOAuthClient(tokensStorage: tokenStorage, - legacyTokenStorage: nil, // Note: The VPN SysExt will stop at the first transition from auth v1 to v2, is up to the user to re-enable it + legacyTokenStorage: legacyTokenStore, authService: authService) apiService.authorizationRefresherCallback = { _ in guard let tokenContainer = tokenStorage.tokenContainer else { From ca8f251547eea6df823133f5547c1ffe21c6a875 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 Jan 2025 11:14:46 +0000 Subject: [PATCH 54/59] pr comments addressed --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift | 2 +- .../NetworkExtensionTargets/MacPacketTunnelProvider.swift | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7c9716a317..87ef4295e5 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "f60b81b0e433eb33e0c7d69eb3167ad3d21b6e0c" + "revision" : "b90dd56f88deb59fbb4d8ede5f32327c6769e132" } }, { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift index e30abff430..eeb397a7db 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift @@ -53,7 +53,7 @@ struct DataBrokerProtectionAppEvents { // In this case, let's disable the agent and delete any left-over data because there's nothing for it to do if let profileQueriesCount = try? DataBrokerProtectionManager.shared.dataManager.profileQueriesCount(), profileQueriesCount > 0 { - Logger.dataBrokerProtection.log("Found \(profileQueriesCount) profile queries in DB. Disabling agent.") + Logger.dataBrokerProtection.log("Found \(profileQueriesCount) profile queries in DB. Restarting agent.") restartBackgroundAgent(loginItemsManager: loginItemsManager) // Wait to make sure the agent has had time to restart before attempting to call a method on it diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index bc841bde2d..98f93cb32d 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -460,8 +460,12 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { switch type { case .deadToken: PixelKit.fire(PrivacyProPixel.privacyProDeadTokenDetected) - case .subscriptionIsActive, .v1MigrationFailed, .v1MigrationSuccessful: // handled by the main app only + case .subscriptionIsActive: // handled by the main app only break + case .v1MigrationFailed: + PixelKit.fire(PrivacyProPixel.authV1MigrationFailed) + case .v1MigrationSuccessful: + PixelKit.fire(PrivacyProPixel.authV1MigrationSucceeded) } } From f737f2d0ba036c193b9eec2762fa63e8fe93d129 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 Jan 2025 12:26:05 +0000 Subject: [PATCH 55/59] bsk updated --- .../xcshareddata/swiftpm/Package.resolved | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 87ef4295e5..fc3bcf4fa6 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "b90dd56f88deb59fbb4d8ede5f32327c6769e132" + "revision" : "c51c7b960e047f6903daf4dadc09a8fe49648f01" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "a539758027d9fd37d9d26213399ac156ca9fb81c", - "version" : "7.1.0" + "revision" : "0502ed7de4130bd8705daebaca9aeb20d3e62d15", + "version" : "7.5.0" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "2e2baf7d31c7d8e158a58bc1cb79498c1c727fd2", - "version" : "7.5.0" + "revision" : "bea4d750913ef82c10cd06e791686501c8e648e4", + "version" : "7.6.0" } }, { From 728401915a5fc92bf58e364acb818f43ee3a4b9f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 20 Jan 2025 11:23:04 +0000 Subject: [PATCH 56/59] Subscription stuff renamed --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../DBP/DataBrokerProtectionFeatureGatekeeper.swift | 2 +- DuckDuckGo/InfoPlist.xcstrings | 2 +- DuckDuckGo/Localizable.xcstrings | 10 +++++----- DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift | 8 ++++---- .../MacPacketTunnelProvider.swift | 2 +- .../UnifiedFeedbackFormViewModel.swift | 8 ++++---- DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift | 2 +- DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift | 2 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 2 +- .../DebugMenu/SubscriptionDebugMenu.swift | 2 +- .../Preferences/PreferencesSubscriptionModel.swift | 6 +++--- .../DataBrokerProtectionFeatureGatekeeperTests.swift | 2 +- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5340dae4e6..2ffd5d0643 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "fc72e5c899631a8c3e16293daf4387f0dc7fb07f" + "revision" : "8bed092b8ca59d0d947b4a5b4da6daf2386cf3d5" } }, { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index 0c1977ed50..9c5c8d1a77 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -90,7 +90,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature let isAuthenticated = subscriptionManager.isUserAuthenticated if !isAuthenticated && freemiumDBPUserStateManager.didActivate { return true } - let hasEntitlements = await subscriptionManager.isFeatureActive(.dataBrokerProtection) + let hasEntitlements = await subscriptionManager.isFeatureAvailableForUser(.dataBrokerProtection) firePrerequisitePixelsAndLogIfNecessary(hasEntitlements: hasEntitlements, isAuthenticatedResult: isAuthenticated) return hasEntitlements && isAuthenticated } diff --git a/DuckDuckGo/InfoPlist.xcstrings b/DuckDuckGo/InfoPlist.xcstrings index 49a7b05432..f7cdec22b9 100644 --- a/DuckDuckGo/InfoPlist.xcstrings +++ b/DuckDuckGo/InfoPlist.xcstrings @@ -14,7 +14,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo App Store" + "value" : "DuckDuckGo" } }, "es" : { diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index f6c062610a..1c9439a4e7 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -33792,7 +33792,7 @@ }, "letsmove.alert.message" : { "comment" : "Message of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", - "extractionState" : "stale", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { @@ -33852,7 +33852,7 @@ }, "letsmove.alert.title" : { "comment" : "Title of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", - "extractionState" : "stale", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { @@ -33912,7 +33912,7 @@ }, "letsmove.could.not.move" : { "comment" : "Error message when moving the app to the /Applications folder failed", - "extractionState" : "stale", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { @@ -33972,7 +33972,7 @@ }, "letsmove.dont.move.button" : { "comment" : "Do Not Move to the /Applications folder button title", - "extractionState" : "stale", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { @@ -34032,7 +34032,7 @@ }, "letsmove.move.button" : { "comment" : "Move the /Applications folder button title", - "extractionState" : "stale", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 7c04339402..82d44ca413 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -970,10 +970,10 @@ final class SubscriptionSubMenu: NSMenu, NSMenuDelegate { let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal } Task { @MainActor in - self.networkProtectionItem.isEnabled = vpnFeature?.enabled ?? false - self.dataBrokerProtectionItem.isEnabled = dbpFeature?.enabled ?? false - let hasIdentityTheftRestoration = itrFeature?.enabled ?? false - let hasIdentityTheftRestorationGlobal = itrgFeature?.enabled ?? false + self.networkProtectionItem.isEnabled = vpnFeature?.availableForUser ?? false + self.dataBrokerProtectionItem.isEnabled = dbpFeature?.availableForUser ?? false + let hasIdentityTheftRestoration = itrFeature?.availableForUser ?? false + let hasIdentityTheftRestorationGlobal = itrgFeature?.availableForUser ?? false self.identityTheftRestorationItem.isEnabled = hasIdentityTheftRestoration || hasIdentityTheftRestorationGlobal } } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 98f93cb32d..e8a3d5a501 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -478,7 +478,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let entitlementsCheck: (() async -> Result) = { Logger.networkProtection.log("Subscription Entitlements check...") - let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection) + let isNetworkProtectionEnabled = await subscriptionManager.isFeatureAvailableForUser(.networkProtection) Logger.networkProtection.log("Network protection is \( isNetworkProtectionEnabled ? "🟢 Enabled" : "⚫️ Disabled", privacy: .public)") return .success(isNetworkProtectionEnabled) } diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift index 3794c1a182..8f24856381 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift @@ -180,14 +180,14 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { let itrFeature = features.first { $0.entitlement == .identityTheftRestoration } let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal } - if vpnFeature?.enabled ?? false { + if vpnFeature?.availableForUser ?? false { availableCategories.append(.vpn) } - if dbpFeature?.enabled ?? false { + if dbpFeature?.availableForUser ?? false { availableCategories.append(.pir) } - let idpEnabled = itrFeature?.enabled ?? false - let idpgEnabled = itrgFeature?.enabled ?? false + let idpEnabled = itrFeature?.availableForUser ?? false + let idpgEnabled = itrgFeature?.availableForUser ?? false if idpEnabled || idpgEnabled { availableCategories.append(.itr) } diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index c6b00200ba..0304f2421f 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -323,7 +323,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { return .init( hasPrivacyProAccount: subscriptionManager.isUserAuthenticated, - hasVPNEntitlement: await subscriptionManager.isFeatureActive(.networkProtection) + hasVPNEntitlement: await subscriptionManager.isFeatureAvailableForUser(.networkProtection) ) } diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index c6ceab718d..2b2c253a2c 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -62,7 +62,7 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { /// For subscription users this means they have entitlements. /// func canStartVPN() async -> Bool { - return await subscriptionManager.isFeatureActive(.networkProtection) + return await subscriptionManager.isFeatureAvailableForUser(.networkProtection) } /// Whether the user can see the VPN entry points in the UI. diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 6f52520bc6..43a1e83f20 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -441,7 +441,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { let entitlementsCheck: (() async -> Result) = { Logger.networkProtection.log("Subscription Entitlements check...") - let isNetworkProtectionEnabled = await self.subscriptionManager.isFeatureActive(.networkProtection) + let isNetworkProtectionEnabled = await self.subscriptionManager.isFeatureAvailableForUser(.networkProtection) Logger.networkProtection.log("Network protection is \( isNetworkProtectionEnabled ? "🟢 Enabled" : "⚫️ Disabled", privacy: .public)") return .success(isNetworkProtectionEnabled) } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index cf746bcfca..f8e7b70130 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -233,7 +233,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { Task { let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: true) let descriptions = features.map({ feature in - "\(feature.entitlement.rawValue): Enabled: \(feature.enabled)" + "\(feature.entitlement.rawValue): Available: \(feature.availableForUser)" }) showAlert(title: "Check Entitlements", message: descriptions.joined(separator: "\n")) } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 7c3b488e17..fd9c2497ee 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -390,9 +390,9 @@ public final class PreferencesSubscriptionModel: ObservableObject { shouldShowITR = itrFeature != nil || itrgFeature != nil // is active/enabled - hasAccessToVPN = vpnFeature?.enabled ?? false - hasAccessToDBP = dbpFeature?.enabled ?? false - hasAccessToITR = itrFeature?.enabled ?? false || itrgFeature?.enabled ?? false + hasAccessToVPN = vpnFeature?.availableForUser ?? false + hasAccessToDBP = dbpFeature?.availableForUser ?? false + hasAccessToITR = itrFeature?.availableForUser ?? false || itrgFeature?.availableForUser ?? false } } diff --git a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift index 2c3b3a4fe1..9704df0f1a 100644 --- a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift +++ b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift @@ -113,7 +113,7 @@ final class DataBrokerProtectionFeatureGatekeeperTests: XCTestCase { func testWhenAccessTokenAndEntitlementAreFound_andIsNotActiveFreemiumUser_thenFeatureIsEnabled() async { // Given mockSubscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() - mockSubscriptionManager.resultFeatures = [ SubscriptionFeature(entitlement: .dataBrokerProtection, enabled: true) ] + mockSubscriptionManager.resultFeatures = [ SubscriptionFeature(entitlement: .dataBrokerProtection, availableForUser: true) ] mockFreemiumDBPUserStateManager.didActivate = false sut = DefaultDataBrokerProtectionFeatureGatekeeper(featureDisabler: mockFeatureDisabler, userDefaults: userDefaults(), From bbde6d6bf6f6f1811c86eb708a93a820ba0922c0 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 20 Jan 2025 12:34:43 +0000 Subject: [PATCH 57/59] BSK updated --- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6c29023834..040f5d841f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "8bed092b8ca59d0d947b4a5b4da6daf2386cf3d5" + "revision" : "72b6b9208c8d24a7b45540fca5ed6474e7132a86" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "7958ddab724c26326333cae13fe81478290607fa", - "version" : "7.6.0" + "revision" : "0ac30560ec969a321caea321f95537120416c323", + "version" : "7.7.0" } }, { From f3a5d246f97334725dd5b1817404b3c3be0f42b2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 21 Jan 2025 12:35:58 +0000 Subject: [PATCH 58/59] BSK update --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 040f5d841f..a92789fe7e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "72b6b9208c8d24a7b45540fca5ed6474e7132a86" + "revision" : "0eebb62e9ecc995185ea1c79c31318d50d05ba40" } }, { From 445fe0756ce3b104a6d3f1522ed5810fe9f65223 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 21 Jan 2025 16:08:54 +0000 Subject: [PATCH 59/59] Tunnel controller now refreshes the token after passing it to the VPN --- .../NetworkProtectionTunnelController.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index a339860c61..e2068e5617 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -611,7 +611,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr var options = [String: NSObject]() options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString - let tokenContainer = try await fetchTokenContainer() + let tokenContainer = try await fetchTokenContainerAndRefresh() options[NetworkProtectionOptionKey.tokenContainer] = tokenContainer.data options[NetworkProtectionOptionKey.selectedEnvironment] = settings.selectedEnvironment.rawValue as NSString @@ -797,10 +797,14 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } } - private func fetchTokenContainer() async throws -> TokenContainer { + private func fetchTokenContainerAndRefresh() async throws -> TokenContainer { do { let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) Logger.networkProtection.log("🟢 TunnelController found token container") + + // refresh token in order to brach it from the one sent to VPN + try await subscriptionManager.getTokenContainer(policy: .localForceRefresh) + return tokenContainer } catch { Logger.networkProtection.fault("🔴 TunnelController found no token container")