From cc168895d3b65a9f32fc824c8900c63794cb9447 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 16 Oct 2024 13:13:31 +0100 Subject: [PATCH 01/41] builds --- DuckDuckGo.xcodeproj/project.pbxproj | 2 + .../xcshareddata/swiftpm/Package.resolved | 36 ++++-- DuckDuckGo/AppDelegate.swift | 27 ++--- DuckDuckGo/AppDependencyProvider.swift | 87 ++++++++++---- .../DefaultNetworkProtectionVisibility.swift | 11 +- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 4 +- .../Feedback/VPNMetadataCollector.swift | 17 ++- DuckDuckGo/MainViewController.swift | 6 +- ...orkProtectionConvenienceInitialisers.swift | 6 +- ...NetworkProtectionDebugViewController.swift | 10 +- DuckDuckGo/NetworkProtectionRootView.swift | 6 +- .../NetworkProtectionTunnelController.swift | 2 +- .../NetworkProtectionVPNLocationView.swift | 2 +- ...rotectionVisibilityForTunnelProvider.swift | 46 ++++---- ...RemoteMessagingConfigMatcherProvider.swift | 13 +-- DuckDuckGo/SettingsState.swift | 5 +- DuckDuckGo/SettingsViewModel.swift | 47 ++++---- ...AccountManagerKeychainAccessDelegate.swift | 26 ++--- ...IdentityTheftRestorationPagesFeature.swift | 8 +- ...scriptionPagesUseSubscriptionFeature.swift | 92 +++++++-------- .../SubscriptionEmailViewModel.swift | 8 +- .../ViewModel/SubscriptionITPViewModel.swift | 2 +- .../SubscriptionRestoreViewModel.swift | 1 - .../SubscriptionSettingsViewModel.swift | 102 ++++++++--------- .../SubscriptionContainerViewFactory.swift | 97 +++++++++------- .../SubscriptionDebugViewController.swift | 77 ++++++------- DuckDuckGo/VPNRedditSessionWorkaround.swift | 8 +- ...etworkProtectionPacketTunnelProvider.swift | 108 +++++++++++------- 28 files changed, 468 insertions(+), 388 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fec6e2b7fe..df440b2f90 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2949,6 +2949,7 @@ F197EA3B1E6885F20029BDC1 /* TextFieldWithInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextFieldWithInsets.swift; path = ../Core/TextFieldWithInsets.swift; sourceTree = ""; }; F198D78D1E39762C0088DA8A /* StringExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = ""; }; F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewConfigurationExtensionTests.swift; sourceTree = ""; }; + F19BD2AE2CBE6F76008DC90A /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F1A886771F29394E0096251E /* WebCacheManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebCacheManager.swift; sourceTree = ""; }; F1AA54601E48D90700223211 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; @@ -4149,6 +4150,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F19BD2AE2CBE6F76008DC90A /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7e03d15de5..21954dd35e 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" : "ec46d991838527dd8c7041a3673428c0e18e0fdc", - "version" : "199.0.0" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -90,6 +81,15 @@ "version" : "2.0.0" } }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", + "version" : "4.13.4" + } + }, { "identity" : "kingfisher", "kind" : "remoteSourceControl", @@ -144,6 +144,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" : "21f7878f2b39d46fd8ba2b06459ccb431cdf876c", + "version" : "3.8.1" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 0a5d2886ca..d3ffd02b5e 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -62,7 +62,7 @@ import os.log @MainActor private lazy var vpnWorkaround: VPNRedditSessionWorkaround = { return VPNRedditSessionWorkaround( - accountManager: AppDependencyProvider.shared.accountManager, + subscriptionManager: AppDependencyProvider.shared.subscriptionManager, tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController ) }() @@ -94,10 +94,6 @@ import os.log @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? - var accountManager: AccountManager { - AppDependencyProvider.shared.accountManager - } - @UserDefaultsWrapper(key: .didCrashDuringCrashHandlersSetUp, defaultValue: false) private var didCrashDuringCrashHandlersSetUp: Bool @@ -375,7 +371,7 @@ import os.log widgetRefreshModel.beginObservingVPNStatus() - AppDependencyProvider.shared.subscriptionManager.loadInitialData() +// AppDependencyProvider.shared.subscriptionManager.loadInitialData() setUpAutofillPixelReporter() @@ -552,7 +548,7 @@ import os.log } } - AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscriptionAndEntitlements { isSubscriptionActive in + AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in if isSubscriptionActive { DailyPixel.fire(pixel: .privacyProSubscriptionActive) } @@ -568,7 +564,8 @@ import os.log private func stopAndRemoveVPNIfNotAuthenticated() async { // Only remove the VPN if the user is not authenticated, and it's installed: - guard !accountManager.isUserAuthenticated, await AppDependencyProvider.shared.networkProtectionTunnelController.isInstalled else { + guard !AppDependencyProvider.shared.subscriptionManager.isUserAuthenticated, + await AppDependencyProvider.shared.networkProtectionTunnelController.isInstalled else { return } @@ -965,7 +962,8 @@ import os.log return } - if case .success(true) = await accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .returnCacheDataDontLoad) { + let entitlements = AppDependencyProvider.shared.subscriptionManager.entitlements + if entitlements.contains(.networkProtection) { let items = [ UIApplicationShortcutItem(type: ShortcutKey.openVPNSettings, localizedTitle: UserText.netPOpenVPNQuickAction, @@ -1043,12 +1041,11 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } func presentNetworkProtectionStatusSettingsModal() { - Task { - if case .success(let hasEntitlements) = await accountManager.hasEntitlement(forProductName: .networkProtection), hasEntitlements { - (window?.rootViewController as? MainViewController)?.segueToVPN() - } else { - (window?.rootViewController as? MainViewController)?.segueToPrivacyPro() - } + let entitlements = AppDependencyProvider.shared.subscriptionManager.entitlements + if entitlements.contains(.networkProtection) { + (window?.rootViewController as? MainViewController)?.segueToVPN() + } else { + (window?.rootViewController as? MainViewController)?.segueToPrivacyPro() } } diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index c25532703b..333656c9d4 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -26,6 +26,8 @@ import Subscription import Common import NetworkProtection import RemoteMessaging +import Networking +import os.log protocol DependencyProvider { @@ -43,7 +45,7 @@ protocol DependencyProvider { var userBehaviorMonitor: UserBehaviorMonitor { get } var subscriptionFeatureAvailability: SubscriptionFeatureAvailability { get } var subscriptionManager: SubscriptionManager { get } - var accountManager: AccountManager { get } + var privacyProInfoProvider: any PrivacyProInfoProvider { get } var vpnFeatureVisibility: DefaultNetworkProtectionVisibility { get } var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore { get } var networkProtectionTunnelController: NetworkProtectionTunnelController { get } @@ -81,9 +83,10 @@ final class AppDependencyProvider: DependencyProvider { // Subscription let subscriptionManager: SubscriptionManager - var accountManager: AccountManager { - subscriptionManager.accountManager - } +// var accountManager: AccountManager { +// subscriptionManager.accountManager +// } + let privacyProInfoProvider: any PrivacyProInfoProvider let vpnFeatureVisibility: DefaultNetworkProtectionVisibility let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore let networkProtectionTunnelController: NetworkProtectionTunnelController @@ -105,31 +108,56 @@ final class AppDependencyProvider: DependencyProvider { let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) vpnSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, - key: UserDefaultsCacheKey.subscriptionEntitlements, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService) +// let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, +// key: UserDefaultsCacheKey.subscriptionEntitlements, +// settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) +// let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) +// let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) +// let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) +// let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, +// entitlementsCache: entitlementsCache, +// subscriptionEndpointService: subscriptionService, +// authEndpointService: authService) + + 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) + let keychainManager = SubscriptionKeychainManager() + let authClient = DefaultOAuthClient(tokensStorage: keychainManager, authService: authService) + self.privacyProInfoProvider = authClient + apiService.authorizationRefresherCallback = { _ in // TODO: is this updated? + // safety check + if keychainManager.tokensContainer?.decodedAccessToken.isExpired() == false { + assertionFailure("Refresh attempted on non expired token") + } + Logger.OAuth.debug("Refreshing tokens") + let tokens = try await authClient.refreshTokens() + return tokens.accessToken + } + let storePurchaseManager = DefaultStorePurchaseManager() - let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: DefaultStorePurchaseManager(), - accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, + let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: authEnvironment.url) + let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, + oAuthClient: authClient, + subscriptionEndpointService: subscriptionEndpointService, subscriptionEnvironment: subscriptionEnvironment) - accountManager.delegate = subscriptionManager - self.subscriptionManager = subscriptionManager let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, purchasePlatform: .appStore) - let accessTokenProvider: () -> String? = { - return { accountManager.accessToken } + let accessTokenProvider: () -> String? = { // TODO: refactor all of this + return { +// try? await authClient.getTokens(policy: .local).accessToken + return "" + } }() #if os(macOS) networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), @@ -139,10 +167,9 @@ final class AppDependencyProvider: DependencyProvider { #else networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) #endif - networkProtectionTunnelController = NetworkProtectionTunnelController(accountManager: accountManager, - tokenStore: networkProtectionKeychainTokenStore) + networkProtectionTunnelController = NetworkProtectionTunnelController(tokenStore: networkProtectionKeychainTokenStore) vpnFeatureVisibility = DefaultNetworkProtectionVisibility(userDefaults: .networkProtectionGroupDefaults, - accountManager: accountManager) + oAuthClient: authClient) } /// Only meant to be used for testing. @@ -151,3 +178,13 @@ final class AppDependencyProvider: DependencyProvider { Self.init() } } + +extension DefaultOAuthClient: PrivacyProInfoProvider { + + var hasVPNEntitlements: Bool { + guard let tokensContainer = tokensStorage.tokensContainer else { + return false + } + return tokensContainer.decodedAccessToken.hasEntitlement(.networkProtection) + } +} diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index baa0cebf53..2fa24bc89e 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -23,21 +23,22 @@ import Waitlist import NetworkProtection import Core import Subscription +import Networking struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let userDefaults: UserDefaults - private let accountManager: AccountManager + private let oAuthClient: any OAuthClient - init(userDefaults: UserDefaults, accountManager: AccountManager) { + init(userDefaults: UserDefaults, oAuthClient: any OAuthClient) { self.userDefaults = userDefaults - self.accountManager = accountManager + self.oAuthClient = oAuthClient } var token: String? { - return accountManager.accessToken + return oAuthClient.currentTokensContainer?.accessToken } func shouldShowVPNShortcut() -> Bool { - return accountManager.isUserAuthenticated + oAuthClient.isUserAuthenticated } } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 55e9d907a5..77770cfd31 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -163,7 +163,7 @@ final class DuckPlayer: DuckPlayerProtocol { // MARK: - Common Message Handlers public func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? { - guard let userValues: UserValues = DecodableHelper.decode(from: params) else { + guard let userValues: UserValues = CodableHelper.decode(from: params) else { assertionFailure("DuckPlayer: expected JSON representation of UserValues") return nil } @@ -274,7 +274,7 @@ final class DuckPlayer: DuckPlayerProtocol { @MainActor private func firePixels(message: WKScriptMessage, userValues: UserValues) { - guard let messageData: WKMessageData = DecodableHelper.decode(from: message.body) else { + guard let messageData: WKMessageData = CodableHelper.decode(from: message.body) else { assertionFailure("DuckPlayer: expected JSON representation of Message") return } diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index a2f6b69669..7943a2c948 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -99,21 +99,26 @@ protocol VPNMetadataCollector { func collectVPNMetadata() async -> VPNMetadata } +protocol PrivacyProInfoProvider { + var isUserAuthenticated: Bool { get } + var hasVPNEntitlements: Bool { get } +} + final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver - private let accountManager: AccountManager + private let privacyProInfoProvider: PrivacyProInfoProvider private let settings: VPNSettings private let defaults: UserDefaults init(statusObserver: ConnectionStatusObserver, serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), - accountManager: AccountManager = AppDependencyProvider.shared.subscriptionManager.accountManager, + privacyProInfoProvider: PrivacyProInfoProvider = AppDependencyProvider.shared.privacyProInfoProvider, settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { self.statusObserver = statusObserver self.serverInfoObserver = serverInfoObserver - self.accountManager = accountManager + self.privacyProInfoProvider = privacyProInfoProvider self.settings = settings self.defaults = defaults } @@ -242,10 +247,10 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { - let hasVPNEntitlement = (try? await accountManager.hasEntitlement(forProductName: .networkProtection).get()) ?? false +// let hasVPNEntitlement = (try? await accountManager.hasEntitlement(forProductName: .networkProtection).get()) ?? false return .init( - hasPrivacyProAccount: accountManager.isUserAuthenticated, - hasVPNEntitlement: hasVPNEntitlement + hasPrivacyProAccount: privacyProInfoProvider.isUserAuthenticated, + hasVPNEntitlement: privacyProInfoProvider.hasVPNEntitlements ) } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 6233baf103..a941a3a21e 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1611,8 +1611,10 @@ class MainViewController: UIViewController { @objc private func onEntitlementsChange(_ notification: Notification) { Task { - let accountManager = AppDependencyProvider.shared.subscriptionManager.accountManager - guard case .success(false) = await accountManager.hasEntitlement(forProductName: .networkProtection) else { return } + let subscriptionManager = AppDependencyProvider.shared.subscriptionManager + + guard let tokensContainer = try? await subscriptionManager.getTokens(policy: .localValid), + tokensContainer.decodedAccessToken.hasEntitlement(.networkProtection) else { return } if await networkProtectionTunnelController.isInstalled { tunnelDefaults.enableEntitlementMessaging() diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index e440446044..57c299c533 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -66,7 +66,7 @@ extension NetworkProtectionVPNSettingsViewModel { extension NetworkProtectionLocationListCompositeRepository { - convenience init(accountManager: AccountManager) { + convenience init() { let settings = AppDependencyProvider.shared.vpnSettings self.init( environment: settings.selectedEnvironment, @@ -78,8 +78,8 @@ extension NetworkProtectionLocationListCompositeRepository { extension NetworkProtectionVPNLocationViewModel { - convenience init(accountManager: AccountManager) { - let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) + convenience init() { + let locationListRepository = NetworkProtectionLocationListCompositeRepository() self.init( locationListRepository: locationListRepository, settings: AppDependencyProvider.shared.vpnSettings diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 9af0df4f9f..0163148d00 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -124,25 +124,21 @@ final class NetworkProtectionDebugViewController: UITableViewController { private var connectionTestResults: [ConnectionTestResult] = [] private var connectionTestResultError: String? private let connectionTestQueue = DispatchQueue(label: "com.duckduckgo.ios.vpnDebugConnectionTestQueue") - private let accountManager: AccountManager // MARK: Lifecycle required init?(coder: NSCoder, tokenStore: NetworkProtectionTokenStore, - debugFeatures: NetworkProtectionDebugFeatures = NetworkProtectionDebugFeatures(), - accountManager: AccountManager) { + debugFeatures: NetworkProtectionDebugFeatures = NetworkProtectionDebugFeatures()) { self.debugFeatures = debugFeatures self.tokenStore = tokenStore - self.accountManager = accountManager super.init(coder: coder) } required convenience init?(coder: NSCoder) { - self.init(coder: coder, tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore, - accountManager: AppDependencyProvider.shared.subscriptionManager.accountManager) + self.init(coder: coder, tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) } override func viewWillAppear(_ animated: Bool) { @@ -671,7 +667,7 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") if let subscriptionOverrideEnabled = defaults.subscriptionOverrideEnabled { if subscriptionOverrideEnabled { defaults.subscriptionOverrideEnabled = false - accountManager.signOut() + AppDependencyProvider.shared.subscriptionManager.signOut() } else { defaults.resetsubscriptionOverrideEnabled() } diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 267be495cf..4757d8d229 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -27,10 +27,10 @@ struct NetworkProtectionRootView: View { let statusViewModel: NetworkProtectionStatusViewModel init() { - let accountManager = AppDependencyProvider.shared.subscriptionManager.accountManager + let subscriptionManager = AppDependencyProvider.shared.subscriptionManager let subscriptionFeatureAvailability = AppDependencyProvider.shared.subscriptionFeatureAvailability - let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) - let usesUnifiedFeedbackForm = accountManager.isUserAuthenticated && subscriptionFeatureAvailability.usesUnifiedFeedbackForm + let locationListRepository = NetworkProtectionLocationListCompositeRepository() + let usesUnifiedFeedbackForm = subscriptionManager.isUserAuthenticated && subscriptionFeatureAvailability.usesUnifiedFeedbackForm statusViewModel = NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, settings: AppDependencyProvider.shared.vpnSettings, statusObserver: AppDependencyProvider.shared.connectionObserver, diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index c7b8ebee6d..8ffab374a1 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -118,7 +118,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } } - init(accountManager: AccountManager, tokenStore: NetworkProtectionKeychainTokenStore) { + init(tokenStore: NetworkProtectionKeychainTokenStore) { self.tokenStore = tokenStore subscribeToSnoozeTimingChanges() subscribeToStatusChanges() diff --git a/DuckDuckGo/NetworkProtectionVPNLocationView.swift b/DuckDuckGo/NetworkProtectionVPNLocationView.swift index eb49cbdff8..a722c54d45 100644 --- a/DuckDuckGo/NetworkProtectionVPNLocationView.swift +++ b/DuckDuckGo/NetworkProtectionVPNLocationView.swift @@ -21,7 +21,7 @@ import Foundation import SwiftUI struct NetworkProtectionVPNLocationView: View { - @StateObject var model = NetworkProtectionVPNLocationViewModel(accountManager: AppDependencyProvider.shared.subscriptionManager.accountManager) + @StateObject var model = NetworkProtectionVPNLocationViewModel() var body: some View { List { diff --git a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift index 1944d2a806..9eb7a1c233 100644 --- a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift @@ -19,28 +19,26 @@ import Foundation import Subscription +import Networking -struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVisibility { - - private let accountManager: AccountManager - - init(accountManager: AccountManager) { - self.accountManager = accountManager - } - - func isPrivacyProLaunched() -> Bool { - accountManager.isUserAuthenticated - } - - func shouldMonitorEntitlement() -> Bool { - isPrivacyProLaunched() - } - - func shouldShowVPNShortcut() -> Bool { - guard isPrivacyProLaunched() else { - return false - } - - return accountManager.isUserAuthenticated - } -} +// struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVisibility { +// +// private let oAuthClient: any OAuthClient +// +// init(oAuthClient: any OAuthClient) { +// self.oAuthClient = oAuthClient +// } +// +// func isPrivacyProLaunched() -> Bool { +// let tokensContainer = oAuthClient.getStoredTokens() +// return tokensContainer?.accessToken != nil +// } +// +// func shouldMonitorEntitlement() -> Bool { +// isPrivacyProLaunched() +// } +// +// func shouldShowVPNShortcut() -> Bool { +// return isPrivacyProLaunched() +// } +// } diff --git a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift index a10a8f9bbf..1e1ed3d491 100644 --- a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift +++ b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift @@ -62,7 +62,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let variantManager = DefaultVariantManager() let subscriptionManager = AppDependencyProvider.shared.subscriptionManager - let isPrivacyProSubscriber = subscriptionManager.accountManager.isUserAuthenticated + let isPrivacyProSubscriber = subscriptionManager.isUserAuthenticated let isPrivacyProEligibleUser = subscriptionManager.canPurchase let activationDateStore = DefaultVPNActivationDateStore() @@ -84,12 +84,9 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let surveyActionMapper: DefaultRemoteMessagingSurveyURLBuilder - if let accessToken = subscriptionManager.accountManager.accessToken { - let subscriptionResult = await subscriptionManager.subscriptionEndpointService.getSubscription( - accessToken: accessToken - ) - - if case let .success(subscription) = subscriptionResult { + if let accessToken = try? await subscriptionManager.getTokens(policy: .localValid).accessToken { + do { + let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: accessToken) privacyProDaysSinceSubscribed = Calendar.current.numberOfDaysBetween(subscription.startedAt, and: Date()) ?? -1 privacyProDaysUntilExpiry = Calendar.current.numberOfDaysBetween(Date(), and: subscription.expiresOrRenewsAt) ?? -1 privacyProPurchasePlatform = subscription.platform.rawValue @@ -109,7 +106,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr statisticsStore: statisticsStore, vpnActivationDateStore: DefaultVPNActivationDateStore(), subscription: subscription) - } else { + } catch { surveyActionMapper = DefaultRemoteMessagingSurveyURLBuilder( statisticsStore: statisticsStore, vpnActivationDateStore: DefaultVPNActivationDateStore(), diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 2548e20354..e6515d9a25 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -19,6 +19,7 @@ import BrowserServicesKit import Subscription +import Networking struct SettingsState { @@ -43,8 +44,8 @@ struct SettingsState { var hasActiveSubscription: Bool var isRestoring: Bool var shouldDisplayRestoreSubscriptionError: Bool - var entitlements: [Entitlement.ProductName] - var platform: DDGSubscription.Platform + var entitlements: [SubscriptionEntitlement] + var platform: PrivacyProSubscription.Platform var isShowingStripeView: Bool } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 8dbc850832..1080854be4 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -25,6 +25,7 @@ import Common import Combine import SyncUI import DuckPlayer +import Networking import Subscription import NetworkProtection @@ -340,7 +341,7 @@ final class SettingsViewModel: ObservableObject { } var usesUnifiedFeedbackForm: Bool { - subscriptionManager.accountManager.isUserAuthenticated && subscriptionFeatureAvailability.usesUnifiedFeedbackForm + subscriptionManager.isUserAuthenticated && subscriptionFeatureAvailability.usesUnifiedFeedbackForm } // MARK: Default Init @@ -694,10 +695,10 @@ extension SettingsViewModel { state.subscription.canPurchase = subscriptionManager.canPurchase // Update if user is signed in based on the presence of token - state.subscription.isSignedIn = subscriptionManager.accountManager.isUserAuthenticated + state.subscription.isSignedIn = subscriptionManager.isUserAuthenticated // Active subscription check - guard let token = subscriptionManager.accountManager.accessToken else { + guard let tokensContainer = try? await subscriptionManager.getTokens(policy: .local) else { // Reset state in case cache was outdated state.subscription.hasActiveSubscription = false state.subscription.entitlements = [] @@ -706,29 +707,26 @@ extension SettingsViewModel { subscriptionStateCache.set(state.subscription) // Sync cache return } - - let subscriptionResult = await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token) - switch subscriptionResult { - - case .success(let subscription): + + do { + let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: tokensContainer.accessToken) state.subscription.platform = subscription.platform state.subscription.hasActiveSubscription = subscription.isActive - // Check entitlements and update state - var currentEntitlements: [Entitlement.ProductName] = [] - let entitlementsToCheck: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - - for entitlement in entitlementsToCheck { - if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) { - currentEntitlements.append(entitlement) - } - } + self.state.subscription.entitlements = tokensContainer.decodedAccessToken.subscriptionEntitlements - self.state.subscription.entitlements = currentEntitlements +// var currentEntitlements: [SubscriptionEntitlement] = [] +// let entitlementsToCheck: [SubscriptionEntitlement] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] +// +// for entitlement in entitlementsToCheck { +// if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) { +// currentEntitlements.append(entitlement) +// } +// } +// +// self.state.subscription.entitlements = currentEntitlements - case .failure: - break - } + } catch {} // Sync Cache subscriptionStateCache.set(state.subscription) @@ -757,10 +755,11 @@ extension SettingsViewModel { func restoreAccountPurchase() async { DispatchQueue.main.async { self.state.subscription.isRestoring = true } - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + + let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift index dbd2042c86..98cdfa8ef6 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift @@ -21,16 +21,16 @@ import Foundation import Core import Subscription -extension DefaultSubscriptionManager: AccountManagerKeychainAccessDelegate { - - public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { - let parameters = [ - PixelParameters.privacyProKeychainAccessType: accessType.rawValue, - PixelParameters.privacyProKeychainError: error.errorDescription, - PixelParameters.source: "browser" - ] - - DailyPixel.fireDailyAndCount(pixel: .privacyProKeychainAccessError, - withAdditionalParameters: parameters) - } -} +// extension DefaultSubscriptionManager: AccountManagerKeychainAccessDelegate { +// +// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { +// let parameters = [ +// PixelParameters.privacyProKeychainAccessType: accessType.rawValue, +// PixelParameters.privacyProKeychainError: error.errorDescription, +// PixelParameters.source: "browser" +// ] +// +// DailyPixel.fireDailyAndCount(pixel: .privacyProKeychainAccessError, +// withAdditionalParameters: parameters) +// } +// } diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index aef3903dbb..f647c3763c 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -42,10 +42,10 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { static let getAccessToken = "getAccessToken" } - private let accountManager: AccountManager + private let subscriptionManager: SubscriptionManager - init(accountManager: AccountManager) { - self.accountManager = accountManager + init(subscriptionManager: SubscriptionManager) { + self.subscriptionManager = subscriptionManager } weak var broker: UserScriptMessageBroker? @@ -71,7 +71,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = accountManager.accessToken { + if let accessToken = try? await subscriptionManager.getTokens(policy: .localValid) { return [Constants.token: accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 3cc20634b2..5309d0c886 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -26,6 +26,7 @@ import Combine import Subscription import Core import os.log +import Networking enum SubscriptionTransactionStatus: String { case idle, purchasing, restoring, polling @@ -90,22 +91,21 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec private let subscriptionAttributionOrigin: String? private let subscriptionManager: SubscriptionManager - private var accountManager: AccountManager { subscriptionManager.accountManager } private let appStorePurchaseFlow: AppStorePurchaseFlow private let appStoreRestoreFlow: AppStoreRestoreFlow - private let appStoreAccountManagementFlow: AppStoreAccountManagementFlow +// private let appStoreAccountManagementFlow: AppStoreAccountManagementFlow private let privacyProDataReporter: PrivacyProDataReporting? init(subscriptionManager: SubscriptionManager, subscriptionAttributionOrigin: String?, appStorePurchaseFlow: AppStorePurchaseFlow, appStoreRestoreFlow: AppStoreRestoreFlow, - appStoreAccountManagementFlow: AppStoreAccountManagementFlow, +// appStoreAccountManagementFlow: AppStoreAccountManagementFlow, privacyProDataReporter: PrivacyProDataReporting? = nil) { self.subscriptionManager = subscriptionManager self.appStorePurchaseFlow = appStorePurchaseFlow self.appStoreRestoreFlow = appStoreRestoreFlow - self.appStoreAccountManagementFlow = appStoreAccountManagementFlow +// self.appStoreAccountManagementFlow = appStoreAccountManagementFlow self.subscriptionAttributionOrigin = subscriptionAttributionOrigin self.privacyProDataReporter = subscriptionAttributionOrigin != nil ? privacyProDataReporter : nil } @@ -191,10 +191,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // MARK: Broker Methods (Called from WebView via UserScripts) func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { - await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() - let authToken = accountManager.authToken ?? Constants.empty - - return [Constants.token: authToken] + let accessToken = (try? await subscriptionManager.getTokens(policy: .localValid).accessToken) ?? Constants.empty + return [Constants.token: accessToken] } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { @@ -224,7 +222,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } let message = original - 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") Logger.subscription.error("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") setTransactionStatus(.idle) @@ -243,8 +241,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String - switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, - emailAccessToken: emailAccessToken) { + switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id) { case .success(let transactionJWS): Logger.subscription.debug("Subscription purchased successfully") purchaseTransactionJWS = transactionJWS @@ -287,7 +284,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func setSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { - guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") Logger.subscription.error("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") setTransactionError(.generalError) @@ -297,17 +294,19 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // Clear subscription Cache subscriptionManager.subscriptionEndpointService.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) - onSetSubscription?() - - } else { - Logger.subscription.error("Failed to obtain subscription options") - setTransactionError(.failedToSetSubscription) - } + // TODO: what are we doing here?? + +// 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) +// onSetSubscription?() +// +// } else { +// Logger.subscription.error("Failed to obtain subscription options") +// setTransactionError(.failedToSetSubscription) +// } return nil } @@ -319,7 +318,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func featureSelected(params: Any, original: WKScriptMessage) async -> Encodable? { - 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") Logger.subscription.error("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") return nil @@ -338,31 +337,34 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func backToSettings(params: Any, original: WKScriptMessage) async -> Encodable? { - guard let accessToken = accountManager.accessToken else { - Logger.subscription.error("Missing access token") - return nil - } - - switch await accountManager.fetchAccountDetails(with: accessToken) { - case .success(let accountDetails): - switch await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: accessToken) { - case .success: - accountManager.storeAccount(token: accessToken, - email: accountDetails.email, - externalID: accountDetails.externalID) - onBackToSettings?() - case .failure(let error): - Logger.subscription.error("Error retrieving subscription details: \(error.localizedDescription)") - } - case .failure(let error): - Logger.subscription.error("Could not get account Details: \(error.localizedDescription)") - setTransactionError(.generalError) - } +// guard let accessToken = accountManager.accessToken else { +// Logger.subscription.error("Missing access token") +// return nil +// } +// +// switch await accountManager.fetchAccountDetails(with: accessToken) { +// case .success(let accountDetails): +// switch await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: accessToken) { +// case .success: +// accountManager.storeAccount(token: accessToken, +// email: accountDetails.email, +// externalID: accountDetails.externalID) +// onBackToSettings?() +// case .failure(let error): +// Logger.subscription.error("Error retrieving subscription details: \(error.localizedDescription)") +// } +// case .failure(let error): +// Logger.subscription.error("Could not get account Details: \(error.localizedDescription)") +// setTransactionError(.generalError) +// } +// return nil + subscriptionManager.refreshAccount() + onBackToSettings?() return nil } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = subscriptionManager.accountManager.accessToken { + if let accessToken = try? await subscriptionManager.getTokens(policy: .localValid) { return [Constants.token: accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index aba3be0689..5f8932b540 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -69,7 +69,7 @@ final class SubscriptionEmailViewModel: ObservableObject { } private var cancellables = Set() - var accountManager: AccountManager { subscriptionManager.accountManager } +// var accountManager: AccountManager { subscriptionManager.accountManager } private var isWelcomePageOrSuccessPage: Bool { let subscriptionActivateSuccessURL = subscriptionManager.url(for: .activateSuccess) @@ -131,12 +131,12 @@ final class SubscriptionEmailViewModel: ObservableObject { func onAppear() { state.shouldDismissView = false // If the user is Authenticated & not in the Welcome page - if accountManager.isUserAuthenticated && !isWelcomePageOrSuccessPage { + if subscriptionManager.isUserAuthenticated && !isWelcomePageOrSuccessPage { // If user is authenticated, we want to "Add or manage email" instead of activating let addEmailToSubscriptionURL = subscriptionManager.url(for: .addEmail) let manageSubscriptionEmailURL = subscriptionManager.url(for: .manageEmail) - emailURL = accountManager.email == nil ? addEmailToSubscriptionURL : manageSubscriptionEmailURL - state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionEditEmailTitle + emailURL = subscriptionManager.userEmail == nil ? addEmailToSubscriptionURL : manageSubscriptionEmailURL + state.viewTitle = subscriptionManager.userEmail == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionEditEmailTitle // Also we assume subscription requires managing, and not activation state.managingSubscriptionEmail = true diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index 3e9692f30b..9e67cc8b86 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -64,7 +64,7 @@ final class SubscriptionITPViewModel: ObservableObject { self.itpURL = subscriptionManager.url(for: .identityTheftRestoration) self.manageITPURL = self.itpURL self.userScript = IdentityTheftRestorationPagesUserScript() - self.subFeature = IdentityTheftRestorationPagesFeature(accountManager: subscriptionManager.accountManager) + self.subFeature = IdentityTheftRestorationPagesFeature(subscriptionManager: subscriptionManager) let webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: Self.allowedDomains, diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 2940149fe3..e14cd01e65 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -28,7 +28,6 @@ final class SubscriptionRestoreViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature let subscriptionManager: SubscriptionManager - var accountManager: AccountManager { subscriptionManager.accountManager } private var cancellables = Set() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index d6773ece49..f3d2d1c3b0 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -24,6 +24,7 @@ import Subscription import Core import os.log import BrowserServicesKit +import Networking final class SubscriptionSettingsViewModel: ObservableObject { @@ -40,7 +41,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { var isShowingGoogleView: Bool = false var isShowingFAQView: Bool = false var isShowingLearnMoreView: Bool = false - var subscriptionInfo: Subscription? + var subscriptionInfo: PrivacyProSubscription? var isLoadingSubscriptionInfo: Bool = false var isLoadingEmailInfo: Bool = false @@ -75,7 +76,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { let subscriptionFAQURL = subscriptionManager.url(for: .faq) let learnMoreURL = subscriptionFAQURL.appendingPathComponent("adding-email") self.state = State(faqURL: subscriptionFAQURL, learnMoreURL: learnMoreURL) - self.usesUnifiedFeedbackForm = subscriptionManager.accountManager.isUserAuthenticated && subscriptionFeatureAvailability.usesUnifiedFeedbackForm + self.usesUnifiedFeedbackForm = subscriptionManager.isUserAuthenticated && subscriptionFeatureAvailability.usesUnifiedFeedbackForm setupNotificationObservers() } @@ -109,15 +110,14 @@ final class SubscriptionSettingsViewModel: ObservableObject { } } - private func fetchAndUpdateSubscriptionDetails(cachePolicy: APICachePolicy, loadingIndicator: Bool) async -> Bool { + private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionCachePolicy, loadingIndicator: Bool) async -> Bool { Logger.subscription.debug("\(#function)") - guard let token = self.subscriptionManager.accountManager.accessToken else { return false } + guard let token = try? await subscriptionManager.getTokens(policy: .localValid).accessToken else { return false } if loadingIndicator { displaySubscriptionLoader(true) } - let subscriptionResult = await self.subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token, - cachePolicy: cachePolicy) - switch subscriptionResult { - case .success(let subscription): + do { + let subscription = try await self.subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token, + cachePolicy: cachePolicy) DispatchQueue.main.async { self.state.subscriptionInfo = subscription if loadingIndicator { self.displaySubscriptionLoader(false) } @@ -127,7 +127,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { product: subscription.productId, billingPeriod: subscription.billingPeriod) return true - case .failure(let error): + } catch { Logger.subscription.error("\(#function) error: \(error.localizedDescription)") DispatchQueue.main.async { if loadingIndicator { self.displaySubscriptionLoader(true) } @@ -136,42 +136,38 @@ final class SubscriptionSettingsViewModel: ObservableObject { } } - func fetchAndUpdateAccountEmail(cachePolicy: APICachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { + func fetchAndUpdateAccountEmail(cachePolicy: SubscriptionCachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { Logger.subscription.debug("\(#function)") - guard let token = self.subscriptionManager.accountManager.accessToken else { return false } + let tokensPolicy: TokensCachePolicy = cachePolicy == .returnCacheDataDontLoad ? .local : .localValid - switch cachePolicy { - case .returnCacheDataDontLoad, .returnCacheDataElseLoad: - DispatchQueue.main.async { - self.state.subscriptionEmail = self.subscriptionManager.accountManager.email - } - return true - case .reloadIgnoringLocalCacheData: - break + guard let tokensContainer = try? await subscriptionManager.getTokens(policy: tokensPolicy) else { return false } + DispatchQueue.main.async { + self.state.subscriptionEmail = tokensContainer.decodedAccessToken.email + if loadingIndicator { self.displayEmailLoader(true) } } + return true - if loadingIndicator { displayEmailLoader(true) } - switch await self.subscriptionManager.accountManager.fetchAccountDetails(with: token) { - case .success(let details): - Logger.subscription.debug("Account details fetched successfully") - DispatchQueue.main.async { - self.state.subscriptionEmail = details.email - if loadingIndicator { self.displayEmailLoader(false) } - } - - // If fetched email is different then update accountManager - if details.email != subscriptionManager.accountManager.email { - let externalID = subscriptionManager.accountManager.externalID - subscriptionManager.accountManager.storeAccount(token: token, email: details.email, externalID: externalID) - } - return true - case .failure(let error): - Logger.subscription.error("\(#function) error: \(error.localizedDescription)") - DispatchQueue.main.async { - if loadingIndicator { self.displayEmailLoader(true) } - } - return false - } +// switch await self.subscriptionManager.accountManager.fetchAccountDetails(with: token) { +// case .success(let details): +// Logger.subscription.debug("Account details fetched successfully") +// DispatchQueue.main.async { +// self.state.subscriptionEmail = details.email +// if loadingIndicator { self.displayEmailLoader(false) } +// } +// +// // If fetched email is different then update accountManager +// if details.email != subscriptionManager.accountManager.email { +// let externalID = subscriptionManager.accountManager.externalID +// subscriptionManager.accountManager.storeAccount(token: token, email: details.email, externalID: externalID) +// } +// return true +// case .failure(let error): +// Logger.subscription.error("\(#function) error: \(error.localizedDescription)") +// DispatchQueue.main.async { +// if loadingIndicator { self.displayEmailLoader(true) } +// } +// return false +// } } private func displaySubscriptionLoader(_ show: Bool) { @@ -211,7 +207,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } @MainActor - private func updateSubscriptionsStatusMessage(status: Subscription.Status, date: Date, product: String, billingPeriod: Subscription.BillingPeriod) { + private func updateSubscriptionsStatusMessage(status: PrivacyProSubscription.Status, date: Date, product: String, billingPeriod: PrivacyProSubscription.BillingPeriod) { let billingPeriod = billingPeriod == .monthly ? UserText.subscriptionMonthlyBillingPeriod : UserText.subscriptionAnnualBillingPeriod let date = dateFormatter.string(from: date) @@ -226,7 +222,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func removeSubscription() { - subscriptionManager.accountManager.signOut() + subscriptionManager.signOut() _ = ActionMessageView() ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, presentationLocation: .withoutBottomBar) @@ -293,13 +289,11 @@ final class SubscriptionSettingsViewModel: ObservableObject { } private func manageStripeSubscription() async { - guard let token = subscriptionManager.accountManager.accessToken, - let externalID = subscriptionManager.accountManager.externalID else { return } - let serviceResponse = await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: token, externalID: externalID) - - // Get Stripe Customer Portal URL and update the model - if case .success(let response) = serviceResponse { - guard let url = URL(string: response.customerPortalUrl) else { return } + guard let tokensContainer = try? await subscriptionManager.getTokens(policy: .localValid) else { return } + do { + // Get Stripe Customer Portal URL and update the model + let serviceResponse = try await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: tokensContainer.accessToken, externalID: tokensContainer.decodedAccessToken.externalID) + guard let url = URL(string: serviceResponse.customerPortalUrl) else { return } if let existingModel = state.stripeViewModel { existingModel.url = url } else { @@ -308,9 +302,11 @@ final class SubscriptionSettingsViewModel: ObservableObject { self.state.stripeViewModel = model } } - } - DispatchQueue.main.async { - self.displayStripeView(true) + DispatchQueue.main.async { + self.displayStripeView(true) + } + } catch { + Logger.subscription.error("\(error.localizedDescription)") } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift index 547523b58f..395c294dd4 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift @@ -19,25 +19,50 @@ import SwiftUI import Subscription +import Networking +import os.log enum SubscriptionContainerViewFactory { + static func makeOAuthClient(subscriptionManager: SubscriptionManager) -> OAuthClient { + 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 = subscriptionManager.currentEnvironment.serviceEnvironment == .production ? .production : .staging + + let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) + let keychainManager = SubscriptionKeychainManager() + let authClient = DefaultOAuthClient(tokensStorage: keychainManager, authService: authService) + + apiService.authorizationRefresherCallback = { _ in // TODO: is this updated? + // safety check + if keychainManager.tokensContainer?.decodedAccessToken.isExpired() == false { + assertionFailure("Refresh attempted on non expired token") + } + Logger.OAuth.debug("Refreshing tokens") + let tokens = try await authClient.refreshTokens() + return tokens.accessToken + } + return authClient + } + static func makeSubscribeFlow(origin: String?, navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager, privacyProDataReporter: PrivacyProDataReporting?) -> some View { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + + let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(oAuthClient: authClient, + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) + appStoreRestoreFlow: appStoreRestoreFlow) let viewModel = SubscriptionContainerViewModel( subscriptionManager: subscriptionManager, @@ -47,7 +72,6 @@ enum SubscriptionContainerViewFactory { subscriptionAttributionOrigin: origin, appStorePurchaseFlow: appStorePurchaseFlow, appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow, privacyProDataReporter: privacyProDataReporter) ) return SubscriptionContainerView(currentView: .subscribe, viewModel: viewModel) @@ -56,28 +80,22 @@ enum SubscriptionContainerViewFactory { static func makeRestoreFlow(navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager) -> some View { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(oAuthClient: authClient, + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) + appStoreRestoreFlow: appStoreRestoreFlow) - let viewModel = SubscriptionContainerViewModel( - subscriptionManager: subscriptionManager, - origin: nil, - userScript: SubscriptionPagesUserScript(), - subFeature: SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, - subscriptionAttributionOrigin: nil, - appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow) + let viewModel = SubscriptionContainerViewModel(subscriptionManager: subscriptionManager, + origin: nil, + userScript: SubscriptionPagesUserScript(), + subFeature: SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, + subscriptionAttributionOrigin: nil, + appStorePurchaseFlow: appStorePurchaseFlow, + appStoreRestoreFlow: appStoreRestoreFlow) ) return SubscriptionContainerView(currentView: .restore, viewModel: viewModel) .environmentObject(navigationCoordinator) @@ -86,18 +104,14 @@ enum SubscriptionContainerViewFactory { static func makeEmailFlow(navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager, onDisappear: @escaping () -> Void) -> some View { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(oAuthClient: authClient, + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) + appStoreRestoreFlow: appStoreRestoreFlow) let viewModel = SubscriptionContainerViewModel( subscriptionManager: subscriptionManager, origin: nil, @@ -105,8 +119,7 @@ enum SubscriptionContainerViewFactory { subFeature: SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow) + appStoreRestoreFlow: appStoreRestoreFlow) ) return SubscriptionContainerView(currentView: .email, viewModel: viewModel) .environmentObject(navigationCoordinator) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 45247ddb76..28292c4330 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -22,6 +22,7 @@ import UIKit import Subscription import Core import NetworkProtection +import Networking final class SubscriptionDebugViewController: UITableViewController { @@ -263,18 +264,23 @@ final class SubscriptionDebugViewController: UITableViewController { } private func clearAuthData() { - subscriptionManager.accountManager.signOut() + subscriptionManager.signOut() showAlert(title: "Data cleared!") } private func showAccountDetails() { - let title = subscriptionManager.accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" - let message = subscriptionManager.accountManager.isUserAuthenticated ? - ["Service Environment: \(subscriptionManager.currentEnvironment.serviceEnvironment.description)", - "AuthToken: \(subscriptionManager.accountManager.authToken ?? "")", - "AccessToken: \(subscriptionManager.accountManager.accessToken ?? "")", - "Email: \(subscriptionManager.accountManager.email ?? "")"].joined(separator: "\n") : nil - showAlert(title: title, message: message) + Task { + let tokensContainer = try? await subscriptionManager.getTokens(policy: .local) + let authenticated = tokensContainer != nil + let title = authenticated ? "Authenticated" : "Not Authenticated" + let message = authenticated ? + ["Service Environment: \(subscriptionManager.currentEnvironment.serviceEnvironment)", + "AuthToken: \(tokensContainer?.accessToken ?? "")", + "Email: \(tokensContainer?.decodedAccessToken.email ?? "")"].joined(separator: "\n") : nil + DispatchQueue.main.async { + self.showAlert(title: title, message: message) + } + } } private func showRandomizedParamters() { @@ -316,14 +322,12 @@ final class SubscriptionDebugViewController: UITableViewController { private func validateToken() { Task { - guard let token = subscriptionManager.accountManager.accessToken else { + do { + let tokensContainer = try await subscriptionManager.getTokens(policy: .localValid) + showAlert(title: "Token details", message: "\(tokensContainer.debugDescription)") + } catch OAuthClientError.missingTokens { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") - return - } - switch await subscriptionManager.authEndpointService.validateToken(accessToken: token) { - case .success(let response): - showAlert(title: "Token details", message: "\(response)") - case .failure(let error): + } catch { showAlert(title: "Error Validating Token", message: "\(error)") } } @@ -331,37 +335,30 @@ final class SubscriptionDebugViewController: UITableViewController { private func getSubscriptionDetails() { Task { - guard let token = subscriptionManager.accountManager.accessToken else { - showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") - return - } - switch await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token, - cachePolicy: .reloadIgnoringLocalCacheData) { - case .success(let response): - showAlert(title: "Subscription info", message: "\(response)") - case .failure(let error): - showAlert(title: "Subscription Error", message: "\(error)") + do { + let tokensContainer = try await subscriptionManager.getTokens(policy: .localValid) + let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: tokensContainer.accessToken, + cachePolicy: .reloadIgnoringLocalCacheData) + showAlert(title: "Subscription info", message: "\(subscription)") + + } catch OAuthClientError.missingTokens { + showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") + } catch { + showAlert(title: "Error retrieving subscription", message: "\(error)") } } } private func checkEntitlements() { Task { - var results: [String] = [] - guard subscriptionManager.accountManager.accessToken != nil else { - showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") - return - } - let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - for entitlement in entitlements { - if case let .success(result) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement, - cachePolicy: .reloadIgnoringLocalCacheData) { - let resultSummary = "Entitlement check for \(entitlement.rawValue): \(result)" - results.append(resultSummary) - print(resultSummary) - } + do { + let tokensContainer = try await subscriptionManager.getTokens(policy: .localValid) + showAlert(title: "Available Entitlements", message: tokensContainer.decodedAccessToken.subscriptionEntitlements.debugDescription) + } catch OAuthClientError.missingTokens { + showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") + } catch { + showAlert(title: "Error retrieving entitlements", message: "\(error)") } - showAlert(title: "Available Entitlements", message: results.joined(separator: "\n")) } } @@ -373,7 +370,7 @@ final class SubscriptionDebugViewController: UITableViewController { newSubscriptionEnvironment.serviceEnvironment = environment if newSubscriptionEnvironment.serviceEnvironment != currentSubscriptionEnvironment.serviceEnvironment { - subscriptionManager.accountManager.signOut() + subscriptionManager.signOut() // Save Subscription environment DefaultSubscriptionManager.save(subscriptionEnvironment: newSubscriptionEnvironment, userDefaults: subscriptionUserDefaults) diff --git a/DuckDuckGo/VPNRedditSessionWorkaround.swift b/DuckDuckGo/VPNRedditSessionWorkaround.swift index dd1839a9ea..59cb95491f 100644 --- a/DuckDuckGo/VPNRedditSessionWorkaround.swift +++ b/DuckDuckGo/VPNRedditSessionWorkaround.swift @@ -28,11 +28,11 @@ final class VPNRedditSessionWorkaround { @UserDefaultsWrapper(key: .vpnRedditWorkaroundInstalled, defaultValue: false) var vpnWorkaroundInstalled: Bool - private let accountManager: AccountManager + private let subscriptionManager: SubscriptionManager private let tunnelController: TunnelController - init(accountManager: AccountManager, tunnelController: TunnelController) { - self.accountManager = accountManager + init(subscriptionManager: SubscriptionManager, tunnelController: TunnelController) { + self.subscriptionManager = subscriptionManager self.tunnelController = tunnelController } @@ -50,7 +50,7 @@ final class VPNRedditSessionWorkaround { @MainActor func installRedditSessionWorkaround(to cookieStore: WKHTTPCookieStore) async { - guard accountManager.isUserAuthenticated, + guard subscriptionManager.isUserAuthenticated, await tunnelController.isConnected, let redditSessionCookie = HTTPCookie.emptyRedditSession else { return diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 9704c74e4e..d286004ca8 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -30,12 +30,27 @@ import WidgetKit import WireGuard import BrowserServicesKit + +public protocol SubscriptionSomething { + func isUserAuthenticated() -> Bool + var subscriptionAuthToken: String? { get } + +} + +extension SubscriptionSomething { + var accessToken: String? { + guard let subscriptionAuthToken else { return nil } + return "ddg:"+subscriptionAuthToken + } +} + + // Initial implementation for initial Network Protection tests. Will be fleshed out with https://app.asana.com/0/1203137811378537/1204630829332227/f final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { private static var vpnLogger = VPNLogger() private var cancellables = Set() - private let accountManager: AccountManager +// private let accountManager: AccountManager private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager @@ -352,24 +367,28 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } // MARK: - Configure Subscription - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults.standard, - key: UserDefaultsCacheKey.subscriptionEntitlements, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) +// let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults.standard, +// key: UserDefaultsCacheKey.subscriptionEntitlements, +// settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService) - self.accountManager = accountManager - let featureVisibility = NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager) +// let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) +// let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) +// let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) +// let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, +// entitlementsCache: entitlementsCache, +// subscriptionEndpointService: subscriptionService, +// authEndpointService: authService) +// self.accountManager = accountManager + +// let featureVisibility = NetworkProtectionVisibilityForTunnelProvider(oAuthClient: <#T##any OAuthClient#>) let accessTokenProvider: () -> String? = { - if featureVisibility.shouldMonitorEntitlement() { - return { accountManager.accessToken } - } +// if featureVisibility.shouldMonitorEntitlement() { + return { +// accountManager.accessToken + "" // TODO: :( + } +// } return { nil } }() let tokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) @@ -394,9 +413,10 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { providerEvents: Self.packetTunnelProviderEvents, settings: settings, defaults: .networkProtectionGroupDefaults, - entitlementCheck: { return await Self.entitlementCheck(accountManager: accountManager) }) + entitlementCheck: { return await Self.entitlementCheck() } + ) - accountManager.delegate = self +// accountManager.delegate = self startMonitoringMemoryPressureEvents() observeServerChanges() APIRequest.Headers.setUserAgent(DefaultUserAgentManager.duckDuckGoUserAgent) @@ -444,19 +464,19 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { WidgetCenter.shared.reloadTimelines(ofKind: "VPNStatusWidget") } - private static func entitlementCheck(accountManager: AccountManager) async -> Result { - - guard NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager).shouldMonitorEntitlement() else { - return .success(true) - } - - let result = await accountManager.hasEntitlement(forProductName: .networkProtection) - switch result { - case .success(let hasEntitlement): - return .success(hasEntitlement) - case .failure(let error): - return .failure(error) - } + private static func entitlementCheck() async -> Result { + return .success(true) // TODO: WTF? +// guard NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager).shouldMonitorEntitlement() else { +// return .success(true) +// } +// +// let result = await accountManager.hasEntitlement(forProductName: .networkProtection) +// switch result { +// case .success(let hasEntitlement): +// return .success(hasEntitlement) +// case .failure(let error): +// return .failure(error) +// } } } @@ -490,16 +510,16 @@ final class DefaultWireGuardInterface: WireGuardInterface { } } -extension NetworkProtectionPacketTunnelProvider: AccountManagerKeychainAccessDelegate { - - public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { - let parameters = [ - PixelParameters.privacyProKeychainAccessType: accessType.rawValue, - PixelParameters.privacyProKeychainError: error.errorDescription, - PixelParameters.source: "vpn" - ] - - DailyPixel.fireDailyAndCount(pixel: .privacyProKeychainAccessError, - withAdditionalParameters: parameters) - } -} +// extension NetworkProtectionPacketTunnelProvider: AccountManagerKeychainAccessDelegate { +// +// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { +// let parameters = [ +// PixelParameters.privacyProKeychainAccessType: accessType.rawValue, +// PixelParameters.privacyProKeychainError: error.errorDescription, +// PixelParameters.source: "vpn" +// ] +// +// DailyPixel.fireDailyAndCount(pixel: .privacyProKeychainAccessError, +// withAdditionalParameters: parameters) +// } +// } From 79faa6ba073b0327c03afbdef5412aa6c004e21b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 21 Oct 2024 15:19:46 +0200 Subject: [PATCH 02/41] auth and first purchase improved --- DuckDuckGo/AppDelegate.swift | 13 +++-- DuckDuckGo/AppDependencyProvider.swift | 49 +++++++--------- ...RemoteMessagingConfigMatcherProvider.swift | 52 +++++++---------- DuckDuckGo/SettingsState.swift | 6 +- DuckDuckGo/SettingsViewModel.swift | 57 +++++++++---------- ...scriptionPagesUseSubscriptionFeature.swift | 28 +++++---- .../SubscriptionSettingsViewModel.swift | 16 ++---- .../SubscriptionContainerViewFactory.swift | 32 +---------- 8 files changed, 99 insertions(+), 154 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index d3ffd02b5e..eb786a89f6 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -371,7 +371,7 @@ import os.log widgetRefreshModel.beginObservingVPNStatus() -// AppDependencyProvider.shared.subscriptionManager.loadInitialData() + AppDependencyProvider.shared.subscriptionManager.loadInitialData() setUpAutofillPixelReporter() @@ -548,11 +548,12 @@ import os.log } } - AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in - if isSubscriptionActive { - DailyPixel.fire(pixel: .privacyProSubscriptionActive) - } - } + // TODO: restore but limit the number of parallel subscription fetches +// AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in +// if isSubscriptionActive { +// DailyPixel.fire(pixel: .privacyProSubscriptionActive) +// } +// } let importPasswordsStatusHandler = ImportPasswordsStatusHandler(syncService: syncService) importPasswordsStatusHandler.checkSyncSuccessStatus() diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 333656c9d4..ad5ccdb529 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -44,7 +44,8 @@ protocol DependencyProvider { var configurationStore: ConfigurationStore { get } var userBehaviorMonitor: UserBehaviorMonitor { get } var subscriptionFeatureAvailability: SubscriptionFeatureAvailability { get } - var subscriptionManager: SubscriptionManager { get } + var subscriptionManager: any SubscriptionManager { get } + var oAuthClient: any OAuthClient { get } var privacyProInfoProvider: any PrivacyProInfoProvider { get } var vpnFeatureVisibility: DefaultNetworkProtectionVisibility { get } var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore { get } @@ -83,9 +84,7 @@ final class AppDependencyProvider: DependencyProvider { // Subscription let subscriptionManager: SubscriptionManager -// var accountManager: AccountManager { -// subscriptionManager.accountManager -// } + let oAuthClient: any OAuthClient let privacyProInfoProvider: any PrivacyProInfoProvider let vpnFeatureVisibility: DefaultNetworkProtectionVisibility let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore @@ -131,49 +130,45 @@ final class AppDependencyProvider: DependencyProvider { let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) let keychainManager = SubscriptionKeychainManager() let authClient = DefaultOAuthClient(tokensStorage: keychainManager, authService: authService) + self.privacyProInfoProvider = authClient - apiService.authorizationRefresherCallback = { _ in // TODO: is this updated? - // safety check - if keychainManager.tokensContainer?.decodedAccessToken.isExpired() == false { - assertionFailure("Refresh attempted on non expired token") + self.oAuthClient = authClient + + apiService.authorizationRefresherCallback = { _ in + guard let tokensContainer = keychainManager.tokensContainer else { + throw OAuthClientError.internalError("Missing refresh token") + } + + if tokensContainer.decodedAccessToken.isExpired() { + Logger.OAuth.debug("Refreshing tokens") + let tokens = try await authClient.refreshTokens() + return tokens.accessToken + } else { + Logger.general.debug("Trying to refresh valid token, using the old one") + return tokensContainer.accessToken } - Logger.OAuth.debug("Refreshing tokens") - let tokens = try await authClient.refreshTokens() - return tokens.accessToken } let storePurchaseManager = DefaultStorePurchaseManager() - let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: authEnvironment.url) + let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, + baseURL: subscriptionEnvironment.serviceEnvironment.url) let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, subscriptionEnvironment: subscriptionEnvironment) self.subscriptionManager = subscriptionManager - - let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( - privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, - purchasePlatform: .appStore) - let accessTokenProvider: () -> String? = { // TODO: refactor all of this + let accessTokenProvider: () -> String? = { return { -// try? await authClient.getTokens(policy: .local).accessToken - return "" + authClient.currentTokensContainer?.accessToken } }() -#if os(macOS) - networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), - serviceName: "\(Bundle.main.bundleIdentifier!).authToken", - errorEvents: .networkProtectionAppDebugEvents, - accessTokenProvider: accessTokenProvider) -#else networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) -#endif networkProtectionTunnelController = NetworkProtectionTunnelController(tokenStore: networkProtectionKeychainTokenStore) vpnFeatureVisibility = DefaultNetworkProtectionVisibility(userDefaults: .networkProtectionGroupDefaults, oAuthClient: authClient) } /// Only meant to be used for testing. - /// static func makeTestingInstance() -> Self { Self.init() } diff --git a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift index 1e1ed3d491..48cfb7c798 100644 --- a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift +++ b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift @@ -84,39 +84,29 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let surveyActionMapper: DefaultRemoteMessagingSurveyURLBuilder - if let accessToken = try? await subscriptionManager.getTokens(policy: .localValid).accessToken { - do { - let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: accessToken) - privacyProDaysSinceSubscribed = Calendar.current.numberOfDaysBetween(subscription.startedAt, and: Date()) ?? -1 - privacyProDaysUntilExpiry = Calendar.current.numberOfDaysBetween(Date(), and: subscription.expiresOrRenewsAt) ?? -1 - privacyProPurchasePlatform = subscription.platform.rawValue - - switch subscription.status { - case .autoRenewable, .gracePeriod: - isPrivacyProSubscriptionActive = true - case .notAutoRenewable: - isPrivacyProSubscriptionExpiring = true - case .expired, .inactive: - isPrivacyProSubscriptionExpired = true - case .unknown: - break // Not supported in RMF - } - - surveyActionMapper = DefaultRemoteMessagingSurveyURLBuilder( - statisticsStore: statisticsStore, - vpnActivationDateStore: DefaultVPNActivationDateStore(), - subscription: subscription) - } catch { - surveyActionMapper = DefaultRemoteMessagingSurveyURLBuilder( - statisticsStore: statisticsStore, - vpnActivationDateStore: DefaultVPNActivationDateStore(), - subscription: nil) + if let subscription = try? await subscriptionManager.currentSubscription(refresh: false) { + privacyProDaysSinceSubscribed = Calendar.current.numberOfDaysBetween(subscription.startedAt, and: Date()) ?? -1 + privacyProDaysUntilExpiry = Calendar.current.numberOfDaysBetween(Date(), and: subscription.expiresOrRenewsAt) ?? -1 + privacyProPurchasePlatform = subscription.platform.rawValue + + switch subscription.status { + case .autoRenewable, .gracePeriod: + isPrivacyProSubscriptionActive = true + case .notAutoRenewable: + isPrivacyProSubscriptionExpiring = true + case .expired, .inactive: + isPrivacyProSubscriptionExpired = true + case .unknown: + break // Not supported in RMF } + + surveyActionMapper = DefaultRemoteMessagingSurveyURLBuilder(statisticsStore: statisticsStore, + vpnActivationDateStore: DefaultVPNActivationDateStore(), + subscription: subscription) } else { - surveyActionMapper = DefaultRemoteMessagingSurveyURLBuilder( - statisticsStore: statisticsStore, - vpnActivationDateStore: DefaultVPNActivationDateStore(), - subscription: nil) + surveyActionMapper = DefaultRemoteMessagingSurveyURLBuilder(statisticsStore: statisticsStore, + vpnActivationDateStore: DefaultVPNActivationDateStore(), + subscription: nil) } let dismissedMessageIds = store.fetchDismissedRemoteMessageIDs() diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index b605e1be42..69cf234e9d 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -37,7 +37,7 @@ struct SettingsState { var size: Int } - struct Subscription: Codable { + struct SubscriptionState: Codable { var enabled: Bool var canPurchase: Bool var isSignedIn: Bool @@ -91,7 +91,7 @@ struct SettingsState { var networkProtectionConnected: Bool // Subscriptions Properties - var subscription: Subscription + var subscription: SubscriptionState // Sync Properties var sync: SyncSettings @@ -127,7 +127,7 @@ struct SettingsState { speechRecognitionAvailable: false, loginsEnabled: false, networkProtectionConnected: false, - subscription: Subscription(enabled: false, + subscription: SubscriptionState(enabled: false, canPurchase: false, isSignedIn: false, hasActiveSubscription: false, diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 1bcf394b14..2eae9c5cb3 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -26,9 +26,9 @@ import Combine import SyncUI import DuckPlayer import Networking - import Subscription import NetworkProtection +import os.log final class SettingsViewModel: ObservableObject { @@ -56,7 +56,7 @@ final class SettingsViewModel: ObservableObject { case subscriptionState = "com.duckduckgo.ios.subscription.state" } // Used to cache the lasts subscription state for up to a week - private let subscriptionStateCache = UserDefaultsCache(key: UserDefaultsCacheKey.subscriptionState, + private let subscriptionStateCache = UserDefaultsCache(key: UserDefaultsCacheKey.subscriptionState, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .days(7))) // Properties private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad @@ -715,37 +715,33 @@ extension SettingsViewModel { // Update if user is signed in based on the presence of token state.subscription.isSignedIn = subscriptionManager.isUserAuthenticated - // Active subscription check - guard let tokensContainer = try? await subscriptionManager.getTokens(policy: .local) else { - // Reset state in case cache was outdated + // Fetch subscription details using a stored access token + do { + if let subscription = try await subscriptionManager.currentSubscription(refresh: true) { + state.subscription.platform = subscription.platform + state.subscription.hasActiveSubscription = subscription.isActive + + // Check entitlements and update state + state.subscription.entitlements = subscriptionManager.entitlements + /*var currentEntitlements: [Entitlement.ProductName] = [] + let entitlementsToCheck: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] + for entitlement in entitlementsToCheck { + if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) { + currentEntitlements.append(entitlement) + } + } + self.state.subscription.entitlements = currentEntitlements*/ + } + } catch SubscriptionEndpointServiceError.noData { + // Auth successful but no Subscription is available + Logger.subscription.debug("Subscription not present") state.subscription.hasActiveSubscription = false state.subscription.entitlements = [] - state.subscription.platform = .unknown - - subscriptionStateCache.set(state.subscription) // Sync cache - return + } catch { + // Generic error, we don't update the cached data + Logger.subscription.error("Failed to load Subscription") } - do { - let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: tokensContainer.accessToken) - state.subscription.platform = subscription.platform - state.subscription.hasActiveSubscription = subscription.isActive - // Check entitlements and update state - self.state.subscription.entitlements = tokensContainer.decodedAccessToken.subscriptionEntitlements - -// var currentEntitlements: [SubscriptionEntitlement] = [] -// let entitlementsToCheck: [SubscriptionEntitlement] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] -// -// for entitlement in entitlementsToCheck { -// if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) { -// currentEntitlements.append(entitlement) -// } -// } -// -// self.state.subscription.entitlements = currentEntitlements - - } catch {} - // Sync Cache subscriptionStateCache.set(state.subscription) } @@ -774,8 +770,7 @@ extension SettingsViewModel { func restoreAccountPurchase() async { DispatchQueue.main.async { self.state.subscription.isRestoring = true } - let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: AppDependencyProvider.shared.oAuthClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 5309d0c886..cc9d007712 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -284,9 +284,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func setSubscription(params: Any, original: WKScriptMessage) async -> 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. + guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") - Logger.subscription.error("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + Logger.subscription.fault("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") setTransactionError(.generalError) return nil } @@ -294,20 +297,15 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // Clear subscription Cache subscriptionManager.subscriptionEndpointService.signOut() - // TODO: what are we doing here?? - -// 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) -// onSetSubscription?() -// -// } else { -// Logger.subscription.error("Failed to obtain subscription options") -// setTransactionError(.failedToSetSubscription) -// } - + let authToken = subscriptionValues.token + do { + let tokensContainer = try await subscriptionManager.exchange(tokenV1: authToken) + Logger.subscription.debug("v1 token exchanged for v2") + onSetSubscription?() + } catch { + Logger.subscription.error("Failed to exchange v1 token for v2") + setTransactionError(.failedToSetSubscription) + } return nil } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index f3d2d1c3b0..457c13bc7b 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -111,13 +111,11 @@ final class SubscriptionSettingsViewModel: ObservableObject { } private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionCachePolicy, loadingIndicator: Bool) async -> Bool { - Logger.subscription.debug("\(#function)") - guard let token = try? await subscriptionManager.getTokens(policy: .localValid).accessToken else { return false } + Logger.subscription.debug("Fetch and update subscription details") if loadingIndicator { displaySubscriptionLoader(true) } - do { - let subscription = try await self.subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: token, - cachePolicy: cachePolicy) + + if let subscription = try? await self.subscriptionManager.currentSubscription(refresh: cachePolicy != SubscriptionCachePolicy.returnCacheDataDontLoad) { DispatchQueue.main.async { self.state.subscriptionInfo = subscription if loadingIndicator { self.displaySubscriptionLoader(false) } @@ -126,14 +124,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { date: subscription.expiresOrRenewsAt, product: subscription.productId, billingPeriod: subscription.billingPeriod) - return true - } catch { - Logger.subscription.error("\(#function) error: \(error.localizedDescription)") - DispatchQueue.main.async { - if loadingIndicator { self.displaySubscriptionLoader(true) } - } - return false } + return true } func fetchAndUpdateAccountEmail(cachePolicy: SubscriptionCachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift index 395c294dd4..bbc94c0976 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift @@ -24,38 +24,12 @@ import os.log enum SubscriptionContainerViewFactory { - static func makeOAuthClient(subscriptionManager: SubscriptionManager) -> OAuthClient { - 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 = subscriptionManager.currentEnvironment.serviceEnvironment == .production ? .production : .staging - - let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) - let keychainManager = SubscriptionKeychainManager() - let authClient = DefaultOAuthClient(tokensStorage: keychainManager, authService: authService) - - apiService.authorizationRefresherCallback = { _ in // TODO: is this updated? - // safety check - if keychainManager.tokensContainer?.decodedAccessToken.isExpired() == false { - assertionFailure("Refresh attempted on non expired token") - } - Logger.OAuth.debug("Refreshing tokens") - let tokens = try await authClient.refreshTokens() - return tokens.accessToken - } - return authClient - } - static func makeSubscribeFlow(origin: String?, navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager, privacyProDataReporter: PrivacyProDataReporting?) -> some View { - let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) + let authClient = AppDependencyProvider.shared.oAuthClient let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) @@ -80,7 +54,7 @@ enum SubscriptionContainerViewFactory { static func makeRestoreFlow(navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager) -> some View { - let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) + let authClient = AppDependencyProvider.shared.oAuthClient let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) @@ -104,7 +78,7 @@ enum SubscriptionContainerViewFactory { static func makeEmailFlow(navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager, onDisappear: @escaping () -> Void) -> some View { - let authClient = SubscriptionContainerViewFactory.makeOAuthClient(subscriptionManager: subscriptionManager) + let authClient = AppDependencyProvider.shared.oAuthClient let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) From c6c2bef9dd6a03e19bef88da5e031d27bed1f765 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 22 Oct 2024 15:26:25 +0200 Subject: [PATCH 03/41] purchase flow fixed --- DuckDuckGo.xcodeproj/project.pbxproj | 73 ++++++++++++------- .../xcschemes/AdhocDebug.xcscheme | 2 +- .../xcschemes/AtbUITests.xcscheme | 2 +- .../xcshareddata/xcschemes/Core.xcscheme | 2 +- .../xcschemes/DuckDuckGo-Alpha.xcscheme | 2 +- .../xcschemes/DuckDuckGo.xcscheme | 2 +- .../xcschemes/FingerprintingUITests.xcscheme | 2 +- .../xcschemes/Instruments.xcscheme | 2 +- .../xcschemes/OpenAction.xcscheme | 2 +- .../xcschemes/PacketTunnelProvider.xcscheme | 2 +- .../xcschemes/PerformanceTests.xcscheme | 2 +- .../xcschemes/ShareExtension.xcscheme | 2 +- .../xcshareddata/xcschemes/UnitTests.xcscheme | 2 +- .../xcschemes/WidgetsExtension.xcscheme | 2 +- DuckDuckGo/SettingsState.swift | 4 +- DuckDuckGo/SettingsSubscriptionView.swift | 17 +++-- DuckDuckGo/SettingsViewModel.swift | 6 +- 17 files changed, 75 insertions(+), 51 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 29653b1fe0..ba26fa9bd6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -377,7 +377,7 @@ 838306E320C733010045E854 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 838306E120C733010045E854 /* InfoPlist.strings */; }; 8390446F20BDCE10006461CD /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8390446E20BDCE10006461CD /* ShareViewController.swift */; }; 8390447220BDCE10006461CD /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8390447020BDCE10006461CD /* MainInterface.storyboard */; }; - 8390447620BDCE10006461CD /* ShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8390446C20BDCE10006461CD /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 8390447620BDCE10006461CD /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 8390446C20BDCE10006461CD /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 83BE9BC3215D69C1009844D9 /* AppConfigurationFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83BE9BC2215D69C1009844D9 /* AppConfigurationFetch.swift */; }; 83E2D2B2253CC16B005605F5 /* httpsMobileV2Bloom.bin in Resources */ = {isa = PBXBuildFile; fileRef = 83E2D2AF253CC16B005605F5 /* httpsMobileV2Bloom.bin */; }; 83E2D2B3253CC16B005605F5 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 83E2D2B0253CC16B005605F5 /* httpsMobileV2FalsePositives.json */; }; @@ -411,7 +411,7 @@ 8512EA5124ED30D20073EE19 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8512EA5024ED30D20073EE19 /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 8512EA5424ED30D20073EE19 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512EA5324ED30D20073EE19 /* Widgets.swift */; }; 8512EA5724ED30D30073EE19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8512EA5624ED30D30073EE19 /* Assets.xcassets */; }; - 8512EA5D24ED30D30073EE19 /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8512EA4D24ED30D20073EE19 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 8512EA5D24ED30D30073EE19 /* WidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 8512EA4D24ED30D20073EE19 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 8512EA9D24EEA6820073EE19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F143C2B11E49D78C00CFDE3A /* Assets.xcassets */; }; 851481882A600EFC00ABC65F /* RemoteMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 851481872A600EFC00ABC65F /* RemoteMessaging */; }; 851624C22B95F8BD002D5CD7 /* HistoryCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851624C12B95F8BD002D5CD7 /* HistoryCapture.swift */; }; @@ -469,7 +469,7 @@ 8546A54A2A672959003929BF /* MainViewController+Email.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8546A5492A672959003929BF /* MainViewController+Email.swift */; }; 85482D8D2462DCD100EDEDD1 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85482D8C2462DCD100EDEDD1 /* ActionViewController.swift */; }; 85482D902462DCD100EDEDD1 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85482D8E2462DCD100EDEDD1 /* MainInterface.storyboard */; }; - 85482D942462DCD100EDEDD1 /* OpenAction.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 85482D882462DCD100EDEDD1 /* OpenAction.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 85482D942462DCD100EDEDD1 /* OpenAction.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 85482D882462DCD100EDEDD1 /* OpenAction.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 85482D992462F1C600EDEDD1 /* ActionIcons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85482D982462F1C600EDEDD1 /* ActionIcons.xcassets */; }; 854858E32937BC550063610B /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE411F22857C4A30003FE64 /* CollectionExtension.swift */; }; 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8548D95D25262B1B005AAE49 /* ViewHighlighter.swift */; }; @@ -1271,17 +1271,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 83E282AC20BC1840005FBE88 /* Embed App Extensions */ = { + 83E282AC20BC1840005FBE88 /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - 85482D942462DCD100EDEDD1 /* OpenAction.appex in Embed App Extensions */, - 8512EA5D24ED30D30073EE19 /* WidgetsExtension.appex in Embed App Extensions */, - 8390447620BDCE10006461CD /* ShareExtension.appex in Embed App Extensions */, + 85482D942462DCD100EDEDD1 /* OpenAction.appex in Embed Foundation Extensions */, + 8512EA5D24ED30D30073EE19 /* WidgetsExtension.appex in Embed Foundation Extensions */, + 8390447620BDCE10006461CD /* ShareExtension.appex in Embed Foundation Extensions */, ); - name = "Embed App Extensions"; + name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; F10307651E7D5B2C0059FEC7 /* Copy Frameworks */ = { @@ -6519,7 +6519,7 @@ F143C2F01E4A4CD400CFDE3A /* Embed Frameworks */, 37B4F3D329D2C84400758752 /* Copy GRDB framework */, F10307651E7D5B2C0059FEC7 /* Copy Frameworks */, - 83E282AC20BC1840005FBE88 /* Embed App Extensions */, + 83E282AC20BC1840005FBE88 /* Embed Foundation Extensions */, EE9286812A812BD2002B7818 /* Embed PacketTunnelProvider */, ); buildRules = ( @@ -6762,8 +6762,9 @@ 84E3418A1E2F7EFB00BDBA6F /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1500; + LastUpgradeCheck = 1600; ORGANIZATIONNAME = DuckDuckGo; TargetAttributes = { 02025661298818B100E694E7 = { @@ -9180,6 +9181,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9217,6 +9219,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9307,6 +9310,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9334,6 +9338,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9387,7 +9392,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -9477,7 +9482,6 @@ 84E341BB1E2F7EFC00BDBA6F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; @@ -9502,12 +9506,12 @@ 84E341BC1E2F7EFC00BDBA6F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9525,7 +9529,6 @@ 84E341BE1E2F7EFC00BDBA6F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; @@ -9545,7 +9548,6 @@ 84E341BF1E2F7EFC00BDBA6F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; @@ -9577,7 +9579,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; - DEAD_CODE_STRIPPING = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9611,7 +9613,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; - DEAD_CODE_STRIPPING = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9644,6 +9646,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9674,6 +9677,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9864,6 +9868,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -9877,6 +9882,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -9889,6 +9895,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */; buildSettings = { + DEAD_CODE_STRIPPING = YES; }; name = Debug; }; @@ -9896,6 +9903,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */; buildSettings = { + DEAD_CODE_STRIPPING = YES; }; name = "Alpha Debug"; }; @@ -9903,6 +9911,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */; buildSettings = { + DEAD_CODE_STRIPPING = YES; }; name = Alpha; }; @@ -9910,6 +9919,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */; buildSettings = { + DEAD_CODE_STRIPPING = YES; }; name = Release; }; @@ -9947,7 +9957,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -9978,7 +9988,6 @@ D664C7DF2B28A0FD00CBFA76 /* Alpha Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; @@ -10015,6 +10024,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10043,6 +10053,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10076,7 +10087,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; - DEAD_CODE_STRIPPING = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10106,6 +10117,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -10144,6 +10156,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -10151,6 +10164,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -10169,6 +10183,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -10206,7 +10221,6 @@ D664C7E72B28A0FD00CBFA76 /* Alpha Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; @@ -10342,7 +10356,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -10370,7 +10384,6 @@ EE5A7C472A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; @@ -10403,6 +10416,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10435,6 +10449,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10472,7 +10487,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; - DEAD_CODE_STRIPPING = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10507,6 +10522,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10547,6 +10563,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -10554,6 +10571,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -10572,6 +10590,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -10609,7 +10628,6 @@ EE5A7C4F2A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; @@ -10724,6 +10742,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -10731,6 +10750,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -10752,10 +10772,12 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -10763,6 +10785,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme index 5750e6f61e..6d74bb82b3 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 23 Oct 2024 15:24:30 +0200 Subject: [PATCH 04/41] purchase fixed --- DuckDuckGo.xcodeproj/project.pbxproj | 16 +++----- ...RemoteMessagingConfigMatcherProvider.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 36 ++++++++--------- .../Extensions/Logger+Subscription.swift | 28 ------------- ...scriptionPagesUseSubscriptionFeature.swift | 40 +++++++++++++------ 5 files changed, 52 insertions(+), 70 deletions(-) delete mode 100644 DuckDuckGo/Subscription/Extensions/Logger+Subscription.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5788417a2c..816097d72d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -826,7 +826,6 @@ B6BA95C528894A28004ABA20 /* BrowsingMenuViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */; }; B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95E728924730004ABA20 /* JSAlertController.storyboard */; }; BBFF18B12C76448100C48D7D /* QuerySubmittedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFF18B02C76448100C48D7D /* QuerySubmittedTests.swift */; }; - BD10B8AA2C7629740033115D /* Logger+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD10B8A92C7629740033115D /* Logger+Subscription.swift */; }; BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD15DB842B959CFD00821457 /* BundleExtension.swift */; }; BD2F39EB2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */; }; BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */; }; @@ -2636,7 +2635,6 @@ B6DFE6CF2BC7E47500A9CE59 /* SwiftLintTool.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftLintTool.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftLintToolBundleConfiguration.xcconfig; sourceTree = ""; }; BBFF18B02C76448100C48D7D /* QuerySubmittedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuerySubmittedTests.swift; sourceTree = ""; }; - BD10B8A92C7629740033115D /* Logger+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Logger+Subscription.swift"; sourceTree = ""; }; BD15DB842B959CFD00821457 /* BundleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtension.swift; sourceTree = ""; }; BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsView.swift; sourceTree = ""; }; BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; @@ -5357,7 +5355,6 @@ D664C7962B289AA000CBFA76 /* Extensions */ = { isa = PBXGroup; children = ( - BD10B8A92C7629740033115D /* Logger+Subscription.swift */, F1FDC9342BF51E41006B1435 /* VPNSettings+Environment.swift */, D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, ); @@ -7870,7 +7867,6 @@ D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, 9FEA222E2C324ECD006B03BF /* ViewVisibility.swift in Sources */, - BD10B8AA2C7629740033115D /* Logger+Subscription.swift in Sources */, 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */, D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */, 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, @@ -9177,7 +9173,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9215,7 +9211,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9306,7 +9302,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9334,7 +9330,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9507,7 +9503,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9575,7 +9571,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift index 48cfb7c798..b48fec3661 100644 --- a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift +++ b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift @@ -84,7 +84,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let surveyActionMapper: DefaultRemoteMessagingSurveyURLBuilder - if let subscription = try? await subscriptionManager.currentSubscription(refresh: false) { + if let subscription = try? await subscriptionManager.currentSubscription(refresh: false) { // TODO: mmmm, review errors handling privacyProDaysSinceSubscribed = Calendar.current.numberOfDaysBetween(subscription.startedAt, and: Date()) ?? -1 privacyProDaysUntilExpiry = Calendar.current.numberOfDaysBetween(Date(), and: subscription.expiresOrRenewsAt) ?? -1 privacyProPurchasePlatform = subscription.platform.rawValue diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 2d949a6469..3878047d04 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -714,23 +714,23 @@ extension SettingsViewModel { // Fetch subscription details using a stored access token do { - if let subscription = try await subscriptionManager.currentSubscription(refresh: true) { - state.subscription.subscriptionExist = true - state.subscription.platform = subscription.platform - state.subscription.hasActiveSubscription = subscription.isActive - - // TODO: check the logic here - // Check entitlements and update state - state.subscription.entitlements = subscriptionManager.entitlements - /*var currentEntitlements: [Entitlement.ProductName] = [] - let entitlementsToCheck: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - for entitlement in entitlementsToCheck { - if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) { - currentEntitlements.append(entitlement) - } - } - self.state.subscription.entitlements = currentEntitlements*/ - } + let subscription = try await subscriptionManager.currentSubscription(refresh: true) + state.subscription.subscriptionExist = true + state.subscription.platform = subscription.platform + state.subscription.hasActiveSubscription = subscription.isActive + + // TODO: check the logic here + // Check entitlements and update state + state.subscription.entitlements = subscriptionManager.entitlements + + /*var currentEntitlements: [Entitlement.ProductName] = [] + let entitlementsToCheck: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] + for entitlement in entitlementsToCheck { + if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) { + currentEntitlements.append(entitlement) + } + } + self.state.subscription.entitlements = currentEntitlements*/ } catch SubscriptionEndpointServiceError.noData { // Auth successful but no Subscription is available Logger.subscription.debug("Subscription not present") @@ -745,7 +745,7 @@ extension SettingsViewModel { // Sync Cache subscriptionStateCache.set(state.subscription) } - + private func setupNotificationObservers() { subscriptionSignOutObserver = NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, diff --git a/DuckDuckGo/Subscription/Extensions/Logger+Subscription.swift b/DuckDuckGo/Subscription/Extensions/Logger+Subscription.swift deleted file mode 100644 index 11ec2a3b73..0000000000 --- a/DuckDuckGo/Subscription/Extensions/Logger+Subscription.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Logger+Subscription.swift -// DuckDuckGo -// -// 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 os.log - -extension Logger { - - static var subscription: Logger = { - Logger(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "SubscriptionPro") - }() -} diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index c18d658226..ab148688cf 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -189,10 +189,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // MARK: Broker Methods (Called from WebView via UserScripts) - + + /// Returns the auth token func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { - let accessToken = (try? await subscriptionManager.getTokens(policy: .localValid).accessToken) ?? Constants.empty - return [Constants.token: accessToken] + do { + let accessToken = try await subscriptionManager.getTokens(policy: .createIfNeeded).accessToken + return [Constants.token: accessToken] + } catch { + Logger.subscription.fault("Failed to fetch token: \(error)") + return Constants.empty + } } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { @@ -239,7 +245,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec return nil } - let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id) { @@ -253,7 +258,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec switch error { case .cancelledByUser: setTransactionError(.cancelledByUser) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.canceled) return nil case .accountCreationFailed: setTransactionError(.accountCreationFailed) @@ -267,21 +272,28 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } setTransactionStatus(.polling) + + guard purchaseTransactionJWS.isEmpty == false else { + Logger.subscription.fault("Purchase transaction JWS is empty") + assertionFailure("Purchase transaction JWS is empty") + setTransactionStatus(.idle) + return nil + } + switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { - case .success(let purchaseUpdate): + case .success: Logger.subscription.debug("Subscription purchase completed successfully") DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseSuccess, pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes) UniquePixel.fire(pixel: .privacyProSubscriptionActivated) Pixel.fireAttribution(pixel: .privacyProSuccessfulSubscriptionAttribution, origin: subscriptionAttributionOrigin, privacyProDataReporter: privacyProDataReporter) - setTransactionStatus(.idle) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure(let error): Logger.subscription.error("App store complete subscription purchase error: \(error.localizedDescription)") - setTransactionStatus(.idle) setTransactionError(.missingEntitlements) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) } + + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) + setTransactionStatus(.idle) return nil } @@ -358,15 +370,17 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // setTransactionError(.generalError) // } // return nil - subscriptionManager.refreshAccount() + await subscriptionManager.refreshAccount() onBackToSettings?() return nil } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = try? await subscriptionManager.getTokens(policy: .localValid) { + do { + let accessToken = try await subscriptionManager.getTokens(policy: .localValid).accessToken return [Constants.token: accessToken] - } else { + } catch { + Logger.subscription.fault("Failed to fetch token: \(error)") return [String: String]() } } From de81314fa8234f5fd5b140358067f0a3bab4c8c2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 23 Oct 2024 15:46:19 +0200 Subject: [PATCH 05/41] token fixes --- .../UserScripts/IdentityTheftRestorationPagesFeature.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index f647c3763c..f2108d24ae 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -71,8 +71,8 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = try? await subscriptionManager.getTokens(policy: .localValid) { - return [Constants.token: accessToken] + if let tokensContainer = try? await subscriptionManager.getTokens(policy: .localValid) { + return [Constants.token: tokensContainer.accessToken] } else { return [String: String]() } From b4c661e02d227dc00bc04628807898c7313007b1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 24 Oct 2024 15:21:42 +0100 Subject: [PATCH 06/41] loggers improved and restore fixed --- DuckDuckGo/AppDependencyProvider.swift | 3 -- DuckDuckGo/MainViewController.swift | 4 +-- DuckDuckGo/SettingsViewModel.swift | 4 +-- ...IdentityTheftRestorationPagesFeature.swift | 2 +- ...ntityTheftRestorationPagesUserScript.swift | 2 +- ...scriptionPagesUseSubscriptionFeature.swift | 34 ++++++++++--------- .../SubscriptionSettingsViewModel.swift | 12 +++---- .../SubscriptionContainerViewFactory.swift | 15 ++++---- .../SubscriptionDebugViewController.swift | 8 ++--- 9 files changed, 40 insertions(+), 44 deletions(-) diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 21ec941d16..de35e95de7 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -43,7 +43,6 @@ protocol DependencyProvider { var configurationStore: ConfigurationStore { get } var userBehaviorMonitor: UserBehaviorMonitor { get } var subscriptionManager: any SubscriptionManager { get } - var oAuthClient: any OAuthClient { get } var privacyProInfoProvider: any PrivacyProInfoProvider { get } var vpnFeatureVisibility: DefaultNetworkProtectionVisibility { get } var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore { get } @@ -79,7 +78,6 @@ final class AppDependencyProvider: DependencyProvider { // Subscription let subscriptionManager: SubscriptionManager - let oAuthClient: any OAuthClient let privacyProInfoProvider: any PrivacyProInfoProvider let vpnFeatureVisibility: DefaultNetworkProtectionVisibility let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore @@ -128,7 +126,6 @@ final class AppDependencyProvider: DependencyProvider { let authClient = DefaultOAuthClient(tokensStorage: keychainManager, authService: authService) self.privacyProInfoProvider = authClient - self.oAuthClient = authClient apiService.authorizationRefresherCallback = { _ in guard let tokensContainer = keychainManager.tokensContainer else { diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 1c581cef76..18c6ade5d2 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1620,8 +1620,8 @@ class MainViewController: UIViewController { Task { let subscriptionManager = AppDependencyProvider.shared.subscriptionManager - guard let tokensContainer = try? await subscriptionManager.getTokens(policy: .localValid), - tokensContainer.decodedAccessToken.hasEntitlement(.networkProtection) else { return } + guard let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .localValid), + tokensContainer.decodedAccessToken.hasEntitlement(.networkProtection) == false else { return } if await networkProtectionTunnelController.isInstalled { tunnelDefaults.enableEntitlementMessaging() diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 3878047d04..dd285b5fd5 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -733,7 +733,7 @@ extension SettingsViewModel { self.state.subscription.entitlements = currentEntitlements*/ } catch SubscriptionEndpointServiceError.noData { // Auth successful but no Subscription is available - Logger.subscription.debug("Subscription not present") + Logger.subscription.log("Subscription not present") state.subscription.subscriptionExist = false state.subscription.hasActiveSubscription = false state.subscription.entitlements = [] @@ -770,7 +770,7 @@ extension SettingsViewModel { func restoreAccountPurchase() async { DispatchQueue.main.async { self.state.subscription.isRestoring = true } - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: AppDependencyProvider.shared.oAuthClient, + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index f2108d24ae..c1c22e5dc5 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -71,7 +71,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let tokensContainer = try? await subscriptionManager.getTokens(policy: .localValid) { + if let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .localValid) { return [Constants.token: tokensContainer.accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift index d68b4fe067..0865486925 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift @@ -64,6 +64,6 @@ extension IdentityTheftRestorationPagesUserScript: WKScriptMessageHandlerWithRep extension IdentityTheftRestorationPagesUserScript: WKScriptMessageHandler { public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // unsupported - Logger.subscription.debug("Unsupported function: \(#function)") + Logger.subscription.log("Unsupported function: \(#function)") } } diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index ab148688cf..73bbc816ca 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -141,7 +141,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - Logger.subscription.debug("WebView handler: \(methodName)") + Logger.subscription.log("WebView handler: \(methodName)") switch methodName { case Handlers.getSubscription: return getSubscription case Handlers.setSubscription: return setSubscription @@ -183,7 +183,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec private func setTransactionStatus(_ status: SubscriptionTransactionStatus) { if status != transactionStatus { - Logger.subscription.debug("Transaction state updated: \(status.rawValue)") + Logger.subscription.log("Transaction state updated: \(status.rawValue)") transactionStatus = status } } @@ -193,7 +193,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec /// Returns the auth token func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { do { - let accessToken = try await subscriptionManager.getTokens(policy: .createIfNeeded).accessToken + let accessToken = try await subscriptionManager.getTokensContainer(policy: .createIfNeeded).accessToken return [Constants.token: accessToken] } catch { Logger.subscription.fault("Failed to fetch token: \(error)") @@ -238,7 +238,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // Check for active subscriptions if await subscriptionManager.storePurchaseManager().hasActiveSubscription() { - Logger.subscription.debug("Subscription already active") + Logger.subscription.log("Subscription already active") setTransactionError(.hasActiveSubscription) Pixel.fire(pixel: .privacyProRestoreAfterPurchaseAttempt) setTransactionStatus(.idle) @@ -249,7 +249,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id) { case .success(let transactionJWS): - Logger.subscription.debug("Subscription purchased successfully") + Logger.subscription.log("Subscription purchased successfully") purchaseTransactionJWS = transactionJWS case .failure(let error): @@ -282,7 +282,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { case .success: - Logger.subscription.debug("Subscription purchase completed successfully") + Logger.subscription.log("Subscription purchase completed successfully") DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseSuccess, pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes) UniquePixel.fire(pixel: .privacyProSubscriptionActivated) @@ -309,12 +309,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Clear subscription Cache - subscriptionManager.subscriptionEndpointService.signOut() + subscriptionManager.subscriptionEndpointService.clearSubscription() let authToken = subscriptionValues.token do { let tokensContainer = try await subscriptionManager.exchange(tokenV1: authToken) - Logger.subscription.debug("v1 token exchanged for v2") + Logger.subscription.log("v1 token exchanged for v2") onSetSubscription?() } catch { Logger.subscription.error("Failed to exchange v1 token for v2") @@ -377,7 +377,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { do { - let accessToken = try await subscriptionManager.getTokens(policy: .localValid).accessToken + let accessToken = try await subscriptionManager.getTokensContainer(policy: .localValid).accessToken return [Constants.token: accessToken] } catch { Logger.subscription.fault("Failed to fetch token: \(error)") @@ -388,31 +388,31 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // MARK: Pixel related actions func subscriptionsMonthlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Logger.subscription.debug("Web function called: \(#function)") + Logger.subscription.log("Web function called: \(#function)") Pixel.fire(pixel: .privacyProOfferMonthlyPriceClick) return nil } func subscriptionsYearlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Logger.subscription.debug("Web function called: \(#function)") + Logger.subscription.log("Web function called: \(#function)") Pixel.fire(pixel: .privacyProOfferYearlyPriceClick) return nil } func subscriptionsUnknownPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { // Not used - Logger.subscription.debug("Web function called: \(#function)") + Logger.subscription.log("Web function called: \(#function)") return nil } func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { - Logger.subscription.debug("Web function called: \(#function)") + Logger.subscription.log("Web function called: \(#function)") UniquePixel.fire(pixel: .privacyProAddEmailSuccess) return nil } func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Logger.subscription.debug("Web function called: \(#function)") + Logger.subscription.log("Web function called: \(#function)") UniquePixel.fire(pixel: .privacyProWelcomeFAQClick) return nil } @@ -438,12 +438,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func restoreAccountFromAppStorePurchase() async throws { setTransactionStatus(.restoring) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() + + setTransactionStatus(.idle) switch result { case .success: - setTransactionStatus(.idle) + Logger.subscription.log("Subscription restored successfully from App Store purchase") case .failure(let error): + Logger.subscription.error("Failed to restore subscription from App Store purchase: \(error.localizedDescription)") let mappedError = mapAppStoreRestoreErrorToTransactionError(error) - setTransactionStatus(.idle) throw mappedError } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 1b7314553c..6709173d8e 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -109,7 +109,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionCachePolicy, loadingIndicator: Bool) async -> Bool { - Logger.subscription.debug("Fetch and update subscription details") + Logger.subscription.log("Fetch and update subscription details") if loadingIndicator { displaySubscriptionLoader(true) } @@ -127,10 +127,10 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func fetchAndUpdateAccountEmail(cachePolicy: SubscriptionCachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { - Logger.subscription.debug("\(#function)") + Logger.subscription.log("\(#function)") let tokensPolicy: TokensCachePolicy = cachePolicy == .returnCacheDataDontLoad ? .local : .localValid - guard let tokensContainer = try? await subscriptionManager.getTokens(policy: tokensPolicy) else { return false } + guard let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: tokensPolicy) else { return false } DispatchQueue.main.async { self.state.subscriptionEmail = tokensContainer.decodedAccessToken.email if loadingIndicator { self.displayEmailLoader(true) } @@ -139,7 +139,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { // switch await self.subscriptionManager.accountManager.fetchAccountDetails(with: token) { // case .success(let details): -// Logger.subscription.debug("Account details fetched successfully") +// Logger.subscription.log("Account details fetched successfully") // DispatchQueue.main.async { // self.state.subscriptionEmail = details.email // if loadingIndicator { self.displayEmailLoader(false) } @@ -173,7 +173,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func manageSubscription() { - Logger.subscription.debug("User action: \(#function)") + Logger.subscription.log("User action: \(#function)") switch state.subscriptionInfo?.platform { case .apple: Task { await manageAppleSubscription() } @@ -279,7 +279,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } private func manageStripeSubscription() async { - guard let tokensContainer = try? await subscriptionManager.getTokens(policy: .localValid) else { return } + guard let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .localValid) else { return } do { // Get Stripe Customer Portal URL and update the model let serviceResponse = try await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: tokensContainer.accessToken, externalID: tokensContainer.decodedAccessToken.externalID) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift index d8962af7aa..c89b287d6f 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift @@ -31,11 +31,10 @@ enum SubscriptionContainerViewFactory { subscriptionFeatureAvailability: SubscriptionFeatureAvailability, privacyProDataReporter: PrivacyProDataReporting?) -> some View { - let authClient = AppDependencyProvider.shared.oAuthClient - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(oAuthClient: authClient, + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), appStoreRestoreFlow: appStoreRestoreFlow) @@ -58,11 +57,10 @@ enum SubscriptionContainerViewFactory { static func makeRestoreFlow(navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager, subscriptionFeatureAvailability: SubscriptionFeatureAvailability) -> some View { - let authClient = AppDependencyProvider.shared.oAuthClient - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(oAuthClient: authClient, + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), appStoreRestoreFlow: appStoreRestoreFlow) @@ -83,11 +81,10 @@ enum SubscriptionContainerViewFactory { subscriptionManager: SubscriptionManager, subscriptionFeatureAvailability: SubscriptionFeatureAvailability, onDisappear: @escaping () -> Void) -> some View { - let authClient = AppDependencyProvider.shared.oAuthClient - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(oAuthClient: authClient, + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, storePurchaseManager: subscriptionManager.storePurchaseManager(), subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(oAuthClient: authClient, + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), appStoreRestoreFlow: appStoreRestoreFlow) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 28292c4330..71e7de8a2a 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -270,7 +270,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func showAccountDetails() { Task { - let tokensContainer = try? await subscriptionManager.getTokens(policy: .local) + let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .local) let authenticated = tokensContainer != nil let title = authenticated ? "Authenticated" : "Not Authenticated" let message = authenticated ? @@ -323,7 +323,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func validateToken() { Task { do { - let tokensContainer = try await subscriptionManager.getTokens(policy: .localValid) + let tokensContainer = try await subscriptionManager.getTokensContainer(policy: .localValid) showAlert(title: "Token details", message: "\(tokensContainer.debugDescription)") } catch OAuthClientError.missingTokens { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") @@ -336,7 +336,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func getSubscriptionDetails() { Task { do { - let tokensContainer = try await subscriptionManager.getTokens(policy: .localValid) + let tokensContainer = try await subscriptionManager.getTokensContainer(policy: .localValid) let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: tokensContainer.accessToken, cachePolicy: .reloadIgnoringLocalCacheData) showAlert(title: "Subscription info", message: "\(subscription)") @@ -352,7 +352,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func checkEntitlements() { Task { do { - let tokensContainer = try await subscriptionManager.getTokens(policy: .localValid) + let tokensContainer = try await subscriptionManager.getTokensContainer(policy: .localValid) showAlert(title: "Available Entitlements", message: tokensContainer.decodedAccessToken.subscriptionEntitlements.debugDescription) } catch OAuthClientError.missingTokens { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") From d123038ead68d93c3963ea2ff35ff667f2507e29 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 25 Oct 2024 10:49:13 +0100 Subject: [PATCH 07/41] subscription refresh improved --- DuckDuckGo/SettingsViewModel.swift | 6 ++- ...scriptionPagesUseSubscriptionFeature.swift | 13 +++--- .../SubscriptionSettingsViewModel.swift | 46 ++++++++----------- .../Views/SubscriptionGoogleView.swift | 3 -- 4 files changed, 30 insertions(+), 38 deletions(-) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index dd285b5fd5..0c015f09b5 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -714,6 +714,10 @@ extension SettingsViewModel { // Fetch subscription details using a stored access token do { + guard subscriptionManager.isUserAuthenticated == true else { + throw SubscriptionEndpointServiceError.noData + } + let subscription = try await subscriptionManager.currentSubscription(refresh: true) state.subscription.subscriptionExist = true state.subscription.platform = subscription.platform @@ -739,7 +743,7 @@ extension SettingsViewModel { state.subscription.entitlements = [] } catch { // Generic error, we don't update the cached data - Logger.subscription.error("Failed to load Subscription") + Logger.subscription.error("Failed to load Subscription: \(error, privacy: .public)") } // Sync Cache diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 73bbc816ca..6d76bc9afb 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -287,13 +287,13 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes) UniquePixel.fire(pixel: .privacyProSubscriptionActivated) Pixel.fireAttribution(pixel: .privacyProSuccessfulSubscriptionAttribution, origin: subscriptionAttributionOrigin, privacyProDataReporter: privacyProDataReporter) + + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) + setTransactionStatus(.idle) case .failure(let error): - Logger.subscription.error("App store complete subscription purchase error: \(error.localizedDescription)") + Logger.subscription.error("App store complete subscription purchase error: \(error.localizedDescription, privacy: .public)") setTransactionError(.missingEntitlements) } - - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) - setTransactionStatus(.idle) return nil } @@ -302,8 +302,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // 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. guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") Logger.subscription.fault("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") setTransactionError(.generalError) return nil } @@ -313,8 +313,9 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let authToken = subscriptionValues.token do { - let tokensContainer = try await subscriptionManager.exchange(tokenV1: authToken) + _ = try await subscriptionManager.exchange(tokenV1: authToken) Logger.subscription.log("v1 token exchanged for v2") + onSetSubscription?() } catch { Logger.subscription.error("Failed to exchange v1 token for v2") diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 6709173d8e..e43dd57070 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -127,37 +127,18 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func fetchAndUpdateAccountEmail(cachePolicy: SubscriptionCachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { - Logger.subscription.log("\(#function)") + Logger.subscription.log("Fetch and update account email") let tokensPolicy: TokensCachePolicy = cachePolicy == .returnCacheDataDontLoad ? .local : .localValid - guard let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: tokensPolicy) else { return false } - DispatchQueue.main.async { - self.state.subscriptionEmail = tokensContainer.decodedAccessToken.email - if loadingIndicator { self.displayEmailLoader(true) } + await subscriptionManager.refreshAccount() + if let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .local) { + DispatchQueue.main.async { + self.state.subscriptionEmail = tokensContainer.decodedAccessToken.email + if loadingIndicator { self.displayEmailLoader(true) } + } + return true } - return true - -// switch await self.subscriptionManager.accountManager.fetchAccountDetails(with: token) { -// case .success(let details): -// Logger.subscription.log("Account details fetched successfully") -// DispatchQueue.main.async { -// self.state.subscriptionEmail = details.email -// if loadingIndicator { self.displayEmailLoader(false) } -// } -// -// // If fetched email is different then update accountManager -// if details.email != subscriptionManager.accountManager.email { -// let externalID = subscriptionManager.accountManager.externalID -// subscriptionManager.accountManager.storeAccount(token: token, email: details.email, externalID: externalID) -// } -// return true -// case .failure(let error): -// Logger.subscription.error("\(#function) error: \(error.localizedDescription)") -// DispatchQueue.main.async { -// if loadingIndicator { self.displayEmailLoader(true) } -// } -// return false -// } + return false } private func displaySubscriptionLoader(_ show: Bool) { @@ -198,6 +179,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { @MainActor private func updateSubscriptionsStatusMessage(status: PrivacyProSubscription.Status, date: Date, product: String, billingPeriod: PrivacyProSubscription.BillingPeriod) { + Logger.subscription.log("Update subscription status: \(status.rawValue)") let billingPeriod = billingPeriod == .monthly ? UserText.subscriptionMonthlyBillingPeriod : UserText.subscriptionAnnualBillingPeriod let date = dateFormatter.string(from: date) @@ -212,6 +194,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func removeSubscription() { + Logger.subscription.log("Remove subscription") subscriptionManager.signOut() _ = ActionMessageView() ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, @@ -219,12 +202,14 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func displayGoogleView(_ value: Bool) { + Logger.subscription.log("Show google") if value != state.isShowingGoogleView { state.isShowingGoogleView = value } } func displayStripeView(_ value: Bool) { + Logger.subscription.log("Show stripe") if value != state.isShowingStripeView { state.isShowingStripeView = value } @@ -237,12 +222,14 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func displayFAQView(_ value: Bool) { + Logger.subscription.log("Show faq") if value != state.isShowingFAQView { state.isShowingFAQView = value } } func displayLearnMoreView(_ value: Bool) { + Logger.subscription.log("Show learn more") if value != state.isShowingLearnMoreView { state.isShowingLearnMoreView = value } @@ -258,12 +245,14 @@ final class SubscriptionSettingsViewModel: ObservableObject { @MainActor func showTermsOfService() { + Logger.subscription.log("Show terms of service") self.openURL(SettingsSubscriptionView.ViewConstants.privacyPolicyURL) } // MARK: - @MainActor private func manageAppleSubscription() async { + Logger.subscription.log("Managing Apple Subscription") if state.subscriptionInfo?.isActive ?? false { let url = subscriptionManager.url(for: .manageSubscriptionsInAppStore) if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { @@ -279,6 +268,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } private func manageStripeSubscription() async { + Logger.subscription.log("Managing Stripe Subscription") guard let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .localValid) else { return } do { // Get Stripe Customer Portal URL and update the model diff --git a/DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift b/DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift index 06f81ecda7..53b5776158 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift @@ -49,8 +49,6 @@ struct SubscriptionGoogleView: View { } - -#if DEBUG struct SubscriptionGoogleView_Previews: PreviewProvider { static var previews: some View { NavigationView { @@ -58,4 +56,3 @@ struct SubscriptionGoogleView_Previews: PreviewProvider { } } } -#endif From 9d90ccf4bc0f81c80541bacd0ad93cc37d7dcc85 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 25 Oct 2024 14:26:56 +0100 Subject: [PATCH 08/41] v1 to v2 auth migration --- DuckDuckGo/AppDependencyProvider.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index de35e95de7..4e4be6c83f 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -123,7 +123,10 @@ final class AppDependencyProvider: DependencyProvider { let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) let keychainManager = SubscriptionKeychainManager() - let authClient = DefaultOAuthClient(tokensStorage: keychainManager, authService: authService) + let legacyAccountStorage = AccountKeychainStorage() + let authClient = DefaultOAuthClient(tokensStorage: keychainManager, + legacyTokenStorage: legacyAccountStorage, + authService: authService) self.privacyProInfoProvider = authClient From 60d401232ac43a642d1958a59c32bc7b67ebe9f4 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 25 Oct 2024 16:41:20 +0100 Subject: [PATCH 09/41] lint ad restore commented code --- DuckDuckGo.xcodeproj/project.pbxproj | 4 - DuckDuckGo/AppDelegate.swift | 11 ++- DuckDuckGo/AppDependencyProvider.swift | 11 --- ...rotectionVisibilityForTunnelProvider.swift | 9 +- ...RemoteMessagingConfigMatcherProvider.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 10 --- ...AccountManagerKeychainAccessDelegate.swift | 36 -------- ...etworkProtectionPacketTunnelProvider.swift | 89 +++++++++++-------- 8 files changed, 57 insertions(+), 115 deletions(-) delete mode 100644 DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b3dae700d1..84e4634a0c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -73,7 +73,6 @@ 1E4F4A5A297193DE00625985 /* MainViewController+CookiesManaged.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4F4A59297193DE00625985 /* MainViewController+CookiesManaged.swift */; }; 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4FAA6327D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift */; }; 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4FAA6527D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift */; }; - 1E53508F2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E53508E2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift */; }; 1E5918472CA422A7008ED2B3 /* Navigation in Frameworks */ = {isa = PBXBuildFile; productRef = 1E5918462CA422A7008ED2B3 /* Navigation */; }; 1E60989B290009C700A508F9 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 1E7060BD28F88EE200E4CCDB /* Common */; }; 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 1E60989C290011E600A508F9 /* ContentBlocking */; }; @@ -1377,7 +1376,6 @@ 1E4F4A59297193DE00625985 /* MainViewController+CookiesManaged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+CookiesManaged.swift"; sourceTree = ""; }; 1E4FAA6327D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingDownloadRowViewModel.swift; sourceTree = ""; }; 1E4FAA6527D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteDownloadRowViewModel.swift; sourceTree = ""; }; - 1E53508E2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift"; sourceTree = ""; }; 1E61BC2927074BED00B2854D /* TextSizeUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextSizeUserScript.swift; sourceTree = ""; }; 1E6A4D682984208800A371D3 /* LocaleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocaleExtension.swift; sourceTree = ""; }; 1E7A71162934EB6400B7EA19 /* OmniBarNotificationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarNotificationAnimator.swift; sourceTree = ""; }; @@ -5331,7 +5329,6 @@ BDE91CD42C6292BF0005CB74 /* Feedback */, F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */, D60170BB2BA32DD6001911B5 /* Subscription.swift */, - 1E53508E2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift */, D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */, D664C7932B289AA000CBFA76 /* ViewModel */, D664C7AC2B289AA000CBFA76 /* Views */, @@ -7754,7 +7751,6 @@ 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */, 1EEF387D285B1A1100383393 /* TrackerImageCache.swift in Sources */, 3151F0EC27357FEE00226F58 /* VoiceSearchFeedbackViewModel.swift in Sources */, - 1E53508F2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift in Sources */, 1DDF402D2BA09482006850D9 /* SettingsMainSettingsView.swift in Sources */, 85010502292FB1000033978F /* FireproofFaviconUpdater.swift in Sources */, F1C4A70E1E57725800A6CA1B /* OmniBar.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index c8004289f5..15d2c5aa97 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -558,12 +558,11 @@ import os.log } } - // TODO: restore but limit the number of parallel subscription fetches -// AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in -// if isSubscriptionActive { -// DailyPixel.fire(pixel: .privacyProSubscriptionActive) -// } -// } + AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in + if isSubscriptionActive { + DailyPixel.fire(pixel: .privacyProSubscriptionActive) + } + } let importPasswordsStatusHandler = ImportPasswordsStatusHandler(syncService: syncService) importPasswordsStatusHandler.checkSyncSuccessStatus() diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 4e4be6c83f..48aa25a2cb 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -101,17 +101,6 @@ final class AppDependencyProvider: DependencyProvider { let subscriptionEnvironment = DefaultSubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) vpnSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) -// let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, -// key: UserDefaultsCacheKey.subscriptionEntitlements, -// settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) -// let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) -// let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) -// let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) -// let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, -// entitlementsCache: entitlementsCache, -// subscriptionEndpointService: subscriptionService, -// authEndpointService: authService) - let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil configuration.requestCachePolicy = .reloadIgnoringLocalCacheData diff --git a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift index 9eb7a1c233..04a6c3cea6 100644 --- a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift @@ -23,15 +23,8 @@ import Networking // struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVisibility { // -// private let oAuthClient: any OAuthClient -// -// init(oAuthClient: any OAuthClient) { -// self.oAuthClient = oAuthClient -// } -// // func isPrivacyProLaunched() -> Bool { -// let tokensContainer = oAuthClient.getStoredTokens() -// return tokensContainer?.accessToken != nil +// return true // } // // func shouldMonitorEntitlement() -> Bool { diff --git a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift index b48fec3661..48cfb7c798 100644 --- a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift +++ b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift @@ -84,7 +84,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let surveyActionMapper: DefaultRemoteMessagingSurveyURLBuilder - if let subscription = try? await subscriptionManager.currentSubscription(refresh: false) { // TODO: mmmm, review errors handling + if let subscription = try? await subscriptionManager.currentSubscription(refresh: false) { privacyProDaysSinceSubscribed = Calendar.current.numberOfDaysBetween(subscription.startedAt, and: Date()) ?? -1 privacyProDaysUntilExpiry = Calendar.current.numberOfDaysBetween(Date(), and: subscription.expiresOrRenewsAt) ?? -1 privacyProPurchasePlatform = subscription.platform.rawValue diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 0c015f09b5..7f75c2ff9b 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -723,18 +723,8 @@ extension SettingsViewModel { state.subscription.platform = subscription.platform state.subscription.hasActiveSubscription = subscription.isActive - // TODO: check the logic here // Check entitlements and update state state.subscription.entitlements = subscriptionManager.entitlements - - /*var currentEntitlements: [Entitlement.ProductName] = [] - let entitlementsToCheck: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - for entitlement in entitlementsToCheck { - if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) { - currentEntitlements.append(entitlement) - } - } - self.state.subscription.entitlements = currentEntitlements*/ } catch SubscriptionEndpointServiceError.noData { // Auth successful but no Subscription is available Logger.subscription.log("Subscription not present") diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift b/DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift deleted file mode 100644 index dcab068df0..0000000000 --- a/DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift -// DuckDuckGo -// -// 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 Core -// import Subscription -// -// extension DefaultSubscriptionManager: AccountManagerKeychainAccessDelegate { -// -// public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { -// let parameters = [ -// PixelParameters.privacyProKeychainAccessType: accessType.rawValue, -// PixelParameters.privacyProKeychainError: error.errorDescription, -// PixelParameters.source: "browser" -// ] -// -// DailyPixel.fireDailyAndCount(pixel: .privacyProKeychainAccessError, -// withAdditionalParameters: parameters) -// } -// } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 4c930a54de..d7ca244889 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -52,7 +52,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { private static var vpnLogger = VPNLogger() private static let persistentPixel: PersistentPixelFiring = PersistentPixel() private var cancellables = Set() -// private let accountManager: AccountManager + private let subscriptionManager: any SubscriptionManager private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager @@ -440,32 +440,50 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } // MARK: - Configure Subscription -// let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults.standard, -// key: UserDefaultsCacheKey.subscriptionEntitlements, -// settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) -// let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) -// let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) -// let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) -// let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, -// entitlementsCache: entitlementsCache, -// subscriptionEndpointService: subscriptionService, -// authEndpointService: authService) -// self.accountManager = accountManager - -// let featureVisibility = NetworkProtectionVisibilityForTunnelProvider(oAuthClient: <#T##any OAuthClient#>) + 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) + let keychainManager = SubscriptionKeychainManager() + let legacyAccountStorage = AccountKeychainStorage() + let authClient = DefaultOAuthClient(tokensStorage: keychainManager, + legacyTokenStorage: legacyAccountStorage, + authService: authService) + + apiService.authorizationRefresherCallback = { _ in + guard let tokensContainer = keychainManager.tokensContainer else { + throw OAuthClientError.internalError("Missing refresh token") + } + + if tokensContainer.decodedAccessToken.isExpired() { + Logger.OAuth.debug("Refreshing tokens") + let tokens = try await authClient.refreshTokens() + return tokens.accessToken + } else { + Logger.general.debug("Trying to refresh valid token, using the old one") + return tokensContainer.accessToken + } + } + let storePurchaseManager = DefaultStorePurchaseManager() + let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, + baseURL: subscriptionEnvironment.serviceEnvironment.url) + let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, + oAuthClient: authClient, + subscriptionEndpointService: subscriptionEndpointService, + subscriptionEnvironment: subscriptionEnvironment) + self.subscriptionManager = subscriptionManager let accessTokenProvider: () -> String? = { -// if featureVisibility.shouldMonitorEntitlement() { - return { -// accountManager.accessToken - "" // TODO: :( - } -// } - return { nil } + return { + authClient.currentTokensContainer?.accessToken + } }() let tokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) - let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() @@ -475,6 +493,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { wrappee: notificationsPresenter ) notificationsPresenter.requestAuthorization() + super.init(notificationsPresenter: notificationsPresenterDecorator, tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: errorStore, @@ -486,10 +505,12 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { providerEvents: Self.packetTunnelProviderEvents, settings: settings, defaults: .networkProtectionGroupDefaults, - entitlementCheck: { return await Self.entitlementCheck() } + entitlementCheck: { + let hasEntitlement = subscriptionManager.entitlements.contains(.networkProtection) + return .success(hasEntitlement) + } ) -// accountManager.delegate = self startMonitoringMemoryPressureEvents() observeServerChanges() APIRequest.Headers.setUserAgent(DefaultUserAgentManager.duckDuckGoUserAgent) @@ -537,19 +558,9 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { WidgetCenter.shared.reloadTimelines(ofKind: "VPNStatusWidget") } - private static func entitlementCheck() async -> Result { - return .success(true) // TODO: WTF? -// guard NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager).shouldMonitorEntitlement() else { -// return .success(true) -// } -// -// let result = await accountManager.hasEntitlement(forProductName: .networkProtection) -// switch result { -// case .success(let hasEntitlement): -// return .success(hasEntitlement) -// case .failure(let error): -// return .failure(error) -// } + private func entitlementCheck() async -> Result { + let hasEntitlement = subscriptionManager.entitlements.contains(.networkProtection) + return .success(hasEntitlement) } } From 07e2cc01a2a8bdd3a04a579476f7698d3d3a2215 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 29 Oct 2024 15:25:54 +0000 Subject: [PATCH 10/41] review suggestions applied --- DuckDuckGo/AppDependencyProvider.swift | 14 ++++++------- .../DefaultNetworkProtectionVisibility.swift | 2 +- DuckDuckGo/MainViewController.swift | 4 ++-- ...IdentityTheftRestorationPagesFeature.swift | 4 ++-- ...scriptionPagesUseSubscriptionFeature.swift | 4 ++-- .../SubscriptionSettingsViewModel.swift | 8 ++++---- .../SubscriptionDebugViewController.swift | 20 +++++++++---------- ...etworkProtectionPacketTunnelProvider.swift | 10 +++++----- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 48aa25a2cb..b634b45fd3 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -120,17 +120,17 @@ final class AppDependencyProvider: DependencyProvider { self.privacyProInfoProvider = authClient apiService.authorizationRefresherCallback = { _ in - guard let tokensContainer = keychainManager.tokensContainer else { + guard let tokenContainer = keychainManager.tokenContainer else { throw OAuthClientError.internalError("Missing refresh token") } - if tokensContainer.decodedAccessToken.isExpired() { + if tokenContainer.decodedAccessToken.isExpired() { Logger.OAuth.debug("Refreshing tokens") - let tokens = try await authClient.refreshTokens() + 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 tokensContainer.accessToken + return tokenContainer.accessToken } } let storePurchaseManager = DefaultStorePurchaseManager() @@ -144,7 +144,7 @@ final class AppDependencyProvider: DependencyProvider { self.subscriptionManager = subscriptionManager let accessTokenProvider: () -> String? = { return { - authClient.currentTokensContainer?.accessToken + authClient.currentTokenContainer?.accessToken } }() networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) @@ -158,9 +158,9 @@ final class AppDependencyProvider: DependencyProvider { extension DefaultOAuthClient: PrivacyProInfoProvider { var hasVPNEntitlements: Bool { - guard let tokensContainer = tokensStorage.tokensContainer else { + guard let tokenContainer = tokensStorage.tokenContainer else { return false } - return tokensContainer.decodedAccessToken.hasEntitlement(.networkProtection) + return tokenContainer.decodedAccessToken.hasEntitlement(.networkProtection) } } diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index 2fa24bc89e..5b1661ad76 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -35,7 +35,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { } var token: String? { - return oAuthClient.currentTokensContainer?.accessToken + return oAuthClient.currentTokenContainer?.accessToken } func shouldShowVPNShortcut() -> Bool { diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 18e35e7863..aaa284410a 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1582,8 +1582,8 @@ class MainViewController: UIViewController { Task { let subscriptionManager = AppDependencyProvider.shared.subscriptionManager - guard let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .localValid), - tokensContainer.decodedAccessToken.hasEntitlement(.networkProtection) == false else { return } + guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid), + tokenContainer.decodedAccessToken.hasEntitlement(.networkProtection) == false else { return } if await networkProtectionTunnelController.isInstalled { tunnelDefaults.enableEntitlementMessaging() diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index c1c22e5dc5..151c536ba4 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -71,8 +71,8 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .localValid) { - return [Constants.token: tokensContainer.accessToken] + if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid) { + return [Constants.token: tokenContainer.accessToken] } else { return [String: String]() } diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 6d76bc9afb..b895a20f51 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -193,7 +193,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec /// Returns the auth token func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { do { - let accessToken = try await subscriptionManager.getTokensContainer(policy: .createIfNeeded).accessToken + let accessToken = try await subscriptionManager.getTokenContainer(policy: .createIfNeeded).accessToken return [Constants.token: accessToken] } catch { Logger.subscription.fault("Failed to fetch token: \(error)") @@ -378,7 +378,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { do { - let accessToken = try await subscriptionManager.getTokensContainer(policy: .localValid).accessToken + let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken return [Constants.token: accessToken] } catch { Logger.subscription.fault("Failed to fetch token: \(error)") diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index e43dd57070..ad3b548b54 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -131,9 +131,9 @@ final class SubscriptionSettingsViewModel: ObservableObject { let tokensPolicy: TokensCachePolicy = cachePolicy == .returnCacheDataDontLoad ? .local : .localValid await subscriptionManager.refreshAccount() - if let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .local) { + if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local) { DispatchQueue.main.async { - self.state.subscriptionEmail = tokensContainer.decodedAccessToken.email + self.state.subscriptionEmail = tokenContainer.decodedAccessToken.email if loadingIndicator { self.displayEmailLoader(true) } } return true @@ -269,10 +269,10 @@ final class SubscriptionSettingsViewModel: ObservableObject { private func manageStripeSubscription() async { Logger.subscription.log("Managing Stripe Subscription") - guard let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .localValid) else { return } + guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid) else { return } do { // Get Stripe Customer Portal URL and update the model - let serviceResponse = try await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: tokensContainer.accessToken, externalID: tokensContainer.decodedAccessToken.externalID) + let serviceResponse = try await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: tokenContainer.accessToken, externalID: tokenContainer.decodedAccessToken.externalID) guard let url = URL(string: serviceResponse.customerPortalUrl) else { return } if let existingModel = state.stripeViewModel { existingModel.url = url diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 71e7de8a2a..68749ab547 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -270,13 +270,13 @@ final class SubscriptionDebugViewController: UITableViewController { private func showAccountDetails() { Task { - let tokensContainer = try? await subscriptionManager.getTokensContainer(policy: .local) - let authenticated = tokensContainer != nil + let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local) + let authenticated = tokenContainer != nil let title = authenticated ? "Authenticated" : "Not Authenticated" let message = authenticated ? ["Service Environment: \(subscriptionManager.currentEnvironment.serviceEnvironment)", - "AuthToken: \(tokensContainer?.accessToken ?? "")", - "Email: \(tokensContainer?.decodedAccessToken.email ?? "")"].joined(separator: "\n") : nil + "AuthToken: \(tokenContainer?.accessToken ?? "")", + "Email: \(tokenContainer?.decodedAccessToken.email ?? "")"].joined(separator: "\n") : nil DispatchQueue.main.async { self.showAlert(title: title, message: message) } @@ -323,8 +323,8 @@ final class SubscriptionDebugViewController: UITableViewController { private func validateToken() { Task { do { - let tokensContainer = try await subscriptionManager.getTokensContainer(policy: .localValid) - showAlert(title: "Token details", message: "\(tokensContainer.debugDescription)") + let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) + showAlert(title: "Token details", message: "\(tokenContainer.debugDescription)") } catch OAuthClientError.missingTokens { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") } catch { @@ -336,8 +336,8 @@ final class SubscriptionDebugViewController: UITableViewController { private func getSubscriptionDetails() { Task { do { - let tokensContainer = try await subscriptionManager.getTokensContainer(policy: .localValid) - let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: tokensContainer.accessToken, + let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) + let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: tokenContainer.accessToken, cachePolicy: .reloadIgnoringLocalCacheData) showAlert(title: "Subscription info", message: "\(subscription)") @@ -352,8 +352,8 @@ final class SubscriptionDebugViewController: UITableViewController { private func checkEntitlements() { Task { do { - let tokensContainer = try await subscriptionManager.getTokensContainer(policy: .localValid) - showAlert(title: "Available Entitlements", message: tokensContainer.decodedAccessToken.subscriptionEntitlements.debugDescription) + let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) + showAlert(title: "Available Entitlements", message: tokenContainer.decodedAccessToken.subscriptionEntitlements.debugDescription) } catch OAuthClientError.missingTokens { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") } catch { diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index d7ca244889..c265574b90 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -457,17 +457,17 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { authService: authService) apiService.authorizationRefresherCallback = { _ in - guard let tokensContainer = keychainManager.tokensContainer else { + guard let tokenContainer = keychainManager.tokenContainer else { throw OAuthClientError.internalError("Missing refresh token") } - if tokensContainer.decodedAccessToken.isExpired() { + if tokenContainer.decodedAccessToken.isExpired() { Logger.OAuth.debug("Refreshing tokens") - let tokens = try await authClient.refreshTokens() + 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 tokensContainer.accessToken + return tokenContainer.accessToken } } let storePurchaseManager = DefaultStorePurchaseManager() @@ -480,7 +480,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { self.subscriptionManager = subscriptionManager let accessTokenProvider: () -> String? = { return { - authClient.currentTokensContainer?.accessToken + authClient.currentTokenContainer?.accessToken } }() let tokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) From bd5bb1b44b0814a05f7003219c18296ed623e2e5 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 30 Oct 2024 15:20:12 +0000 Subject: [PATCH 11/41] tests and logs --- Core/Logger+Pixel.swift | 25 +++++++++++++++++++ Core/PersistentPixel.swift | 2 +- Core/Pixel.swift | 4 +-- DuckDuckGo.xcodeproj/project.pbxproj | 4 +++ .../xcshareddata/swiftpm/Package.resolved | 4 +-- ...scriptionPagesUseSubscriptionFeature.swift | 3 +-- 6 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 Core/Logger+Pixel.swift diff --git a/Core/Logger+Pixel.swift b/Core/Logger+Pixel.swift new file mode 100644 index 0000000000..2e22d2e774 --- /dev/null +++ b/Core/Logger+Pixel.swift @@ -0,0 +1,25 @@ +// +// Logger+Pixel.swift +// DuckDuckGo +// +// 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 os.log + +public extension Logger { + static var pixels = { Logger(subsystem: "Pixels", category: "") }() +} diff --git a/Core/PersistentPixel.swift b/Core/PersistentPixel.swift index d26dd31cba..bbba933836 100644 --- a/Core/PersistentPixel.swift +++ b/Core/PersistentPixel.swift @@ -98,7 +98,7 @@ public final class PersistentPixel: PersistentPixelFiring { var additionalParameters = additionalParameters additionalParameters[PixelParameters.originalPixelTimestamp] = dateString - Logger.general.debug("Firing persistent pixel named \(pixel.name)") + Logger.pixels.debug("Firing persistent pixel named \(pixel.name)") pixelFiring.fire(pixel: pixel, error: error, diff --git a/Core/Pixel.swift b/Core/Pixel.swift index a7a6c7e3e2..f018d4cb43 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -234,7 +234,7 @@ public class Pixel { } guard !isDryRun else { - Logger.general.debug("Pixel fired \(pixelName.replacingOccurrences(of: "_", with: "."), privacy: .public) \(params.count > 0 ? "\(params)" : "", privacy: .public)") + Logger.pixels.debug("Pixel fired \(pixelName.replacingOccurrences(of: "_", with: "."), privacy: .public) \(params.count > 0 ? "\(params)" : "", privacy: .public)") // simulate server response time for Dry Run mode DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { onComplete(nil) @@ -265,7 +265,7 @@ public class Pixel { headers: headers) let request = APIRequest(configuration: configuration, urlSession: .session(useMainThreadCallbackQueue: true)) request.fetch { _, error in - Logger.general.debug("Pixel fired \(pixelName, privacy: .public) \(params, privacy: .public)") + Logger.pixels.debug("Pixel fired \(pixelName, privacy: .public) \(params, privacy: .public)") onComplete(error) } } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 84e4634a0c..225d32cd76 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1117,6 +1117,7 @@ F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CA3C361F045878005FADB3 /* PrivacyStore.swift */; }; F1CA3C391F045885005FADB3 /* PrivacyUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CA3C381F045885005FADB3 /* PrivacyUserDefaults.swift */; }; F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CA3C3A1F045B65005FADB3 /* Authenticator.swift */; }; + F1D07FB92CD277F60016F7FD /* Logger+Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D07FB82CD277F00016F7FD /* Logger+Pixel.swift */; }; F1D43AFA2B99C1D300BAB743 /* BareBonesBrowserKit in Frameworks */ = {isa = PBXBuildFile; productRef = F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */; }; F1D43AFC2B99C56000BAB743 /* RootDebugViewController+VanillaBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D43AFB2B99C56000BAB743 /* RootDebugViewController+VanillaBrowser.swift */; }; F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D477C51F2126CC0031ED49 /* OmniBarState.swift */; }; @@ -2969,6 +2970,7 @@ F1CA3C381F045885005FADB3 /* PrivacyUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyUserDefaults.swift; sourceTree = ""; }; F1CA3C3A1F045B65005FADB3 /* Authenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authenticator.swift; sourceTree = ""; }; F1CB8EA21F26B39000A7171B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + F1D07FB82CD277F00016F7FD /* Logger+Pixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Pixel.swift"; sourceTree = ""; }; F1D43AFB2B99C56000BAB743 /* RootDebugViewController+VanillaBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RootDebugViewController+VanillaBrowser.swift"; sourceTree = ""; }; F1D477C51F2126CC0031ED49 /* OmniBarState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarState.swift; sourceTree = ""; }; F1D477C81F2139410031ED49 /* SmallOmniBarStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SmallOmniBarStateTests.swift; sourceTree = ""; }; @@ -5596,6 +5598,7 @@ CB2A7EF028410DF700885F67 /* PixelEvent.swift */, BDC234F62B27F51100D3C798 /* UniquePixel.swift */, 853A717520F62FE800FE60BC /* Pixel.swift */, + F1D07FB82CD277F00016F7FD /* Logger+Pixel.swift */, 6F03CB062C32F173004179A8 /* PixelFiring.swift */, 6F03CB082C32F331004179A8 /* PixelFiringAsync.swift */, 6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */, @@ -8303,6 +8306,7 @@ F1134EA61F3E2AF400B73467 /* StatisticsStore.swift in Sources */, F17D723C1E8BB374003E8B0E /* AppDeepLinkSchemes.swift in Sources */, 1E8AD1DB27C51AE000ABA377 /* TimeIntervalExtension.swift in Sources */, + F1D07FB92CD277F60016F7FD /* Logger+Pixel.swift in Sources */, 9FCFCD812C6B020D006EB7A0 /* LaunchOptionsHandler.swift in Sources */, B652DF0D287C2A6300C12A9C /* PrivacyFeatures.swift in Sources */, F10E522D1E946F8800CE1253 /* NSAttributedStringExtension.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4c62714831..e8a4a45ff3 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -158,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "21f7878f2b39d46fd8ba2b06459ccb431cdf876c", - "version" : "3.8.1" + "revision" : "8fa345c2081cfbd4851dffff5dd5bed48efe6081", + "version" : "3.9.0" } }, { diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index b895a20f51..87f384c0c6 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -287,13 +287,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes) UniquePixel.fire(pixel: .privacyProSubscriptionActivated) Pixel.fireAttribution(pixel: .privacyProSuccessfulSubscriptionAttribution, origin: subscriptionAttributionOrigin, privacyProDataReporter: privacyProDataReporter) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) - setTransactionStatus(.idle) case .failure(let error): Logger.subscription.error("App store complete subscription purchase error: \(error.localizedDescription, privacy: .public)") setTransactionError(.missingEntitlements) } + setTransactionStatus(.idle) return nil } From e92d0db6403783d6806e6c8b79b25fc68495d0e0 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 31 Oct 2024 10:22:56 +0000 Subject: [PATCH 12/41] vpn fixed --- DuckDuckGo/AppDependencyProvider.swift | 25 +++++++++------ .../NetworkProtectionTunnelController.swift | 15 ++++----- ...scriptionPagesUseSubscriptionFeature.swift | 6 ++-- .../SubscriptionSettingsViewModel.swift | 13 ++++++-- ...etworkProtectionPacketTunnelProvider.swift | 31 ++++++++++++------- 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index b634b45fd3..e7b796cafb 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -111,16 +111,20 @@ final class AppDependencyProvider: DependencyProvider { let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) - let keychainManager = SubscriptionKeychainManager() - let legacyAccountStorage = AccountKeychainStorage() - let authClient = DefaultOAuthClient(tokensStorage: keychainManager, + + // 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) self.privacyProInfoProvider = authClient apiService.authorizationRefresherCallback = { _ in - guard let tokenContainer = keychainManager.tokenContainer else { + guard let tokenContainer = tokenStorage.tokenContainer else { throw OAuthClientError.internalError("Missing refresh token") } @@ -142,12 +146,13 @@ final class AppDependencyProvider: DependencyProvider { subscriptionEndpointService: subscriptionEndpointService, subscriptionEnvironment: subscriptionEnvironment) self.subscriptionManager = subscriptionManager - let accessTokenProvider: () -> String? = { - return { - authClient.currentTokenContainer?.accessToken + networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: { + guard let token = subscriptionManager.getTokenContainerSynchronously(policy: .localValid)?.accessToken else { + Logger.networkProtection.error("NetworkProtectionKeychainTokenStore failed to provide token") + return nil } - }() - networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) + return token + }) networkProtectionTunnelController = NetworkProtectionTunnelController(tokenStore: networkProtectionKeychainTokenStore, persistentPixel: persistentPixel) vpnFeatureVisibility = DefaultNetworkProtectionVisibility(userDefaults: .networkProtectionGroupDefaults, @@ -158,7 +163,7 @@ final class AppDependencyProvider: DependencyProvider { extension DefaultOAuthClient: PrivacyProInfoProvider { var hasVPNEntitlements: Bool { - guard let tokenContainer = tokensStorage.tokenContainer else { + guard let tokenContainer = tokenStorage.tokenContainer else { return false } return tokenContainer.decodedAccessToken.hasEntitlement(.networkProtection) diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index 773660e17b..2654c5c5d8 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -89,7 +89,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr case loadFromPreferencesFailed(Error) case saveToPreferencesFailed(Error) case startVPNFailed(Error) - case fetchAuthTokenFailed(Error) + case fetchAuthTokenFailed case configSystemPermissionsDenied(Error) public var errorCode: Int { @@ -106,13 +106,13 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr public var errorUserInfo: [String: Any] { switch self { case - .simulateControllerFailureError: + .simulateControllerFailureError, + .fetchAuthTokenFailed: return [:] case .loadFromPreferencesFailed(let error), .saveToPreferencesFailed(let error), .startVPNFailed(let error), - .fetchAuthTokenFailed(let error), .configSystemPermissionsDenied(let error): return [NSUnderlyingErrorKey: error] } @@ -256,10 +256,11 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } options["activationAttemptId"] = UUID().uuidString as NSString - do { - options["authToken"] = try tokenStore.fetchToken() as NSString? - } catch { - throw StartError.fetchAuthTokenFailed(error) + + if let token = tokenStore.fetchToken() as NSString? { + options["authToken"] = token + } else { + throw StartError.fetchAuthTokenFailed } options[NetworkProtectionOptionKey.selectedEnvironment] = AppDependencyProvider.shared.vpnSettings .selectedEnvironment.rawValue as NSString diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 87f384c0c6..bceef2c1a3 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -289,7 +289,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec Pixel.fireAttribution(pixel: .privacyProSuccessfulSubscriptionAttribution, origin: subscriptionAttributionOrigin, privacyProDataReporter: privacyProDataReporter) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) case .failure(let error): - Logger.subscription.error("App store complete subscription purchase error: \(error.localizedDescription, privacy: .public)") + Logger.subscription.error("App store complete subscription purchase error: \(error, privacy: .public)") setTransactionError(.missingEntitlements) } setTransactionStatus(.idle) @@ -297,7 +297,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func setSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { - + Logger.subscription.log("Setting Subscription") // 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. guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { @@ -324,6 +324,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func activateSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { + Logger.subscription.log("Activating Subscription") Pixel.fire(pixel: .privacyProRestorePurchaseOfferPageEntry, debounce: 2) onActivateSubscription?() return nil @@ -349,6 +350,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func backToSettings(params: Any, original: WKScriptMessage) async -> Encodable? { + Logger.subscription.log("Back to settings") // guard let accessToken = accountManager.accessToken else { // Logger.subscription.error("Missing access token") // return nil diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index ad3b548b54..547e51b602 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -128,10 +128,17 @@ final class SubscriptionSettingsViewModel: ObservableObject { func fetchAndUpdateAccountEmail(cachePolicy: SubscriptionCachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { Logger.subscription.log("Fetch and update account email") - let tokensPolicy: TokensCachePolicy = cachePolicy == .returnCacheDataDontLoad ? .local : .localValid + var tokensPolicy: TokensCachePolicy = .local + switch cachePolicy { + case .reloadIgnoringLocalCacheData: + tokensPolicy = .localForceRefresh + case .returnCacheDataElseLoad: + tokensPolicy = .localValid + case .returnCacheDataDontLoad: + tokensPolicy = .local + } - await subscriptionManager.refreshAccount() - if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local) { + if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: tokensPolicy) { DispatchQueue.main.async { self.state.subscriptionEmail = tokenContainer.decodedAccessToken.email if loadingIndicator { self.displayEmailLoader(true) } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index c265574b90..fedf3891f9 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -53,6 +53,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { private static let persistentPixel: PersistentPixelFiring = PersistentPixel() private var cancellables = Set() private let subscriptionManager: any SubscriptionManager + private let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager @@ -450,14 +451,18 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let authEnvironment: OAuthEnvironment = subscriptionEnvironment.serviceEnvironment == .production ? .production : .staging let authService = DefaultOAuthService(baseURL: authEnvironment.url, apiService: apiService) - let keychainManager = SubscriptionKeychainManager() - let legacyAccountStorage = AccountKeychainStorage() - let authClient = DefaultOAuthClient(tokensStorage: keychainManager, + + // 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 = keychainManager.tokenContainer else { + guard let tokenContainer = tokenStorage.tokenContainer else { throw OAuthClientError.internalError("Missing refresh token") } @@ -478,12 +483,6 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { subscriptionEndpointService: subscriptionEndpointService, subscriptionEnvironment: subscriptionEnvironment) self.subscriptionManager = subscriptionManager - let accessTokenProvider: () -> String? = { - return { - authClient.currentTokenContainer?.accessToken - } - }() - let tokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() @@ -494,13 +493,21 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { ) notificationsPresenter.requestAuthorization() + self.networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: { + guard let token = subscriptionManager.getTokenContainerSynchronously(policy: .localValid)?.accessToken else { + Logger.networkProtection.error("NetworkProtectionKeychainTokenStore failed to provide token") + return nil + } + return token + }) + super.init(notificationsPresenter: notificationsPresenterDecorator, tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: errorStore, snoozeTimingStore: NetworkProtectionSnoozeTimingStore(userDefaults: .networkProtectionGroupDefaults), wireGuardInterface: DefaultWireGuardInterface(), keychainType: .dataProtection(.unspecified), - tokenStore: tokenStore, + tokenStore: self.networkProtectionKeychainTokenStore, debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), providerEvents: Self.packetTunnelProviderEvents, settings: settings, @@ -559,7 +566,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } private func entitlementCheck() async -> Result { - let hasEntitlement = subscriptionManager.entitlements.contains(.networkProtection) + let hasEntitlement = self.subscriptionManager.entitlements.contains(.networkProtection) return .success(hasEntitlement) } } From 140631541845058659e943bf548517aeff33b34b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 31 Oct 2024 10:29:48 +0000 Subject: [PATCH 13/41] debug menu improved --- DuckDuckGo/SubscriptionDebugViewController.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 68749ab547..1aaaf16701 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -353,7 +353,10 @@ final class SubscriptionDebugViewController: UITableViewController { Task { do { let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) - showAlert(title: "Available Entitlements", message: tokenContainer.decodedAccessToken.subscriptionEntitlements.debugDescription) + let entitlementsDescription = tokenContainer.decodedAccessToken.subscriptionEntitlements.map { entitlement in + return entitlement.rawValue + }.joined(separator: "\n") + showAlert(title: "Available Entitlements", message: entitlementsDescription) } catch OAuthClientError.missingTokens { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") } catch { From eebbc48c6a5a92ef9270ae69b84ca526799fb323 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 31 Oct 2024 15:21:12 +0000 Subject: [PATCH 14/41] unit tests and lint --- DuckDuckGo.xcodeproj/project.pbxproj | 8 - ...rotectionVisibilityForTunnelProvider.swift | 37 - DuckDuckGo/SettingsViewModel.swift | 3 +- ...scriptionPagesUseSubscriptionFeature.swift | 2 +- .../SubscriptionSettingsViewModel.swift | 4 +- .../SubscriptionContainerViewFactory.swift | 12 +- .../SubscriptionDebugViewController.swift | 5 +- ...workProtectionFeatureVisibilityTests.swift | 93 - .../SubscriptionContainerViewModelTests.swift | 62 +- .../SubscriptionFlowViewModelTests.swift | 72 +- ...tionPagesUseSubscriptionFeatureTests.swift | 2076 +++++++++-------- 11 files changed, 1111 insertions(+), 1263 deletions(-) delete mode 100644 DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift delete mode 100644 DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 225d32cd76..839ebdb237 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -846,10 +846,8 @@ BDE91CE02C6515420005CB74 /* UnifiedFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CDF2C6515410005CB74 /* UnifiedFeedbackFormViewModel.swift */; }; BDF8D0022C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */; }; BDFF031D2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */; }; - BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */; }; BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; }; BDFF03232BA3D8E300F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; }; - BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */; }; C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; }; C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; }; C12324C32C4697C900FBB26B /* AutofillBreakageReportTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1836CE42C35A0EA0016D057 /* AutofillBreakageReportTableViewCell.swift */; }; @@ -2657,8 +2655,6 @@ BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsViewModel.swift; sourceTree = ""; }; BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = ""; }; BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNetworkProtectionVisibility.swift; sourceTree = ""; }; - BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVisibilityForTunnelProvider.swift; sourceTree = ""; }; - BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibilityTests.swift; sourceTree = ""; }; C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillViews.swift; sourceTree = ""; }; C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOrFolderTests.swift; sourceTree = ""; }; C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptView.swift; sourceTree = ""; }; @@ -5048,7 +5044,6 @@ children = ( BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */, BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */, - BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */, ); name = "Feature Visibility"; sourceTree = ""; @@ -5531,7 +5526,6 @@ children = ( EEFE9C722A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift */, EEC02C152B065BE00045CE11 /* NetworkProtectionVPNLocationViewModelTests.swift */, - BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */, ); name = NetworkProtection; sourceTree = ""; @@ -7294,7 +7288,6 @@ 0214407A2C7FB28F00426724 /* VPNAgentConfigurationURLProvider.swift in Sources */, EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, F1FDC9362BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */, - BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */, EEFC6A602AC0F2F80065027D /* UserText.swift in Sources */, 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */, ); @@ -7994,7 +7987,6 @@ EEC02C162B065BE00045CE11 /* NetworkProtectionVPNLocationViewModelTests.swift in Sources */, 6F934F862C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift in Sources */, 987130C5294AAB9F00AB05E0 /* BookmarkEditorViewModelTests.swift in Sources */, - BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */, 0283A2042C6E572F00508FBD /* BrokenSitePromptLimiterTests.swift in Sources */, D62EC3BA2C246A7000FC9D04 /* YoutublePlayerNavigationHandlerTests.swift in Sources */, 1EAABE712C99FC75003F5137 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, diff --git a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift deleted file mode 100644 index 04a6c3cea6..0000000000 --- a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// NetworkProtectionVisibilityForTunnelProvider.swift -// DuckDuckGo -// -// 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 - -// struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVisibility { -// -// func isPrivacyProLaunched() -> Bool { -// return true -// } -// -// func shouldMonitorEntitlement() -> Bool { -// isPrivacyProLaunched() -// } -// -// func shouldShowVPNShortcut() -> Bool { -// return isPrivacyProLaunched() -// } -// } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 7f75c2ff9b..4f9cfefc8a 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -765,8 +765,7 @@ extension SettingsViewModel { DispatchQueue.main.async { self.state.subscription.isRestoring = true } let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) + storePurchaseManager: subscriptionManager.storePurchaseManager()) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index bceef2c1a3..1844986276 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -308,7 +308,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Clear subscription Cache - subscriptionManager.subscriptionEndpointService.clearSubscription() + subscriptionManager.clearSubscriptionCache() let authToken = subscriptionValues.token do { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 547e51b602..2332371dba 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -276,11 +276,9 @@ final class SubscriptionSettingsViewModel: ObservableObject { private func manageStripeSubscription() async { Logger.subscription.log("Managing Stripe Subscription") - guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid) else { return } do { // Get Stripe Customer Portal URL and update the model - let serviceResponse = try await subscriptionManager.subscriptionEndpointService.getCustomerPortalURL(accessToken: tokenContainer.accessToken, externalID: tokenContainer.decodedAccessToken.externalID) - guard let url = URL(string: serviceResponse.customerPortalUrl) else { return } + let url = try await subscriptionManager.getCustomerPortalURL() if let existingModel = state.stripeViewModel { existingModel.url = url } else { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift index c89b287d6f..ef688c4765 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift @@ -32,10 +32,8 @@ enum SubscriptionContainerViewFactory { privacyProDataReporter: PrivacyProDataReporting?) -> some View { let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) + storePurchaseManager: subscriptionManager.storePurchaseManager()) let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), appStoreRestoreFlow: appStoreRestoreFlow) @@ -58,10 +56,8 @@ enum SubscriptionContainerViewFactory { subscriptionManager: SubscriptionManager, subscriptionFeatureAvailability: SubscriptionFeatureAvailability) -> some View { let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) + storePurchaseManager: subscriptionManager.storePurchaseManager()) let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), appStoreRestoreFlow: appStoreRestoreFlow) let subscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, @@ -82,10 +78,8 @@ enum SubscriptionContainerViewFactory { subscriptionFeatureAvailability: SubscriptionFeatureAvailability, onDisappear: @escaping () -> Void) -> some View { let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService) + storePurchaseManager: subscriptionManager.storePurchaseManager()) let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, storePurchaseManager: subscriptionManager.storePurchaseManager(), appStoreRestoreFlow: appStoreRestoreFlow) let viewModel = SubscriptionContainerViewModel( diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 1aaaf16701..168ed8d92b 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -336,11 +336,8 @@ final class SubscriptionDebugViewController: UITableViewController { private func getSubscriptionDetails() { Task { do { - let tokenContainer = try await subscriptionManager.getTokenContainer(policy: .localValid) - let subscription = try await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: tokenContainer.accessToken, - cachePolicy: .reloadIgnoringLocalCacheData) + let subscription = try await subscriptionManager.currentSubscription(refresh: true) showAlert(title: "Subscription info", message: "\(subscription)") - } catch OAuthClientError.missingTokens { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") } catch { diff --git a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift b/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift deleted file mode 100644 index 04b77fcfa8..0000000000 --- a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// NetworkProtectionFeatureVisibilityTests.swift -// DuckDuckGo -// -// 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 XCTest -@testable import DuckDuckGo -import Subscription -import SubscriptionTestingUtilities -import Common - -/// Test all permutations according to https://app.asana.com/0/0/1206812323779606/f -final class NetworkProtectionFeatureVisibilityTests: XCTestCase { - - func testPrivacyProNotYetLaunched() { - // Waitlist beta OFF, not current waitlist user -> Show nothing, use nothing - let mockWithNothing = NetworkProtectionFeatureVisibilityMocks(with: []) - XCTAssertFalse(mockWithNothing.shouldMonitorEntitlement()) - XCTAssertFalse(mockWithNothing.shouldShowVPNShortcut()) - } - - func testPrivacyProLaunched() { - // Waitlist beta OFF, not current waitlist user -> Enforce entitlement check, nothing else - let mockWithNothingElse = NetworkProtectionFeatureVisibilityMocks(with: [.isPrivacyProLaunched]) - XCTAssertTrue(mockWithNothingElse.shouldMonitorEntitlement()) - XCTAssertFalse(mockWithNothingElse.shouldShowVPNShortcut()) - } -} - -struct NetworkProtectionFeatureVisibilityMocks: NetworkProtectionFeatureVisibility { - - let accountManager: AccountManager - - func shouldShowVPNShortcut() -> Bool { - if isPrivacyProLaunched() { - return accountManager.isUserAuthenticated - } else { - return false - } - } - - struct Options: OptionSet { - let rawValue: Int - - static let isPrivacyProLaunched = Options(rawValue: 1 << 0) - } - - let options: Options - - init(with options: Options) { - self.options = options - - let subscriptionAppGroup = "NetworkProtectionFeatureVisibilityTests" - 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 accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService) - } - - func adding(_ additionalOptions: Options) -> NetworkProtectionFeatureVisibilityMocks { - NetworkProtectionFeatureVisibilityMocks(with: options.union(additionalOptions)) - } - - func isPrivacyProLaunched() -> Bool { - options.contains(.isPrivacyProLaunched) - } - - func shouldMonitorEntitlement() -> Bool { - isPrivacyProLaunched() - } -} diff --git a/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift index 2d4979997f..e3a9ab49d8 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift @@ -24,21 +24,7 @@ import SubscriptionTestingUtilities final class SubscriptionContainerViewModelTests: XCTestCase { var sut: SubscriptionContainerViewModel! - - let subscriptionManager: SubscriptionManager = { - let accountManager = AccountManagerMock() - let subscriptionService = SubscriptionEndpointServiceMock() - let authService = AuthEndpointServiceMock() - let storePurchaseManager = StorePurchaseManagerMock() - return SubscriptionManagerMock(accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - storePurchaseManager: storePurchaseManager, - currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, - purchasePlatform: .appStore), - canPurchase: true) - }() - + let subscriptionManager = SubscriptionManagerMock() let subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { @@ -46,19 +32,13 @@ final class SubscriptionContainerViewModelTests: XCTestCase { let origin = "test_origin" let queryParameter = URLQueryItem(name: "origin", value: "test_origin") let expectedURL = SubscriptionURL.purchase.subscriptionURL(environment: .production).appending(percentEncodedQueryItem: queryParameter) - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) - + let storePurchaseManager = DefaultStorePurchaseManager() + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager, + appStoreRestoreFlow: appStoreRestoreFlow) + subscriptionManager.resultURL = SubscriptionURL.purchase.subscriptionURL(environment: .production) // URL(string: "https://duckduckgo.com") // WHEN sut = .init(subscriptionManager: subscriptionManager, origin: origin, @@ -67,27 +47,20 @@ final class SubscriptionContainerViewModelTests: XCTestCase { subscriptionFeatureAvailability: subscriptionFeatureAvailability, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow)) + appStoreRestoreFlow: appStoreRestoreFlow)) // THEN XCTAssertEqual(sut.flow.purchaseURL, expectedURL) } func testWhenInitWithoutOriginThenSubscriptionFlowPurchaseURLDoesNotHaveOriginSet() { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) - + let storePurchaseManager = DefaultStorePurchaseManager() + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager, + appStoreRestoreFlow: appStoreRestoreFlow) + subscriptionManager.resultURL = SubscriptionURL.purchase.subscriptionURL(environment: .production) // WHEN sut = .init(subscriptionManager: subscriptionManager, origin: nil, @@ -96,8 +69,7 @@ final class SubscriptionContainerViewModelTests: XCTestCase { subscriptionFeatureAvailability: subscriptionFeatureAvailability, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow)) + appStoreRestoreFlow: appStoreRestoreFlow)) // THEN XCTAssertEqual(sut.flow.purchaseURL, SubscriptionURL.purchase.subscriptionURL(environment: .production)) diff --git a/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift index 67235b541e..efcd07daa7 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift @@ -25,19 +25,19 @@ import SubscriptionTestingUtilities final class SubscriptionFlowViewModelTests: XCTestCase { private var sut: SubscriptionFlowViewModel! - let subscriptionManager: SubscriptionManager = { - let accountManager = AccountManagerMock() - let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: .production) - let authService = DefaultAuthEndpointService(currentServiceEnvironment: .production) - let storePurchaseManager = DefaultStorePurchaseManager() - return SubscriptionManagerMock(accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - storePurchaseManager: storePurchaseManager, - currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, - purchasePlatform: .appStore), - canPurchase: true) - }() + let subscriptionManager = SubscriptionManagerMock() +// let accountManager = AccountManagerMock() +// let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: .production) +// let authService = DefaultAuthEndpointService(currentServiceEnvironment: .production) +// let storePurchaseManager = DefaultStorePurchaseManager() +// return SubscriptionManagerMock(accountManager: accountManager, +// subscriptionEndpointService: subscriptionService, +// authEndpointService: authService, +// storePurchaseManager: storePurchaseManager, +// currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, +// purchasePlatform: .appStore), +// canPurchase: true) +// }() let subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled @@ -46,26 +46,19 @@ final class SubscriptionFlowViewModelTests: XCTestCase { let origin = "test_origin" let queryParameter = URLQueryItem(name: "origin", value: "test_origin") let expectedURL = SubscriptionURL.purchase.subscriptionURL(environment: .production).appending(percentEncodedQueryItem: queryParameter) - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) - + let storePurchaseManager = DefaultStorePurchaseManager() + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager, + appStoreRestoreFlow: appStoreRestoreFlow) + subscriptionManager.resultURL = SubscriptionURL.purchase.subscriptionURL(environment: .production) // WHEN sut = .init(origin: origin, userScript: .init(), subFeature: .init(subscriptionManager: subscriptionManager, subscriptionFeatureAvailability: subscriptionFeatureAvailability, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow), + appStoreRestoreFlow: appStoreRestoreFlow), subscriptionManager: subscriptionManager) // THEN @@ -73,26 +66,19 @@ final class SubscriptionFlowViewModelTests: XCTestCase { } func testWhenInitWithoutOriginThenSubscriptionFlowPurchaseURLDoesNotHaveOriginSet() { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - authEndpointService: subscriptionManager.authEndpointService) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager, - appStoreRestoreFlow: appStoreRestoreFlow, - authEndpointService: subscriptionManager.authEndpointService) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, - storePurchaseManager: subscriptionManager.storePurchaseManager(), - accountManager: subscriptionManager.accountManager) - + let storePurchaseManager = DefaultStorePurchaseManager() + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager, + appStoreRestoreFlow: appStoreRestoreFlow) + subscriptionManager.resultURL = SubscriptionURL.purchase.subscriptionURL(environment: .production) // WHEN sut = .init(origin: nil, userScript: .init(), subFeature: .init(subscriptionManager: subscriptionManager, subscriptionFeatureAvailability: subscriptionFeatureAvailability, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow), + appStoreRestoreFlow: appStoreRestoreFlow), subscriptionManager: subscriptionManager) // THEN diff --git a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 8636921470..4784e4af32 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -28,1041 +28,1081 @@ import BrowserServicesKit import OHHTTPStubs import OHHTTPStubsSwift import os.log +import Networking 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 mostRecentTransactionJWS = "dGhpcyBpcyBub3QgYSByZWFsIEFw(...)cCBTdG9yZSB0cmFuc2FjdGlvbiBKV1M=" - - static let subscriptionOptions = SubscriptionOptions(platform: SubscriptionPlatformName.ios.rawValue, - options: [ - SubscriptionOption(id: "1", - cost: SubscriptionOptionCost(displayPrice: "9 USD", recurrence: "monthly")), - SubscriptionOption(id: "2", - cost: SubscriptionOptionCost(displayPrice: "99 USD", recurrence: "yearly")) - ], - features: [ - SubscriptionFeature(name: "vpn"), - 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 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 accountManager: AccountManager! - var subscriptionManager: SubscriptionManager! - var subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled - var feature: SubscriptionPagesUseSubscriptionFeature! - - var pixelsFired: [String] = [] + var subscriptionManager: SubscriptionManagerMock! + var storePurchaseManager: StorePurchaseManagerMock! override func setUpWithError() throws { - // Pixels - Pixel.isDryRun = false - stub(condition: isHost("improving.duckduckgo.com")) { request -> HTTPStubsResponse in - if let path = request.url?.path { - let pixelName = path.dropping(prefix: "/t/") - .dropping(suffix: "_ios_phone") - .dropping(suffix: "_ios_tablet") - self.pixelsFired.append(pixelName) - } - - return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) - } - - // Reset all daily pixel storage - [Pixel.storage, DailyPixel.storage, UniquePixel.storage].forEach { storage in - storage.dictionaryRepresentation().keys.forEach(storage.removeObject(forKey:)) - } - - // Mocks - subscriptionService = SubscriptionEndpointServiceMock() - authService = AuthEndpointServiceMock() storePurchaseManager = StorePurchaseManagerMock() - subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, - purchasePlatform: .appStore) - accountStorage = AccountKeychainStorageMock() - accessTokenStorage = SubscriptionTokenKeychainStorageMock() - - userDefaults = UserDefaults(suiteName: Constants.userDefaultsSuiteName)! - userDefaults.removePersistentDomain(forName: Constants.userDefaultsSuiteName) - - 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) - // Real SubscriptionManager - subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - subscriptionEnvironment: subscriptionEnvironment) - - feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, - subscriptionFeatureAvailability: subscriptionFeatureAvailability, + subscriptionManager = SubscriptionManagerMock() + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, + storePurchaseManager: storePurchaseManager, + appStoreRestoreFlow: appStoreRestoreFlow) + + feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: SubscriptionManagerMock(), + subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock(isFeatureAvailable: true, + isSubscriptionPurchaseAllowed: true, + usesUnifiedFeedbackForm: true), subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow) + privacyProDataReporter: nil) } override func tearDownWithError() throws { - Pixel.isDryRun = true - pixelsFired.removeAll() - HTTPStubs.removeAllStubs() - - subscriptionService = nil - authService = nil - storePurchaseManager = nil - subscriptionEnvironment = nil - - userDefaults = nil - - accountStorage = nil - accessTokenStorage = nil - - entitlementsCache.reset() - entitlementsCache = nil - - accountManager = nil - - // Real Flows - appStorePurchaseFlow = nil - appStoreRestoreFlow = nil - appStoreAccountManagementFlow = nil - - subscriptionManager = nil - feature = nil + subscriptionManager = nil + storePurchaseManager = nil } - // MARK: - Tests for getSubscription - - func testGetSubscriptionSuccessRefreshingAuthToken() async throws { - // Given - ensureUserAuthenticatedState() - - let newAuthToken = UUID().uuidString - - authService.validateTokenResult = .failure(Constants.invalidTokenError) - storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS - authService.storeLoginResult = .success(StoreLoginResponse(authToken: newAuthToken, - email: Constants.email, - externalID: Constants.externalID, - id: 1, status: "authenticated")) - - // When - let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let resultDictionary = try XCTUnwrap(result as? [String: String]) - - XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], newAuthToken) - XCTAssertEqual(accountManager.authToken, newAuthToken) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testGetSubscriptionSuccessWithoutRefreshingAuthToken() async throws { - // Given - ensureUserAuthenticatedState() - - authService.validateTokenResult = .success(Constants.validateTokenResponse) - - // When - let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let resultDictionary = try XCTUnwrap(result as? [String: String]) - - XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], Constants.authToken) - XCTAssertEqual(accountManager.authToken, Constants.authToken) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testGetSubscriptionSuccessErrorWhenUnauthenticated() async throws { - // Given - ensureUserUnauthenticatedState() - - authService.validateTokenResult = .failure(Constants.invalidTokenError) - storePurchaseManager.mostRecentTransactionResult = nil - - // When - let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let resultDictionary = try XCTUnwrap(result as? [String: String]) - - XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], SubscriptionPagesUseSubscriptionFeature.Constants.empty) - XCTAssertFalse(accountManager.isUserAuthenticated) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - // MARK: - Tests for getSubscriptionOptions - - func testGetSubscriptionOptionsSuccess() async throws { - // Given - storePurchaseManager.subscriptionOptionsResult = Constants.subscriptionOptions - - // When - let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) - - XCTAssertEqual(subscriptionOptionsResult, Constants.subscriptionOptions) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testGetSubscriptionOptionsReturnsEmptyOptionsWhenNoSubscriptionOptions() async throws { - // Given - storePurchaseManager.subscriptionOptionsResult = nil - - // When - let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) - XCTAssertEqual(subscriptionOptionsResult, SubscriptionOptions.empty) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .failedToGetSubscriptionOptions) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testGetSubscriptionOptionsReturnsEmptyOptionsWhenPurchaseNotAllowed() async throws { - // Given - let subscriptionFeatureAvailabilityWithoutPurchaseAllowed = SubscriptionFeatureAvailabilityMock( - isFeatureAvailable: true, - isSubscriptionPurchaseAllowed: false, - usesUnifiedFeedbackForm: true - ) - - feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, - subscriptionFeatureAvailability: subscriptionFeatureAvailabilityWithoutPurchaseAllowed, - subscriptionAttributionOrigin: nil, - appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow) - - storePurchaseManager.subscriptionOptionsResult = Constants.subscriptionOptions - - // When - let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) - XCTAssertEqual(subscriptionOptionsResult, SubscriptionOptions.empty) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - // MARK: - Tests for subscriptionSelected - - func testSubscriptionSelectedSuccessWhenPurchasingFirstTime() async throws { - // Given - ensureUserUnauthenticatedState() - - XCTAssertFalse(accountManager.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)) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c", - Pixel.Event.privacyProPurchaseSuccess.name + "_d", - Pixel.Event.privacyProPurchaseSuccess.name + "_c", - Pixel.Event.privacyProSubscriptionActivated.name, - Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) - } - - func testSubscriptionSelectedSuccessWhenRepurchasingForExpiredAppleSubscription() async throws { - // Given - ensureUserAuthenticatedState() - - XCTAssertTrue(accountManager.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)) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertFalse(authService.createAccountCalled) - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c", - Pixel.Event.privacyProPurchaseSuccess.name + "_d", - Pixel.Event.privacyProPurchaseSuccess.name + "_c", - Pixel.Event.privacyProSubscriptionActivated.name, - Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) - } - - func testSubscriptionSelectedSuccessWhenRepurchasingForExpiredStripeSubscription() async throws { - // Given - ensureUserAuthenticatedState() - - XCTAssertTrue(accountManager.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)) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertFalse(authService.createAccountCalled) - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c", - Pixel.Event.privacyProPurchaseSuccess.name + "_d", - Pixel.Event.privacyProPurchaseSuccess.name + "_c", - Pixel.Event.privacyProSubscriptionActivated.name, - Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) - } - - func testSubscriptionSelectedErrorWhenPurchasingWhenHavingActiveSubscription() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = true - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .hasActiveSubscription) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c", - Pixel.Event.privacyProRestoreAfterPurchaseAttempt.name]) - } - - func testSubscriptionSelectedErrorWhenPurchasingWhenUnauthenticatedAndHavingActiveSubscriptionOnAppleID() async throws { - // Given - ensureUserUnauthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = true - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .hasActiveSubscription) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c", - Pixel.Event.privacyProRestoreAfterPurchaseAttempt.name]) - } - - func testSubscriptionSelectedErrorWhenUnauthenticatedAndAccountCreationFails() async throws { - // Given - ensureUserUnauthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - storePurchaseManager.mostRecentTransactionResult = nil - - authService.createAccountResult = .failure(Constants.invalidTokenError) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .accountCreationFailed) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - func testSubscriptionSelectedErrorWhenPurchaseCancelledByUser() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseCancelledByUser) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .cancelledByUser) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - func testSubscriptionSelectedErrorWhenProductNotFound() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.productNotFound) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .purchaseFailed) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - func testSubscriptionSelectedErrorWhenExternalIDIsNotValidUUID() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.externalIDisNotAValidUUID) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .purchaseFailed) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - func testSubscriptionSelectedErrorWhenPurchaseFailed() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseFailed) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .purchaseFailed) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - func testSubscriptionSelectedErrorWhenTransactionCannotBeVerified() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionCannotBeVerified) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .purchaseFailed) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - func testSubscriptionSelectedErrorWhenTransactionPendingAuthentication() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionPendingAuthentication) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .purchaseFailed) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - func testSubscriptionSelectedErrorDueToUnknownPurchaseError() async throws { - // Given - ensureUserAuthenticatedState() - - storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) - storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.unknownError) - - // When - let subscriptionSelectedParams = ["id": "some-subscription-id"] - let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) - - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .purchaseFailed) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", - Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) - } - - // MARK: - Tests for setSubscription - - func testSetSubscriptionSuccess() async throws { - // Given - ensureUserUnauthenticatedState() - - authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) - - let onSetSubscriptionCalled = expectation(description: "onSetSubscription") - feature.onSetSubscription = { - onSetSubscriptionCalled.fulfill() - } - - // When - let setSubscriptionParams = ["token": Constants.authToken] - let result = 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) - - await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testSetSubscriptionErrorWhenFailedToExchangeToken() async throws { - // Given - ensureUserUnauthenticatedState() - - authService.getAccessTokenResult = .failure(Constants.invalidTokenError) - - let onSetSubscriptionCalled = expectation(description: "onSetSubscription") - onSetSubscriptionCalled.isInverted = true - feature.onSetSubscription = { - onSetSubscriptionCalled.fulfill() - } - - // When - let setSubscriptionParams = ["token": Constants.authToken] - let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertNil(accountManager.authToken) - XCTAssertFalse(accountManager.isUserAuthenticated) - - await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .failedToSetSubscription) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testSetSubscriptionErrorWhenFailedToFetchAccountDetails() async throws { - // Given - ensureUserUnauthenticatedState() - - authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) - authService.validateTokenResult = .failure(Constants.invalidTokenError) - - let onSetSubscriptionCalled = expectation(description: "onSetSubscription") - onSetSubscriptionCalled.isInverted = true - feature.onSetSubscription = { - onSetSubscriptionCalled.fulfill() - } - - // When - let setSubscriptionParams = ["token": Constants.authToken] - let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertNil(accountManager.authToken) - XCTAssertFalse(accountManager.isUserAuthenticated) - - await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .failedToSetSubscription) - - await XCTAssertPrivacyPixelsFired([]) - } - - // MARK: - Tests for activateSubscription - - func testActivateSubscriptionTokenSuccess() async throws { - // Given - ensureUserAuthenticatedState() - - let onActivateSubscriptionCalled = expectation(description: "onActivateSubscription") - feature.onActivateSubscription = { - onActivateSubscriptionCalled.fulfill() - } - - // When - let result = await feature.activateSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - await fulfillment(of: [onActivateSubscriptionCalled], timeout: 0.5) - XCTAssertNil(result) - - await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProRestorePurchaseOfferPageEntry.name]) - } - - // MARK: - Tests for featureSelected - - func testFeatureSelectedSuccess() async throws { - // Given - ensureUserAuthenticatedState() - - let onFeatureSelectedCalled = expectation(description: "onFeatureSelected") - feature.onFeatureSelected = { selection in - onFeatureSelectedCalled.fulfill() - XCTAssertEqual(selection, SubscriptionFeatureSelection.itr) - } - - // When - let featureSelectionParams = ["feature": SubscriptionFeatureName.itr] - let result = await feature.featureSelected(params: featureSelectionParams, original: Constants.mockScriptMessage) - - // Then - await fulfillment(of: [onFeatureSelectedCalled], timeout: 0.5) - XCTAssertNil(result) - - await XCTAssertPrivacyPixelsFired([]) - } - - // MARK: - Tests for backToSettings - - func testBackToSettingsSuccess() async throws { - // Given - ensureUserAuthenticatedState() - accountStorage.email = nil - - XCTAssertNil(accountManager.email) - - let onBackToSettingsCalled = expectation(description: "onBackToSettings") - feature.onBackToSettings = { - onBackToSettingsCalled.fulfill() - } - - authService.validateTokenResult = .success(Constants.validateTokenResponse) - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) - - // When - let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) + // MARK: - - // Then - await fulfillment(of: [onBackToSettingsCalled], timeout: 0.5) - - XCTAssertEqual(accountManager.email, Constants.email) - XCTAssertNil(result) - - await XCTAssertPrivacyPixelsFired([]) + func testGetSubscription() async throws { +// let result = await feature.getSubscription(params: <#T##Any#>, original: <#T##WKScriptMessage#>) } +} - func testBackToSettingsErrorOnFetchingAccountDetails() async throws { - // Given - ensureUserAuthenticatedState() - - let onBackToSettingsCalled = expectation(description: "onBackToSettings") - onBackToSettingsCalled.isInverted = true - feature.onBackToSettings = { - onBackToSettingsCalled.fulfill() - } - - authService.validateTokenResult = .failure(Constants.invalidTokenError) - - // When - let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - await fulfillment(of: [onBackToSettingsCalled], timeout: 0.5) - - XCTAssertEqual(feature.transactionError, .generalError) - XCTAssertNil(result) - - await XCTAssertPrivacyPixelsFired([]) - } - - // MARK: - Tests for getAccessToken - func testGetAccessTokenSuccess() async throws { - // Given - ensureUserAuthenticatedState() - - // When - let result = try await feature.getAccessToken(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let resultDictionary = try XCTUnwrap(result as? [String: String]) - XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], Constants.accessToken) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testGetAccessTokenEmptyOnMissingToken() async throws { - // Given - ensureUserUnauthenticatedState() - XCTAssertNil(accountManager.accessToken) - - // When - let result = try await feature.getAccessToken(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let resultDictionary = try XCTUnwrap(result as? [String: String]) - XCTAssertEqual(resultDictionary, [String: String]()) - - await XCTAssertPrivacyPixelsFired([]) - } - - // MARK: - Tests for restoreAccountFromAppStorePurchase - - func testRestoreAccountFromAppStorePurchaseSuccess() async throws { - // Given - ensureUserUnauthenticatedState() - - 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) - - // When - try await feature.restoreAccountFromAppStorePurchase() - - // Then - XCTAssertTrue(accountManager.isUserAuthenticated) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testRestoreAccountFromAppStorePurchaseErrorDueToExpiredSubscription() async throws { - // Given - ensureUserUnauthenticatedState() - - 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.expiredSubscription) - - - do { - // When - try await feature.restoreAccountFromAppStorePurchase() - XCTFail("Unexpected success") - } catch let error { - // Then - guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { - XCTFail("Unexpected error type") - return - } - - XCTAssertEqual(error, .subscriptionExpired) - XCTAssertFalse(accountManager.isUserAuthenticated) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - } - - func testRestoreAccountFromAppStorePurchaseErrorDueToNoTransaction() async throws { - // Given - ensureUserUnauthenticatedState() - - storePurchaseManager.mostRecentTransactionResult = nil - - do { - // When - try await feature.restoreAccountFromAppStorePurchase() - XCTFail("Unexpected success") - } catch let error { - // Then - guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { - XCTFail("Unexpected error type") - return - } - - XCTAssertEqual(error, .subscriptionNotFound) - XCTAssertFalse(accountManager.isUserAuthenticated) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - } - - func testRestoreAccountFromAppStorePurchaseErrorDueToOtherError() async throws { - // Given - ensureUserUnauthenticatedState() - - storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS - authService.storeLoginResult = .failure(Constants.invalidTokenError) - - do { - // When - try await feature.restoreAccountFromAppStorePurchase() - XCTFail("Unexpected success") - } catch let error { - // Then - guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { - XCTFail("Unexpected error type") - return - } - - XCTAssertEqual(error, .failedToRestorePastPurchase) - XCTAssertFalse(accountManager.isUserAuthenticated) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - } -} - -extension SubscriptionPagesUseSubscriptionFeatureTests { - - func ensureUserAuthenticatedState() { - accountStorage.authToken = Constants.authToken - accountStorage.email = Constants.email - accountStorage.externalID = Constants.externalID - accessTokenStorage.accessToken = Constants.accessToken - } - - func ensureUserUnauthenticatedState() { - try? accessTokenStorage.removeAccessToken() - try? accountStorage.clearAuthenticationState() - } - - public func XCTAssertPrivacyPixelsFired(_ pixels: [String], file: StaticString = #file, line: UInt = #line) async { - try? await Task.sleep(seconds: 0.1) - - let pixelsFired = Set(pixelsFired) - let expectedPixels = Set(pixels) - - // Assert expected pixels were fired - XCTAssertTrue(expectedPixels.isSubset(of: pixelsFired), - "Expected Privacy Pro pixels were not fired: \(expectedPixels.subtracting(pixelsFired))", - file: file, - line: line) - - // Assert no other Privacy Pro pixels were fired except the expected - let privacyProPixelPrefix = "m_privacy-pro" - let otherPixels = pixelsFired.subtracting(expectedPixels) - let otherPrivacyProPixels = otherPixels.filter { $0.hasPrefix(privacyProPixelPrefix) } - XCTAssertTrue(otherPrivacyProPixels.isEmpty, - "Unexpected Privacy Pro pixels fired: \(otherPrivacyProPixels)", - file: file, - line: line) - } -} +// 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 = [SubscriptionEntitlement.dataBrokerProtection, +// SubscriptionEntitlement.identityTheftRestoration, +// SubscriptionEntitlement.networkProtection] +// +// static let mostRecentTransactionJWS = "dGhpcyBpcyBub3QgYSByZWFsIEFw(...)cCBTdG9yZSB0cmFuc2FjdGlvbiBKV1M=" +// +// static let subscriptionOptions = SubscriptionOptions(platform: SubscriptionPlatformName.ios.rawValue, +// options: [ +// SubscriptionOption(id: "1", +// cost: SubscriptionOptionCost(displayPrice: "9 USD", recurrence: "monthly")), +// SubscriptionOption(id: "2", +// cost: SubscriptionOptionCost(displayPrice: "99 USD", recurrence: "yearly")) +// ], +// features: [ +// SubscriptionFeature(name: "vpn"), +// 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 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 accountManager: AccountManager! +// var subscriptionManager: SubscriptionManager! +// var subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled +// +// var feature: SubscriptionPagesUseSubscriptionFeature! +// +// var pixelsFired: [String] = [] +// +// override func setUpWithError() throws { +// // Pixels +// Pixel.isDryRun = false +// stub(condition: isHost("improving.duckduckgo.com")) { request -> HTTPStubsResponse in +// if let path = request.url?.path { +// let pixelName = path.dropping(prefix: "/t/") +// .dropping(suffix: "_ios_phone") +// .dropping(suffix: "_ios_tablet") +// self.pixelsFired.append(pixelName) +// } +// +// return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) +// } +// +// // Reset all daily pixel storage +// [Pixel.storage, DailyPixel.storage, UniquePixel.storage].forEach { storage in +// storage.dictionaryRepresentation().keys.forEach(storage.removeObject(forKey:)) +// } +// +// // Mocks +// subscriptionService = SubscriptionEndpointServiceMock() +// authService = AuthEndpointServiceMock() +// +// storePurchaseManager = StorePurchaseManagerMock() +// subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, +// purchasePlatform: .appStore) +// accountStorage = AccountKeychainStorageMock() +// accessTokenStorage = SubscriptionTokenKeychainStorageMock() +// +// userDefaults = UserDefaults(suiteName: Constants.userDefaultsSuiteName)! +// userDefaults.removePersistentDomain(forName: Constants.userDefaultsSuiteName) +// +// 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) +// // Real SubscriptionManager +// subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, +// accountManager: accountManager, +// subscriptionEndpointService: subscriptionService, +// authEndpointService: authService, +// subscriptionEnvironment: subscriptionEnvironment) +// +// feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, +// subscriptionFeatureAvailability: subscriptionFeatureAvailability, +// subscriptionAttributionOrigin: nil, +// appStorePurchaseFlow: appStorePurchaseFlow, +// appStoreRestoreFlow: appStoreRestoreFlow, +// appStoreAccountManagementFlow: appStoreAccountManagementFlow) +// } +// +// override func tearDownWithError() throws { +// Pixel.isDryRun = true +// pixelsFired.removeAll() +// HTTPStubs.removeAllStubs() +// +// subscriptionService = nil +// authService = nil +// storePurchaseManager = nil +// subscriptionEnvironment = nil +// +// userDefaults = nil +// +// accountStorage = nil +// accessTokenStorage = nil +// +// entitlementsCache.reset() +// entitlementsCache = nil +// +// accountManager = nil +// +// // Real Flows +// appStorePurchaseFlow = nil +// appStoreRestoreFlow = nil +// appStoreAccountManagementFlow = nil +// +// subscriptionManager = nil +// +// feature = nil +// } +// +// // MARK: - Tests for getSubscription +// +// func testGetSubscriptionSuccessRefreshingAuthToken() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// let newAuthToken = UUID().uuidString +// +// authService.validateTokenResult = .failure(Constants.invalidTokenError) +// storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS +// authService.storeLoginResult = .success(StoreLoginResponse(authToken: newAuthToken, +// email: Constants.email, +// externalID: Constants.externalID, +// id: 1, status: "authenticated")) +// +// // When +// let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let resultDictionary = try XCTUnwrap(result as? [String: String]) +// +// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], newAuthToken) +// XCTAssertEqual(accountManager.authToken, newAuthToken) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testGetSubscriptionSuccessWithoutRefreshingAuthToken() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// authService.validateTokenResult = .success(Constants.validateTokenResponse) +// +// // When +// let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let resultDictionary = try XCTUnwrap(result as? [String: String]) +// +// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], Constants.authToken) +// XCTAssertEqual(accountManager.authToken, Constants.authToken) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testGetSubscriptionSuccessErrorWhenUnauthenticated() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// authService.validateTokenResult = .failure(Constants.invalidTokenError) +// storePurchaseManager.mostRecentTransactionResult = nil +// +// // When +// let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let resultDictionary = try XCTUnwrap(result as? [String: String]) +// +// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], SubscriptionPagesUseSubscriptionFeature.Constants.empty) +// XCTAssertFalse(accountManager.isUserAuthenticated) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// // MARK: - Tests for getSubscriptionOptions +// +// func testGetSubscriptionOptionsSuccess() async throws { +// // Given +// storePurchaseManager.subscriptionOptionsResult = Constants.subscriptionOptions +// +// // When +// let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) +// +// XCTAssertEqual(subscriptionOptionsResult, Constants.subscriptionOptions) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testGetSubscriptionOptionsReturnsEmptyOptionsWhenNoSubscriptionOptions() async throws { +// // Given +// storePurchaseManager.subscriptionOptionsResult = nil +// +// // When +// let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) +// XCTAssertEqual(subscriptionOptionsResult, SubscriptionOptions.empty) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .failedToGetSubscriptionOptions) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testGetSubscriptionOptionsReturnsEmptyOptionsWhenPurchaseNotAllowed() async throws { +// // Given +// let subscriptionFeatureAvailabilityWithoutPurchaseAllowed = SubscriptionFeatureAvailabilityMock( +// isFeatureAvailable: true, +// isSubscriptionPurchaseAllowed: false, +// usesUnifiedFeedbackForm: true +// ) +// +// feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, +// subscriptionFeatureAvailability: subscriptionFeatureAvailabilityWithoutPurchaseAllowed, +// subscriptionAttributionOrigin: nil, +// appStorePurchaseFlow: appStorePurchaseFlow, +// appStoreRestoreFlow: appStoreRestoreFlow, +// appStoreAccountManagementFlow: appStoreAccountManagementFlow) +// +// storePurchaseManager.subscriptionOptionsResult = Constants.subscriptionOptions +// +// // When +// let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) +// XCTAssertEqual(subscriptionOptionsResult, SubscriptionOptions.empty) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// // MARK: - Tests for subscriptionSelected +// +// func testSubscriptionSelectedSuccessWhenPurchasingFirstTime() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// XCTAssertFalse(accountManager.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)) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c", +// Pixel.Event.privacyProPurchaseSuccess.name + "_d", +// Pixel.Event.privacyProPurchaseSuccess.name + "_c", +// Pixel.Event.privacyProSubscriptionActivated.name, +// Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) +// } +// +// func testSubscriptionSelectedSuccessWhenRepurchasingForExpiredAppleSubscription() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// XCTAssertTrue(accountManager.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)) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertFalse(authService.createAccountCalled) +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c", +// Pixel.Event.privacyProPurchaseSuccess.name + "_d", +// Pixel.Event.privacyProPurchaseSuccess.name + "_c", +// Pixel.Event.privacyProSubscriptionActivated.name, +// Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) +// } +// +// func testSubscriptionSelectedSuccessWhenRepurchasingForExpiredStripeSubscription() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// XCTAssertTrue(accountManager.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)) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertFalse(authService.createAccountCalled) +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c", +// Pixel.Event.privacyProPurchaseSuccess.name + "_d", +// Pixel.Event.privacyProPurchaseSuccess.name + "_c", +// Pixel.Event.privacyProSubscriptionActivated.name, +// Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) +// } +// +// func testSubscriptionSelectedErrorWhenPurchasingWhenHavingActiveSubscription() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = true +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .hasActiveSubscription) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c", +// Pixel.Event.privacyProRestoreAfterPurchaseAttempt.name]) +// } +// +// func testSubscriptionSelectedErrorWhenPurchasingWhenUnauthenticatedAndHavingActiveSubscriptionOnAppleID() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = true +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .hasActiveSubscription) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c", +// Pixel.Event.privacyProRestoreAfterPurchaseAttempt.name]) +// } +// +// func testSubscriptionSelectedErrorWhenUnauthenticatedAndAccountCreationFails() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// storePurchaseManager.mostRecentTransactionResult = nil +// +// authService.createAccountResult = .failure(Constants.invalidTokenError) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .accountCreationFailed) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// func testSubscriptionSelectedErrorWhenPurchaseCancelledByUser() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseCancelledByUser) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .cancelledByUser) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// func testSubscriptionSelectedErrorWhenProductNotFound() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.productNotFound) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .purchaseFailed) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// func testSubscriptionSelectedErrorWhenExternalIDIsNotValidUUID() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.externalIDisNotAValidUUID) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .purchaseFailed) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// func testSubscriptionSelectedErrorWhenPurchaseFailed() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseFailed) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .purchaseFailed) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// func testSubscriptionSelectedErrorWhenTransactionCannotBeVerified() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionCannotBeVerified) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .purchaseFailed) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// func testSubscriptionSelectedErrorWhenTransactionPendingAuthentication() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionPendingAuthentication) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .purchaseFailed) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// func testSubscriptionSelectedErrorDueToUnknownPurchaseError() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// storePurchaseManager.hasActiveSubscriptionResult = false +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.unknownError) +// +// // When +// let subscriptionSelectedParams = ["id": "some-subscription-id"] +// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) +// +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .purchaseFailed) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", +// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) +// } +// +// // MARK: - Tests for setSubscription +// +// func testSetSubscriptionSuccess() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) +// +// let onSetSubscriptionCalled = expectation(description: "onSetSubscription") +// feature.onSetSubscription = { +// onSetSubscriptionCalled.fulfill() +// } +// +// // When +// let setSubscriptionParams = ["token": Constants.authToken] +// let result = 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) +// +// await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testSetSubscriptionErrorWhenFailedToExchangeToken() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// authService.getAccessTokenResult = .failure(Constants.invalidTokenError) +// +// let onSetSubscriptionCalled = expectation(description: "onSetSubscription") +// onSetSubscriptionCalled.isInverted = true +// feature.onSetSubscription = { +// onSetSubscriptionCalled.fulfill() +// } +// +// // When +// let setSubscriptionParams = ["token": Constants.authToken] +// let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertNil(accountManager.authToken) +// XCTAssertFalse(accountManager.isUserAuthenticated) +// +// await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .failedToSetSubscription) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testSetSubscriptionErrorWhenFailedToFetchAccountDetails() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .failure(Constants.invalidTokenError) +// +// let onSetSubscriptionCalled = expectation(description: "onSetSubscription") +// onSetSubscriptionCalled.isInverted = true +// feature.onSetSubscription = { +// onSetSubscriptionCalled.fulfill() +// } +// +// // When +// let setSubscriptionParams = ["token": Constants.authToken] +// let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) +// +// // Then +// XCTAssertNil(accountManager.authToken) +// XCTAssertFalse(accountManager.isUserAuthenticated) +// +// await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) +// XCTAssertNil(result) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, .failedToSetSubscription) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// // MARK: - Tests for activateSubscription +// +// func testActivateSubscriptionTokenSuccess() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// let onActivateSubscriptionCalled = expectation(description: "onActivateSubscription") +// feature.onActivateSubscription = { +// onActivateSubscriptionCalled.fulfill() +// } +// +// // When +// let result = await feature.activateSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// await fulfillment(of: [onActivateSubscriptionCalled], timeout: 0.5) +// XCTAssertNil(result) +// +// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProRestorePurchaseOfferPageEntry.name]) +// } +// +// // MARK: - Tests for featureSelected +// +// func testFeatureSelectedSuccess() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// let onFeatureSelectedCalled = expectation(description: "onFeatureSelected") +// feature.onFeatureSelected = { selection in +// onFeatureSelectedCalled.fulfill() +// XCTAssertEqual(selection, SubscriptionFeatureSelection.itr) +// } +// +// // When +// let featureSelectionParams = ["feature": SubscriptionFeatureName.itr] +// let result = await feature.featureSelected(params: featureSelectionParams, original: Constants.mockScriptMessage) +// +// // Then +// await fulfillment(of: [onFeatureSelectedCalled], timeout: 0.5) +// XCTAssertNil(result) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// // MARK: - Tests for backToSettings +// +// func testBackToSettingsSuccess() async throws { +// // Given +// ensureUserAuthenticatedState() +// accountStorage.email = nil +// +// XCTAssertNil(accountManager.email) +// +// let onBackToSettingsCalled = expectation(description: "onBackToSettings") +// feature.onBackToSettings = { +// onBackToSettingsCalled.fulfill() +// } +// +// authService.validateTokenResult = .success(Constants.validateTokenResponse) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) +// +// // When +// let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// await fulfillment(of: [onBackToSettingsCalled], timeout: 0.5) +// +// XCTAssertEqual(accountManager.email, Constants.email) +// XCTAssertNil(result) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testBackToSettingsErrorOnFetchingAccountDetails() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// let onBackToSettingsCalled = expectation(description: "onBackToSettings") +// onBackToSettingsCalled.isInverted = true +// feature.onBackToSettings = { +// onBackToSettingsCalled.fulfill() +// } +// +// authService.validateTokenResult = .failure(Constants.invalidTokenError) +// +// // When +// let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// await fulfillment(of: [onBackToSettingsCalled], timeout: 0.5) +// +// XCTAssertEqual(feature.transactionError, .generalError) +// XCTAssertNil(result) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// // MARK: - Tests for getAccessToken +// func testGetAccessTokenSuccess() async throws { +// // Given +// ensureUserAuthenticatedState() +// +// // When +// let result = try await feature.getAccessToken(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let resultDictionary = try XCTUnwrap(result as? [String: String]) +// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], Constants.accessToken) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testGetAccessTokenEmptyOnMissingToken() async throws { +// // Given +// ensureUserUnauthenticatedState() +// XCTAssertNil(accountManager.accessToken) +// +// // When +// let result = try await feature.getAccessToken(params: Constants.mockParams, original: Constants.mockScriptMessage) +// +// // Then +// let resultDictionary = try XCTUnwrap(result as? [String: String]) +// XCTAssertEqual(resultDictionary, [String: String]()) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// // MARK: - Tests for restoreAccountFromAppStorePurchase +// +// func testRestoreAccountFromAppStorePurchaseSuccess() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// 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) +// +// // When +// try await feature.restoreAccountFromAppStorePurchase() +// +// // Then +// XCTAssertTrue(accountManager.isUserAuthenticated) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// +// func testRestoreAccountFromAppStorePurchaseErrorDueToExpiredSubscription() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// 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.expiredSubscription) +// +// +// do { +// // When +// try await feature.restoreAccountFromAppStorePurchase() +// XCTFail("Unexpected success") +// } catch let error { +// // Then +// guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { +// XCTFail("Unexpected error type") +// return +// } +// +// XCTAssertEqual(error, .subscriptionExpired) +// XCTAssertFalse(accountManager.isUserAuthenticated) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// } +// +// func testRestoreAccountFromAppStorePurchaseErrorDueToNoTransaction() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// storePurchaseManager.mostRecentTransactionResult = nil +// +// do { +// // When +// try await feature.restoreAccountFromAppStorePurchase() +// XCTFail("Unexpected success") +// } catch let error { +// // Then +// guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { +// XCTFail("Unexpected error type") +// return +// } +// +// XCTAssertEqual(error, .subscriptionNotFound) +// XCTAssertFalse(accountManager.isUserAuthenticated) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// } +// +// func testRestoreAccountFromAppStorePurchaseErrorDueToOtherError() async throws { +// // Given +// ensureUserUnauthenticatedState() +// +// storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS +// authService.storeLoginResult = .failure(Constants.invalidTokenError) +// +// do { +// // When +// try await feature.restoreAccountFromAppStorePurchase() +// XCTFail("Unexpected success") +// } catch let error { +// // Then +// guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { +// XCTFail("Unexpected error type") +// return +// } +// +// XCTAssertEqual(error, .failedToRestorePastPurchase) +// XCTAssertFalse(accountManager.isUserAuthenticated) +// +// XCTAssertEqual(feature.transactionStatus, .idle) +// XCTAssertEqual(feature.transactionError, nil) +// +// await XCTAssertPrivacyPixelsFired([]) +// } +// } +// } +// +// extension SubscriptionPagesUseSubscriptionFeatureTests { +// +// func ensureUserAuthenticatedState() { +// accountStorage.authToken = Constants.authToken +// accountStorage.email = Constants.email +// accountStorage.externalID = Constants.externalID +// accessTokenStorage.accessToken = Constants.accessToken +// } +// +// func ensureUserUnauthenticatedState() { +// try? accessTokenStorage.removeAccessToken() +// try? accountStorage.clearAuthenticationState() +// } +// +// public func XCTAssertPrivacyPixelsFired(_ pixels: [String], file: StaticString = #file, line: UInt = #line) async { +// try? await Task.sleep(seconds: 0.1) +// +// let pixelsFired = Set(pixelsFired) +// let expectedPixels = Set(pixels) +// +// // Assert expected pixels were fired +// XCTAssertTrue(expectedPixels.isSubset(of: pixelsFired), +// "Expected Privacy Pro pixels were not fired: \(expectedPixels.subtracting(pixelsFired))", +// file: file, +// line: line) +// +// // Assert no other Privacy Pro pixels were fired except the expected +// let privacyProPixelPrefix = "m_privacy-pro" +// let otherPixels = pixelsFired.subtracting(expectedPixels) +// let otherPrivacyProPixels = otherPixels.filter { $0.hasPrefix(privacyProPixelPrefix) } +// XCTAssertTrue(otherPrivacyProPixels.isEmpty, +// "Unexpected Privacy Pro pixels fired: \(otherPrivacyProPixels)", +// file: file, +// line: line) +// } +// } From 8ea3254e54f100f1ce78066561409e322734fb27 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 1 Nov 2024 18:29:19 +0000 Subject: [PATCH 15/41] unit tests and improvements --- DuckDuckGo/MainViewController.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 10 ++++---- ...scriptionPagesUseSubscriptionFeature.swift | 23 +++++++++++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index aaa284410a..18b3bbe0d8 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1582,7 +1582,7 @@ class MainViewController: UIViewController { Task { let subscriptionManager = AppDependencyProvider.shared.subscriptionManager - guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid), + guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local), tokenContainer.decodedAccessToken.hasEntitlement(.networkProtection) == false else { return } if await networkProtectionTunnelController.isInstalled { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 4f9cfefc8a..f5e716c71a 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -698,6 +698,7 @@ extension SettingsViewModel { @MainActor private func setupSubscriptionEnvironment() async { + Logger.subscription.log("Setting up Subscription Environment") // If there's cached data use it by default if let cachedSubscription = subscriptionStateCache.get() { state.subscription = cachedSubscription @@ -719,21 +720,22 @@ extension SettingsViewModel { } let subscription = try await subscriptionManager.currentSubscription(refresh: true) + Logger.subscription.log("Subscription loaded: \(subscription.debugDescription, privacy: .public)") state.subscription.subscriptionExist = true state.subscription.platform = subscription.platform state.subscription.hasActiveSubscription = subscription.isActive - - // Check entitlements and update state state.subscription.entitlements = subscriptionManager.entitlements - } catch SubscriptionEndpointServiceError.noData { + } catch SubscriptionEndpointServiceError.noData, OAuthClientError.missingTokens { // Auth successful but no Subscription is available Logger.subscription.log("Subscription not present") + state.subscription.subscriptionExist = false + state.subscription.platform = .unknown state.subscription.hasActiveSubscription = false state.subscription.entitlements = [] } catch { // Generic error, we don't update the cached data - Logger.subscription.error("Failed to load Subscription: \(error, privacy: .public)") + Logger.subscription.debug("Failed to load Subscription: \(error, privacy: .public)") } // Sync Cache diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 1844986276..f034431823 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -141,7 +141,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - Logger.subscription.log("WebView handler: \(methodName)") + Logger.subscription.debug("WebView handler: \(methodName)") switch methodName { case Handlers.getSubscription: return getSubscription case Handlers.setSubscription: return setSubscription @@ -193,20 +193,22 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec /// Returns the auth token func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { do { - let accessToken = try await subscriptionManager.getTokenContainer(policy: .createIfNeeded).accessToken + let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken return [Constants.token: accessToken] } catch { - Logger.subscription.fault("Failed to fetch token: \(error)") - return Constants.empty + Logger.subscription.debug("No subscription available: \(error)") + return [Constants.token: Constants.empty] } } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { resetSubscriptionFlow() if let subscriptionOptions = await subscriptionManager.storePurchaseManager().subscriptionOptions() { + Logger.subscription.debug("Subscription options retrieved: \(String(describing: subscriptionOptions), privacy: .public)") if subscriptionFeatureAvailability.isSubscriptionPurchaseAllowed { return subscriptionOptions } else { + Logger.subscription.log("Subscription purchase not allowed") return SubscriptionOptions.empty } } else { @@ -283,21 +285,24 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { case .success: Logger.subscription.log("Subscription purchase completed successfully") + DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseSuccess, pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes) UniquePixel.fire(pixel: .privacyProSubscriptionActivated) Pixel.fireAttribution(pixel: .privacyProSuccessfulSubscriptionAttribution, origin: subscriptionAttributionOrigin, privacyProDataReporter: privacyProDataReporter) + + setTransactionStatus(.idle) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) case .failure(let error): Logger.subscription.error("App store complete subscription purchase error: \(error, privacy: .public)") + setTransactionStatus(.idle) setTransactionError(.missingEntitlements) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) } - setTransactionStatus(.idle) return nil } func setSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { - Logger.subscription.log("Setting Subscription") // 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. guard let subscriptionValues: SubscriptionValues = CodableHelper.decode(from: params) else { @@ -308,7 +313,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Clear subscription Cache - subscriptionManager.clearSubscriptionCache() + subscriptionManager.signOut() let authToken = subscriptionValues.token do { @@ -372,7 +377,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // setTransactionError(.generalError) // } // return nil - await subscriptionManager.refreshAccount() + _ = try? await subscriptionManager.getTokenContainer(policy: .localForceRefresh) onBackToSettings?() return nil } @@ -382,7 +387,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken return [Constants.token: accessToken] } catch { - Logger.subscription.fault("Failed to fetch token: \(error)") + Logger.subscription.debug("No access token available: \(error)") return [String: String]() } } From c78a9c22dc08697ed85c831e19e905519cf674b9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 1 Nov 2024 19:45:25 +0000 Subject: [PATCH 16/41] signout as async --- ...NetworkProtectionDebugViewController.swift | 6 ++-- ...scriptionPagesUseSubscriptionFeature.swift | 5 ++- .../SubscriptionSettingsViewModel.swift | 11 +++--- .../SubscriptionDebugViewController.swift | 34 +++++++++++-------- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 0163148d00..a140383bc6 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -667,9 +667,11 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") if let subscriptionOverrideEnabled = defaults.subscriptionOverrideEnabled { if subscriptionOverrideEnabled { defaults.subscriptionOverrideEnabled = false - AppDependencyProvider.shared.subscriptionManager.signOut() + Task { + await AppDependencyProvider.shared.subscriptionManager.signOut() + } } else { - defaults.resetsubscriptionOverrideEnabled() + defaults.resetSubscriptionOverrideEnabled() } } else { defaults.subscriptionOverrideEnabled = true diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index f034431823..bf24037b9c 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -295,6 +295,9 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) case .failure(let error): Logger.subscription.error("App store complete subscription purchase error: \(error, privacy: .public)") + + await subscriptionManager.signOut() + setTransactionStatus(.idle) setTransactionError(.missingEntitlements) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate.completed) @@ -313,7 +316,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Clear subscription Cache - subscriptionManager.signOut() + await subscriptionManager.signOut() let authToken = subscriptionValues.token do { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 2332371dba..bb81f40b68 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -202,10 +202,13 @@ final class SubscriptionSettingsViewModel: ObservableObject { func removeSubscription() { Logger.subscription.log("Remove subscription") - subscriptionManager.signOut() - _ = ActionMessageView() - ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, - presentationLocation: .withoutBottomBar) + + Task { + await subscriptionManager.signOut() + _ = await ActionMessageView() + await ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, + presentationLocation: .withoutBottomBar) + } } func displayGoogleView(_ value: Bool) { diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 168ed8d92b..ff71e1feb0 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -264,8 +264,10 @@ final class SubscriptionDebugViewController: UITableViewController { } private func clearAuthData() { - subscriptionManager.signOut() - showAlert(title: "Data cleared!") + Task { + await subscriptionManager.signOut() + showAlert(title: "Data cleared!") + } } private func showAccountDetails() { @@ -370,20 +372,22 @@ final class SubscriptionDebugViewController: UITableViewController { newSubscriptionEnvironment.serviceEnvironment = environment if newSubscriptionEnvironment.serviceEnvironment != currentSubscriptionEnvironment.serviceEnvironment { - subscriptionManager.signOut() - - // Save Subscription environment - DefaultSubscriptionManager.save(subscriptionEnvironment: newSubscriptionEnvironment, userDefaults: subscriptionUserDefaults) - - // The VPN environment is forced to match the subscription environment - let settings = AppDependencyProvider.shared.vpnSettings - switch newSubscriptionEnvironment.serviceEnvironment { - case .production: - settings.selectedEnvironment = .production - case .staging: - settings.selectedEnvironment = .staging + Task { + await subscriptionManager.signOut() + + // Save Subscription environment + DefaultSubscriptionManager.save(subscriptionEnvironment: newSubscriptionEnvironment, userDefaults: subscriptionUserDefaults) + + // The VPN environment is forced to match the subscription environment + let settings = AppDependencyProvider.shared.vpnSettings + switch newSubscriptionEnvironment.serviceEnvironment { + case .production: + settings.selectedEnvironment = .production + case .staging: + settings.selectedEnvironment = .staging + } + NetworkProtectionLocationListCompositeRepository.clearCache() } - NetworkProtectionLocationListCompositeRepository.clearCache() } } } From f06370b694860a7a2b7c5a3230f07cfe9e3a9270 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 5 Nov 2024 16:10:05 +0000 Subject: [PATCH 17/41] lint --- Core/PixelEvent.swift | 2 + DuckDuckGo.xcodeproj/project.pbxproj | 72 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDelegate.swift | 5 + DuckDuckGo/AppDependencyProvider.swift | 9 +- DuckDuckGo/Info.plist | 1 + .../TokenBackgroundRefreshTask.swift | 104 ++ ...tionPagesUseSubscriptionFeatureTests.swift | 1108 ----------------- ...etworkProtectionPacketTunnelProvider.swift | 9 +- 9 files changed, 166 insertions(+), 1148 deletions(-) create mode 100644 DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift delete mode 100644 DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index f300d9febc..f3eddd1391 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -715,6 +715,7 @@ extension Pixel { case privacyProSubscriptionCookieRefreshedWithAccessToken case privacyProSubscriptionCookieRefreshedWithEmptyValue case privacyProSubscriptionCookieFailedToSetSubscriptionCookie + case privacyProDeadTokenDetected // MARK: Pixel Experiment case pixelExperimentEnrollment @@ -1532,6 +1533,7 @@ extension Pixel.Event { case .privacyProSubscriptionCookieRefreshedWithAccessToken: return "m_privacy-pro_subscription-cookie-refreshed_with_access_token" case .privacyProSubscriptionCookieRefreshedWithEmptyValue: return "m_privacy-pro_subscription-cookie-refreshed_with_empty_value" case .privacyProSubscriptionCookieFailedToSetSubscriptionCookie: return "m_privacy-pro_subscription-cookie-failed_to_set_subscription_cookie" + case .privacyProDeadTokenDetected: return "m_privacy-pro_dead_token_detected" // MARK: Pixel Experiment case .pixelExperimentEnrollment: return "pixel_experiment_enrollment" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index efe8934aaf..ed7932e744 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1100,6 +1100,7 @@ F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C141E57336D00DEDCAF /* TabManager.swift */; }; F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */; }; + F1619A1C2CDA32D100D6D4C9 /* TokenBackgroundRefreshTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1619A1B2CDA32D100D6D4C9 /* TokenBackgroundRefreshTask.swift */; }; F16393FF1ECCB9CC00DDD653 /* FileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16393FE1ECCB9CC00DDD653 /* FileLoader.swift */; }; F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1668BCD1E798081008CBA04 /* BookmarksViewController.swift */; }; F176699F1E40BC86003D3222 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F176699D1E40BC86003D3222 /* Settings.storyboard */; }; @@ -1118,7 +1119,6 @@ F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */; }; F1BDDBFD2C340D9C00459306 /* SubscriptionContainerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBF92C340D9C00459306 /* SubscriptionContainerViewModelTests.swift */; }; F1BDDBFE2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBFA2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift */; }; - F1BDDBFF2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBFB2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */; }; F1BDDC022C340DDF00459306 /* SyncManagementViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDC002C340DDF00459306 /* SyncManagementViewModelTests.swift */; }; F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */; }; F1C4A70E1E57725800A6CA1B /* OmniBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4A70D1E57725800A6CA1B /* OmniBar.swift */; }; @@ -2927,6 +2927,7 @@ F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherViewController.swift; sourceTree = ""; }; F1617C141E57336D00DEDCAF /* TabManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherDelegate.swift; sourceTree = ""; }; + F1619A1B2CDA32D100D6D4C9 /* TokenBackgroundRefreshTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenBackgroundRefreshTask.swift; sourceTree = ""; }; F16393F41ECCA85900DDD653 /* DomainsProtectionUserDefaultsStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsProtectionUserDefaultsStoreTests.swift; sourceTree = ""; }; F16393FE1ECCB9CC00DDD653 /* FileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileLoader.swift; sourceTree = ""; }; F1668BCD1E798081008CBA04 /* BookmarksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksViewController.swift; sourceTree = ""; }; @@ -2950,7 +2951,6 @@ F1B745211E549D550072547E /* UIColorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIColorExtension.swift; path = ../Core/UIColorExtension.swift; sourceTree = ""; }; F1BDDBF92C340D9C00459306 /* SubscriptionContainerViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewModelTests.swift; sourceTree = ""; }; F1BDDBFA2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModelTests.swift; sourceTree = ""; }; - F1BDDBFB2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeatureTests.swift; sourceTree = ""; }; F1BDDC002C340DDF00459306 /* SyncManagementViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncManagementViewModelTests.swift; sourceTree = ""; }; F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialSettings.swift; sourceTree = ""; }; F1C4A70D1E57725800A6CA1B /* OmniBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBar.swift; sourceTree = ""; }; @@ -5360,6 +5360,7 @@ D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */, BDE219E52C406D19005D5884 /* PrivacyProDataReporting.swift */, 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */, + F1619A1B2CDA32D100D6D4C9 /* TokenBackgroundRefreshTask.swift */, ); path = Subscription; sourceTree = ""; @@ -6150,7 +6151,6 @@ BDE219E92C457B46005D5884 /* PrivacyProDataReporterTests.swift */, F1BDDBF92C340D9C00459306 /* SubscriptionContainerViewModelTests.swift */, F1BDDBFA2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift */, - F1BDDBFB2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */, 1E4E6C562C78B8540059C0FA /* StorePurchaseManagerTests.swift */, ); path = Subscription; @@ -7869,6 +7869,7 @@ 6FD1BAE62B87A107000C475C /* AdAttributionFetcher.swift in Sources */, EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */, EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, + F1619A1C2CDA32D100D6D4C9 /* TokenBackgroundRefreshTask.swift in Sources */, C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */, CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */, AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, @@ -8116,7 +8117,6 @@ F1134ED61F40F29F00B73467 /* StatisticsUserDefaultsTests.swift in Sources */, 98629D342C21BE37001E6031 /* BookmarksStateValidationTests.swift in Sources */, C1FFBD462C761BE20073622B /* SyncPromoManagerTests.swift in Sources */, - F1BDDBFF2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift in Sources */, 98EA2C3C218B9AAD0023E1DC /* ThemeManagerTests.swift in Sources */, 569437292BDD487600C0881B /* SyncCredentialsAdapterTests.swift in Sources */, 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */, @@ -9214,8 +9214,8 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9305,8 +9305,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9333,8 +9333,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9506,8 +9506,8 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9608,8 +9608,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9641,8 +9641,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9672,8 +9672,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10019,8 +10019,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10048,8 +10048,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10082,8 +10082,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10112,8 +10112,8 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -10411,8 +10411,8 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10444,8 +10444,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10482,8 +10482,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10517,8 +10517,8 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10767,8 +10767,8 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 0; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb81d1d95a..2abfc10b2a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -158,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "8fa345c2081cfbd4851dffff5dd5bed48efe6081", - "version" : "3.9.0" + "revision" : "06dc63c6d8da54ee11ceb268cde1fa68161afc96", + "version" : "3.9.1" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index dba5c2a78e..d1c1f444b8 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -91,6 +91,7 @@ import os.log private var subscriptionCookieManager: SubscriptionCookieManaging! private var subscriptionCookieManagerFeatureFlagCancellable: AnyCancellable? var privacyProDataReporter: PrivacyProDataReporting! + private var tokenBackgroundRefreshTask: TokenBackgroundRefreshTask? // MARK: - Feature specific app event handlers @@ -374,6 +375,9 @@ import os.log // Having both in `didBecomeActive` can sometimes cause the exception when running on a physical device, so registration happens here. AppConfigurationFetch.registerBackgroundRefreshTaskHandler() + tokenBackgroundRefreshTask = TokenBackgroundRefreshTask(subscriptionManager: AppDependencyProvider.shared.subscriptionManager) + tokenBackgroundRefreshTask?.registerBackgroundRefreshTaskHandler() + UNUserNotificationCenter.current().delegate = self window?.windowScene?.screenshotService?.delegate = self @@ -738,6 +742,7 @@ import os.log suspendSync() syncDataProviders.bookmarksAdapter.cancelFaviconsFetching(application) privacyProDataReporter.saveApplicationLastSessionEnded() + tokenBackgroundRefreshTask?.scheduleTask() } private func suspendSync() { diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index e7b796cafb..f1bfeffa95 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -141,10 +141,17 @@ final class AppDependencyProvider: DependencyProvider { let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) + let pixelHandler: SubscriptionManager.PixelHandler = { type in + switch type { + case .deadToken: + Pixel.fire(pixel: .privacyProDeadTokenDetected) + } + } let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionEnvironment: subscriptionEnvironment) + subscriptionEnvironment: subscriptionEnvironment, + pixelHandler: pixelHandler) self.subscriptionManager = subscriptionManager networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: { guard let token = subscriptionManager.getTokenContainerSynchronously(policy: .localValid)?.accessToken else { diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 6d3941b0ca..f0c871a752 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -10,6 +10,7 @@ com.duckduckgo.app.configurationRefresh com.duckduckgo.app.remoteMessageRefresh + com.duckduckgo.app.backgroundTokenRefresh CFBundleDevelopmentRegion en diff --git a/DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift b/DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift new file mode 100644 index 0000000000..27ac409785 --- /dev/null +++ b/DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift @@ -0,0 +1,104 @@ +// +// TokenBackgroundRefreshTask.swift +// DuckDuckGo +// +// 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 BackgroundTasks +import Subscription +import Core + +class TokenBackgroundRefreshTask { + + private let taskName = "Refresh authentication token" + private let taskIdentifier = "com.duckduckgo.app.backgroundTokenRefresh" + private let minimumConfigurationRefreshInterval: TimeInterval = TimeInterval.days(7) + private let subscriptionManager: SubscriptionManager + + init(subscriptionManager: SubscriptionManager) { + self.subscriptionManager = subscriptionManager + } + + func registerBackgroundRefreshTaskHandler() { + BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { [weak self] task in + + guard let self else { + Logger.subscription.fault("Failed to refresh token, self is nil") + task.setTaskCompleted(success: false) + return + } + + guard self.subscriptionManager.isUserAuthenticated else { + task.setTaskCompleted(success: true) + self.scheduleTask() + return + } + + self.handle(task: task) + } + } + + func handle(task: BGTask) { + Logger.subscription.log("Token background refresh task started") + + scheduleTask() + + let refreshStartDate = Date() + task.expirationHandler = { + Logger.subscription.error("Background refresh task expired") + task.setTaskCompleted(success: false) + } + + Task { [weak self] in + guard let self else { + Logger.subscription.fault("Failed to refresh token, self is nil") + task.setTaskCompleted(success: false) + return + } + do { + try await self.subscriptionManager.getTokenContainer(policy: .localForceRefresh) + Logger.subscription.log("Token background refresh task completed successfully in \(Date().timeIntervalSince(refreshStartDate)) seconds") + task.setTaskCompleted(success: true) + } catch { + Logger.subscription.error("Failed to refresh token: \(error)") + task.setTaskCompleted(success: false) + } + } + } + + func scheduleTask() { + let task = BGProcessingTaskRequest(identifier: taskIdentifier) + task.requiresNetworkConnectivity = true + task.earliestBeginDate = Date(timeIntervalSinceNow: minimumConfigurationRefreshInterval) + + // Background tasks can be debugged by breaking on the `submit` call, stepping over, then running the following LLDB command, before resuming: + // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.duckduckgo.app.configurationRefresh"] + // + // Task expiration can be simulated similarly: + // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.duckduckgo.app.configurationRefresh"] + + #if !targetEnvironment(simulator) + do { + try BGTaskScheduler.shared.submit(task) + Logger.subscription.debug("Token background refresh task scheduled") + } catch { + Logger.subscription.error("Failed to schedule token background refresh task: \(error)") + Pixel.fire(pixel: .backgroundTaskSubmissionFailed, error: error) + } + #endif + } +} diff --git a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift deleted file mode 100644 index 4784e4af32..0000000000 --- a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ /dev/null @@ -1,1108 +0,0 @@ -// -// SubscriptionPagesUseSubscriptionFeatureTests.swift -// DuckDuckGo -// -// 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 XCTest -@testable import DuckDuckGo -@testable import Core -@testable import Subscription -import SubscriptionTestingUtilities -import Common -import WebKit -import BrowserServicesKit -import OHHTTPStubs -import OHHTTPStubsSwift -import os.log -import Networking - -final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { - - var feature: SubscriptionPagesUseSubscriptionFeature! - var subscriptionManager: SubscriptionManagerMock! - var storePurchaseManager: StorePurchaseManagerMock! - - override func setUpWithError() throws { - - storePurchaseManager = StorePurchaseManagerMock() - subscriptionManager = SubscriptionManagerMock() - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager, - storePurchaseManager: storePurchaseManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, - storePurchaseManager: storePurchaseManager, - appStoreRestoreFlow: appStoreRestoreFlow) - - feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: SubscriptionManagerMock(), - subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock(isFeatureAvailable: true, - isSubscriptionPurchaseAllowed: true, - usesUnifiedFeedbackForm: true), - subscriptionAttributionOrigin: nil, - appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - privacyProDataReporter: nil) - } - - override func tearDownWithError() throws { - feature = nil - subscriptionManager = nil - storePurchaseManager = nil - } - - // MARK: - - - func testGetSubscription() async throws { -// let result = await feature.getSubscription(params: <#T##Any#>, original: <#T##WKScriptMessage#>) - } -} - -// 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 = [SubscriptionEntitlement.dataBrokerProtection, -// SubscriptionEntitlement.identityTheftRestoration, -// SubscriptionEntitlement.networkProtection] -// -// static let mostRecentTransactionJWS = "dGhpcyBpcyBub3QgYSByZWFsIEFw(...)cCBTdG9yZSB0cmFuc2FjdGlvbiBKV1M=" -// -// static let subscriptionOptions = SubscriptionOptions(platform: SubscriptionPlatformName.ios.rawValue, -// options: [ -// SubscriptionOption(id: "1", -// cost: SubscriptionOptionCost(displayPrice: "9 USD", recurrence: "monthly")), -// SubscriptionOption(id: "2", -// cost: SubscriptionOptionCost(displayPrice: "99 USD", recurrence: "yearly")) -// ], -// features: [ -// SubscriptionFeature(name: "vpn"), -// 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 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 accountManager: AccountManager! -// var subscriptionManager: SubscriptionManager! -// var subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled -// -// var feature: SubscriptionPagesUseSubscriptionFeature! -// -// var pixelsFired: [String] = [] -// -// override func setUpWithError() throws { -// // Pixels -// Pixel.isDryRun = false -// stub(condition: isHost("improving.duckduckgo.com")) { request -> HTTPStubsResponse in -// if let path = request.url?.path { -// let pixelName = path.dropping(prefix: "/t/") -// .dropping(suffix: "_ios_phone") -// .dropping(suffix: "_ios_tablet") -// self.pixelsFired.append(pixelName) -// } -// -// return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) -// } -// -// // Reset all daily pixel storage -// [Pixel.storage, DailyPixel.storage, UniquePixel.storage].forEach { storage in -// storage.dictionaryRepresentation().keys.forEach(storage.removeObject(forKey:)) -// } -// -// // Mocks -// subscriptionService = SubscriptionEndpointServiceMock() -// authService = AuthEndpointServiceMock() -// -// storePurchaseManager = StorePurchaseManagerMock() -// subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, -// purchasePlatform: .appStore) -// accountStorage = AccountKeychainStorageMock() -// accessTokenStorage = SubscriptionTokenKeychainStorageMock() -// -// userDefaults = UserDefaults(suiteName: Constants.userDefaultsSuiteName)! -// userDefaults.removePersistentDomain(forName: Constants.userDefaultsSuiteName) -// -// 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) -// // Real SubscriptionManager -// subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, -// accountManager: accountManager, -// subscriptionEndpointService: subscriptionService, -// authEndpointService: authService, -// subscriptionEnvironment: subscriptionEnvironment) -// -// feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, -// subscriptionFeatureAvailability: subscriptionFeatureAvailability, -// subscriptionAttributionOrigin: nil, -// appStorePurchaseFlow: appStorePurchaseFlow, -// appStoreRestoreFlow: appStoreRestoreFlow, -// appStoreAccountManagementFlow: appStoreAccountManagementFlow) -// } -// -// override func tearDownWithError() throws { -// Pixel.isDryRun = true -// pixelsFired.removeAll() -// HTTPStubs.removeAllStubs() -// -// subscriptionService = nil -// authService = nil -// storePurchaseManager = nil -// subscriptionEnvironment = nil -// -// userDefaults = nil -// -// accountStorage = nil -// accessTokenStorage = nil -// -// entitlementsCache.reset() -// entitlementsCache = nil -// -// accountManager = nil -// -// // Real Flows -// appStorePurchaseFlow = nil -// appStoreRestoreFlow = nil -// appStoreAccountManagementFlow = nil -// -// subscriptionManager = nil -// -// feature = nil -// } -// -// // MARK: - Tests for getSubscription -// -// func testGetSubscriptionSuccessRefreshingAuthToken() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// let newAuthToken = UUID().uuidString -// -// authService.validateTokenResult = .failure(Constants.invalidTokenError) -// storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS -// authService.storeLoginResult = .success(StoreLoginResponse(authToken: newAuthToken, -// email: Constants.email, -// externalID: Constants.externalID, -// id: 1, status: "authenticated")) -// -// // When -// let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let resultDictionary = try XCTUnwrap(result as? [String: String]) -// -// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], newAuthToken) -// XCTAssertEqual(accountManager.authToken, newAuthToken) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testGetSubscriptionSuccessWithoutRefreshingAuthToken() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// authService.validateTokenResult = .success(Constants.validateTokenResponse) -// -// // When -// let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let resultDictionary = try XCTUnwrap(result as? [String: String]) -// -// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], Constants.authToken) -// XCTAssertEqual(accountManager.authToken, Constants.authToken) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testGetSubscriptionSuccessErrorWhenUnauthenticated() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// authService.validateTokenResult = .failure(Constants.invalidTokenError) -// storePurchaseManager.mostRecentTransactionResult = nil -// -// // When -// let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let resultDictionary = try XCTUnwrap(result as? [String: String]) -// -// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], SubscriptionPagesUseSubscriptionFeature.Constants.empty) -// XCTAssertFalse(accountManager.isUserAuthenticated) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// // MARK: - Tests for getSubscriptionOptions -// -// func testGetSubscriptionOptionsSuccess() async throws { -// // Given -// storePurchaseManager.subscriptionOptionsResult = Constants.subscriptionOptions -// -// // When -// let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) -// -// XCTAssertEqual(subscriptionOptionsResult, Constants.subscriptionOptions) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testGetSubscriptionOptionsReturnsEmptyOptionsWhenNoSubscriptionOptions() async throws { -// // Given -// storePurchaseManager.subscriptionOptionsResult = nil -// -// // When -// let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) -// XCTAssertEqual(subscriptionOptionsResult, SubscriptionOptions.empty) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .failedToGetSubscriptionOptions) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testGetSubscriptionOptionsReturnsEmptyOptionsWhenPurchaseNotAllowed() async throws { -// // Given -// let subscriptionFeatureAvailabilityWithoutPurchaseAllowed = SubscriptionFeatureAvailabilityMock( -// isFeatureAvailable: true, -// isSubscriptionPurchaseAllowed: false, -// usesUnifiedFeedbackForm: true -// ) -// -// feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, -// subscriptionFeatureAvailability: subscriptionFeatureAvailabilityWithoutPurchaseAllowed, -// subscriptionAttributionOrigin: nil, -// appStorePurchaseFlow: appStorePurchaseFlow, -// appStoreRestoreFlow: appStoreRestoreFlow, -// appStoreAccountManagementFlow: appStoreAccountManagementFlow) -// -// storePurchaseManager.subscriptionOptionsResult = Constants.subscriptionOptions -// -// // When -// let result = await feature.getSubscriptionOptions(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let subscriptionOptionsResult = try XCTUnwrap(result as? SubscriptionOptions) -// XCTAssertEqual(subscriptionOptionsResult, SubscriptionOptions.empty) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// // MARK: - Tests for subscriptionSelected -// -// func testSubscriptionSelectedSuccessWhenPurchasingFirstTime() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// XCTAssertFalse(accountManager.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)) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c", -// Pixel.Event.privacyProPurchaseSuccess.name + "_d", -// Pixel.Event.privacyProPurchaseSuccess.name + "_c", -// Pixel.Event.privacyProSubscriptionActivated.name, -// Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) -// } -// -// func testSubscriptionSelectedSuccessWhenRepurchasingForExpiredAppleSubscription() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// XCTAssertTrue(accountManager.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)) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertFalse(authService.createAccountCalled) -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c", -// Pixel.Event.privacyProPurchaseSuccess.name + "_d", -// Pixel.Event.privacyProPurchaseSuccess.name + "_c", -// Pixel.Event.privacyProSubscriptionActivated.name, -// Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) -// } -// -// func testSubscriptionSelectedSuccessWhenRepurchasingForExpiredStripeSubscription() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// XCTAssertTrue(accountManager.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)) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertFalse(authService.createAccountCalled) -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c", -// Pixel.Event.privacyProPurchaseSuccess.name + "_d", -// Pixel.Event.privacyProPurchaseSuccess.name + "_c", -// Pixel.Event.privacyProSubscriptionActivated.name, -// Pixel.Event.privacyProSuccessfulSubscriptionAttribution.name]) -// } -// -// func testSubscriptionSelectedErrorWhenPurchasingWhenHavingActiveSubscription() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = true -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .hasActiveSubscription) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c", -// Pixel.Event.privacyProRestoreAfterPurchaseAttempt.name]) -// } -// -// func testSubscriptionSelectedErrorWhenPurchasingWhenUnauthenticatedAndHavingActiveSubscriptionOnAppleID() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = true -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .hasActiveSubscription) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c", -// Pixel.Event.privacyProRestoreAfterPurchaseAttempt.name]) -// } -// -// func testSubscriptionSelectedErrorWhenUnauthenticatedAndAccountCreationFails() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// storePurchaseManager.mostRecentTransactionResult = nil -// -// authService.createAccountResult = .failure(Constants.invalidTokenError) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertFalse(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .accountCreationFailed) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// func testSubscriptionSelectedErrorWhenPurchaseCancelledByUser() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseCancelledByUser) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .cancelledByUser) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// func testSubscriptionSelectedErrorWhenProductNotFound() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.productNotFound) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .purchaseFailed) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// func testSubscriptionSelectedErrorWhenExternalIDIsNotValidUUID() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.externalIDisNotAValidUUID) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .purchaseFailed) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// func testSubscriptionSelectedErrorWhenPurchaseFailed() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseFailed) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .purchaseFailed) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// func testSubscriptionSelectedErrorWhenTransactionCannotBeVerified() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionCannotBeVerified) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .purchaseFailed) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// func testSubscriptionSelectedErrorWhenTransactionPendingAuthentication() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionPendingAuthentication) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .purchaseFailed) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// func testSubscriptionSelectedErrorDueToUnknownPurchaseError() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// storePurchaseManager.hasActiveSubscriptionResult = false -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) -// storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.unknownError) -// -// // When -// let subscriptionSelectedParams = ["id": "some-subscription-id"] -// let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) -// -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .purchaseFailed) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProPurchaseAttempt.name + "_d", -// Pixel.Event.privacyProPurchaseAttempt.name + "_c"]) -// } -// -// // MARK: - Tests for setSubscription -// -// func testSetSubscriptionSuccess() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .success(Constants.validateTokenResponse) -// -// let onSetSubscriptionCalled = expectation(description: "onSetSubscription") -// feature.onSetSubscription = { -// onSetSubscriptionCalled.fulfill() -// } -// -// // When -// let setSubscriptionParams = ["token": Constants.authToken] -// let result = 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) -// -// await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testSetSubscriptionErrorWhenFailedToExchangeToken() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// authService.getAccessTokenResult = .failure(Constants.invalidTokenError) -// -// let onSetSubscriptionCalled = expectation(description: "onSetSubscription") -// onSetSubscriptionCalled.isInverted = true -// feature.onSetSubscription = { -// onSetSubscriptionCalled.fulfill() -// } -// -// // When -// let setSubscriptionParams = ["token": Constants.authToken] -// let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertNil(accountManager.authToken) -// XCTAssertFalse(accountManager.isUserAuthenticated) -// -// await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .failedToSetSubscription) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testSetSubscriptionErrorWhenFailedToFetchAccountDetails() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) -// authService.validateTokenResult = .failure(Constants.invalidTokenError) -// -// let onSetSubscriptionCalled = expectation(description: "onSetSubscription") -// onSetSubscriptionCalled.isInverted = true -// feature.onSetSubscription = { -// onSetSubscriptionCalled.fulfill() -// } -// -// // When -// let setSubscriptionParams = ["token": Constants.authToken] -// let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) -// -// // Then -// XCTAssertNil(accountManager.authToken) -// XCTAssertFalse(accountManager.isUserAuthenticated) -// -// await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) -// XCTAssertNil(result) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, .failedToSetSubscription) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// // MARK: - Tests for activateSubscription -// -// func testActivateSubscriptionTokenSuccess() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// let onActivateSubscriptionCalled = expectation(description: "onActivateSubscription") -// feature.onActivateSubscription = { -// onActivateSubscriptionCalled.fulfill() -// } -// -// // When -// let result = await feature.activateSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// await fulfillment(of: [onActivateSubscriptionCalled], timeout: 0.5) -// XCTAssertNil(result) -// -// await XCTAssertPrivacyPixelsFired([Pixel.Event.privacyProRestorePurchaseOfferPageEntry.name]) -// } -// -// // MARK: - Tests for featureSelected -// -// func testFeatureSelectedSuccess() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// let onFeatureSelectedCalled = expectation(description: "onFeatureSelected") -// feature.onFeatureSelected = { selection in -// onFeatureSelectedCalled.fulfill() -// XCTAssertEqual(selection, SubscriptionFeatureSelection.itr) -// } -// -// // When -// let featureSelectionParams = ["feature": SubscriptionFeatureName.itr] -// let result = await feature.featureSelected(params: featureSelectionParams, original: Constants.mockScriptMessage) -// -// // Then -// await fulfillment(of: [onFeatureSelectedCalled], timeout: 0.5) -// XCTAssertNil(result) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// // MARK: - Tests for backToSettings -// -// func testBackToSettingsSuccess() async throws { -// // Given -// ensureUserAuthenticatedState() -// accountStorage.email = nil -// -// XCTAssertNil(accountManager.email) -// -// let onBackToSettingsCalled = expectation(description: "onBackToSettings") -// feature.onBackToSettings = { -// onBackToSettingsCalled.fulfill() -// } -// -// authService.validateTokenResult = .success(Constants.validateTokenResponse) -// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) -// -// // When -// let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// await fulfillment(of: [onBackToSettingsCalled], timeout: 0.5) -// -// XCTAssertEqual(accountManager.email, Constants.email) -// XCTAssertNil(result) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testBackToSettingsErrorOnFetchingAccountDetails() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// let onBackToSettingsCalled = expectation(description: "onBackToSettings") -// onBackToSettingsCalled.isInverted = true -// feature.onBackToSettings = { -// onBackToSettingsCalled.fulfill() -// } -// -// authService.validateTokenResult = .failure(Constants.invalidTokenError) -// -// // When -// let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// await fulfillment(of: [onBackToSettingsCalled], timeout: 0.5) -// -// XCTAssertEqual(feature.transactionError, .generalError) -// XCTAssertNil(result) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// // MARK: - Tests for getAccessToken -// func testGetAccessTokenSuccess() async throws { -// // Given -// ensureUserAuthenticatedState() -// -// // When -// let result = try await feature.getAccessToken(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let resultDictionary = try XCTUnwrap(result as? [String: String]) -// XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], Constants.accessToken) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testGetAccessTokenEmptyOnMissingToken() async throws { -// // Given -// ensureUserUnauthenticatedState() -// XCTAssertNil(accountManager.accessToken) -// -// // When -// let result = try await feature.getAccessToken(params: Constants.mockParams, original: Constants.mockScriptMessage) -// -// // Then -// let resultDictionary = try XCTUnwrap(result as? [String: String]) -// XCTAssertEqual(resultDictionary, [String: String]()) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// // MARK: - Tests for restoreAccountFromAppStorePurchase -// -// func testRestoreAccountFromAppStorePurchaseSuccess() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// 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) -// -// // When -// try await feature.restoreAccountFromAppStorePurchase() -// -// // Then -// XCTAssertTrue(accountManager.isUserAuthenticated) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// -// func testRestoreAccountFromAppStorePurchaseErrorDueToExpiredSubscription() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// 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.expiredSubscription) -// -// -// do { -// // When -// try await feature.restoreAccountFromAppStorePurchase() -// XCTFail("Unexpected success") -// } catch let error { -// // Then -// guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { -// XCTFail("Unexpected error type") -// return -// } -// -// XCTAssertEqual(error, .subscriptionExpired) -// XCTAssertFalse(accountManager.isUserAuthenticated) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// } -// -// func testRestoreAccountFromAppStorePurchaseErrorDueToNoTransaction() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// storePurchaseManager.mostRecentTransactionResult = nil -// -// do { -// // When -// try await feature.restoreAccountFromAppStorePurchase() -// XCTFail("Unexpected success") -// } catch let error { -// // Then -// guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { -// XCTFail("Unexpected error type") -// return -// } -// -// XCTAssertEqual(error, .subscriptionNotFound) -// XCTAssertFalse(accountManager.isUserAuthenticated) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// } -// -// func testRestoreAccountFromAppStorePurchaseErrorDueToOtherError() async throws { -// // Given -// ensureUserUnauthenticatedState() -// -// storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS -// authService.storeLoginResult = .failure(Constants.invalidTokenError) -// -// do { -// // When -// try await feature.restoreAccountFromAppStorePurchase() -// XCTFail("Unexpected success") -// } catch let error { -// // Then -// guard let error = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError else { -// XCTFail("Unexpected error type") -// return -// } -// -// XCTAssertEqual(error, .failedToRestorePastPurchase) -// XCTAssertFalse(accountManager.isUserAuthenticated) -// -// XCTAssertEqual(feature.transactionStatus, .idle) -// XCTAssertEqual(feature.transactionError, nil) -// -// await XCTAssertPrivacyPixelsFired([]) -// } -// } -// } -// -// extension SubscriptionPagesUseSubscriptionFeatureTests { -// -// func ensureUserAuthenticatedState() { -// accountStorage.authToken = Constants.authToken -// accountStorage.email = Constants.email -// accountStorage.externalID = Constants.externalID -// accessTokenStorage.accessToken = Constants.accessToken -// } -// -// func ensureUserUnauthenticatedState() { -// try? accessTokenStorage.removeAccessToken() -// try? accountStorage.clearAuthenticationState() -// } -// -// public func XCTAssertPrivacyPixelsFired(_ pixels: [String], file: StaticString = #file, line: UInt = #line) async { -// try? await Task.sleep(seconds: 0.1) -// -// let pixelsFired = Set(pixelsFired) -// let expectedPixels = Set(pixels) -// -// // Assert expected pixels were fired -// XCTAssertTrue(expectedPixels.isSubset(of: pixelsFired), -// "Expected Privacy Pro pixels were not fired: \(expectedPixels.subtracting(pixelsFired))", -// file: file, -// line: line) -// -// // Assert no other Privacy Pro pixels were fired except the expected -// let privacyProPixelPrefix = "m_privacy-pro" -// let otherPixels = pixelsFired.subtracting(expectedPixels) -// let otherPrivacyProPixels = otherPixels.filter { $0.hasPrefix(privacyProPixelPrefix) } -// XCTAssertTrue(otherPrivacyProPixels.isEmpty, -// "Unexpected Privacy Pro pixels fired: \(otherPrivacyProPixels)", -// file: file, -// line: line) -// } -// } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 342e85f836..db204b63cc 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -482,10 +482,17 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let storePurchaseManager = DefaultStorePurchaseManager() let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) + let pixelHandler: SubscriptionManager.PixelHandler = { type in + switch type { + case .deadToken: + Pixel.fire(pixel: .privacyProDeadTokenDetected) + } + } let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionEnvironment: subscriptionEnvironment) + subscriptionEnvironment: subscriptionEnvironment, + pixelHandler: pixelHandler) self.subscriptionManager = subscriptionManager let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() From 5f69e541526c25aa2ec2242f53447fad95ac27d7 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 5 Nov 2024 16:17:22 +0000 Subject: [PATCH 18/41] BSK points to branch now --- 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 ed7932e744..62065fb27d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2944,7 +2944,6 @@ F197EA3B1E6885F20029BDC1 /* TextFieldWithInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextFieldWithInsets.swift; path = ../Core/TextFieldWithInsets.swift; sourceTree = ""; }; F198D78D1E39762C0088DA8A /* StringExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = ""; }; F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewConfigurationExtensionTests.swift; sourceTree = ""; }; - F19BD2AE2CBE6F76008DC90A /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F1A886771F29394E0096251E /* WebCacheManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebCacheManager.swift; sourceTree = ""; }; F1AA54601E48D90700223211 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; @@ -4152,7 +4151,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - F19BD2AE2CBE6F76008DC90A /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -10982,8 +10980,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 203.1.0; + branch = fcappelli/subscription_oauth_api_v2; + kind = branch; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2abfc10b2a..2edf99893e 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" : "133b24519ed913f6fbb040e30d24eab524c02724" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 929e12d4f2ee555419dc5059968463f4de2d8666 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 5 Nov 2024 17:59:49 +0000 Subject: [PATCH 19/41] bsk updated --- .../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 2edf99893e..520df2ada0 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" : "133b24519ed913f6fbb040e30d24eab524c02724" + "revision" : "7a1edcc879fc1143235c46a0f67938a4065c21b4" } }, { From b018cced5aa5dbf2b9b8ac44cd0ef9aba2f68db1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 7 Nov 2024 09:02:20 +0000 Subject: [PATCH 20/41] bsk local --- DuckDuckGo.xcodeproj/project.pbxproj | 66 ++++++++++--------- .../xcshareddata/swiftpm/Package.resolved | 13 +--- 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2b578e79b4..1017211e0d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2908,6 +2908,7 @@ F1134ECF1F40EBE200B73467 /* JsonTestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonTestDataLoader.swift; sourceTree = ""; }; F1134ED41F40F15800B73467 /* StatisticsUserDefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticsUserDefaultsTests.swift; sourceTree = ""; }; F114C55A1E66EB020018F95F /* NibLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoading.swift; sourceTree = ""; }; + F12B62C02CDBA648007CD69E /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F130D7391E5776C500C45811 /* OmniBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarDelegate.swift; sourceTree = ""; }; F132D6A42C62239B00D85426 /* SubscriptionSettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSettingsHeaderView.swift; sourceTree = ""; }; F1386BA31E6846C40062FC3C /* TabDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabDelegate.swift; sourceTree = ""; }; @@ -4159,6 +4160,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F12B62C02CDBA648007CD69E /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -9190,8 +9192,8 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9319,8 +9321,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9347,8 +9349,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9520,8 +9522,8 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9622,8 +9624,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9655,8 +9657,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9686,8 +9688,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10033,8 +10035,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10062,8 +10064,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10096,8 +10098,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10126,8 +10128,8 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -10425,8 +10427,8 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10458,8 +10460,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10496,8 +10498,8 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10531,8 +10533,8 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10781,8 +10783,8 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 520df2ada0..6e0378bdd2 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" : { - "branch" : "fcappelli/subscription_oauth_api_v2", - "revision" : "7a1edcc879fc1143235c46a0f67938a4065c21b4" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "48fee2508995d4ac02d18b3d55424adedcb4ce4f", - "version" : "6.28.0" + "revision" : "6cab7bdb584653a5dc007cc1ae827ec41c5a91bc", + "version" : "6.29.0" } }, { From a955a788e72337498b863346fbf078ad5a272d84 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 7 Nov 2024 11:51:33 +0000 Subject: [PATCH 21/41] BSK --- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6e0378bdd2..e6d9889c30 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" : "da30fc99ede40c13b2a730d7b52fd0639a61652a" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 47ac372e273ccb68806309457f8cdaf92fa1a7b5 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 8 Nov 2024 09:15:23 +0000 Subject: [PATCH 22/41] BSK update --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1017211e0d..50a625938b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2908,7 +2908,6 @@ F1134ECF1F40EBE200B73467 /* JsonTestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonTestDataLoader.swift; sourceTree = ""; }; F1134ED41F40F15800B73467 /* StatisticsUserDefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticsUserDefaultsTests.swift; sourceTree = ""; }; F114C55A1E66EB020018F95F /* NibLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoading.swift; sourceTree = ""; }; - F12B62C02CDBA648007CD69E /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F130D7391E5776C500C45811 /* OmniBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarDelegate.swift; sourceTree = ""; }; F132D6A42C62239B00D85426 /* SubscriptionSettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSettingsHeaderView.swift; sourceTree = ""; }; F1386BA31E6846C40062FC3C /* TabDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabDelegate.swift; sourceTree = ""; }; @@ -4160,7 +4159,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - F12B62C02CDBA648007CD69E /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e6d9889c30..904e8cbd7c 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" : "da30fc99ede40c13b2a730d7b52fd0639a61652a" + "revision" : "99ac675527ed541a8124ed6620d3f1c32345daa3" } }, { From bd51e3f1d12d1f401fd69f3168c708bec0c1f45a Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 19 Nov 2024 10:35:38 +0000 Subject: [PATCH 23/41] DI improved --- DuckDuckGo.xcodeproj/project.pbxproj | 2 ++ .../xcshareddata/swiftpm/Package.resolved | 9 --------- DuckDuckGo/AppDependencyProvider.swift | 14 -------------- DuckDuckGo/Feedback/VPNMetadataCollector.swift | 16 +++++----------- .../IdentityTheftRestorationPagesFeature.swift | 8 +++++--- 5 files changed, 12 insertions(+), 37 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 446b113a15..6a8c5bcdb1 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2955,6 +2955,7 @@ F17D723B1E8BB374003E8B0E /* AppDeepLinkSchemes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDeepLinkSchemes.swift; sourceTree = ""; }; F189AED61F18F6DE001EBAE1 /* TabTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabTests.swift; sourceTree = ""; }; F189AEE31F18FDAF001EBAE1 /* LinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkTests.swift; sourceTree = ""; }; + F18D697F2CECA0EB00E54F38 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F194FAEC1F14E2B3009B4DF8 /* UIFontExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFontExtension.swift; sourceTree = ""; }; F194FAFA1F14E622009B4DF8 /* UIFontExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFontExtensionTests.swift; sourceTree = ""; }; F197EA3B1E6885F20029BDC1 /* TextFieldWithInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextFieldWithInsets.swift; path = ../Core/TextFieldWithInsets.swift; sourceTree = ""; }; @@ -4161,6 +4162,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F18D697F2CECA0EB00E54F38 /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c3ff5c3a9e..b5044f8e16 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" : "aab97954107399096a9bbe64b6aff8eb49bc648b" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index e4413da215..3788a567b3 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -43,7 +43,6 @@ protocol DependencyProvider { var configurationManager: ConfigurationManager { get } var configurationStore: ConfigurationStore { get } var subscriptionManager: any SubscriptionManager { get } - var privacyProInfoProvider: any PrivacyProInfoProvider { get } var pageRefreshMonitor: PageRefreshMonitor { get } var vpnFeatureVisibility: DefaultNetworkProtectionVisibility { get } var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore { get } @@ -80,7 +79,6 @@ final class AppDependencyProvider: DependencyProvider { // Subscription let subscriptionManager: SubscriptionManager - let privacyProInfoProvider: any PrivacyProInfoProvider let vpnFeatureVisibility: DefaultNetworkProtectionVisibility let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore let networkProtectionTunnelController: NetworkProtectionTunnelController @@ -123,8 +121,6 @@ final class AppDependencyProvider: DependencyProvider { legacyTokenStorage: legacyAccountStorage, authService: authService) - self.privacyProInfoProvider = authClient - apiService.authorizationRefresherCallback = { _ in guard let tokenContainer = tokenStorage.tokenContainer else { throw OAuthClientError.internalError("Missing refresh token") @@ -170,13 +166,3 @@ final class AppDependencyProvider: DependencyProvider { oAuthClient: authClient) } } - -extension DefaultOAuthClient: PrivacyProInfoProvider { - - var hasVPNEntitlements: Bool { - guard let tokenContainer = tokenStorage.tokenContainer else { - return false - } - return tokenContainer.decodedAccessToken.hasEntitlement(.networkProtection) - } -} diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index ef86c45043..a3c74f2803 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -99,26 +99,21 @@ protocol VPNMetadataCollector { func collectVPNMetadata() async -> VPNMetadata } -protocol PrivacyProInfoProvider { - var isUserAuthenticated: Bool { get } - var hasVPNEntitlements: Bool { get } -} - final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver - private let privacyProInfoProvider: PrivacyProInfoProvider + private let subscriptionManager: any SubscriptionManager private let settings: VPNSettings private let defaults: UserDefaults init(statusObserver: ConnectionStatusObserver, serverInfoObserver: ConnectionServerInfoObserver,// ConnectionServerInfoObserverThroughSession(), - privacyProInfoProvider: PrivacyProInfoProvider = AppDependencyProvider.shared.privacyProInfoProvider, + subscriptionManager: any SubscriptionManager = AppDependencyProvider.shared.subscriptionManager, settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { self.statusObserver = statusObserver self.serverInfoObserver = serverInfoObserver - self.privacyProInfoProvider = privacyProInfoProvider + self.subscriptionManager = subscriptionManager self.settings = settings self.defaults = defaults } @@ -247,10 +242,9 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { -// let hasVPNEntitlement = (try? await accountManager.hasEntitlement(forProductName: .networkProtection).get()) ?? false return .init( - hasPrivacyProAccount: privacyProInfoProvider.isUserAuthenticated, - hasVPNEntitlement: privacyProInfoProvider.hasVPNEntitlements + hasPrivacyProAccount: subscriptionManager.isUserAuthenticated, + hasVPNEntitlement: subscriptionManager.isEntitlementActive(.networkProtection) ) } diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index 151c536ba4..903b8e7c68 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -71,9 +71,11 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .localValid) { - return [Constants.token: tokenContainer.accessToken] - } else { + do { + let accessToken = try await subscriptionManager.getTokenContainer(policy: .localValid).accessToken + return [Constants.token: accessToken] + } catch { + Logger.subscription.debug("No access token available: \(error)") return [String: String]() } } From 67d5d50458e75ac7e4ecc3577f1daed738031de8 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 22 Nov 2024 11:21:38 +0000 Subject: [PATCH 24/41] token v1/v2 exchange fixed, token providing improved --- DuckDuckGo.xcodeproj/project.pbxproj | 2 ++ .../xcshareddata/swiftpm/Package.resolved | 9 --------- DuckDuckGo/AppDependencyProvider.swift | 11 +---------- .../NetworkProtectionConvenienceInitialisers.swift | 2 +- .../NetworkProtectionDebugViewController.swift | 5 +---- DuckDuckGo/NetworkProtectionTunnelController.swift | 12 ++++++------ .../SubscriptionPagesUseSubscriptionFeature.swift | 2 +- .../NetworkProtectionPacketTunnelProvider.swift | 12 +----------- 8 files changed, 13 insertions(+), 42 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d8a9e0eb33..aa11c1e978 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2968,6 +2968,7 @@ F17D723B1E8BB374003E8B0E /* AppDeepLinkSchemes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDeepLinkSchemes.swift; sourceTree = ""; }; F189AED61F18F6DE001EBAE1 /* TabTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabTests.swift; sourceTree = ""; }; F189AEE31F18FDAF001EBAE1 /* LinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkTests.swift; sourceTree = ""; }; + F18D69802CEE5A1300E54F38 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F194FAEC1F14E2B3009B4DF8 /* UIFontExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFontExtension.swift; sourceTree = ""; }; F194FAFA1F14E622009B4DF8 /* UIFontExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFontExtensionTests.swift; sourceTree = ""; }; F197EA3B1E6885F20029BDC1 /* TextFieldWithInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextFieldWithInsets.swift; path = ../Core/TextFieldWithInsets.swift; sourceTree = ""; }; @@ -4175,6 +4176,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F18D69802CEE5A1300E54F38 /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 41230b49a5..88028c3fb2 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" : "13547dc2718fa2bb7d6e10c926b0ac273b110c17" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 3788a567b3..1156c7ac11 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -45,7 +45,6 @@ protocol DependencyProvider { var subscriptionManager: any SubscriptionManager { get } var pageRefreshMonitor: PageRefreshMonitor { get } var vpnFeatureVisibility: DefaultNetworkProtectionVisibility { get } - var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore { get } var networkProtectionTunnelController: NetworkProtectionTunnelController { get } var connectionObserver: ConnectionStatusObserver { get } var serverInfoObserver: ConnectionServerInfoObserver { get } @@ -80,7 +79,6 @@ final class AppDependencyProvider: DependencyProvider { // Subscription let subscriptionManager: SubscriptionManager let vpnFeatureVisibility: DefaultNetworkProtectionVisibility - let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore let networkProtectionTunnelController: NetworkProtectionTunnelController let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) @@ -151,14 +149,7 @@ final class AppDependencyProvider: DependencyProvider { subscriptionEnvironment: subscriptionEnvironment, pixelHandler: pixelHandler) self.subscriptionManager = subscriptionManager - networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: { - guard let token = subscriptionManager.getTokenContainerSynchronously(policy: .localValid)?.accessToken else { - Logger.networkProtection.error("NetworkProtectionKeychainTokenStore failed to provide token") - return nil - } - return token - }) - networkProtectionTunnelController = NetworkProtectionTunnelController(tokenStore: networkProtectionKeychainTokenStore, + networkProtectionTunnelController = NetworkProtectionTunnelController(tokenProvider: subscriptionManager, featureFlagger: featureFlagger, persistentPixel: persistentPixel, settings: vpnSettings) diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index ea3d15fd5e..5362a5290f 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -71,7 +71,7 @@ extension NetworkProtectionLocationListCompositeRepository { let settings = AppDependencyProvider.shared.vpnSettings self.init( environment: settings.selectedEnvironment, - tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore, + tokenProvider: AppDependencyProvider.shared.subscriptionManager, errorEvents: .networkProtectionAppDebugEvents ) } diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 7b95b991b8..8caa577764 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -107,7 +107,6 @@ final class NetworkProtectionDebugViewController: UITableViewController { // MARK: Properties private let debugFeatures: NetworkProtectionDebugFeatures - private let tokenStore: NetworkProtectionTokenStore private let pathMonitor = NWPathMonitor() private var currentNetworkPath: String? @@ -129,17 +128,15 @@ final class NetworkProtectionDebugViewController: UITableViewController { // MARK: Lifecycle required init?(coder: NSCoder, - tokenStore: NetworkProtectionTokenStore, debugFeatures: NetworkProtectionDebugFeatures = NetworkProtectionDebugFeatures()) { self.debugFeatures = debugFeatures - self.tokenStore = tokenStore super.init(coder: coder) } required convenience init?(coder: NSCoder) { - self.init(coder: coder, tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) + self.init(coder: coder, debugFeatures: NetworkProtectionDebugFeatures()) } override func viewWillAppear(_ animated: Bool) { diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index b93f7606ef..172dcc00a7 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -38,7 +38,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr private let featureFlagger: FeatureFlagger private var internalManager: NETunnelProviderManager? private let debugFeatures = NetworkProtectionDebugFeatures() - private let tokenStore: NetworkProtectionKeychainTokenStore + private let tokenProvider: any SubscriptionTokenProvider private let errorStore = NetworkProtectionTunnelErrorStore() private let snoozeTimingStore = NetworkProtectionSnoozeTimingStore(userDefaults: .networkProtectionGroupDefaults) private let notificationCenter: NotificationCenter = .default @@ -130,7 +130,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr // MARK: - Initializers - init(tokenStore: NetworkProtectionKeychainTokenStore, + init(tokenProvider: any SubscriptionTokenProvider, featureFlagger: FeatureFlagger, persistentPixel: PersistentPixelFiring, settings: VPNSettings) { @@ -138,7 +138,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr self.featureFlagger = featureFlagger self.persistentPixel = persistentPixel self.settings = settings - self.tokenStore = tokenStore + self.tokenProvider = tokenProvider subscribeToSnoozeTimingChanges() subscribeToStatusChanges() @@ -267,7 +267,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr // Intentional no-op break default: - try start(tunnelManager) + try await start(tunnelManager) } } @@ -275,7 +275,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr internalManager = nil } - private func start(_ tunnelManager: NETunnelProviderManager) throws { + private func start(_ tunnelManager: NETunnelProviderManager) async throws { var options = [String: NSObject]() if Self.shouldSimulateFailure { @@ -285,7 +285,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr options["activationAttemptId"] = UUID().uuidString as NSString - if let token = tokenStore.fetchToken() as NSString? { + if let token = try await tokenProvider.getTokenContainer(policy: .localValid).accessToken as NSString? { options["authToken"] = token } else { throw StartError.fetchAuthTokenFailed diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index bf24037b9c..fb10d6c6c9 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -140,8 +140,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } 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 diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 15d7a27d3f..256a23d112 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -45,7 +45,6 @@ extension SubscriptionSomething { } } - // Initial implementation for initial Network Protection tests. Will be fleshed out with https://app.asana.com/0/1203137811378537/1204630829332227/f final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { @@ -53,7 +52,6 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { private static let persistentPixel: PersistentPixelFiring = PersistentPixel() private var cancellables = Set() private let subscriptionManager: any SubscriptionManager - private let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager @@ -495,21 +493,13 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { ) notificationsPresenter.requestAuthorization() - self.networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: { - guard let token = subscriptionManager.getTokenContainerSynchronously(policy: .localValid)?.accessToken else { - Logger.networkProtection.error("NetworkProtectionKeychainTokenStore failed to provide token") - return nil - } - return token - }) - super.init(notificationsPresenter: notificationsPresenterDecorator, tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: errorStore, snoozeTimingStore: NetworkProtectionSnoozeTimingStore(userDefaults: .networkProtectionGroupDefaults), wireGuardInterface: DefaultWireGuardInterface(), keychainType: .dataProtection(.unspecified), - tokenStore: self.networkProtectionKeychainTokenStore, + tokenProvider: subscriptionManager, debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), providerEvents: Self.packetTunnelProviderEvents, settings: settings, From d0c834428eaf08aa021f82918d4c0083b9904b8d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 27 Nov 2024 12:03:25 +0000 Subject: [PATCH 25/41] updates after mac integration --- .../xcshareddata/swiftpm/Package.resolved | 8 +++---- ...etworkProtectionPacketTunnelProvider.swift | 23 +++++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 88028c3fb2..bce5c24f7d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "32c3e2bce3cc20a079d96b3ee23a9cde5d2337c3", - "version" : "6.35.0" + "revision" : "dfef00ef77f5181d1d8a4f7cc88f7b7c0514dd34", + "version" : "6.39.0" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "53fd1a0f8d91fcf475d9220f810141007300dffd", - "version" : "7.1.1" + "revision" : "757bbbae1e2afbb421caee9bfca04ee5c56c3af8", + "version" : "7.2.0" } }, { diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 256a23d112..1eca99f6d7 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -493,6 +493,18 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { ) notificationsPresenter.requestAuthorization() + 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) + } + } + super.init(notificationsPresenter: notificationsPresenterDecorator, tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: errorStore, @@ -504,11 +516,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { providerEvents: Self.packetTunnelProviderEvents, settings: settings, defaults: .networkProtectionGroupDefaults, - entitlementCheck: { - let hasEntitlement = subscriptionManager.entitlements.contains(.networkProtection) - return .success(hasEntitlement) - } - ) + entitlementCheck: entitlementsCheck) startMonitoringMemoryPressureEvents() observeServerChanges() @@ -561,11 +569,6 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { activationDateStore.updateLastActiveDate() WidgetCenter.shared.reloadTimelines(ofKind: "VPNStatusWidget") } - - private func entitlementCheck() async -> Result { - let hasEntitlement = self.subscriptionManager.entitlements.contains(.networkProtection) - return .success(hasEntitlement) - } } final class DefaultWireGuardInterface: WireGuardInterface { From 170ed9e738716c953f891cfa12e7b76701ba769d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 27 Nov 2024 12:37:00 +0000 Subject: [PATCH 26/41] merge --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/AppDelegate.swift | 6 ++---- DuckDuckGo/NetworkProtectionStatusView.swift | 2 +- DuckDuckGo/SettingsOthersView.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 2 +- .../Feedback/UnifiedFeedbackFormViewModel.swift | 8 ++++---- .../Subscription/Views/SubscriptionSettingsView.swift | 2 +- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bce5c24f7d..3368c8cc29 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -158,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "06dc63c6d8da54ee11ceb268cde1fa68161afc96", - "version" : "3.9.1" + "revision" : "ff0f781cf7c6a22d52957e50b104f5768b50c779", + "version" : "3.10.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 0190ec2148..2910a05d20 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -1086,8 +1086,7 @@ import os.log return } - let entitlements = AppDependencyProvider.shared.subscriptionManager.entitlements - if entitlements.contains(.networkProtection) { + if AppDependencyProvider.shared.subscriptionManager.isEntitlementActive(.networkProtection) { let items = [ UIApplicationShortcutItem(type: ShortcutKey.openVPNSettings, localizedTitle: UserText.netPOpenVPNQuickAction, @@ -1165,8 +1164,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } func presentNetworkProtectionStatusSettingsModal() { - let entitlements = AppDependencyProvider.shared.subscriptionManager.entitlements - if entitlements.contains(.networkProtection) { + if AppDependencyProvider.shared.subscriptionManager.isEntitlementActive(.networkProtection) { (window?.rootViewController as? MainViewController)?.segueToVPN() } else { (window?.rootViewController as? MainViewController)?.segueToPrivacyPro() diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 5e03d70ffe..284d69cb7d 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -310,7 +310,7 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func about() -> some View { - let viewModel = UnifiedFeedbackFormViewModel(accountManager: AppDependencyProvider.shared.accountManager, + let viewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: AppDependencyProvider.shared.subscriptionManager, apiService: DefaultAPIService(), vpnMetadataCollector: DefaultVPNMetadataCollector(), source: .vpn) diff --git a/DuckDuckGo/SettingsOthersView.swift b/DuckDuckGo/SettingsOthersView.swift index bcb4d39b34..b85b666ac4 100644 --- a/DuckDuckGo/SettingsOthersView.swift +++ b/DuckDuckGo/SettingsOthersView.swift @@ -35,7 +35,7 @@ struct SettingsOthersView: View { // Share Feedback if viewModel.usesUnifiedFeedbackForm { - let formViewModel = UnifiedFeedbackFormViewModel(accountManager: AppDependencyProvider.shared.accountManager, + let formViewModel = UnifiedFeedbackFormViewModel(subscriptionTokenProvider: AppDependencyProvider.shared.subscriptionManager, apiService: DefaultAPIService(), vpnMetadataCollector: DefaultVPNMetadataCollector(), source: .settings) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 8dca9692eb..e6fca201df 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -740,7 +740,7 @@ extension SettingsViewModel { state.subscription.subscriptionExist = true state.subscription.platform = subscription.platform state.subscription.hasActiveSubscription = subscription.isActive - state.subscription.entitlements = subscriptionManager.entitlements + state.subscription.entitlements = subscriptionManager.currentEntitlements } catch SubscriptionEndpointServiceError.noData, OAuthClientError.missingTokens { // Auth successful but no Subscription is available Logger.subscription.log("Subscription not present") diff --git a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift index ea36971224..a00095e8c3 100644 --- a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift @@ -135,7 +135,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { } } - 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 @@ -143,7 +143,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { let source: String - init(accountManager: any AccountManager, + init(subscriptionTokenProvider: any SubscriptionTokenProvider, apiService: any Networking.APIService, vpnMetadataCollector: any UnifiedMetadataCollector, defaultMetadatCollector: any UnifiedMetadataCollector = DefaultMetadataCollector(), @@ -151,7 +151,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { source: Source = .unknown) { self.viewState = .feedbackPending - self.accountManager = accountManager + self.subscriptionTokenProvider = subscriptionTokenProvider self.apiService = apiService self.vpnMetadataCollector = vpnMetadataCollector self.defaultMetadataCollector = defaultMetadatCollector @@ -269,7 +269,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { private func submitIssue(metadata: UnifiedFeedbackMetadata?) async throws { guard !userEmail.isEmpty, let selectedCategory else { return } - guard let accessToken = accountManager.accessToken else { + guard let accessToken = try? await subscriptionTokenProvider.getTokenContainer(policy: .localValid) else { throw Error.missingAccessToken } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index a06e664b76..ac387b0cdd 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -245,7 +245,7 @@ struct SubscriptionSettingsView: View { @ViewBuilder private var supportButton: some View { let viewModel = UnifiedFeedbackFormViewModel( - accountManager: AppDependencyProvider.shared.accountManager, + subscriptionTokenProvider: AppDependencyProvider.shared.subscriptionManager, apiService: DefaultAPIService(), vpnMetadataCollector: DefaultVPNMetadataCollector(), source: .ppro From 87455ba232b9b2b1f61d6ea32e4332f59e277201 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 27 Nov 2024 12:39:23 +0000 Subject: [PATCH 27/41] local bsk --- DuckDuckGo.xcodeproj/project.pbxproj | 17 ++++----- .../xcshareddata/swiftpm/Package.resolved | 36 +++++-------------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fcb5896dd8..ef18341803 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -365,9 +365,9 @@ 6FEC0B852C999352006B4F6E /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */; }; 6FEC0B882C999961006B4F6E /* FavoritesListInteractingAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; - 6FF9AD452CE766F700C5A406 /* NewTabPageControllerPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */; }; 6FF9AD3F2CE63DD800C5A406 /* TabSwitcherOpenDailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD3E2CE63DC200C5A406 /* TabSwitcherOpenDailyPixel.swift */; }; 6FF9AD412CE6610F00C5A406 /* TabSwitcherDailyPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD402CE6610600C5A406 /* TabSwitcherDailyPixelTests.swift */; }; + 6FF9AD452CE766F700C5A406 /* NewTabPageControllerPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */; }; 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */; }; 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */; }; 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */; }; @@ -470,7 +470,6 @@ 853A717820F645FB00FE60BC /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853A717720F645FB00FE60BC /* PixelTests.swift */; }; 853C5F6121C277C7001F7A05 /* global.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853C5F6021C277C7001F7A05 /* global.swift */; }; 8540BBA22440857A00017FE4 /* FireproofingWorking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BBA12440857A00017FE4 /* FireproofingWorking.swift */; }; - 8540BD5223D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift */; }; 8540BD5423D8D5080057FDD2 /* FireproofingAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5323D8D5080057FDD2 /* FireproofingAlert.swift */; }; 8540BD5623D9E9C20057FDD2 /* FireproofingSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5523D9E9C20057FDD2 /* FireproofingSettingsViewController.swift */; }; 85449EF523FDA02800512AAF /* KeyboardSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85449EF423FDA02800512AAF /* KeyboardSettingsViewController.swift */; }; @@ -651,7 +650,7 @@ 98424AAD2CED4FF10071C7DB /* WKWebViewConfigurationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */; }; 98424AAE2CED4FF10071C7DB /* WebCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850559D123CF710C0055C0D5 /* WebCacheManagerTests.swift */; }; 98424AAF2CED4FF10071C7DB /* UserAgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DF990248FDDF60075EA48 /* UserAgentTests.swift */; }; - 98424AB02CED4FF10071C7DB /* PreserveLoginsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */; }; + 98424AB02CED4FF10071C7DB /* UserDefaultsFireproofingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift */; }; 98424AB22CEDD6150071C7DB /* BrowserServicesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 98424AB12CEDD6150071C7DB /* BrowserServicesKit */; }; 98424AB42CEDD61C0071C7DB /* BrowserServicesKitTestsUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 98424AB32CEDD61C0071C7DB /* BrowserServicesKitTestsUtils */; }; 9847C00027A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9847BFFF27A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift */; }; @@ -1698,9 +1697,9 @@ 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = ""; }; 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesListInteractingAdapter.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; - 6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerPixelTests.swift; sourceTree = ""; }; 6FF9AD3E2CE63DC200C5A406 /* TabSwitcherOpenDailyPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherOpenDailyPixel.swift; sourceTree = ""; }; 6FF9AD402CE6610600C5A406 /* TabSwitcherDailyPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherDailyPixelTests.swift; sourceTree = ""; }; + 6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerPixelTests.swift; sourceTree = ""; }; 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = ""; }; 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; @@ -2025,7 +2024,6 @@ 983C52E32C2C050B007B5747 /* BookmarksStateRepair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksStateRepair.swift; sourceTree = ""; }; 983C52E52C2C0ABA007B5747 /* BookmarkStateRepairTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStateRepairTests.swift; sourceTree = ""; }; 983D71B02A286E810072E26D /* SyncDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncDebugViewController.swift; sourceTree = ""; }; - 983D88D52CED006000E99B46 /* UnitTests copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "UnitTests copy-Info.plist"; path = "/Users/bartek/Develop/iOS/UnitTests copy-Info.plist"; sourceTree = ""; }; 983E1349251EABF200149BD9 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = ""; }; 983E134A251EABF200149BD9 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = ""; }; 983E134C251EABF200149BD9 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2040,7 +2038,6 @@ 984147C424F026C800362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/HomeRow.storyboard; sourceTree = ""; }; 984147CA24F02E9E00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DaxOnboarding.storyboard; sourceTree = ""; }; 98424AA92CED4F430071C7DB /* WebViewUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebViewUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 98424AAA2CED4F430071C7DB /* UnitTests copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "UnitTests copy-Info.plist"; path = "/Users/bartek/Develop/iOS/UnitTests copy-Info.plist"; sourceTree = ""; }; 9846AA6622BD3BBF007DE48E /* InitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitHelpers.swift; sourceTree = ""; }; 9847BFFD27A2DDB400DB07AA /* ContentBlocking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlocking.swift; sourceTree = ""; }; 9847BFFF27A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppPrivacyConfigurationDataProvider.swift; sourceTree = ""; }; @@ -3029,6 +3026,7 @@ F1D477C51F2126CC0031ED49 /* OmniBarState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarState.swift; sourceTree = ""; }; F1D477C81F2139410031ED49 /* SmallOmniBarStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SmallOmniBarStateTests.swift; sourceTree = ""; }; F1D477CA1F2149C40031ED49 /* Type.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Type.swift; sourceTree = ""; }; + F1D5FEC12CF74A6600E00C3A /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F1D796EB1E7AB8930019D451 /* SaveBookmarkActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveBookmarkActivity.swift; sourceTree = ""; }; F1D796EF1E7B07610019D451 /* BookmarksViewControllerCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksViewControllerCells.swift; sourceTree = ""; }; F1D796F31E7C2A410019D451 /* BookmarksDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksDelegate.swift; sourceTree = ""; }; @@ -4235,6 +4233,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F1D5FEC12CF74A6600E00C3A /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -4258,8 +4257,6 @@ 83ED3B8D1FA8E63700B47556 /* README.md */, 83ED3B8C1FA8E61D00B47556 /* ManualTestsScript.md */, 85A313962028E78A00327D00 /* release_notes.txt */, - 983D88D52CED006000E99B46 /* UnitTests copy-Info.plist */, - 98424AAA2CED4F430071C7DB /* UnitTests copy-Info.plist */, ); sourceTree = ""; }; @@ -4785,7 +4782,7 @@ isa = PBXGroup; children = ( 834DF990248FDDF60075EA48 /* UserAgentTests.swift */, - 8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */, + 8540BD5123D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift */, 850559D123CF710C0055C0D5 /* WebCacheManagerTests.swift */, 981C49AF2C8FA61D00DF11E8 /* DataStoreIdManagerTests.swift */, F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */, @@ -8390,7 +8387,7 @@ 98424AAD2CED4FF10071C7DB /* WKWebViewConfigurationExtensionTests.swift in Sources */, 98424AAE2CED4FF10071C7DB /* WebCacheManagerTests.swift in Sources */, 98424AAF2CED4FF10071C7DB /* UserAgentTests.swift in Sources */, - 98424AB02CED4FF10071C7DB /* PreserveLoginsTests.swift in Sources */, + 98424AB02CED4FF10071C7DB /* UserDefaultsFireproofingTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3368c8cc29..e72e1ceabf 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" : "f83b1f5ebd328bc2447d1a3793149bb21037d685", + "version" : "211.1.3" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -81,15 +90,6 @@ "version" : "2.0.0" } }, - { - "identity" : "jwt-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/jwt-kit.git", - "state" : { - "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", - "version" : "4.13.4" - } - }, { "identity" : "kingfisher", "kind" : "remoteSourceControl", @@ -144,24 +144,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" : "ff0f781cf7c6a22d52957e50b104f5768b50c779", - "version" : "3.10.0" - } - }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", From 0191b026c301d4d138b78c33d0c0f0d1ecc72790 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 27 Nov 2024 13:52:11 +0000 Subject: [PATCH 28/41] project fix --- DuckDuckGo.xcodeproj/project.pbxproj | 34 ++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 36 ++++++++++++++----- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ef18341803..5687e836ec 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -73,7 +73,6 @@ 1E4F4A5A297193DE00625985 /* MainViewController+CookiesManaged.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4F4A59297193DE00625985 /* MainViewController+CookiesManaged.swift */; }; 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4FAA6327D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift */; }; 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4FAA6527D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift */; }; - 1E53508F2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E53508E2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift */; }; 1E5918472CA422A7008ED2B3 /* Navigation in Frameworks */ = {isa = PBXBuildFile; productRef = 1E5918462CA422A7008ED2B3 /* Navigation */; }; 1E60989B290009C700A508F9 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 1E7060BD28F88EE200E4CCDB /* Common */; }; 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 1E60989C290011E600A508F9 /* ContentBlocking */; }; @@ -870,7 +869,6 @@ B6BA95C528894A28004ABA20 /* BrowsingMenuViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */; }; B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95E728924730004ABA20 /* JSAlertController.storyboard */; }; BBFF18B12C76448100C48D7D /* QuerySubmittedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFF18B02C76448100C48D7D /* QuerySubmittedTests.swift */; }; - BD10B8AA2C7629740033115D /* Logger+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD10B8A92C7629740033115D /* Logger+Subscription.swift */; }; BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD15DB842B959CFD00821457 /* BundleExtension.swift */; }; BD2F39EB2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */; }; BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */; }; @@ -890,7 +888,6 @@ BDE91CE02C6515420005CB74 /* UnifiedFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CDF2C6515410005CB74 /* UnifiedFeedbackFormViewModel.swift */; }; BDF8D0022C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */; }; BDFF031D2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */; }; - BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */; }; BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; }; BDFF03232BA3D8E300F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; }; BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */; }; @@ -1165,6 +1162,8 @@ F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D477C51F2126CC0031ED49 /* OmniBarState.swift */; }; F1D477C91F2139410031ED49 /* SmallOmniBarStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D477C81F2139410031ED49 /* SmallOmniBarStateTests.swift */; }; F1D477CB1F2149C40031ED49 /* Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D477CA1F2149C40031ED49 /* Type.swift */; }; + F1D5FEC32CF74B6800E00C3A /* Logger+Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D5FEC22CF74B6800E00C3A /* Logger+Pixel.swift */; }; + F1D5FEC52CF75B4900E00C3A /* TokenBackgroundRefreshTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D5FEC42CF75B4900E00C3A /* TokenBackgroundRefreshTask.swift */; }; F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D796EB1E7AB8930019D451 /* SaveBookmarkActivity.swift */; }; F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F143C32C1E4A9A4800CFDE3A /* UIViewControllerExtension.swift */; }; F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D796EF1E7B07610019D451 /* BookmarksViewControllerCells.swift */; }; @@ -1424,7 +1423,6 @@ 1E4F4A59297193DE00625985 /* MainViewController+CookiesManaged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+CookiesManaged.swift"; sourceTree = ""; }; 1E4FAA6327D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingDownloadRowViewModel.swift; sourceTree = ""; }; 1E4FAA6527D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteDownloadRowViewModel.swift; sourceTree = ""; }; - 1E53508E2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift"; sourceTree = ""; }; 1E6A4D682984208800A371D3 /* LocaleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocaleExtension.swift; sourceTree = ""; }; 1E7A71162934EB6400B7EA19 /* OmniBarNotificationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarNotificationAnimator.swift; sourceTree = ""; }; 1E7A71182934EC6100B7EA19 /* OmniBarNotificationContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarNotificationContainerView.swift; sourceTree = ""; }; @@ -2694,7 +2692,6 @@ B6DFE6CF2BC7E47500A9CE59 /* SwiftLintTool.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftLintTool.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftLintToolBundleConfiguration.xcconfig; sourceTree = ""; }; BBFF18B02C76448100C48D7D /* QuerySubmittedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuerySubmittedTests.swift; sourceTree = ""; }; - BD10B8A92C7629740033115D /* Logger+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Logger+Subscription.swift"; sourceTree = ""; }; BD15DB842B959CFD00821457 /* BundleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtension.swift; sourceTree = ""; }; BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsView.swift; sourceTree = ""; }; BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; @@ -2714,7 +2711,6 @@ BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsViewModel.swift; sourceTree = ""; }; BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = ""; }; BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNetworkProtectionVisibility.swift; sourceTree = ""; }; - BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVisibilityForTunnelProvider.swift; sourceTree = ""; }; BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibilityTests.swift; sourceTree = ""; }; C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillViews.swift; sourceTree = ""; }; C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOrFolderTests.swift; sourceTree = ""; }; @@ -3027,6 +3023,8 @@ F1D477C81F2139410031ED49 /* SmallOmniBarStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SmallOmniBarStateTests.swift; sourceTree = ""; }; F1D477CA1F2149C40031ED49 /* Type.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Type.swift; sourceTree = ""; }; F1D5FEC12CF74A6600E00C3A /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; + F1D5FEC22CF74B6800E00C3A /* Logger+Pixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Pixel.swift"; sourceTree = ""; }; + F1D5FEC42CF75B4900E00C3A /* TokenBackgroundRefreshTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenBackgroundRefreshTask.swift; sourceTree = ""; }; F1D796EB1E7AB8930019D451 /* SaveBookmarkActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveBookmarkActivity.swift; sourceTree = ""; }; F1D796EF1E7B07610019D451 /* BookmarksViewControllerCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksViewControllerCells.swift; sourceTree = ""; }; F1D796F31E7C2A410019D451 /* BookmarksDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksDelegate.swift; sourceTree = ""; }; @@ -5194,7 +5192,6 @@ children = ( BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */, BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */, - BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */, ); name = "Feature Visibility"; sourceTree = ""; @@ -5467,18 +5464,18 @@ D664C7922B289AA000CBFA76 /* Subscription */ = { isa = PBXGroup; children = ( - BDE91CD42C6292BF0005CB74 /* Feedback */, - F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */, - D60170BB2BA32DD6001911B5 /* Subscription.swift */, - 1E53508E2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift */, D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */, - D664C7932B289AA000CBFA76 /* ViewModel */, - D664C7AC2B289AA000CBFA76 /* Views */, - D664C7B02B289AA000CBFA76 /* UserScripts */, D664C7962B289AA000CBFA76 /* Extensions */, - D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */, + BDE91CD42C6292BF0005CB74 /* Feedback */, BDE219E52C406D19005D5884 /* PrivacyProDataReporting.swift */, + D60170BB2BA32DD6001911B5 /* Subscription.swift */, + D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */, 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */, + F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */, + F1D5FEC42CF75B4900E00C3A /* TokenBackgroundRefreshTask.swift */, + D664C7B02B289AA000CBFA76 /* UserScripts */, + D664C7932B289AA000CBFA76 /* ViewModel */, + D664C7AC2B289AA000CBFA76 /* Views */, ); path = Subscription; sourceTree = ""; @@ -5501,7 +5498,6 @@ D664C7962B289AA000CBFA76 /* Extensions */ = { isa = PBXGroup; children = ( - BD10B8A92C7629740033115D /* Logger+Subscription.swift */, F1FDC9342BF51E41006B1435 /* VPNSettings+Environment.swift */, D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, ); @@ -6040,6 +6036,7 @@ F143C3191E4A99DD00CFDE3A /* Utilities */ = { isa = PBXGroup; children = ( + F1D5FEC22CF74B6800E00C3A /* Logger+Pixel.swift */, 6F98573F2CD2933B001BE9A0 /* StorageInconsistencyMonitor.swift */, 6F9857332CD27F98001BE9A0 /* BoolFileMarker.swift */, 6F395BB92CD2C84300B92FC3 /* BoolFileMarkerTests.swift */, @@ -7470,7 +7467,6 @@ 0214407A2C7FB28F00426724 /* VPNAgentConfigurationURLProvider.swift in Sources */, EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, F1FDC9362BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */, - BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */, EEFC6A602AC0F2F80065027D /* UserText.swift in Sources */, 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */, ); @@ -7677,6 +7673,7 @@ 986DA94A24884B18004A7E39 /* WebViewTransition.swift in Sources */, 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, C1641EB32BC2F53C0012607A /* ImportPasswordsViewModel.swift in Sources */, + F1D5FEC52CF75B4900E00C3A /* TokenBackgroundRefreshTask.swift in Sources */, 9FDEC7BF2C91264C00C7A692 /* OnboardingAddressBarPositionPicker.swift in Sources */, 8536A1FD2ACF114B003AC5BA /* Theme+DesignSystem.swift in Sources */, F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */, @@ -7950,7 +7947,6 @@ 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */, 1EEF387D285B1A1100383393 /* TrackerImageCache.swift in Sources */, 3151F0EC27357FEE00226F58 /* VoiceSearchFeedbackViewModel.swift in Sources */, - 1E53508F2C7C9A1F00818DAA /* DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift in Sources */, 1DDF402D2BA09482006850D9 /* SettingsMainSettingsView.swift in Sources */, 85010502292FB1000033978F /* FireproofFaviconUpdater.swift in Sources */, F1C4A70E1E57725800A6CA1B /* OmniBar.swift in Sources */, @@ -8073,7 +8069,6 @@ D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, 9FEA222E2C324ECD006B03BF /* ViewVisibility.swift in Sources */, - BD10B8AA2C7629740033115D /* Logger+Subscription.swift in Sources */, D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */, 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, 9F23B8012C2BC94400950875 /* OnboardingBackground.swift in Sources */, @@ -8483,6 +8478,7 @@ 4BE2756827304F57006B20B0 /* URLRequestExtension.swift in Sources */, 85BA79911F6FF75000F59015 /* ContentBlockerStoreConstants.swift in Sources */, 85E242172AB1B54D000F3E28 /* ReturnUserMeasurement.swift in Sources */, + F1D5FEC32CF74B6800E00C3A /* Logger+Pixel.swift in Sources */, 85BDC3142434D8F80053DB07 /* DebugUserScript.swift in Sources */, 85011867290028C400BDEE27 /* BookmarksDatabase.swift in Sources */, 1D8F727F2BA86D8000E31493 /* PixelExperiment.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e72e1ceabf..3368c8cc29 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", @@ -90,6 +81,15 @@ "version" : "2.0.0" } }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", + "version" : "4.13.4" + } + }, { "identity" : "kingfisher", "kind" : "remoteSourceControl", @@ -144,6 +144,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-syntax", "kind" : "remoteSourceControl", From bd5cb7cc422e8111bf0cb51603cd2cfc96588a02 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 28 Nov 2024 10:16:00 +0000 Subject: [PATCH 29/41] unit tests builds --- DuckDuckGo.xcodeproj/project.pbxproj | 4 - ...tionPagesUseSubscriptionFeatureTests.swift | 322 ++++++------------ 2 files changed, 101 insertions(+), 225 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5687e836ec..e18800300d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -890,7 +890,6 @@ BDFF031D2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */; }; BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; }; BDFF03232BA3D8E300F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; }; - BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */; }; C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; }; C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; }; C12324C32C4697C900FBB26B /* AutofillBreakageReportTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1836CE42C35A0EA0016D057 /* AutofillBreakageReportTableViewCell.swift */; }; @@ -2711,7 +2710,6 @@ BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsViewModel.swift; sourceTree = ""; }; BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = ""; }; BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNetworkProtectionVisibility.swift; sourceTree = ""; }; - BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibilityTests.swift; sourceTree = ""; }; C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillViews.swift; sourceTree = ""; }; C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOrFolderTests.swift; sourceTree = ""; }; C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptView.swift; sourceTree = ""; }; @@ -5669,7 +5667,6 @@ children = ( EEFE9C722A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift */, EEC02C152B065BE00045CE11 /* NetworkProtectionVPNLocationViewModelTests.swift */, - BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */, ); name = NetworkProtection; sourceTree = ""; @@ -8192,7 +8189,6 @@ EEC02C162B065BE00045CE11 /* NetworkProtectionVPNLocationViewModelTests.swift in Sources */, 6F934F862C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift in Sources */, 987130C5294AAB9F00AB05E0 /* BookmarkEditorViewModelTests.swift in Sources */, - BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */, 9F8E0F332CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift in Sources */, D62EC3BA2C246A7000FC9D04 /* YoutublePlayerNavigationHandlerTests.swift in Sources */, 1EAABE712C99FC75003F5137 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, diff --git a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index f664d38d41..a5ff45817a 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -28,6 +28,8 @@ import BrowserServicesKit import OHHTTPStubs import OHHTTPStubsSwift import os.log +import Networking +import TestUtils final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { @@ -40,9 +42,9 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: 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=" @@ -59,34 +61,20 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { 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 accountStorage: AccountKeychainStorageMock! - var accessTokenStorage: SubscriptionTokenKeychainStorageMock! - var entitlementsCache: UserDefaultsCache<[Entitlement]>! - - var subscriptionService: SubscriptionEndpointServiceMock! - var authService: AuthEndpointServiceMock! +// var subscriptionService: SubscriptionEndpointServiceMock! var storePurchaseManager: StorePurchaseManagerMock! var subscriptionEnvironment: SubscriptionEnvironment! var appStorePurchaseFlow: AppStorePurchaseFlow! var appStoreRestoreFlow: AppStoreRestoreFlow! - var appStoreAccountManagementFlow: AppStoreAccountManagementFlow! - - var accountManager: AccountManager! - var subscriptionManager: SubscriptionManager! + var subscriptionManager: SubscriptionManagerMock! var subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled var feature: SubscriptionPagesUseSubscriptionFeature! @@ -114,86 +102,40 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { } // Mocks - subscriptionService = SubscriptionEndpointServiceMock() - authService = AuthEndpointServiceMock() - +// subscriptionService = SubscriptionEndpointServiceMock() storePurchaseManager = StorePurchaseManagerMock() subscriptionEnvironment = SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore) - accountStorage = AccountKeychainStorageMock() - accessTokenStorage = SubscriptionTokenKeychainStorageMock() - userDefaults = UserDefaults(suiteName: Constants.userDefaultsSuiteName)! userDefaults.removePersistentDomain(forName: Constants.userDefaultsSuiteName) - 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) + subscriptionManager = SubscriptionManagerMock() // 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) - // Real SubscriptionManager - subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - subscriptionEnvironment: subscriptionEnvironment) + appStoreRestoreFlow: appStoreRestoreFlow) feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager, subscriptionFeatureAvailability: subscriptionFeatureAvailability, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow) + appStoreRestoreFlow: appStoreRestoreFlow) } override func tearDownWithError() throws { Pixel.isDryRun = true pixelsFired.removeAll() HTTPStubs.removeAllStubs() - - subscriptionService = nil - authService = nil +// subscriptionService = nil storePurchaseManager = nil subscriptionEnvironment = nil - userDefaults = nil - - accountStorage = nil - accessTokenStorage = nil - - entitlementsCache?.reset() - entitlementsCache = nil - - accountManager = nil - - // Real Flows appStorePurchaseFlow = nil appStoreRestoreFlow = nil - appStoreAccountManagementFlow = nil - subscriptionManager = nil - feature = nil } @@ -202,45 +144,16 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testGetSubscriptionSuccessRefreshingAuthToken() async throws { // Given ensureUserAuthenticatedState() + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() - let newAuthToken = UUID().uuidString - - authService.validateTokenResult = .failure(Constants.invalidTokenError) storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS - authService.storeLoginResult = .success(StoreLoginResponse(authToken: newAuthToken, - email: Constants.email, - externalID: Constants.externalID, - id: 1, status: "authenticated")) - - // When - let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) - - // Then - let resultDictionary = try XCTUnwrap(result as? [String: String]) - - XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], newAuthToken) - XCTAssertEqual(accountManager.authToken, newAuthToken) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, nil) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testGetSubscriptionSuccessWithoutRefreshingAuthToken() async throws { - // Given - ensureUserAuthenticatedState() - - authService.validateTokenResult = .success(Constants.validateTokenResponse) - // When let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) // Then let resultDictionary = try XCTUnwrap(result as? [String: String]) - XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], Constants.authToken) - XCTAssertEqual(accountManager.authToken, Constants.authToken) + XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], subscriptionManager.resultTokenContainer?.accessToken) XCTAssertEqual(feature.transactionStatus, .idle) XCTAssertEqual(feature.transactionError, nil) @@ -251,10 +164,8 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testGetSubscriptionSuccessErrorWhenUnauthenticated() async throws { // Given ensureUserUnauthenticatedState() - - authService.validateTokenResult = .failure(Constants.invalidTokenError) storePurchaseManager.mostRecentTransactionResult = nil - + subscriptionManager.resultTokenContainer = nil // When let result = await feature.getSubscription(params: Constants.mockParams, original: Constants.mockScriptMessage) @@ -262,7 +173,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { let resultDictionary = try XCTUnwrap(result as? [String: String]) XCTAssertEqual(resultDictionary[SubscriptionPagesUseSubscriptionFeature.Constants.token], SubscriptionPagesUseSubscriptionFeature.Constants.empty) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertEqual(feature.transactionStatus, .idle) XCTAssertEqual(feature.transactionError, nil) @@ -319,8 +230,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { subscriptionFeatureAvailability: subscriptionFeatureAvailabilityWithoutPurchaseAllowed, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow) + appStoreRestoreFlow: appStoreRestoreFlow) storePurchaseManager.subscriptionOptionsResult = Constants.subscriptionOptions @@ -343,20 +253,21 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() - 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)) + subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -380,30 +291,32 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserAuthenticatedState() - 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) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) + subscriptionManager.resultSubscription = 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) + subscriptionManager.resultTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() + 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"] let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) // Then - XCTAssertFalse(authService.createAccountCalled) +// XCTAssertFalse(authService.createAccountCalled) XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) XCTAssertNil(result) @@ -423,21 +336,21 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserAuthenticatedState() - XCTAssertTrue(accountManager.isUserAuthenticated) + XCTAssertTrue(subscriptionManager.isUserAuthenticated) storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredStripeSubscription 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"] let result = await feature.subscriptionSelected(params: subscriptionSelectedParams, original: Constants.mockScriptMessage) // Then - XCTAssertFalse(authService.createAccountCalled) +// XCTAssertFalse(authService.createAccountCalled) XCTAssertTrue(storePurchaseManager.purchaseSubscriptionCalled) XCTAssertNil(result) @@ -505,7 +418,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { storePurchaseManager.hasActiveSubscriptionResult = false storePurchaseManager.mostRecentTransactionResult = nil - authService.createAccountResult = .failure(Constants.invalidTokenError) +// authService.createAccountResult = .failure(Constants.invalidTokenError) // When let subscriptionSelectedParams = ["id": "some-subscription-id"] @@ -528,7 +441,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserAuthenticatedState() storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseCancelledByUser) // When @@ -552,7 +465,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserAuthenticatedState() storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.productNotFound) // When @@ -576,7 +489,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserAuthenticatedState() storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.externalIDisNotAValidUUID) // When @@ -600,7 +513,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserAuthenticatedState() storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.purchaseFailed) // When @@ -624,7 +537,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserAuthenticatedState() storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionCannotBeVerified) // When @@ -648,7 +561,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserAuthenticatedState() storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.transactionPendingAuthentication) // When @@ -672,7 +585,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserAuthenticatedState() storePurchaseManager.hasActiveSubscriptionResult = false - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredStripeSubscription) storePurchaseManager.purchaseSubscriptionResult = .failure(StorePurchaseManagerError.unknownError) // When @@ -697,8 +610,10 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() - authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) - authService.validateTokenResult = .success(Constants.validateTokenResponse) +// authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) +// authService.validateTokenResult = .success(Constants.validateTokenResponse) + subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription + subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() let onSetSubscriptionCalled = expectation(description: "onSetSubscription") feature.onSetSubscription = { @@ -709,11 +624,9 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { let setSubscriptionParams = ["token": Constants.authToken] let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) + let tokens = try await subscriptionManager.getTokenContainer(policy: .local) // Then - XCTAssertEqual(accountManager.authToken, Constants.authToken) - XCTAssertEqual(accountManager.accessToken, Constants.accessToken) - XCTAssertEqual(accountManager.email, Constants.email) - XCTAssertEqual(accountManager.externalID, Constants.externalID) + XCTAssertEqual(tokens, subscriptionManager.resultExchangeTokenContainer) await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) XCTAssertNil(result) @@ -728,7 +641,8 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Given ensureUserUnauthenticatedState() - authService.getAccessTokenResult = .failure(Constants.invalidTokenError) +// subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription + subscriptionManager.resultExchangeTokenContainer = nil let onSetSubscriptionCalled = expectation(description: "onSetSubscription") onSetSubscriptionCalled.isInverted = true @@ -741,38 +655,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) // Then - XCTAssertNil(accountManager.authToken) - XCTAssertFalse(accountManager.isUserAuthenticated) - - await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) - XCTAssertNil(result) - - XCTAssertEqual(feature.transactionStatus, .idle) - XCTAssertEqual(feature.transactionError, .failedToSetSubscription) - - await XCTAssertPrivacyPixelsFired([]) - } - - func testSetSubscriptionErrorWhenFailedToFetchAccountDetails() async throws { - // Given - ensureUserUnauthenticatedState() - - authService.getAccessTokenResult = .success(.init(accessToken: Constants.accessToken)) - authService.validateTokenResult = .failure(Constants.invalidTokenError) - - let onSetSubscriptionCalled = expectation(description: "onSetSubscription") - onSetSubscriptionCalled.isInverted = true - feature.onSetSubscription = { - onSetSubscriptionCalled.fulfill() - } - - // When - let setSubscriptionParams = ["token": Constants.authToken] - let result = await feature.setSubscription(params: setSubscriptionParams, original: Constants.mockScriptMessage) - - // Then - XCTAssertNil(accountManager.authToken) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) await fulfillment(of: [onSetSubscriptionCalled], timeout: 0.5) XCTAssertNil(result) @@ -832,17 +715,16 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { func testBackToSettingsSuccess() async throws { // Given ensureUserAuthenticatedState() - accountStorage.email = nil - XCTAssertNil(accountManager.email) + XCTAssertNil(subscriptionManager.userEmail) let onBackToSettingsCalled = expectation(description: "onBackToSettings") feature.onBackToSettings = { onBackToSettingsCalled.fulfill() } - authService.validateTokenResult = .success(Constants.validateTokenResponse) - subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.subscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription // When let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) @@ -850,7 +732,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { // Then await fulfillment(of: [onBackToSettingsCalled], timeout: 0.5) - XCTAssertEqual(accountManager.email, Constants.email) + XCTAssertEqual(subscriptionManager.userEmail, Constants.email) XCTAssertNil(result) await XCTAssertPrivacyPixelsFired([]) @@ -866,7 +748,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { onBackToSettingsCalled.fulfill() } - authService.validateTokenResult = .failure(Constants.invalidTokenError) +// authService.validateTokenResult = .failure(Constants.invalidTokenError) // When let result = await feature.backToSettings(params: Constants.mockParams, original: Constants.mockScriptMessage) @@ -901,7 +783,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { 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) @@ -920,19 +802,20 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserUnauthenticatedState() 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) + subscriptionManager.resultSubscription = SubscriptionMockFactory.subscription + subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() // When try await feature.restoreAccountFromAppStorePurchase() // Then - XCTAssertTrue(accountManager.isUserAuthenticated) + XCTAssertTrue(subscriptionManager.isUserAuthenticated) XCTAssertEqual(feature.transactionStatus, .idle) XCTAssertEqual(feature.transactionError, nil) @@ -945,14 +828,15 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserUnauthenticatedState() 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.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) +// subscriptionService.getSubscriptionResult = .success(SubscriptionMockFactory.expiredSubscription) + subscriptionManager.resultSubscription = SubscriptionMockFactory.expiredSubscription + subscriptionManager.resultExchangeTokenContainer = OAuthTokensFactory.makeValidTokenContainerWithEntitlements() do { // When @@ -966,7 +850,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { } XCTAssertEqual(error, .subscriptionExpired) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertEqual(feature.transactionStatus, .idle) XCTAssertEqual(feature.transactionError, nil) @@ -993,7 +877,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { } XCTAssertEqual(error, .subscriptionNotFound) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertEqual(feature.transactionStatus, .idle) XCTAssertEqual(feature.transactionError, nil) @@ -1007,7 +891,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { ensureUserUnauthenticatedState() storePurchaseManager.mostRecentTransactionResult = Constants.mostRecentTransactionJWS - authService.storeLoginResult = .failure(Constants.invalidTokenError) +// authService.storeLoginResult = .failure(Constants.invalidTokenError) do { // When @@ -1021,7 +905,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { } XCTAssertEqual(error, .failedToRestorePastPurchase) - XCTAssertFalse(accountManager.isUserAuthenticated) + XCTAssertFalse(subscriptionManager.isUserAuthenticated) XCTAssertEqual(feature.transactionStatus, .idle) XCTAssertEqual(feature.transactionError, nil) @@ -1034,19 +918,15 @@ 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.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) async { - try? await Task.sleep(seconds: 0.1) + try? await Task.sleep(interval: 0.1) let pixelsFired = Set(pixelsFired) let expectedPixels = Set(pixels) From e4fbf00316faf7a3ce4fb5ff1afe84aef14e0cc8 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 29 Nov 2024 13:25:28 +0000 Subject: [PATCH 30/41] BSK > branch --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++-- .../xcshareddata/swiftpm/Package.resolved | 36 +++++-------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4f66a331c7..fdff9ea5c4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3022,7 +3022,6 @@ F1D477C51F2126CC0031ED49 /* OmniBarState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarState.swift; sourceTree = ""; }; F1D477C81F2139410031ED49 /* SmallOmniBarStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SmallOmniBarStateTests.swift; sourceTree = ""; }; F1D477CA1F2149C40031ED49 /* Type.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Type.swift; sourceTree = ""; }; - F1D5FEC12CF74A6600E00C3A /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F1D5FEC22CF74B6800E00C3A /* Logger+Pixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Pixel.swift"; sourceTree = ""; }; F1D5FEC42CF75B4900E00C3A /* TokenBackgroundRefreshTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenBackgroundRefreshTask.swift; sourceTree = ""; }; F1D796EB1E7AB8930019D451 /* SaveBookmarkActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveBookmarkActivity.swift; sourceTree = ""; }; @@ -4231,7 +4230,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - F1D5FEC12CF74A6600E00C3A /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -11240,8 +11238,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 212.0.0; + branch = fcappelli/subscription_oauth_api_v2; + kind = branch; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3368c8cc29..19a857ee61 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" : "5a24a885425dcb5626353bf5a8ca6af79c44c1cb", + "version" : "212.0.0" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -81,15 +90,6 @@ "version" : "2.0.0" } }, - { - "identity" : "jwt-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/jwt-kit.git", - "state" : { - "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", - "version" : "4.13.4" - } - }, { "identity" : "kingfisher", "kind" : "remoteSourceControl", @@ -144,24 +144,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" : "ff0f781cf7c6a22d52957e50b104f5768b50c779", - "version" : "3.10.0" - } - }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", From a745b4936f2a501fc06a97abe592ba511b7ce521 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 29 Nov 2024 14:12:08 +0000 Subject: [PATCH 31/41] packages --- .../xcshareddata/swiftpm/Package.resolved | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4b616ab2cf..48b9eef41c 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "db9c29a429896138fab29da987981a5f4a8d6712", - "version" : "212.1.1" + "branch" : "fcappelli/subscription_oauth_api_v2", + "revision" : "97ddc9c243337cb028c979aa488e3037f7e268c3" } }, { @@ -90,6 +90,15 @@ "version" : "2.0.0" } }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a", + "version" : "4.13.4" + } + }, { "identity" : "kingfisher", "kind" : "remoteSourceControl", @@ -122,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "757bbbae1e2afbb421caee9bfca04ee5c56c3af8", - "version" : "7.2.0" + "revision" : "49db79829dcb166b3524afdbc1c680890452ce1c", + "version" : "7.2.1" } }, { @@ -144,6 +153,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-syntax", "kind" : "remoteSourceControl", From edf1bfa75f4f4f213efeae3b6de1a101cfe31f51 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 12 Dec 2024 14:50:21 +0000 Subject: [PATCH 32/41] updates from mac and unit tests fixed --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../xcshareddata/swiftpm/Package.resolved | 9 ++++++ DuckDuckGo/AppDelegate.swift | 24 ++++++++++---- DuckDuckGo/AppDependencyProvider.swift | 5 +-- .../Feedback/VPNMetadataCollector.swift | 3 +- ...RemoteMessagingConfigMatcherProvider.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 13 ++++++-- .../UnifiedFeedbackFormViewModel.swift | 32 +++++++++++++------ .../SubscriptionSettingsViewModel.swift | 6 ++-- .../SubscriptionDebugViewController.swift | 8 ++--- DuckDuckGo/UserText.swift | 4 +-- ...tionPagesUseSubscriptionFeatureTests.swift | 2 +- ...etworkProtectionPacketTunnelProvider.swift | 18 +++-------- 13 files changed, 75 insertions(+), 53 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d7a14fcb10..9d378186be 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3068,7 +3068,6 @@ F143C32C1E4A9A4800CFDE3A /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIViewControllerExtension.swift; path = ../Core/UIViewControllerExtension.swift; sourceTree = ""; }; F143C3451E4AA32D00CFDE3A /* SearchBarExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchBarExtension.swift; path = ../Core/SearchBarExtension.swift; sourceTree = ""; }; F14E491E1E391CE900DC037C /* URLExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtensionTests.swift; sourceTree = ""; }; - F14E5D512CFDE79200B91BE6 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = ""; }; F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteViewController.swift; sourceTree = ""; }; F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherViewController.swift; sourceTree = ""; }; @@ -4364,7 +4363,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - F14E5D512CFDE79200B91BE6 /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9cb44a7b6f..cd9b1bfe81 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" : "50d4d64cba660ee779d987585b6c866d5b710057" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 3de580fb7b..c91e83c57f 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -317,7 +317,11 @@ import os.log let url = URL.pixelUrl(forPixelNamed: pixelName) let apiHeaders = APIRequestV2.HeadersV2(additionalHeaders: headers) - let request = APIRequestV2(url: url, method: .get, queryItems: parameters, headers: apiHeaders) + guard let request = APIRequestV2(url: url, method: .get, queryItems: parameters, headers: apiHeaders) else { + onComplete(false, nil) + return + } + Task { do { _ = try await DefaultAPIService().fetch(request: request) @@ -1152,7 +1156,7 @@ import os.log return } - if AppDependencyProvider.shared.subscriptionManager.isEntitlementActive(.networkProtection) { + if await AppDependencyProvider.shared.subscriptionManager.isFeatureActive(.networkProtection) { let items = [ UIApplicationShortcutItem(type: ShortcutKey.openVPNSettings, localizedTitle: UserText.netPOpenVPNQuickAction, @@ -1228,12 +1232,18 @@ extension AppDelegate: UNUserNotificationCenterDelegate { completionHandler() } - + func presentNetworkProtectionStatusSettingsModal() { - if AppDependencyProvider.shared.subscriptionManager.isEntitlementActive(.networkProtection) { - (window?.rootViewController as? MainViewController)?.segueToVPN() - } else { - (window?.rootViewController as? MainViewController)?.segueToPrivacyPro() + Task { + if await AppDependencyProvider.shared.subscriptionManager.isFeatureActive(.networkProtection) { + Task { @MainActor in + (window?.rootViewController as? MainViewController)?.segueToVPN() + } + } else { + Task { @MainActor in + (window?.rootViewController as? MainViewController)?.segueToPrivacyPro() + } + } } } diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 39717add6a..b7953471d8 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -153,9 +153,7 @@ final class AppDependencyProvider: DependencyProvider { } let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) - let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, - userDefaults: subscriptionUserDefaults) - let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionFeatureMappingCache) + let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionEndpointService) let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { case .deadToken: @@ -165,7 +163,6 @@ final class AppDependencyProvider: DependencyProvider { let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: subscriptionEnvironment, subscriptionFeatureFlagger: subscriptionFeatureFlagger, pixelHandler: pixelHandler) diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index a3c74f2803..8fc6595584 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -244,10 +244,9 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { return .init( hasPrivacyProAccount: subscriptionManager.isUserAuthenticated, - hasVPNEntitlement: subscriptionManager.isEntitlementActive(.networkProtection) + hasVPNEntitlement: await subscriptionManager.isFeatureActive(.networkProtection) ) } - } private extension NSError { diff --git a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift index 48cfb7c798..9295cfa728 100644 --- a/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift +++ b/DuckDuckGo/RemoteMessagingConfigMatcherProvider.swift @@ -84,7 +84,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr let surveyActionMapper: DefaultRemoteMessagingSurveyURLBuilder - if let subscription = try? await subscriptionManager.currentSubscription(refresh: false) { + 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 diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 131b7c7ee5..2f8d0cbd9d 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -766,13 +766,20 @@ extension SettingsViewModel { throw SubscriptionEndpointServiceError.noData } - let subscription = try await subscriptionManager.currentSubscription(refresh: true) + let subscription = try await subscriptionManager.getSubscription(cachePolicy: .reloadIgnoringLocalCacheData) Logger.subscription.log("Subscription loaded: \(subscription.debugDescription, privacy: .public)") state.subscription.subscriptionExist = true state.subscription.platform = subscription.platform state.subscription.hasActiveSubscription = subscription.isActive - state.subscription.entitlements = subscriptionManager.currentEntitlements - state.subscription.subscriptionFeatures = subscriptionManager.currentEntitlements + let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: true) + state.subscription.entitlements = features.compactMap({ feature in + if feature.enabled { + return feature.entitlement + } else { + return nil + } + }) + state.subscription.subscriptionFeatures = features.map({ feature in feature.entitlement }) } catch SubscriptionEndpointServiceError.noData, OAuthClientError.missingTokens { // Auth successful but no Subscription is available Logger.subscription.log("Subscription not present") diff --git a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift index c2280988e0..d49a278877 100644 --- a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift @@ -67,6 +67,7 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { enum Error: String, Swift.Error { case missingAccessToken case invalidResponse + case invalidRequest } struct Payload: Codable { @@ -158,15 +159,24 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { self.feedbackSender = feedbackSender self.source = source.rawValue - 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) + } } } @@ -292,7 +302,9 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { problemSubCategory: selectedSubcategory ?? "", customMetadata: metadata?.toString() ?? "") let headers = APIRequestV2.HeadersV2(additionalHeaders: [HTTPHeaderKey.authorization: "Bearer \(accessToken)"]) - let request = APIRequestV2(url: Self.feedbackEndpoint, method: .post, headers: headers, body: payload.toData()) + guard let request = APIRequestV2(url: Self.feedbackEndpoint, method: .post, headers: headers, body: payload.toData()) else { + throw Error.invalidRequest + } let response: Response = try await apiService.fetch(request: request).decodeBody() if let error = response.error, !error.isEmpty { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 24ae463af2..7badc0aeaa 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -114,8 +114,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { if loadingIndicator { displaySubscriptionLoader(true) } - if let subscription = try? await self.subscriptionManager.currentSubscription(refresh: cachePolicy != SubscriptionCachePolicy.returnCacheDataDontLoad) { - DispatchQueue.main.async { + if let subscription = try? await self.subscriptionManager.getSubscription(cachePolicy: cachePolicy) { + Task { @MainActor in self.state.subscriptionInfo = subscription if loadingIndicator { self.displaySubscriptionLoader(false) } } @@ -140,7 +140,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } if let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: tokensPolicy) { - DispatchQueue.main.async { + Task { @MainActor in self.state.subscriptionEmail = tokenContainer.decodedAccessToken.email if loadingIndicator { self.displayEmailLoader(true) } } diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index da8082314a..df4a54ebb4 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -404,12 +404,10 @@ final class SubscriptionDebugViewController: UITableViewController { private func getSubscriptionDetails() { Task { do { - let subscription = try await subscriptionManager.currentSubscription(refresh: true) - showAlert(title: "Subscription info", message: "\(subscription)") - } catch OAuthClientError.missingTokens { - showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") + let subscription = try await subscriptionManager.getSubscription(cachePolicy: .reloadIgnoringLocalCacheData) + showAlert(title: "Subscription info", message: subscription.debugDescription) } catch { - showAlert(title: "Error retrieving subscription", message: "\(error)") + showAlert(title: "Subscription info", message: "\(error)") } } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 86a2337975..db4991f111 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1170,7 +1170,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionSubscribed = NSLocalizedString("subscription.subscribed", value: "Subscribed", comment: "Subtitle in header when subscribed") public static let subscriptionCloseButton = NSLocalizedString("subscription.close", value: "Close", comment: "Navigation Button for closing subscription view") - static func renewingSubscriptionInfo(billingPeriod: Subscription.BillingPeriod, renewalDate: String) -> String { + static func renewingSubscriptionInfo(billingPeriod: PrivacyProSubscription.BillingPeriod, renewalDate: String) -> String { let localized: String switch billingPeriod { @@ -1191,7 +1191,7 @@ But if you *do* want a peek under the hood, you can find more information about return String(format: localized, renewalDate) } - static func expiringSubscriptionInfo(billingPeriod: Subscription.BillingPeriod, expiryDate: String) -> String { + static func expiringSubscriptionInfo(billingPeriod: PrivacyProSubscription.BillingPeriod, expiryDate: String) -> String { let localized: String switch billingPeriod { diff --git a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift index 95e54fb3dd..92b4e554ea 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -55,7 +55,7 @@ final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { SubscriptionOption(id: "2", cost: SubscriptionOptionCost(displayPrice: "99 USD", recurrence: "yearly")) ], - features: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) + availableEntitlements: [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]) static let mockParams: [String: String] = [:] @MainActor static let mockScriptMessage = MockWKScriptMessage(name: "", body: "", webView: WKWebView() ) diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 69c140c388..49bafd7e87 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -489,9 +489,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, baseURL: subscriptionEnvironment.serviceEnvironment.url) - let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionEndpointService, - userDefaults: UserDefaults.standard) - let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionFeatureMappingCache) + let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionEndpointService) let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { @@ -502,7 +500,6 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, oAuthClient: authClient, subscriptionEndpointService: subscriptionEndpointService, - subscriptionFeatureMappingCache: subscriptionFeatureMappingCache, subscriptionEnvironment: subscriptionEnvironment, subscriptionFeatureFlagger: nil, pixelHandler: pixelHandler) @@ -518,15 +515,10 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { notificationsPresenter.requestAuthorization() 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) } super.init(notificationsPresenter: notificationsPresenterDecorator, From 88cd2d9f825f58b0e0141b22287259ecf6718448 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 6 Jan 2025 09:53:39 +0000 Subject: [PATCH 33/41] testing comments fixed --- DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift b/DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift index 27ac409785..ecf09478eb 100644 --- a/DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift +++ b/DuckDuckGo/Subscription/TokenBackgroundRefreshTask.swift @@ -86,10 +86,10 @@ class TokenBackgroundRefreshTask { task.earliestBeginDate = Date(timeIntervalSinceNow: minimumConfigurationRefreshInterval) // Background tasks can be debugged by breaking on the `submit` call, stepping over, then running the following LLDB command, before resuming: - // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.duckduckgo.app.configurationRefresh"] + // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.duckduckgo.app.backgroundTokenRefresh"] // // Task expiration can be simulated similarly: - // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.duckduckgo.app.configurationRefresh"] + // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.duckduckgo.app.backgroundTokenRefresh"] #if !targetEnvironment(simulator) do { From 7b93366c9a946cdf16142fd3eb92a0ec1609c818 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 7 Jan 2025 17:21:07 +0000 Subject: [PATCH 34/41] BSK updated --- DuckDuckGo.xcodeproj/project.pbxproj | 32 +++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 10 +++--- .../AdAttributionFetcherTests.swift | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9d378186be..41f4c9edea 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -659,7 +659,6 @@ 98424A982CED4F430071C7DB /* ContentBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 984249C62CED4F430071C7DB /* ContentBlocking */; }; 98424A992CED4F430071C7DB /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 984249C92CED4F430071C7DB /* SubscriptionTestingUtilities */; }; 98424A9A2CED4F430071C7DB /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 984249C82CED4F430071C7DB /* Subscription */; }; - 98424A9B2CED4F430071C7DB /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 984249C52CED4F430071C7DB /* TestUtils */; }; 98424A9C2CED4F430071C7DB /* NetworkProtectionTestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 984249C32CED4F430071C7DB /* NetworkProtectionTestUtils */; }; 98424A9D2CED4F430071C7DB /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 984249C72CED4F430071C7DB /* Common */; }; 98424AAB2CED4FF10071C7DB /* CookieStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AD49ED2B6149110085D2D1 /* CookieStorageTests.swift */; }; @@ -1120,7 +1119,6 @@ F1134ED21F40EF3A00B73467 /* JsonTestDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1134ECF1F40EBE200B73467 /* JsonTestDataLoader.swift */; }; F1134ED61F40F29F00B73467 /* StatisticsUserDefaultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1134ED41F40F15800B73467 /* StatisticsUserDefaultsTests.swift */; }; F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = F114C55A1E66EB020018F95F /* NibLoading.swift */; }; - F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = F115ED9B2B4EFC8E001A0453 /* TestUtils */; }; F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130D7391E5776C500C45811 /* OmniBarDelegate.swift */; }; F132D6A52C62239B00D85426 /* SubscriptionSettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F132D6A42C62239B00D85426 /* SubscriptionSettingsHeaderView.swift */; }; F1386BA41E6846C40062FC3C /* TabDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1386BA31E6846C40062FC3C /* TabDelegate.swift */; }; @@ -1162,6 +1160,8 @@ F198D78E1E39762C0088DA8A /* StringExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D78D1E39762C0088DA8A /* StringExtensionTests.swift */; }; F1A886781F29394E0096251E /* WebCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A886771F29394E0096251E /* WebCacheManager.swift */; }; F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */; }; + F1B3DEC12D2D90BB00D33FA5 /* NetworkingTestingUtils in Frameworks */ = {isa = PBXBuildFile; productRef = F1B3DEC02D2D90BB00D33FA5 /* NetworkingTestingUtils */; }; + F1B3DEC32D2D90D500D33FA5 /* NetworkingTestingUtils in Frameworks */ = {isa = PBXBuildFile; productRef = F1B3DEC22D2D90D500D33FA5 /* NetworkingTestingUtils */; }; F1BDDBFD2C340D9C00459306 /* SubscriptionContainerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBF92C340D9C00459306 /* SubscriptionContainerViewModelTests.swift */; }; F1BDDBFE2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBFA2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift */; }; F1BDDBFF2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBFB2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */; }; @@ -3210,7 +3210,7 @@ 4BE67B052B96B9AB007335F7 /* ContentBlocking in Frameworks */, F15531922BF215ED0029ED04 /* SubscriptionTestingUtilities in Frameworks */, F15531902BF215ED0029ED04 /* Subscription in Frameworks */, - F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, + F1B3DEC12D2D90BB00D33FA5 /* NetworkingTestingUtils in Frameworks */, EEFAB4672A73C230008A38E4 /* NetworkProtectionTestUtils in Frameworks */, 4BE67B072B96B9B0007335F7 /* Common in Frameworks */, ); @@ -3271,13 +3271,13 @@ buildActionMask = 2147483647; files = ( 98424AB42CEDD61C0071C7DB /* BrowserServicesKitTestsUtils in Frameworks */, + F1B3DEC32D2D90D500D33FA5 /* NetworkingTestingUtils in Frameworks */, 98424A962CED4F430071C7DB /* OHHTTPStubs in Frameworks */, 98424A972CED4F430071C7DB /* OHHTTPStubsSwift in Frameworks */, 98424A982CED4F430071C7DB /* ContentBlocking in Frameworks */, 98424AB22CEDD6150071C7DB /* BrowserServicesKit in Frameworks */, 98424A992CED4F430071C7DB /* SubscriptionTestingUtilities in Frameworks */, 98424A9A2CED4F430071C7DB /* Subscription in Frameworks */, - 98424A9B2CED4F430071C7DB /* TestUtils in Frameworks */, 98424A9C2CED4F430071C7DB /* NetworkProtectionTestUtils in Frameworks */, 98424A9D2CED4F430071C7DB /* Common in Frameworks */, ); @@ -6890,11 +6890,11 @@ F486D3352506A037002D07D7 /* OHHTTPStubs */, F486D3372506A225002D07D7 /* OHHTTPStubsSwift */, EEFAB4662A73C230008A38E4 /* NetworkProtectionTestUtils */, - F115ED9B2B4EFC8E001A0453 /* TestUtils */, 4BE67B042B96B9AB007335F7 /* ContentBlocking */, 4BE67B062B96B9B0007335F7 /* Common */, F155318F2BF215ED0029ED04 /* Subscription */, F15531912BF215ED0029ED04 /* SubscriptionTestingUtilities */, + F1B3DEC02D2D90BB00D33FA5 /* NetworkingTestingUtils */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -7023,13 +7023,13 @@ 984249C02CED4F430071C7DB /* OHHTTPStubs */, 984249C22CED4F430071C7DB /* OHHTTPStubsSwift */, 984249C32CED4F430071C7DB /* NetworkProtectionTestUtils */, - 984249C52CED4F430071C7DB /* TestUtils */, 984249C62CED4F430071C7DB /* ContentBlocking */, 984249C72CED4F430071C7DB /* Common */, 984249C82CED4F430071C7DB /* Subscription */, 984249C92CED4F430071C7DB /* SubscriptionTestingUtilities */, 98424AB12CEDD6150071C7DB /* BrowserServicesKit */, 98424AB32CEDD61C0071C7DB /* BrowserServicesKitTestsUtils */, + F1B3DEC22D2D90D500D33FA5 /* NetworkingTestingUtils */, ); productName = DuckDuckGoTests; productReference = 98424AA92CED4F430071C7DB /* WebViewUnitTests.xctest */; @@ -11939,11 +11939,6 @@ package = 984249C42CED4F430071C7DB /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = NetworkProtectionTestUtils; }; - 984249C52CED4F430071C7DB /* TestUtils */ = { - isa = XCSwiftPackageProductDependency; - package = 984249C42CED4F430071C7DB /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = TestUtils; - }; 984249C62CED4F430071C7DB /* ContentBlocking */ = { isa = XCSwiftPackageProductDependency; package = 984249C42CED4F430071C7DB /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -12061,11 +12056,6 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = NetworkProtectionTestUtils; }; - F115ED9B2B4EFC8E001A0453 /* TestUtils */ = { - isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = TestUtils; - }; F155318F2BF215ED0029ED04 /* Subscription */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -12086,6 +12076,16 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = SubscriptionTestingUtilities; }; + F1B3DEC02D2D90BB00D33FA5 /* NetworkingTestingUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = NetworkingTestingUtils; + }; + F1B3DEC22D2D90D500D33FA5 /* NetworkingTestingUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = NetworkingTestingUtils; + }; F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */ = { isa = XCSwiftPackageProductDependency; package = F1D43AF82B99C1D300BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cd9b1bfe81..02986367bd 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" : "50d4d64cba660ee779d987585b6c866d5b710057" + "revision" : "d1998cae838ac33584b24c99662dd898e2eca0f0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "93ea6c3e771bc0b743b38cefbff548c10add9898", - "version" : "6.42.0" + "revision" : "bc808eb735d9eb72d5c54cf2452b104b6a370e25", + "version" : "6.43.0" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "022c845b06ace6a4aa712a4fa3e79da32193d5c6", - "version" : "7.4.0" + "revision" : "2e2baf7d31c7d8e158a58bc1cb79498c1c727fd2", + "version" : "7.5.0" } }, { diff --git a/DuckDuckGoTests/AdAttributionFetcherTests.swift b/DuckDuckGoTests/AdAttributionFetcherTests.swift index 28b9c94541..a3296e29d5 100644 --- a/DuckDuckGoTests/AdAttributionFetcherTests.swift +++ b/DuckDuckGoTests/AdAttributionFetcherTests.swift @@ -20,7 +20,7 @@ import XCTest @testable import DuckDuckGo -@testable import TestUtils +import NetworkingTestingUtils final class AdAttributionFetcherTests: XCTestCase { From 62bd2da5f1df6f4afae1b4e0b885faa633583624 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 Jan 2025 16:49:22 +0000 Subject: [PATCH 35/41] BSK updated, some flows updated --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../NetworkProtectionDebugViewController.swift | 2 +- ...SubscriptionPagesUseSubscriptionFeature.swift | 12 +++++++++--- .../ViewModel/SubscriptionFlowViewModel.swift | 16 +++++++++++++++- .../SubscriptionSettingsViewModel.swift | 2 +- .../Views/SubscriptionFlowView.swift | 4 ++-- DuckDuckGo/SubscriptionDebugViewController.swift | 4 ++-- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a9a2e0214d..aff9bef425 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" : "e6591e5fc40cc32e6309fa4ff4f1e2dfbb865833" } }, { diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 8caa577764..313b489512 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -680,7 +680,7 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") if subscriptionOverrideEnabled { defaults.subscriptionOverrideEnabled = false Task { - await AppDependencyProvider.shared.subscriptionManager.signOut() + await AppDependencyProvider.shared.subscriptionManager.signOut(notifyUI: true) } } else { defaults.resetSubscriptionOverrideEnabled() diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index d92dbb21ad..dd36ef44a2 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -296,7 +296,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec case .failure(let error): Logger.subscription.error("App store complete subscription purchase error: \(error, privacy: .public)") - await subscriptionManager.signOut() + await subscriptionManager.signOut(notifyUI: true) setTransactionStatus(.idle) setTransactionError(.missingEntitlements) @@ -316,7 +316,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Clear subscription Cache - await subscriptionManager.signOut() + await subscriptionManager.signOut(notifyUI: false) let authToken = subscriptionValues.token do { @@ -452,7 +452,13 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec throw mappedError } } - + + func cancelRestoreAccountFromAppStorePurchase() async throws { + setTransactionStatus(.idle) + await subscriptionManager.signOut(notifyUI: false) + } + + // MARK: Utility Methods func mapAppStoreRestoreErrorToTransactionError(_ error: AppStoreRestoreFlowError) -> UseSubscriptionError { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index fd9ae97268..10b2bc333d 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -339,7 +339,21 @@ final class SubscriptionFlowViewModel: ObservableObject { } } } - + + @MainActor + func cancelAppstoreTransaction() { + clearTransactionError() + Task { + do { + try await subFeature.cancelRestoreAccountFromAppStorePurchase() + } catch let error { + if let specificError = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError { + handleTransactionError(error: specificError) + } + } + } + } + @MainActor func navigateBack() async { await webViewModel.navigationCoordinator.goBack() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 7badc0aeaa..2b69935059 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -205,7 +205,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { Logger.subscription.log("Remove subscription") Task { - await subscriptionManager.signOut() + await subscriptionManager.signOut(notifyUI: true) _ = await ActionMessageView() await ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, presentationLocation: .withoutBottomBar) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 430475bbe4..b01dc7c5ad 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -189,8 +189,8 @@ struct SubscriptionFlowView: View { title: Text(UserText.subscriptionFoundTitle), message: Text(UserText.subscriptionFoundText), primaryButton: .cancel(Text(UserText.subscriptionFoundCancel)) { - viewModel.clearTransactionError() - dismiss() + viewModel.cancelAppstoreTransaction() + dismiss() }, secondaryButton: .default(Text(UserText.subscriptionFoundRestore)) { viewModel.restoreAppstoreTransaction() diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 125b7dffdc..d9409456e3 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -300,7 +300,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func clearAuthData() { Task { - await subscriptionManager.signOut() + await subscriptionManager.signOut(notifyUI: true) showAlert(title: "Data cleared!") } } @@ -406,7 +406,7 @@ final class SubscriptionDebugViewController: UITableViewController { if newSubscriptionEnvironment.serviceEnvironment != currentSubscriptionEnvironment.serviceEnvironment { Task { - await subscriptionManager.signOut() + await subscriptionManager.signOut(notifyUI: true) // Save Subscription environment DefaultSubscriptionManager.save(subscriptionEnvironment: newSubscriptionEnvironment, userDefaults: subscriptionUserDefaults) From b72a122a526075e76cb2a387bc02bf10941e92db Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Sun, 12 Jan 2025 15:06:47 +0000 Subject: [PATCH 36/41] subscription manager load initial data improved, pixels added --- Core/PixelEvent.swift | 4 ++ .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/AppDependencyProvider.swift | 6 +++ .../AppLifecycle/AppStates/Active.swift | 10 ++--- .../AppLifecycle/AppStates/Launched.swift | 6 ++- DuckDuckGo/OldAppDelegate.swift | 16 ++++---- DuckDuckGo/SettingsViewModel.swift | 2 +- .../UnifiedFeedbackFormViewModel.swift | 37 ++++++++++--------- .../Feedback/UnifiedFeedbackRootView.swift | 1 + .../SubscriptionSettingsViewModel.swift | 16 +++++--- ...etworkProtectionPacketTunnelProvider.swift | 4 +- 11 files changed, 63 insertions(+), 41 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index b844a2cf91..7235e4894b 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -805,6 +805,8 @@ extension Pixel { case privacyProSubscriptionCookieRefreshedWithEmptyValue case privacyProSubscriptionCookieFailedToSetSubscriptionCookie case privacyProDeadTokenDetected + case authV1MigrationFailed + case authV1MigrationSucceeded // MARK: Pixel Experiment case pixelExperimentEnrollment @@ -1756,6 +1758,8 @@ extension Pixel.Event { case .privacyProSubscriptionCookieRefreshedWithEmptyValue: return "m_privacy-pro_subscription-cookie-refreshed_with_empty_value" case .privacyProSubscriptionCookieFailedToSetSubscriptionCookie: return "m_privacy-pro_subscription-cookie-failed_to_set_subscription_cookie" 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" // MARK: Pixel Experiment case .pixelExperimentEnrollment: return "pixel_experiment_enrollment" diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index aff9bef425..e707102ffb 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" : "e6591e5fc40cc32e6309fa4ff4f1e2dfbb865833" + "revision" : "9fd4aeaef05e212b8e93cd5e025c0ae38f013453" } }, { diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index b27b62cda5..73f524163c 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -148,6 +148,12 @@ final class AppDependencyProvider: DependencyProvider { switch type { case .deadToken: Pixel.fire(pixel: .privacyProDeadTokenDetected) + case .subscriptionIsActive: + DailyPixel.fire(pixel: .privacyProSubscriptionActive) + case .v1MigrationFailed: + Pixel.fire(pixel: .authV1MigrationFailed) + case .v1MigrationSuccessful: + Pixel.fire(pixel: .authV1MigrationSucceeded) } } let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, diff --git a/DuckDuckGo/AppLifecycle/AppStates/Active.swift b/DuckDuckGo/AppLifecycle/AppStates/Active.swift index 0efd488454..d52f74f7ce 100644 --- a/DuckDuckGo/AppLifecycle/AppStates/Active.swift +++ b/DuckDuckGo/AppLifecycle/AppStates/Active.swift @@ -179,11 +179,11 @@ struct Active: AppState { } } - AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in - if isSubscriptionActive { - DailyPixel.fire(pixel: .privacyProSubscriptionActive) - } - } +// AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in +// if isSubscriptionActive { +// DailyPixel.fire(pixel: .privacyProSubscriptionActive) +// } +// } Task { await appDependencies.subscriptionService.subscriptionCookieManager.refreshSubscriptionCookie() diff --git a/DuckDuckGo/AppLifecycle/AppStates/Launched.swift b/DuckDuckGo/AppLifecycle/AppStates/Launched.swift index 0178939bd9..d0146843aa 100644 --- a/DuckDuckGo/AppLifecycle/AppStates/Launched.swift +++ b/DuckDuckGo/AppLifecycle/AppStates/Launched.swift @@ -90,6 +90,10 @@ struct Launched: AppState { @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) var privacyConfigCustomURL: String? + Task { + await AppDependencyProvider.shared.subscriptionManager.loadInitialData() + } + application = stateContext.application privacyProDataReporter = PrivacyProDataReporter(fireproofing: fireproofing) vpnWorkaround = VPNRedditSessionWorkaround(subscriptionManager: AppDependencyProvider.shared.subscriptionManager, @@ -460,8 +464,6 @@ struct Launched: AppState { widgetRefreshModel.beginObservingVPNStatus() - AppDependencyProvider.shared.subscriptionManager.loadInitialData() - let autofillUsageMonitor = AutofillUsageMonitor() autofillPixelReporter = AutofillPixelReporter( userDefaults: .standard, diff --git a/DuckDuckGo/OldAppDelegate.swift b/DuckDuckGo/OldAppDelegate.swift index da05c32130..a8929a52bb 100644 --- a/DuckDuckGo/OldAppDelegate.swift +++ b/DuckDuckGo/OldAppDelegate.swift @@ -452,6 +452,10 @@ final class OldAppDelegate: NSObject, UIApplicationDelegate, DDGApp { self.voiceSearchHelper.migrateSettingsFlagIfNecessary() + Task { + await AppDependencyProvider.shared.subscriptionManager.loadInitialData() + } + // Task handler registration needs to happen before the end of `didFinishLaunching`, otherwise submitting a task can throw an exception. // Having both in `didBecomeActive` can sometimes cause the exception when running on a physical device, so registration happens here. AppConfigurationFetch.registerBackgroundRefreshTaskHandler() @@ -475,8 +479,6 @@ final class OldAppDelegate: NSObject, UIApplicationDelegate, DDGApp { widgetRefreshModel.beginObservingVPNStatus() - AppDependencyProvider.shared.subscriptionManager.loadInitialData() - setUpAutofillPixelReporter() if didCrashDuringCrashHandlersSetUp { @@ -717,11 +719,11 @@ final class OldAppDelegate: NSObject, UIApplicationDelegate, DDGApp { } } - AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in - if isSubscriptionActive { - DailyPixel.fire(pixel: .privacyProSubscriptionActive) - } - } +// AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscription { isSubscriptionActive in +// if isSubscriptionActive { +// DailyPixel.fire(pixel: .privacyProSubscriptionActive) +// } +// } Task { await subscriptionCookieManager.refreshSubscriptionCookie() diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index c883fc7b1c..85f4b24e65 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -773,7 +773,7 @@ extension SettingsViewModel { state.subscription.subscriptionExist = true state.subscription.platform = subscription.platform state.subscription.hasActiveSubscription = subscription.isActive - let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: true) + let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false) state.subscription.entitlements = features.compactMap({ feature in if feature.enabled { return feature.entitlement diff --git a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift index d49a278877..fb00c1c64b 100644 --- a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift @@ -158,25 +158,26 @@ final class UnifiedFeedbackFormViewModel: ObservableObject { self.defaultMetadataCollector = defaultMetadatCollector self.feedbackSender = feedbackSender self.source = source.rawValue + } - 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) - } + @MainActor + func updateCategories() 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?.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/Subscription/Feedback/UnifiedFeedbackRootView.swift b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackRootView.swift index bd11bbcbdf..10eafd44ba 100644 --- a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackRootView.swift +++ b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackRootView.swift @@ -46,6 +46,7 @@ struct UnifiedFeedbackRootView: View { } .onFirstAppear { Task { + await viewModel.updateCategories() await viewModel.process(action: .reportActions) } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 2b69935059..1c635b30a6 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -79,13 +79,17 @@ final class SubscriptionSettingsViewModel: ObservableObject { setupNotificationObservers() } - private var dateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .long - formatter.timeStyle = .none - return formatter + private var dateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .long +#if DEBUG + dateFormatter.timeStyle = .medium +#else + dateFormatter.timeStyle = .none +#endif + return dateFormatter }() - + func onFirstAppear() { Task { // Load initial state from the cache diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index a1c124e342..39aad55c62 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -495,6 +495,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { switch type { case .deadToken: Pixel.fire(pixel: .privacyProDeadTokenDetected) + case .subscriptionIsActive, .v1MigrationFailed, .v1MigrationSuccessful: // handled by the main app only + break } } let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: storePurchaseManager, @@ -526,7 +528,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { snoozeTimingStore: NetworkProtectionSnoozeTimingStore(userDefaults: .networkProtectionGroupDefaults), wireGuardInterface: DefaultWireGuardInterface(), keychainType: .dataProtection(.unspecified), - tokenProvider: subscriptionManager, + subscriptionManager: subscriptionManager, debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), providerEvents: Self.packetTunnelProviderEvents, settings: settings, From 4e887d44026510d5f04718b867ae4b6c29022faa Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 13 Jan 2025 16:03:40 +0000 Subject: [PATCH 37/41] BSK update with subscription cache bug fix --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/AppDependencyProvider.swift | 11 ++++++++++- .../NetworkProtectionPacketTunnelProvider.swift | 12 ++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e707102ffb..879a92716a 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/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 73f524163c..3e38a69754 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -141,8 +141,17 @@ final class AppDependencyProvider: DependencyProvider { return tokenContainer.accessToken } } +#if DEBUG + let cacheExpiration: Int = 1 +#else + let cacheExpiration: Int = 120 +#endif + let subscriptionCache = UserDefaultsCache(userDefaults: subscriptionUserDefaults, + key: UserDefaultsCacheKey.subscription, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: subscriptionEnvironment.serviceEnvironment.url) + baseURL: subscriptionEnvironment.serviceEnvironment.url, + subscriptionCache: subscriptionCache) let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionEndpointService) let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 39aad55c62..940c6ff396 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -487,8 +487,16 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } } +#if DEBUG + let cacheExpiration: Int = 1 +#else + let cacheExpiration: Int = 120 +#endif + let subscriptionCache = UserDefaultsCache(key: UserDefaultsCacheKey.subscription, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: subscriptionEnvironment.serviceEnvironment.url) + baseURL: subscriptionEnvironment.serviceEnvironment.url, + subscriptionCache: subscriptionCache) let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionEndpointService) let pixelHandler: SubscriptionManager.PixelHandler = { type in @@ -516,7 +524,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { notificationsPresenter.requestAuthorization() 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)") return .success(isNetworkProtectionEnabled) From 6f3a7adb97f3156cf25da0126a44f8b8a9133a0d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 Jan 2025 15:39:49 +0000 Subject: [PATCH 38/41] settings and init improvements --- DuckDuckGo.xcodeproj/project.pbxproj | 2 ++ .../xcshareddata/swiftpm/Package.resolved | 36 ++++++++++++++----- DuckDuckGo/AppDependencyProvider.swift | 11 +----- .../SubscriptionSettingsViewModel.swift | 18 +++++++--- ...etworkProtectionPacketTunnelProvider.swift | 11 +----- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f394d0eef5..8b57539b5e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3159,6 +3159,7 @@ F1CA3C381F045885005FADB3 /* PrivacyUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyUserDefaults.swift; sourceTree = ""; }; F1CA3C3A1F045B65005FADB3 /* Authenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authenticator.swift; sourceTree = ""; }; F1CB8EA21F26B39000A7171B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + F1CD81772D3678E80036B8F0 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; F1D43AFB2B99C56000BAB743 /* RootDebugViewController+VanillaBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RootDebugViewController+VanillaBrowser.swift"; sourceTree = ""; }; F1D477C51F2126CC0031ED49 /* OmniBarState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarState.swift; sourceTree = ""; }; F1D477C81F2139410031ED49 /* SmallOmniBarStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SmallOmniBarStateTests.swift; sourceTree = ""; }; @@ -4456,6 +4457,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F1CD81772D3678E80036B8F0 /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 879a92716a..477fe00fb4 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" : "092c3344c3b186d41effb13ac798f1cef8d7c70f" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -162,6 +153,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" : "82a4ae7170d98d8538ec77238b7eb8e7199ef2e8", + "version" : "1.3.1" + } + }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -225,6 +234,15 @@ "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" + } + }, { "identity" : "zipfoundation", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 3e38a69754..73f524163c 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -141,17 +141,8 @@ final class AppDependencyProvider: DependencyProvider { return tokenContainer.accessToken } } -#if DEBUG - let cacheExpiration: Int = 1 -#else - let cacheExpiration: Int = 120 -#endif - let subscriptionCache = UserDefaultsCache(userDefaults: subscriptionUserDefaults, - key: UserDefaultsCacheKey.subscription, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: subscriptionEnvironment.serviceEnvironment.url, - subscriptionCache: subscriptionCache) + baseURL: subscriptionEnvironment.serviceEnvironment.url) let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionEndpointService) let pixelHandler: SubscriptionManager.PixelHandler = { type in switch type { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 1c635b30a6..b8f455a2eb 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -116,10 +116,13 @@ final class SubscriptionSettingsViewModel: ObservableObject { private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionCachePolicy, loadingIndicator: Bool) async -> Bool { Logger.subscription.log("Fetch and update subscription details") - if loadingIndicator { displaySubscriptionLoader(true) } + DispatchQueue.main.async { + if loadingIndicator { self.displaySubscriptionLoader(true) } + } - if let subscription = try? await self.subscriptionManager.getSubscription(cachePolicy: cachePolicy) { - Task { @MainActor in + do { + let subscription = try await self.subscriptionManager.getSubscription(cachePolicy: cachePolicy) + DispatchQueue.main.async { self.state.subscriptionInfo = subscription if loadingIndicator { self.displaySubscriptionLoader(false) } } @@ -127,8 +130,14 @@ final class SubscriptionSettingsViewModel: ObservableObject { date: subscription.expiresOrRenewsAt, product: subscription.productId, billingPeriod: subscription.billingPeriod) + return true + } catch { + Logger.subscription.error("\(#function) error: \(error.localizedDescription)") + DispatchQueue.main.async { + if loadingIndicator { self.displaySubscriptionLoader(true) } + } + return false } - return true } func fetchAndUpdateAccountEmail(cachePolicy: SubscriptionCachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { @@ -175,6 +184,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { case .stripe: Task { await manageStripeSubscription() } default: + assertionFailure("Invalid subscription platform") return } } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 940c6ff396..375a7c2c43 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -486,17 +486,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { return tokenContainer.accessToken } } - -#if DEBUG - let cacheExpiration: Int = 1 -#else - let cacheExpiration: Int = 120 -#endif - let subscriptionCache = UserDefaultsCache(key: UserDefaultsCacheKey.subscription, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(cacheExpiration))) let subscriptionEndpointService = DefaultSubscriptionEndpointService(apiService: apiService, - baseURL: subscriptionEnvironment.serviceEnvironment.url, - subscriptionCache: subscriptionCache) + baseURL: subscriptionEnvironment.serviceEnvironment.url) let storePurchaseManager = DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionEndpointService) let pixelHandler: SubscriptionManager.PixelHandler = { type in From ef81d0983cf52bb01487520cdd101eb0ec1c973e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 20 Jan 2025 11:36:21 +0000 Subject: [PATCH 39/41] Subscription stuff renamed --- DuckDuckGo.xcodeproj/project.pbxproj | 32 +++++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 10 +++--- .../AppLifecycle/AppStates/Active.swift | 2 +- DuckDuckGo/AppServices/UNService.swift | 2 +- DuckDuckGo/AppShortcuts.swift | 2 +- .../Feedback/VPNMetadataCollector.swift | 2 +- DuckDuckGo/OldAppDelegate.swift | 4 +-- DuckDuckGo/SettingsViewModel.swift | 2 +- .../UnifiedFeedbackFormViewModel.swift | 8 ++--- ...etworkProtectionPacketTunnelProvider.swift | 2 +- 10 files changed, 49 insertions(+), 17 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4630714cf5..a39ffc275a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1183,6 +1183,10 @@ F15531922BF215ED0029ED04 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15531912BF215ED0029ED04 /* SubscriptionTestingUtilities */; }; F15531942BF215F60029ED04 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F15531932BF215F60029ED04 /* Subscription */; }; F15531962BF215F60029ED04 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15531952BF215F60029ED04 /* SubscriptionTestingUtilities */; }; + F15764C02D3E6AE60086AA21 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = F15764BF2D3E6AE60086AA21 /* Networking */; }; + F15764C22D3E6AF60086AA21 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = F15764C12D3E6AF60086AA21 /* Networking */; }; + F15764C42D3E6AF60086AA21 /* NetworkingTestingUtils in Frameworks */ = {isa = PBXBuildFile; productRef = F15764C32D3E6AF60086AA21 /* NetworkingTestingUtils */; }; + F15764C62D3E6B010086AA21 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = F15764C52D3E6B010086AA21 /* Networking */; }; F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */; }; F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C141E57336D00DEDCAF /* TabManager.swift */; }; @@ -3294,6 +3298,7 @@ F1B3DEC12D2D90BB00D33FA5 /* NetworkingTestingUtils in Frameworks */, F1AFDF822D2EE1D100DB313F /* PersistenceTestingUtils in Frameworks */, EEFAB4672A73C230008A38E4 /* NetworkProtectionTestUtils in Frameworks */, + F15764C02D3E6AE60086AA21 /* Networking in Frameworks */, 4BE67B072B96B9B0007335F7 /* Common in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3328,6 +3333,8 @@ 4BE67B012B96B741007335F7 /* Common in Frameworks */, F15531962BF215F60029ED04 /* SubscriptionTestingUtilities in Frameworks */, F15531942BF215F60029ED04 /* Subscription in Frameworks */, + F15764C42D3E6AF60086AA21 /* NetworkingTestingUtils in Frameworks */, + F15764C22D3E6AF60086AA21 /* Networking in Frameworks */, 4BE67B032B96B864007335F7 /* ContentBlocking in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3359,6 +3366,7 @@ 98424A962CED4F430071C7DB /* OHHTTPStubs in Frameworks */, 98424A972CED4F430071C7DB /* OHHTTPStubsSwift in Frameworks */, 98424A982CED4F430071C7DB /* ContentBlocking in Frameworks */, + F15764C62D3E6B010086AA21 /* Networking in Frameworks */, 98424AB22CEDD6150071C7DB /* BrowserServicesKit in Frameworks */, 98424A992CED4F430071C7DB /* SubscriptionTestingUtilities in Frameworks */, 98424A9A2CED4F430071C7DB /* Subscription in Frameworks */, @@ -7073,6 +7081,7 @@ F15531912BF215ED0029ED04 /* SubscriptionTestingUtilities */, F1B3DEC02D2D90BB00D33FA5 /* NetworkingTestingUtils */, F1AFDF812D2EE1D100DB313F /* PersistenceTestingUtils */, + F15764BF2D3E6AE60086AA21 /* Networking */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -7138,6 +7147,8 @@ 4BE67B022B96B864007335F7 /* ContentBlocking */, F15531932BF215F60029ED04 /* Subscription */, F15531952BF215F60029ED04 /* SubscriptionTestingUtilities */, + F15764C12D3E6AF60086AA21 /* Networking */, + F15764C32D3E6AF60086AA21 /* NetworkingTestingUtils */, ); productName = IntegrationTests; productReference = 85D33FCB25C97B6E002B91A6 /* IntegrationTests.xctest */; @@ -7210,6 +7221,7 @@ 98424AB32CEDD61C0071C7DB /* BrowserServicesKitTestsUtils */, F1B3DEC22D2D90D500D33FA5 /* NetworkingTestingUtils */, F1AFDF7F2D2EE10A00DB313F /* PersistenceTestingUtils */, + F15764C52D3E6B010086AA21 /* Networking */, ); productName = DuckDuckGoTests; productReference = 98424AA92CED4F430071C7DB /* WebViewUnitTests.xctest */; @@ -12304,6 +12316,26 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = SubscriptionTestingUtilities; }; + F15764BF2D3E6AE60086AA21 /* Networking */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Networking; + }; + F15764C12D3E6AF60086AA21 /* Networking */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Networking; + }; + F15764C32D3E6AF60086AA21 /* NetworkingTestingUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = NetworkingTestingUtils; + }; + F15764C52D3E6B010086AA21 /* Networking */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Networking; + }; F1AFDF7F2D2EE10A00DB313F /* PersistenceTestingUtils */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3f04c656a7..dcc66a1d8f 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" : "c51c7b960e047f6903daf4dadc09a8fe49648f01" + "revision" : "8bed092b8ca59d0d947b4a5b4da6daf2386cf3d5" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "0502ed7de4130bd8705daebaca9aeb20d3e62d15", - "version" : "7.5.0" + "revision" : "7958ddab724c26326333cae13fe81478290607fa", + "version" : "7.6.0" } }, { @@ -203,8 +203,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/sync_crypto", "state" : { - "revision" : "0c8bf3c0e75591bc366407b9d7a73a9fcfc7736f", - "version" : "0.3.0" + "revision" : "cc726cebb67367466bc31ced4784e16d44ac68d1", + "version" : "0.4.0" } }, { diff --git a/DuckDuckGo/AppLifecycle/AppStates/Active.swift b/DuckDuckGo/AppLifecycle/AppStates/Active.swift index 4c54c3ac2e..deb19cfab2 100644 --- a/DuckDuckGo/AppLifecycle/AppStates/Active.swift +++ b/DuckDuckGo/AppLifecycle/AppStates/Active.swift @@ -452,7 +452,7 @@ struct Active: AppState { @MainActor func presentNetworkProtectionStatusSettingsModal() { Task { - if await AppDependencyProvider.shared.subscriptionManager.isFeatureActive(.networkProtection) { + if await AppDependencyProvider.shared.subscriptionManager.isFeatureAvailableForUser(.networkProtection) { Task { @MainActor in (window.rootViewController as? MainViewController)?.segueToVPN() } diff --git a/DuckDuckGo/AppServices/UNService.swift b/DuckDuckGo/AppServices/UNService.swift index b770270ee8..a8509e825c 100644 --- a/DuckDuckGo/AppServices/UNService.swift +++ b/DuckDuckGo/AppServices/UNService.swift @@ -59,7 +59,7 @@ extension UNService: UNUserNotificationCenterDelegate { private func presentNetworkProtectionStatusSettingsModal() { Task { - if await AppDependencyProvider.shared.subscriptionManager.isFeatureActive(.networkProtection) { + if await AppDependencyProvider.shared.subscriptionManager.isFeatureAvailableForUser(.networkProtection) { Task { @MainActor in (window.rootViewController as? MainViewController)?.segueToVPN() } diff --git a/DuckDuckGo/AppShortcuts.swift b/DuckDuckGo/AppShortcuts.swift index c98abff185..3104d70341 100644 --- a/DuckDuckGo/AppShortcuts.swift +++ b/DuckDuckGo/AppShortcuts.swift @@ -24,7 +24,7 @@ extension UIApplication { func refreshVPNShortcuts(vpnFeatureVisibility: DefaultNetworkProtectionVisibility, subscriptionManager: any SubscriptionManager) async { guard vpnFeatureVisibility.shouldShowVPNShortcut(), - await AppDependencyProvider.shared.subscriptionManager.isFeatureActive(.networkProtection) + await AppDependencyProvider.shared.subscriptionManager.isFeatureAvailableForUser(.networkProtection) else { shortcutItems = nil return diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index 8fc6595584..c38fbe34e4 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -244,7 +244,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/OldAppDelegate.swift b/DuckDuckGo/OldAppDelegate.swift index 33c18400fb..de0995d233 100644 --- a/DuckDuckGo/OldAppDelegate.swift +++ b/DuckDuckGo/OldAppDelegate.swift @@ -1154,7 +1154,7 @@ final class OldAppDelegate: NSObject, UIApplicationDelegate, DDGApp { return } - if await AppDependencyProvider.shared.subscriptionManager.isFeatureActive(.networkProtection) { + if await AppDependencyProvider.shared.subscriptionManager.isFeatureAvailableForUser(.networkProtection) { let items = [ UIApplicationShortcutItem(type: AppDelegate.ShortcutKey.openVPNSettings, localizedTitle: UserText.netPOpenVPNQuickAction, @@ -1234,7 +1234,7 @@ extension OldAppDelegate: UNUserNotificationCenterDelegate { func presentNetworkProtectionStatusSettingsModal() { Task { - if await AppDependencyProvider.shared.subscriptionManager.isFeatureActive(.networkProtection) { + if await AppDependencyProvider.shared.subscriptionManager.isFeatureAvailableForUser(.networkProtection) { Task { @MainActor in (window?.rootViewController as? MainViewController)?.segueToVPN() } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 85f4b24e65..e4de818ea6 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -775,7 +775,7 @@ extension SettingsViewModel { state.subscription.hasActiveSubscription = subscription.isActive let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false) state.subscription.entitlements = features.compactMap({ feature in - if feature.enabled { + if feature.availableForUser { return feature.entitlement } else { return nil diff --git a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift index fb00c1c64b..eeb0bc4266 100644 --- a/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift +++ b/DuckDuckGo/Subscription/Feedback/UnifiedFeedbackFormViewModel.swift @@ -168,14 +168,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/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index fa94eabb9f..07107024cc 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -518,7 +518,7 @@ final class NetworkProtectionPacketTunnelProvider: 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("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)") return .success(isNetworkProtectionEnabled) } From b4196db434a93247bc45dc5a95ae17eae473c3eb Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 20 Jan 2025 11:41:24 +0000 Subject: [PATCH 40/41] 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 dcc66a1d8f..f18cbb66a9 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" : "c8faa3ae29839e0208970e71668a68e58c6a4d53" } }, { @@ -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" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "bea4d750913ef82c10cd06e791686501c8e648e4", - "version" : "7.6.0" + "revision" : "c52bd5d851b1f8f0482e82b8720852670f525497", + "version" : "8.1.0" } }, { From d045cce3cd02233ef074f10cbcaffb699938a2cb Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 23 Jan 2025 11:32:18 +0000 Subject: [PATCH 41/41] pr suggestions --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/AppLifecycle/AppStates/Launching.swift | 4 +++- .../ViewModel/SubscriptionSettingsViewModel.swift | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f18cbb66a9..2a6af9a009 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" : "c8faa3ae29839e0208970e71668a68e58c6a4d53" + "revision" : "ecf8c5d3cee2eab26c04da06979f3ff70ab2c6fe" } }, { diff --git a/DuckDuckGo/AppLifecycle/AppStates/Launching.swift b/DuckDuckGo/AppLifecycle/AppStates/Launching.swift index ea42d2375a..34f4b301c9 100644 --- a/DuckDuckGo/AppLifecycle/AppStates/Launching.swift +++ b/DuckDuckGo/AppLifecycle/AppStates/Launching.swift @@ -329,7 +329,9 @@ struct Launching: AppState { let url = URL.pixelUrl(forPixelNamed: pixelName) let apiHeaders = APIRequestV2.HeadersV2(additionalHeaders: headers) - guard let request = APIRequestV2(url: url, method: .get, queryItems: parameters, headers: apiHeaders) else { + guard let request = APIRequestV2(url: url, method: .get, + queryItems: parameters.map({ (key, value) in QueryItem(key: key, value: value) }), + headers: apiHeaders) else { onComplete(false, nil) return } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index b8f455a2eb..88795ed4de 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -142,7 +142,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { func fetchAndUpdateAccountEmail(cachePolicy: SubscriptionCachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool) async -> Bool { Logger.subscription.log("Fetch and update account email") - var tokensPolicy: TokensCachePolicy = .local + var tokensPolicy: AuthTokensCachePolicy = .local switch cachePolicy { case .reloadIgnoringLocalCacheData: tokensPolicy = .localForceRefresh