From ee75110226596c83570540efd1e85dc36f2fe3c6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 1 May 2024 09:16:40 +0100 Subject: [PATCH 01/41] BSK local --- DuckDuckGo.xcodeproj/project.pbxproj | 2 ++ .../xcshareddata/swiftpm/Package.resolved | 9 --------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 975b9f1315..a701309684 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -4024,6 +4024,7 @@ F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; + F1915CAE2BE10B9200FBE25B /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; @@ -6403,6 +6404,7 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( + F1915CAE2BE10B9200FBE25B /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 72e9ff06b4..a4c4905c48 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" : "2681b5271a4e0582f175771737617adb8a4d6e78", - "version" : "142.0.0" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From d0fd0956b320ce1f7e1e3f44529e938199b571e3 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 2 May 2024 09:27:50 +0100 Subject: [PATCH 02/41] AccountManager injected --- DuckDuckGo.xcodeproj/project.pbxproj | 6 + DuckDuckGo/Application/AppDelegate.swift | 44 +- ...erProtectionSubscriptionEventHandler.swift | 2 +- DuckDuckGo/Menus/MainMenu.swift | 28 +- DuckDuckGo/Menus/MainMenuActions.swift | 2 +- .../NavigationBar/View/MoreOptionsMenu.swift | 8 +- .../View/NavigationBarViewController.swift | 5 +- ...rkProtection+ConvenienceInitializers.swift | 2 +- .../NetworkProtectionAppEvents.swift | 2 +- .../NetworkProtectionNavBarButtonModel.swift | 2 +- ...NetworkProtectionIPCTunnelController.swift | 2 +- .../NetworkProtectionRemoteMessaging.swift | 2 +- ...rkProtectionSubscriptionEventHandler.swift | 2 +- .../Model/PreferencesSection.swift | 2 +- .../Model/PreferencesSidebarModel.swift | 4 +- .../Model/VPNPreferencesModel.swift | 2 +- .../View/PreferencesRootView.swift | 5 +- ...ntityTheftRestorationPagesUserScript.swift | 2 +- .../SubscriptionAppStoreRestorer.swift | 17 +- ...scriptionPagesUseSubscriptionFeature.swift | 485 ++++++++++++++++++ .../SubscriptionPagesUserScript.swift | 449 ---------------- DuckDuckGo/Tab/UserScripts/UserScripts.swift | 3 +- .../VPNMetadataCollector.swift | 3 +- .../NetworkProtectionFeatureVisibility.swift | 6 +- .../DebugMenu/DebugPurchaseModel.swift | 12 +- .../DebugPurchaseViewController.swift | 4 +- .../DebugMenu/SubscriptionDebugMenu.swift | 13 +- .../PreferencesSubscriptionModel.swift | 11 +- .../Model/ShareSubscriptionAccessModel.swift | 11 +- .../SubscriptionAccessViewController.swift | 6 +- 30 files changed, 596 insertions(+), 546 deletions(-) create mode 100644 DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a701309684..a5be9c9d6d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2543,6 +2543,8 @@ F116A7C32BD1924B00F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C22BD1924B00F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C72BD1925500F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C62BD1925500F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C92BD1929000F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C82BD1929000F3FCF7 /* PixelKitTestingUtilities */; }; + F119CCC42BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; + F119CCC52BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; @@ -4021,6 +4023,7 @@ EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+VPNAgentConvenienceInitializers.swift"; sourceTree = ""; }; EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = ""; }; EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; + F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; @@ -8120,6 +8123,7 @@ isa = PBXGroup; children = ( 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */, + F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */, F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */, F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */, ); @@ -10084,6 +10088,7 @@ 3706FC78293F65D500E42796 /* SecureVaultReporter.swift in Sources */, 3706FC79293F65D500E42796 /* NSImageExtensions.swift in Sources */, 3706FEBD293F6EFF00E42796 /* BWCommand.swift in Sources */, + F119CCC52BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, 3706FC7B293F65D500E42796 /* PasswordManagementViewController.swift in Sources */, 3706FC7C293F65D500E42796 /* ImportedBookmarks.swift in Sources */, 3706FC7D293F65D500E42796 /* NSMenuExtension.swift in Sources */, @@ -11119,6 +11124,7 @@ B6830961274CDE99004B46BB /* FireproofDomainsContainer.swift in Sources */, B687B7CC2947A1E9001DEA6F /* ExternalAppSchemeHandler.swift in Sources */, B65536AE2685E17200085A79 /* GeolocationService.swift in Sources */, + F119CCC42BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, 4B02198925E05FAC00ED7DEA /* FireproofingURLExtensions.swift in Sources */, 3168506D2AF3AD1D009A2828 /* WaitlistViewControllerPresenter.swift in Sources */, 7B1E819E27C8874900FF0E60 /* ContentOverlayPopover.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 2f93c17ac4..c38eb0b5b2 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -34,7 +34,6 @@ import ServiceManagement import SyncDataProviders import UserNotifications import Lottie - import NetworkProtection import Subscription @@ -86,10 +85,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { var privacyDashboardWindow: NSWindow? // Needs to be lazy as indirectly depends on AppDelegate - private lazy var networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler() + private let networkProtectionSubscriptionEventHandler: NetworkProtectionSubscriptionEventHandler #if DBP - private let dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler() + private let dataBrokerProtectionSubscriptionEventHandler: DataBrokerProtectionSubscriptionEventHandler #endif private var didFinishLaunching = false @@ -105,6 +104,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return firstLaunchDate >= Date.weekAgo } + public static let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + // swiftlint:disable:next function_body_length override init() { do { @@ -183,6 +184,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #else SubscriptionPurchaseEnvironment.current = .stripe #endif + + networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler(accountManager: AppDelegate.accountManager) +#if DBP + dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler(accountManager: AppDelegate.accountManager) +#endif } static func configurePixelKit() { @@ -261,10 +267,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { SubscriptionPurchaseEnvironment.currentServiceEnvironment = currentEnvironment Task { - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - if let token = accountManager.accessToken { + if let token = AppDelegate.accountManager.accessToken { _ = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) - _ = await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) + _ = await AppDelegate.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } } @@ -306,7 +311,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { networkProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() - NetworkProtectionAppEvents().applicationDidFinishLaunching() + let defaultNetworkProtectionVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) + NetworkProtectionAppEvents(featureVisibility: defaultNetworkProtectionVisibility).applicationDidFinishLaunching() UNUserNotificationCenter.current().delegate = self #if DBP @@ -326,7 +332,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { syncService?.initializeIfNeeded() syncService?.scheduler.notifyAppLifecycleEvent() - NetworkProtectionAppEvents().applicationDidBecomeActive() + let defaultNetworkProtectionVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) + NetworkProtectionAppEvents(featureVisibility: defaultNetworkProtectionVisibility).applicationDidBecomeActive() #if DBP DataBrokerProtectionAppEvents().applicationDidBecomeActive() @@ -565,21 +572,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } } -} - -func updateSubscriptionStatus() { - Task { - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - - guard let token = accountManager.accessToken else { return } + func updateSubscriptionStatus() { + Task { + guard let token = AppDelegate.accountManager.accessToken else { return } - if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { - if subscription.isActive { - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) + if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { + if subscription.isActive { + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) + } } - } - _ = await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) + _ = await AppDelegate.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) + } } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index 57dd6d5c33..34782e0da6 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -28,7 +28,7 @@ final class DataBrokerProtectionSubscriptionEventHandler { private let authRepository: AuthenticationRepository private let featureDisabler: DataBrokerProtectionFeatureDisabling - init(accountManager: AccountManaging = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)), + init(accountManager: AccountManaging, authRepository: AuthenticationRepository = KeychainAuthenticationData(), featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) { self.accountManager = accountManager diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 66e026dc1d..8101d5c0f0 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -540,7 +540,7 @@ import SubscriptionUI toggleBookmarksShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .bookmarks) toggleDownloadsShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .downloads) - if DefaultNetworkProtectionVisibility().isVPNVisible() { + if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isVPNVisible() { toggleNetworkProtectionShortcutMenuItem.isHidden = false toggleNetworkProtectionShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .networkProtection) } else { @@ -622,21 +622,19 @@ import SubscriptionUI let currentEnvironmentWrapper = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: SubscriptionPurchaseEnvironment.ServiceEnvironment.default) let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false) - SubscriptionDebugMenu( - currentEnvironment: { currentEnvironmentWrapper.wrappedValue.rawValue }, - updateEnvironment: { - guard let newEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: $0) else { return } - currentEnvironmentWrapper.wrappedValue = newEnvironment - SubscriptionPurchaseEnvironment.currentServiceEnvironment = newEnvironment - VPNSettings(defaults: .netP).selectedEnvironment = newEnvironment == .staging ? .staging : .production - }, - isInternalTestingEnabled: { isInternalTestingWrapper.wrappedValue }, - updateInternalTestingFlag: { isInternalTestingWrapper.wrappedValue = $0 }, - currentViewController: { - WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + SubscriptionDebugMenu(currentEnvironment: { currentEnvironmentWrapper.wrappedValue.rawValue }, + updateEnvironment: { + guard let newEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: $0) else { return } + currentEnvironmentWrapper.wrappedValue = newEnvironment + SubscriptionPurchaseEnvironment.currentServiceEnvironment = newEnvironment + VPNSettings(defaults: .netP).selectedEnvironment = newEnvironment == .staging ? .staging : .production }, - subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs) - ) + isInternalTestingEnabled: { isInternalTestingWrapper.wrappedValue }, + updateInternalTestingFlag: { isInternalTestingWrapper.wrappedValue = $0 }, + currentViewController: { + WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + }, + accountManager: AppDelegate.accountManager) NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) } diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index eca7f8004d..935189c20c 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -752,7 +752,7 @@ extension MainViewController { /// Clears the PrivacyPro state to make testing easier. /// private func clearPrivacyProState() { - AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).signOut() + AppDelegate.accountManager.signOut() resetThankYouModalChecks(nil) UserDefaults.netP.networkProtectionEntitlementsExpired = false diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 5d5a841f87..21b5a04dcc 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -57,7 +57,7 @@ final class MoreOptionsMenu: NSMenu { private let passwordManagerCoordinator: PasswordManagerCoordinating private let internalUserDecider: InternalUserDecider private lazy var sharingMenu: NSMenu = SharingMenu(title: UserText.shareMenuItem) - private lazy var accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + private let accountManager: AccountManager private let networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility @@ -68,15 +68,17 @@ final class MoreOptionsMenu: NSMenu { init(tabCollectionViewModel: TabCollectionViewModel, emailManager: EmailManager = EmailManager(), passwordManagerCoordinator: PasswordManagerCoordinator, - networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), + networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), sharingMenu: NSMenu? = nil, - internalUserDecider: InternalUserDecider) { + internalUserDecider: InternalUserDecider, + accountManager: AccountManager) { self.tabCollectionViewModel = tabCollectionViewModel self.emailManager = emailManager self.passwordManagerCoordinator = passwordManagerCoordinator self.networkProtectionFeatureVisibility = networkProtectionFeatureVisibility self.internalUserDecider = internalUserDecider + self.accountManager = accountManager super.init(title: "") diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 6b81061e5e..7e9866bc5f 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -268,7 +268,8 @@ final class NavigationBarViewController: NSViewController { let internalUserDecider = NSApp.delegateTyped.internalUserDecider let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: PasswordManagerCoordinator.shared, - internalUserDecider: internalUserDecider) + internalUserDecider: internalUserDecider, + accountManager: AppDelegate.accountManager) menu.actionDelegate = self let location = NSPoint(x: -menu.size.width + sender.bounds.width, y: sender.bounds.height + 4) menu.popUp(positioning: nil, at: location, in: sender) @@ -897,7 +898,7 @@ extension NavigationBarViewController: NSMenuDelegate { let isPopUpWindow = view.window?.isPopUpWindow ?? false - if !isPopUpWindow && DefaultNetworkProtectionVisibility().isVPNVisible() { + if !isPopUpWindow && DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isVPNVisible() { let networkProtectionTitle = LocalPinningManager.shared.shortcutTitle(for: .networkProtection) menu.addItem(withTitle: networkProtectionTitle, action: #selector(toggleNetworkProtectionPanelPinning), keyEquivalent: "N") } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index 9898138c7f..ef1df6ec7c 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -53,7 +53,7 @@ extension NetworkProtectionKeychainTokenStore { } convenience init(isSubscriptionEnabled: Bool) { - let accessTokenProvider: () -> String? = { AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).accessToken } + let accessTokenProvider: () -> String? = { AppDelegate.accountManager.accessToken } self.init(keychainType: .default, errorEvents: .networkProtectionAppDebugEvents, isSubscriptionEnabled: isSubscriptionEnabled, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift index 25be11ba61..c267567944 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift @@ -50,7 +50,7 @@ final class NetworkProtectionAppEvents { private let featureDisabler: NetworkProtectionFeatureDisabling private let defaults: UserDefaults - init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), + init(featureVisibility: NetworkProtectionFeatureVisibility, featureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), defaults: UserDefaults = .netP) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index e4742d3521..b6b0b44e82 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -71,7 +71,7 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { init(popoverManager: NetPPopoverManager, pinningManager: PinningManager = LocalPinningManager.shared, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), statusReporter: NetworkProtectionStatusReporter, iconProvider: IconProvider = NavigationBarIconProvider()) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift index a39fcb6de4..06f120e264 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -53,7 +53,7 @@ final class NetworkProtectionIPCTunnelController { private let pixelKit: PixelFiring? private let errorRecorder: VPNOperationErrorRecorder - init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), + init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), loginItemsManager: LoginItemsManaging = LoginItemsManager(), ipcClient: NetworkProtectionIPCClient, pixelKit: PixelFiring? = PixelKit.shared, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift index 6daeb90569..5e6b98ab96 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift @@ -55,7 +55,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.networkProtection(), waitlistStorage: WaitlistStorage = WaitlistKeychainStore(waitlistIdentifier: "networkprotection", keychainAppGroup: Bundle.main.appGroup(bundle: .netP)), waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), - networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), + networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), minimumRefreshInterval: TimeInterval, userDefaults: UserDefaults = .standard ) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 31795df3a3..f8a1ea0016 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -31,7 +31,7 @@ final class NetworkProtectionSubscriptionEventHandler { private let userDefaults: UserDefaults private var cancellables = Set() - init(accountManager: AccountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)), + init(accountManager: AccountManager, networkProtectionTokenStorage: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), userDefaults: UserDefaults = .netP) { diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 777b1a54dd..f1bab8d56b 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -63,7 +63,7 @@ struct PreferencesSection: Hashable, Identifiable { var shouldHidePrivacyProDueToNoProducts = SubscriptionPurchaseEnvironment.current == .appStore && SubscriptionPurchaseEnvironment.canPurchase == false - if AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).isUserAuthenticated { + if AppDelegate.accountManager.isUserAuthenticated { shouldHidePrivacyProDueToNoProducts = false } diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index 1bcd647e37..f85a5338be 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -42,7 +42,7 @@ final class PreferencesSidebarModel: ObservableObject { tabSwitcherTabs: [Tab.TabContent], privacyConfigurationManager: PrivacyConfigurationManaging, syncService: DDGSyncing, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility() + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) ) { self.loadSections = loadSections self.tabSwitcherTabs = tabSwitcherTabs @@ -77,7 +77,7 @@ final class PreferencesSidebarModel: ObservableObject { tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, syncService: DDGSyncing, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), includeDuckPlayer: Bool, userDefaults: UserDefaults = .netP ) { diff --git a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift index c79ef7a8e2..3c9bf99887 100644 --- a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift @@ -59,7 +59,7 @@ final class VPNPreferencesModel: ObservableObject { private var onboardingStatus: OnboardingStatus { didSet { - showUninstallVPN = DefaultNetworkProtectionVisibility().isInstalled + showUninstallVPN = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isInstalled } } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index 8cecf62db6..efe249804a 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -167,7 +167,8 @@ enum Preferences { return } - await SubscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) + let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: AppDelegate.accountManager) + await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) } } }, @@ -177,7 +178,7 @@ enum Preferences { return PreferencesSubscriptionModel(openURLHandler: openURL, userEventHandler: handleUIEvent, sheetActionHandler: sheetActionHandler, - subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + accountManager: AppDelegate.accountManager) } } } diff --git a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift index a613d64566..8b63cc4b14 100644 --- a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift @@ -92,7 +92,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).accessToken { + if let accessToken = AppDelegate.accountManager.accessToken { return ["token": accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 35661ff131..d01d5990b9 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -25,8 +25,14 @@ import PixelKit @available(macOS 12.0, *) struct SubscriptionAppStoreRestorer { + let accountManager: AccountManager + + public init(accountManager: AccountManager) { + self.accountManager = accountManager + } + // swiftlint:disable:next cyclomatic_complexity - static func restoreAppStoreSubscription(mainViewController: MainViewController, windowController: MainWindowController) async { + func restoreAppStoreSubscription(mainViewController: MainViewController, windowController: MainWindowController) async { let progressViewController = await ProgressViewController(title: UserText.restoringSubscriptionTitle) defer { @@ -63,7 +69,8 @@ struct SubscriptionAppStoreRestorer { } } - let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: accountManager) + let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: @@ -78,13 +85,13 @@ struct SubscriptionAppStoreRestorer { switch error { case .missingAccountOrTransactions: SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) - await windowController.showSubscriptionNotFoundAlert() +// await windowController.showSubscriptionNotFoundAlert() case .subscriptionExpired: SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionExpired) - await windowController.showSubscriptionInactiveAlert() +// await windowController.showSubscriptionInactiveAlert() case .pastTransactionAuthenticationError, .failedToObtainAccessToken, .failedToFetchAccountDetails, .failedToFetchSubscriptionDetails: SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) - await windowController.showSomethingWentWrongAlert() +// await windowController.showSomethingWentWrongAlert() } } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift new file mode 100644 index 0000000000..f5f532a583 --- /dev/null +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -0,0 +1,485 @@ +// +// SubscriptionPagesUseSubscriptionFeature.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import BrowserServicesKit +import Common +import Combine +import Navigation +import WebKit +import UserScript +import Subscription +import SubscriptionUI +import PixelKit + +/// Use Subscription sub-feature +final class SubscriptionPagesUseSubscriptionFeature: Subfeature { + weak var broker: UserScriptMessageBroker? + var featureName = "useSubscription" + var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ + .exact(hostname: "duckduckgo.com"), + .exact(hostname: "abrown.duckduckgo.com") + ]) + + @MainActor + var window: NSWindow? { + WindowControllersManager.shared.lastKeyMainWindowController?.window + } + let accountManager: AccountManager + let stripePurchaseFlow: StripePurchaseFlow + + public init(accountManager: AccountManager) { + self.accountManager = accountManager + self.stripePurchaseFlow = StripePurchaseFlow(accountManager: accountManager) + } + + func with(broker: UserScriptMessageBroker) { + self.broker = broker + } + + struct Handlers { + static let getSubscription = "getSubscription" + static let setSubscription = "setSubscription" + static let backToSettings = "backToSettings" + static let getSubscriptionOptions = "getSubscriptionOptions" + static let subscriptionSelected = "subscriptionSelected" + static let activateSubscription = "activateSubscription" + static let featureSelected = "featureSelected" + static let completeStripePayment = "completeStripePayment" + // Pixels related events + static let subscriptionsMonthlyPriceClicked = "subscriptionsMonthlyPriceClicked" + static let subscriptionsYearlyPriceClicked = "subscriptionsYearlyPriceClicked" + static let subscriptionsUnknownPriceClicked = "subscriptionsUnknownPriceClicked" + static let subscriptionsAddEmailSuccess = "subscriptionsAddEmailSuccess" + static let subscriptionsWelcomeFaqClicked = "subscriptionsWelcomeFaqClicked" + } + + // swiftlint:disable:next cyclomatic_complexity + func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { + switch methodName { + case Handlers.getSubscription: return getSubscription + case Handlers.setSubscription: return setSubscription + case Handlers.backToSettings: return backToSettings + case Handlers.getSubscriptionOptions: return getSubscriptionOptions + case Handlers.subscriptionSelected: return subscriptionSelected + case Handlers.activateSubscription: return activateSubscription + case Handlers.featureSelected: return featureSelected + case Handlers.completeStripePayment: return completeStripePayment + // Pixel related events + case Handlers.subscriptionsMonthlyPriceClicked: return subscriptionsMonthlyPriceClicked + case Handlers.subscriptionsYearlyPriceClicked: return subscriptionsYearlyPriceClicked + case Handlers.subscriptionsUnknownPriceClicked: return subscriptionsUnknownPriceClicked + case Handlers.subscriptionsAddEmailSuccess: return subscriptionsAddEmailSuccess + case Handlers.subscriptionsWelcomeFaqClicked: return subscriptionsWelcomeFaqClicked + default: + return nil + } + } + + struct Subscription: Encodable { + let token: String + } + + /// Values that the Frontend can use to determine the current state. + struct SubscriptionValues: Codable { + enum CodingKeys: String, CodingKey { + case token + } + let token: String + } + + func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + if let authToken = accountManager.authToken, accountManager.accessToken != nil { + return Subscription(token: authToken) + } else { + return Subscription(token: "") + } + } + + func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) + + guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + return nil + } + + let authToken = subscriptionValues.token + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + + return nil + } + + func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { + if let accessToken = accountManager.accessToken, + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) + } + + return nil + } + + func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard DefaultSubscriptionFeatureAvailability().isSubscriptionPurchaseAllowed else { return SubscriptionOptions.empty } + + if SubscriptionPurchaseEnvironment.current == .appStore { + if #available(macOS 12.0, *) { + let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + switch await appStorePurchaseFlow.subscriptionOptions() { + case .success(let subscriptionOptions): + return subscriptionOptions + case .failure: + break + } + } + } else if SubscriptionPurchaseEnvironment.current == .stripe { + switch await stripePurchaseFlow.subscriptionOptions() { + case .success(let subscriptionOptions): + return subscriptionOptions + case .failure: + break + } + } + + return SubscriptionOptions.empty + } + + // swiftlint:disable:next function_body_length cyclomatic_complexity + func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + + PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndCount) + struct SubscriptionSelection: Decodable { + let id: String + } + + let message = original + + if SubscriptionPurchaseEnvironment.current == .appStore { + if #available(macOS 12.0, *) { + let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + let progressViewController = await ProgressViewController(title: UserText.purchasingSubscriptionTitle) + + defer { + Task { + await mainViewController?.dismiss(progressViewController) + } + } + + guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") + SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + return nil + } + + os_log(.info, log: .subscription, "[Purchase] Starting purchase for: %{public}s", subscriptionSelection.id) + + await mainViewController?.presentAsSheet(progressViewController) + + // Check for active subscriptions + if await PurchaseManager.hasActiveSubscription() { + PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) + os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") + SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) + await showSubscriptionFoundAlert(originalMessage: message) + return nil + } + + let emailAccessToken = try? EmailManager().getToken() + let purchaseTransactionJWS: String + let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + + os_log(.info, log: .subscription, "[Purchase] Purchasing") + switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + case .success(let transactionJWS): + purchaseTransactionJWS = transactionJWS + case .failure(let error): + switch error { + case .noProductsFound: + SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + case .activeSubscriptionAlreadyPresent: + SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) + case .authenticatingWithTransactionFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + case .accountCreationFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + case .purchaseFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) + case .cancelledByUser: + SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) + case .missingEntitlements: + SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) + } + + if error != .cancelledByUser { + await showSomethingWentWrongAlert() + } + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) + return nil + } + + await progressViewController.updateTitleText(UserText.completingPurchaseTitle) + + os_log(.info, log: .subscription, "[Purchase] Completing purchase") + + switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { + case .success(let purchaseUpdate): + os_log(.info, log: .subscription, "[Purchase] Purchase complete") + PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .unique) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) + case .failure(let error): + switch error { + case .noProductsFound: + SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + case .activeSubscriptionAlreadyPresent: + SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) + case .authenticatingWithTransactionFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + case .accountCreationFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + case .purchaseFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) + case .cancelledByUser: + SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) + case .missingEntitlements: + SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) + DispatchQueue.main.async { + NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) + } + return nil + } + + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) + } + } + } else if SubscriptionPurchaseEnvironment.current == .stripe { + let emailAccessToken = try? EmailManager().getToken() + + let result = await stripePurchaseFlow.prepareSubscriptionPurchase(emailAccessToken: emailAccessToken) + + switch result { + case .success(let success): + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: success) + case .failure(let error): + await showSomethingWentWrongAlert() + + switch error { + case .noProductsFound: + SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + case .accountCreationFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + } + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) + } + } + + return nil + } + + func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseOfferPageEntry) + guard let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController, + let windowControllerManager = await WindowControllersManager.shared.lastKeyMainWindowController else { + return nil + } + let message = original + + let actionHandlers = SubscriptionAccessActionHandlers(restorePurchases: { + if #available(macOS 12.0, *) { + Task { @MainActor in + let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: self.accountManager) + await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) + message.webView?.reload() + } + } + }, openURLHandler: { url in + DispatchQueue.main.async { + WindowControllersManager.shared.showTab(with: .subscription(url)) + } + }, uiActionHandler: { event in + switch event { + case .activateAddEmailClick: + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) + default: + break + } + }) + + let subscriptionAccessViewController = await SubscriptionAccessViewController(accountManager: accountManager, actionHandlers: actionHandlers) + await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.presentAsSheet(subscriptionAccessViewController) + + return nil + } + + func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct FeatureSelection: Codable { + let feature: String + } + + guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") + return nil + } + + guard let subscriptionFeatureName = SubscriptionFeatureName(rawValue: featureSelection.feature) else { + assertionFailure("SubscriptionPagesUserScript: feature name does not matches mapping") + return nil + } + + switch subscriptionFeatureName { + case .privateBrowsing: + NotificationCenter.default.post(name: .openPrivateBrowsing, object: self, userInfo: nil) + case .privateSearch: + NotificationCenter.default.post(name: .openPrivateSearch, object: self, userInfo: nil) + case .emailProtection: + NotificationCenter.default.post(name: .openEmailProtection, object: self, userInfo: nil) + case .appTrackingProtection: + NotificationCenter.default.post(name: .openAppTrackingProtection, object: self, userInfo: nil) + case .vpn: + PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .unique) + NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) + case .personalInformationRemoval: + PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .unique) + NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) + await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) + case .identityTheftRestoration: + PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) + await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) + } + + return nil + } + + func completeStripePayment(params: Any, original: WKScriptMessage) async throws -> Encodable? { + let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + let progressViewController = await ProgressViewController(title: UserText.completingPurchaseTitle) + + await mainViewController?.presentAsSheet(progressViewController) + await stripePurchaseFlow.completeSubscriptionPurchase() + await mainViewController?.dismiss(progressViewController) + + PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) + return [String: String]() // cannot be nil, the web app expect something back before redirecting the user to the final page + } + + // MARK: Pixel related actions + + func subscriptionsMonthlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProOfferMonthlyPriceClick) + return nil + } + + func subscriptionsYearlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProOfferYearlyPriceClick) + return nil + } + + func subscriptionsUnknownPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + // Not used + return nil + } + + func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .unique) + return nil + } + + func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .unique) + return nil + } + + // MARK: Push actions + + enum SubscribeActionName: String { + case onPurchaseUpdate + } + + @MainActor + func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) async { + pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) + } + + func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { + guard let broker else { + assertionFailure("Cannot continue without broker instance") + return + } + + broker.push(method: method.rawValue, params: params, for: self, into: webView) + } + + // MARK: - UI interactions + + @MainActor + func showSomethingWentWrongAlert() { + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) + guard let window else { return } + + window.show(.somethingWentWrongAlert()) + } + + @MainActor + func showSubscriptionNotFoundAlert() { + guard let window else { return } + + window.show(.subscriptionNotFoundAlert(), firstButtonAction: { + WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) + }) + } + + @MainActor + func showSubscriptionInactiveAlert() { + guard let window else { return } + + window.show(.subscriptionInactiveAlert(), firstButtonAction: { + WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) + }) + } + + @MainActor + func showSubscriptionFoundAlert(originalMessage: WKScriptMessage) { + guard let window else { return } + + window.show(.subscriptionFoundAlert(), firstButtonAction: { + if #available(macOS 12.0, *) { + Task { + let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: self.accountManager) + let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() + switch result { + case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) + case .failure: break + } + originalMessage.webView?.reload() + } + } + }) + } +} diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index bee8634adc..c828b22456 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -72,452 +72,3 @@ extension SubscriptionPagesUserScript: WKScriptMessageHandler { // unsupported } } - -/// -/// Use Subscription sub-feature -/// -final class SubscriptionPagesUseSubscriptionFeature: Subfeature { - weak var broker: UserScriptMessageBroker? - var featureName = "useSubscription" - var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ - .exact(hostname: "duckduckgo.com"), - .exact(hostname: "abrown.duckduckgo.com") - ]) - - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - - func with(broker: UserScriptMessageBroker) { - self.broker = broker - } - - struct Handlers { - static let getSubscription = "getSubscription" - static let setSubscription = "setSubscription" - static let backToSettings = "backToSettings" - static let getSubscriptionOptions = "getSubscriptionOptions" - static let subscriptionSelected = "subscriptionSelected" - static let activateSubscription = "activateSubscription" - static let featureSelected = "featureSelected" - static let completeStripePayment = "completeStripePayment" - // Pixels related events - static let subscriptionsMonthlyPriceClicked = "subscriptionsMonthlyPriceClicked" - static let subscriptionsYearlyPriceClicked = "subscriptionsYearlyPriceClicked" - static let subscriptionsUnknownPriceClicked = "subscriptionsUnknownPriceClicked" - static let subscriptionsAddEmailSuccess = "subscriptionsAddEmailSuccess" - static let subscriptionsWelcomeFaqClicked = "subscriptionsWelcomeFaqClicked" - } - - // swiftlint:disable:next cyclomatic_complexity - func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - switch methodName { - case Handlers.getSubscription: return getSubscription - case Handlers.setSubscription: return setSubscription - case Handlers.backToSettings: return backToSettings - case Handlers.getSubscriptionOptions: return getSubscriptionOptions - case Handlers.subscriptionSelected: return subscriptionSelected - case Handlers.activateSubscription: return activateSubscription - case Handlers.featureSelected: return featureSelected - case Handlers.completeStripePayment: return completeStripePayment - // Pixel related events - case Handlers.subscriptionsMonthlyPriceClicked: return subscriptionsMonthlyPriceClicked - case Handlers.subscriptionsYearlyPriceClicked: return subscriptionsYearlyPriceClicked - case Handlers.subscriptionsUnknownPriceClicked: return subscriptionsUnknownPriceClicked - case Handlers.subscriptionsAddEmailSuccess: return subscriptionsAddEmailSuccess - case Handlers.subscriptionsWelcomeFaqClicked: return subscriptionsWelcomeFaqClicked - default: - return nil - } - } - - struct Subscription: Encodable { - let token: String - } - - /// Values that the Frontend can use to determine the current state. - struct SubscriptionValues: Codable { - enum CodingKeys: String, CodingKey { - case token - } - let token: String - } - - func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let authToken = accountManager.authToken, accountManager.accessToken != nil { - return Subscription(token: authToken) - } else { - return Subscription(token: "") - } - } - - func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) - - guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") - return nil - } - - let authToken = subscriptionValues.token - if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAuthToken(token: authToken) - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) - } - - return nil - } - - func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = accountManager.accessToken, - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) - } - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) - } - - return nil - } - - func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard DefaultSubscriptionFeatureAvailability().isSubscriptionPurchaseAllowed else { return SubscriptionOptions.empty } - - if SubscriptionPurchaseEnvironment.current == .appStore { - if #available(macOS 12.0, *) { - switch await AppStorePurchaseFlow.subscriptionOptions() { - case .success(let subscriptionOptions): - return subscriptionOptions - case .failure: - break - } - } - } else if SubscriptionPurchaseEnvironment.current == .stripe { - switch await StripePurchaseFlow.subscriptionOptions() { - case .success(let subscriptionOptions): - return subscriptionOptions - case .failure: - break - } - } - - return SubscriptionOptions.empty - } - - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - - // swiftlint:disable:next function_body_length cyclomatic_complexity - func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndCount) - struct SubscriptionSelection: Decodable { - let id: String - } - - let message = original - - if SubscriptionPurchaseEnvironment.current == .appStore { - if #available(macOS 12.0, *) { - let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController - let progressViewController = await ProgressViewController(title: UserText.purchasingSubscriptionTitle) - - defer { - Task { - await mainViewController?.dismiss(progressViewController) - } - } - - guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) - return nil - } - - os_log(.info, log: .subscription, "[Purchase] Starting purchase for: %{public}s", subscriptionSelection.id) - - await mainViewController?.presentAsSheet(progressViewController) - - // Check for active subscriptions - if await PurchaseManager.hasActiveSubscription() { - PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) - os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") - SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) - await WindowControllersManager.shared.lastKeyMainWindowController?.showSubscriptionFoundAlert(originalMessage: message) - return nil - } - - let emailAccessToken = try? EmailManager().getToken() - let purchaseTransactionJWS: String - - os_log(.info, log: .subscription, "[Purchase] Purchasing") - switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken, subscriptionAppGroup: subscriptionAppGroup) { - case .success(let transactionJWS): - purchaseTransactionJWS = transactionJWS - case .failure(let error): - switch error { - case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) - case .activeSubscriptionAlreadyPresent: - SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) - case .authenticatingWithTransactionFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) - case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) - case .purchaseFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) - case .cancelledByUser: - SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) - case .missingEntitlements: - SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) - } - - if error != .cancelledByUser { - await WindowControllersManager.shared.lastKeyMainWindowController?.showSomethingWentWrongAlert() - } - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) - return nil - } - - await progressViewController.updateTitleText(UserText.completingPurchaseTitle) - - os_log(.info, log: .subscription, "[Purchase] Completing purchase") - - switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, subscriptionAppGroup: subscriptionAppGroup) { - case .success(let purchaseUpdate): - os_log(.info, log: .subscription, "[Purchase] Purchase complete") - PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .unique) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) - case .failure(let error): - switch error { - case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) - case .activeSubscriptionAlreadyPresent: - SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) - case .authenticatingWithTransactionFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) - case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) - case .purchaseFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) - case .cancelledByUser: - SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) - case .missingEntitlements: - SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) - DispatchQueue.main.async { - NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) - } - return nil - } - - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) - } - } - } else if SubscriptionPurchaseEnvironment.current == .stripe { - let emailAccessToken = try? EmailManager().getToken() - - let result = await StripePurchaseFlow.prepareSubscriptionPurchase(emailAccessToken: emailAccessToken, subscriptionAppGroup: subscriptionAppGroup) - - switch result { - case .success(let success): - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: success) - case .failure(let error): - await WindowControllersManager.shared.lastKeyMainWindowController?.showSomethingWentWrongAlert() - - switch error { - case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) - case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) - } - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) - } - } - - return nil - } - - func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseOfferPageEntry) - guard let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController, - let windowControllerManager = await WindowControllersManager.shared.lastKeyMainWindowController else { - return nil - } - let message = original - - let actionHandlers = SubscriptionAccessActionHandlers(restorePurchases: { - if #available(macOS 12.0, *) { - Task { @MainActor in - await SubscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) - message.webView?.reload() - } - } - }, openURLHandler: { url in - DispatchQueue.main.async { - WindowControllersManager.shared.showTab(with: .subscription(url)) - } - }, uiActionHandler: { event in - switch event { - case .activateAddEmailClick: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) - default: - break - } - }) - - let vc = await SubscriptionAccessViewController(accountManager: accountManager, actionHandlers: actionHandlers, subscriptionAppGroup: subscriptionAppGroup) - await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.presentAsSheet(vc) - - return nil - } - - func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - struct FeatureSelection: Codable { - let feature: String - } - - guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") - return nil - } - - guard let subscriptionFeatureName = SubscriptionFeatureName(rawValue: featureSelection.feature) else { - assertionFailure("SubscriptionPagesUserScript: feature name does not matches mapping") - return nil - } - - switch subscriptionFeatureName { - case .privateBrowsing: - NotificationCenter.default.post(name: .openPrivateBrowsing, object: self, userInfo: nil) - case .privateSearch: - NotificationCenter.default.post(name: .openPrivateSearch, object: self, userInfo: nil) - case .emailProtection: - NotificationCenter.default.post(name: .openEmailProtection, object: self, userInfo: nil) - case .appTrackingProtection: - NotificationCenter.default.post(name: .openAppTrackingProtection, object: self, userInfo: nil) - case .vpn: - PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .unique) - NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) - case .personalInformationRemoval: - PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .unique) - NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) - await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) - case .identityTheftRestoration: - PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) - await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) - } - - return nil - } - - func completeStripePayment(params: Any, original: WKScriptMessage) async throws -> Encodable? { - let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController - let progressViewController = await ProgressViewController(title: UserText.completingPurchaseTitle) - - await mainViewController?.presentAsSheet(progressViewController) - await StripePurchaseFlow.completeSubscriptionPurchase(subscriptionAppGroup: subscriptionAppGroup) - await mainViewController?.dismiss(progressViewController) - - PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) - return [String: String]() // cannot be nil, the web app expect something back before redirecting the user to the final page - } - - // MARK: Pixel related actions - - func subscriptionsMonthlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProOfferMonthlyPriceClick) - return nil - } - - func subscriptionsYearlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProOfferYearlyPriceClick) - return nil - } - - func subscriptionsUnknownPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - // Not used - return nil - } - - func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .unique) - return nil - } - - func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .unique) - return nil - } - - // MARK: Push actions - - enum SubscribeActionName: String { - case onPurchaseUpdate - } - - @MainActor - func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) async { - pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) - } - - func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { - guard let broker else { - assertionFailure("Cannot continue without broker instance") - return - } - - broker.push(method: method.rawValue, params: params, for: self, into: webView) - } -} - -extension MainWindowController { - - @MainActor - func showSomethingWentWrongAlert() { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) - guard let window else { return } - - window.show(.somethingWentWrongAlert()) - } - - @MainActor - func showSubscriptionNotFoundAlert() { - guard let window else { return } - - window.show(.subscriptionNotFoundAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) - } - - @MainActor - func showSubscriptionInactiveAlert() { - guard let window else { return } - - window.show(.subscriptionInactiveAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) - } - - @MainActor - func showSubscriptionFoundAlert(originalMessage: WKScriptMessage) { - guard let window else { return } - - window.show(.subscriptionFoundAlert(), firstButtonAction: { - if #available(macOS 12.0, *) { - Task { - let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - switch result { - case .success: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) - case .failure: break - } - originalMessage.webView?.reload() - } - } - }) - } -} diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index 1bc9443977..93d6071fed 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -50,6 +50,7 @@ final class UserScripts: UserScriptsProvider { clickToLoadScript = ClickToLoadUserScript(scriptSourceProvider: sourceProvider) contentBlockerRulesScript = ContentBlockerRulesUserScript(configuration: sourceProvider.contentBlockerRulesConfig!) surrogatesScript = SurrogatesUserScript(configuration: sourceProvider.surrogatesConfig!) + let isGPCEnabled = WebTrackingProtectionPreferences.shared.isGPCEnabled let privacyConfig = sourceProvider.privacyConfigurationManager.privacyConfig let sessionKey = sourceProvider.sessionKey ?? "" @@ -92,7 +93,7 @@ final class UserScripts: UserScriptsProvider { } if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature()) + subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature(accountManager: AppDelegate.accountManager)) userScripts.append(subscriptionPagesUserScript) identityTheftRestorationPagesUserScript.registerSubfeature(delegate: IdentityTheftRestorationPagesFeature()) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index 700149aad7..6c57d1c604 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -302,8 +302,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - + let accountManager = AppDelegate.accountManager let hasVPNEntitlement = (try? await accountManager.hasEntitlement(for: .networkProtection).get()) ?? false return .init( diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 6956ba22ae..feaeac4280 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -46,7 +46,6 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let privacyConfigurationManager: PrivacyConfigurationManaging private let defaults: UserDefaults - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let accountManager: AccountManager init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, @@ -54,14 +53,15 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { featureOverrides: WaitlistBetaOverriding = DefaultWaitlistBetaOverrides(), featureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), defaults: UserDefaults = .netP, - log: OSLog = .networkProtection) { + log: OSLog = .networkProtection, + accountManager: AccountManager) { self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionFeatureActivation = networkProtectionFeatureActivation self.featureDisabler = featureDisabler self.featureOverrides = featureOverrides self.defaults = defaults - self.accountManager = AccountManager(subscriptionAppGroup: subscriptionAppGroup) + self.accountManager = accountManager } var isInstalled: Bool { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift index 556d1d4b9f..57f545bff8 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift @@ -24,16 +24,16 @@ import Subscription public final class DebugPurchaseModel: ObservableObject { var purchaseManager: PurchaseManager - var accountManager: AccountManager - private let subscriptionAppGroup: String + let appStorePurchaseFlow: AppStorePurchaseFlow @Published var subscriptions: [SubscriptionRowModel] - init(manager: PurchaseManager, subscriptions: [SubscriptionRowModel] = [], subscriptionAppGroup: String) { + init(manager: PurchaseManager, + subscriptions: [SubscriptionRowModel] = [], + accountManager: AccountManager) { self.purchaseManager = manager self.subscriptions = subscriptions - self.subscriptionAppGroup = subscriptionAppGroup - self.accountManager = AccountManager(subscriptionAppGroup: subscriptionAppGroup) + self.appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) } @MainActor @@ -41,7 +41,7 @@ public final class DebugPurchaseModel: ObservableObject { print("Attempting purchase: \(product.displayName)") Task { - await AppStorePurchaseFlow.purchaseSubscription(with: product.id, emailAccessToken: nil, subscriptionAppGroup: subscriptionAppGroup) + await appStorePurchaseFlow.purchaseSubscription(with: product.id, emailAccessToken: nil) } } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift index 1573bec52e..2fcc6c932e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift @@ -34,9 +34,9 @@ public final class DebugPurchaseViewController: NSViewController { fatalError("init(coder:) has not been implemented") } - public init(subscriptionAppGroup: String) { + public init(accountManager: AccountManager) { manager = PurchaseManager.shared - model = DebugPurchaseModel(manager: manager, subscriptionAppGroup: subscriptionAppGroup) + model = DebugPurchaseModel(manager: manager, accountManager: accountManager) super.init(nibName: nil, bundle: nil) } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 4ab7eadd32..b253063e88 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -29,8 +29,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { private var purchasePlatformItem: NSMenuItem? var currentViewController: () -> NSViewController? - private let accountManager: AccountManager - private let subscriptionAppGroup: String + let accountManager: AccountManager private var _purchaseManager: Any? @available(macOS 12.0, *) @@ -51,14 +50,13 @@ public final class SubscriptionDebugMenu: NSMenuItem { isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, - subscriptionAppGroup: String) { + accountManager: AccountManager) { self.currentEnvironment = currentEnvironment self.updateEnvironment = updateEnvironment self.isInternalTestingEnabled = isInternalTestingEnabled self.updateInternalTestingFlag = updateInternalTestingFlag self.currentViewController = currentViewController - self.accountManager = AccountManager(subscriptionAppGroup: subscriptionAppGroup) - self.subscriptionAppGroup = subscriptionAppGroup + self.accountManager = accountManager super.init(title: "Subscription", action: nil, keyEquivalent: "") self.submenu = makeSubmenu() } @@ -237,7 +235,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { @IBAction func showPurchaseView(_ sender: Any?) { if #available(macOS 12.0, *) { - currentViewController()?.presentAsSheet(DebugPurchaseViewController(subscriptionAppGroup: subscriptionAppGroup)) + currentViewController()?.presentAsSheet(DebugPurchaseViewController(accountManager: accountManager)) } } @@ -296,7 +294,8 @@ public final class SubscriptionDebugMenu: NSMenuItem { func restorePurchases(_ sender: Any?) { if #available(macOS 12.0, *) { Task { - await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: subscriptionAppGroup) + let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: accountManager) + await appStoreRestoreFlow.restoreAccountFromPastPurchase() } } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index f18041dace..32d7a4914d 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -39,7 +39,6 @@ public final class PreferencesSubscriptionModel: ObservableObject { private let openURLHandler: (URL) -> Void public let userEventHandler: (UserEvent) -> Void private let sheetActionHandler: SubscriptionAccessActionHandlers - private let subscriptionAppGroup: String private var fetchSubscriptionDetailsTask: Task<(), Never>? @@ -89,12 +88,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { public init(openURLHandler: @escaping (URL) -> Void, userEventHandler: @escaping (UserEvent) -> Void, sheetActionHandler: SubscriptionAccessActionHandlers, - subscriptionAppGroup: String) { - self.accountManager = AccountManager(subscriptionAppGroup: subscriptionAppGroup) + accountManager: AccountManager) { + self.accountManager = accountManager self.openURLHandler = openURLHandler self.userEventHandler = userEventHandler self.sheetActionHandler = sheetActionHandler - self.subscriptionAppGroup = subscriptionAppGroup self.isUserAuthenticated = accountManager.isUserAuthenticated @@ -136,7 +134,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { if accountManager.isUserAuthenticated { - ShareSubscriptionAccessModel(actionHandlers: sheetActionHandler, email: accountManager.email, subscriptionAppGroup: subscriptionAppGroup) + ShareSubscriptionAccessModel(actionHandlers: sheetActionHandler, email: accountManager.email, accountManager: accountManager) } else { ActivateSubscriptionAccessModel(actionHandlers: sheetActionHandler, shouldShowRestorePurchase: SubscriptionPurchaseEnvironment.current == .appStore) } @@ -242,7 +240,8 @@ public final class PreferencesSubscriptionModel: ObservableObject { if SubscriptionPurchaseEnvironment.current == .appStore { if #available(macOS 12.0, *) { Task { - _ = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: subscriptionAppGroup) + let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: accountManager) + await appStoreRestoreFlow.restoreAccountFromPastPurchase() fetchAndUpdateSubscriptionDetails() } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index 9e007f4e7e..d26cddb0ac 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -22,19 +22,17 @@ import Subscription public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { public var title = UserText.shareModalTitle public var description = UserText.shareModalDescription(platform: SubscriptionPurchaseEnvironment.current) - - private let subscriptionAppGroup: String private var actionHandlers: SubscriptionAccessActionHandlers - public var email: String? public var emailLabel: String { UserText.email } public var emailDescription: String { hasEmail ? UserText.shareModalHasEmailDescription : UserText.shareModalNoEmailDescription } public var emailButtonTitle: String { hasEmail ? UserText.manageEmailButton : UserText.addEmailButton } + let accountManager: AccountManager - public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, subscriptionAppGroup: String) { + public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, accountManager: AccountManager) { self.actionHandlers = actionHandlers self.email = email - self.subscriptionAppGroup = subscriptionAppGroup + self.accountManager = accountManager } private var hasEmail: Bool { !(email?.isEmpty ?? true) } @@ -51,7 +49,8 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { Task { if SubscriptionPurchaseEnvironment.current == .appStore { if #available(macOS 12.0, iOS 15.0, *) { - await AppStoreAccountManagementFlow.refreshAuthTokenIfNeeded(subscriptionAppGroup: subscriptionAppGroup) + let appStoreAccountManagementFlow = AppStoreAccountManagementFlow(accountManager: accountManager) + await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift index 3833c60ff0..b253352d07 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -24,16 +24,14 @@ public final class SubscriptionAccessViewController: NSViewController { private let accountManager: AccountManager private var actionHandlers: SubscriptionAccessActionHandlers - private let subscriptionAppGroup: String public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public init(accountManager: AccountManager, actionHandlers: SubscriptionAccessActionHandlers, subscriptionAppGroup: String) { + public init(accountManager: AccountManager, actionHandlers: SubscriptionAccessActionHandlers) { self.accountManager = accountManager self.actionHandlers = actionHandlers - self.subscriptionAppGroup = subscriptionAppGroup super.init(nibName: nil, bundle: nil) } @@ -57,7 +55,7 @@ public final class SubscriptionAccessViewController: NSViewController { private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { if accountManager.isUserAuthenticated { - ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: accountManager.email, subscriptionAppGroup: subscriptionAppGroup) + ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: accountManager.email, accountManager: accountManager) } else { ActivateSubscriptionAccessModel(actionHandlers: actionHandlers, shouldShowRestorePurchase: SubscriptionPurchaseEnvironment.current == .appStore) } From 1b41a142ee3f81455f344e59ecd03b59fa72fe02 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 2 May 2024 09:47:12 +0100 Subject: [PATCH 03/41] unit tests fixed --- UnitTests/Menus/MoreOptionsMenuTests.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index 6bda1607b7..86d644c415 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -31,11 +31,13 @@ final class MoreOptionsMenuTests: XCTestCase { var capturingActionDelegate: CapturingOptionsButtonMenuDelegate! @MainActor lazy var moreOptionMenu: MoreOptionsMenu! = { + let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: passwordManagerCoordinator, networkProtectionFeatureVisibility: networkProtectionVisibilityMock, sharingMenu: NSMenu(), - internalUserDecider: internalUserDecider) + internalUserDecider: internalUserDecider, + accountManager: accountManager) menu.actionDelegate = capturingActionDelegate return menu }() @@ -66,11 +68,13 @@ final class MoreOptionsMenuTests: XCTestCase { @MainActor func testThatMoreOptionMenuHasTheExpectedItems() { + let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) moreOptionMenu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: passwordManagerCoordinator, networkProtectionFeatureVisibility: NetworkProtectionVisibilityMock(isInstalled: false, visible: true), sharingMenu: NSMenu(), - internalUserDecider: internalUserDecider) + internalUserDecider: internalUserDecider, + accountManager: accountManager) XCTAssertEqual(moreOptionMenu.items[0].title, UserText.sendFeedback) XCTAssertTrue(moreOptionMenu.items[1].isSeparatorItem) From 8fa8789df9de4a805d74712b79f9623a7588f8a6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 2 May 2024 10:38:16 +0100 Subject: [PATCH 04/41] AccountManaging protocol --- DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift | 4 ++-- .../NetworkProtectionSubscriptionEventHandler.swift | 4 ++-- .../Subscription/SubscriptionAppStoreRestorer.swift | 4 ++-- .../SubscriptionPagesUseSubscriptionFeature.swift | 4 ++-- .../Waitlist/NetworkProtectionFeatureVisibility.swift | 4 ++-- DuckDuckGoVPN/NetworkProtectionBouncer.swift | 2 +- .../SubscriptionUI/DebugMenu/DebugPurchaseModel.swift | 2 +- .../DebugMenu/DebugPurchaseViewController.swift | 2 +- .../SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift | 4 ++-- .../Preferences/PreferencesSubscriptionModel.swift | 6 +++--- .../Model/ShareSubscriptionAccessModel.swift | 4 ++-- .../SubscriptionAccessViewController.swift | 4 ++-- UnitTests/Menus/MoreOptionsMenuTests.swift | 2 +- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 21b5a04dcc..980e37b627 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -57,7 +57,7 @@ final class MoreOptionsMenu: NSMenu { private let passwordManagerCoordinator: PasswordManagerCoordinating private let internalUserDecider: InternalUserDecider private lazy var sharingMenu: NSMenu = SharingMenu(title: UserText.shareMenuItem) - private let accountManager: AccountManager + private let accountManager: AccountManaging private let networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility @@ -71,7 +71,7 @@ final class MoreOptionsMenu: NSMenu { networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), sharingMenu: NSMenu? = nil, internalUserDecider: InternalUserDecider, - accountManager: AccountManager) { + accountManager: AccountManaging) { self.tabCollectionViewModel = tabCollectionViewModel self.emailManager = emailManager diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index f8a1ea0016..064458b5fc 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -25,13 +25,13 @@ import NetworkProtectionUI final class NetworkProtectionSubscriptionEventHandler { - private let accountManager: AccountManager + private let accountManager: AccountManaging private let networkProtectionTokenStorage: NetworkProtectionTokenStore private let networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling private let userDefaults: UserDefaults private var cancellables = Set() - init(accountManager: AccountManager, + init(accountManager: AccountManaging, networkProtectionTokenStorage: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), userDefaults: UserDefaults = .netP) { diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index d01d5990b9..073cec82a1 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -25,9 +25,9 @@ import PixelKit @available(macOS 12.0, *) struct SubscriptionAppStoreRestorer { - let accountManager: AccountManager + let accountManager: AccountManaging - public init(accountManager: AccountManager) { + public init(accountManager: AccountManaging) { self.accountManager = accountManager } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index f5f532a583..f01389de26 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -40,10 +40,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { var window: NSWindow? { WindowControllersManager.shared.lastKeyMainWindowController?.window } - let accountManager: AccountManager + let accountManager: AccountManaging let stripePurchaseFlow: StripePurchaseFlow - public init(accountManager: AccountManager) { + public init(accountManager: AccountManaging) { self.accountManager = accountManager self.stripePurchaseFlow = StripePurchaseFlow(accountManager: accountManager) } diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index feaeac4280..0c6da7b31a 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -46,7 +46,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let privacyConfigurationManager: PrivacyConfigurationManaging private let defaults: UserDefaults - let accountManager: AccountManager + let accountManager: AccountManaging init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), @@ -54,7 +54,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { featureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), defaults: UserDefaults = .netP, log: OSLog = .networkProtection, - accountManager: AccountManager) { + accountManager: AccountManaging) { self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionFeatureActivation = networkProtectionFeatureActivation diff --git a/DuckDuckGoVPN/NetworkProtectionBouncer.swift b/DuckDuckGoVPN/NetworkProtectionBouncer.swift index 302245b270..1db66857a8 100644 --- a/DuckDuckGoVPN/NetworkProtectionBouncer.swift +++ b/DuckDuckGoVPN/NetworkProtectionBouncer.swift @@ -31,8 +31,8 @@ final class NetworkProtectionBouncer { /// current app. /// func requireAuthTokenOrKillApp(controller: TunnelController) async { - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) guard !accountManager.isUserAuthenticated else { return } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift index 57f545bff8..f1628fa561 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift @@ -30,7 +30,7 @@ public final class DebugPurchaseModel: ObservableObject { init(manager: PurchaseManager, subscriptions: [SubscriptionRowModel] = [], - accountManager: AccountManager) { + accountManager: AccountManaging) { self.purchaseManager = manager self.subscriptions = subscriptions self.appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift index 2fcc6c932e..c9a0e55b84 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift @@ -34,7 +34,7 @@ public final class DebugPurchaseViewController: NSViewController { fatalError("init(coder:) has not been implemented") } - public init(accountManager: AccountManager) { + public init(accountManager: AccountManaging) { manager = PurchaseManager.shared model = DebugPurchaseModel(manager: manager, accountManager: accountManager) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index b253063e88..c6282cbc46 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -29,7 +29,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { private var purchasePlatformItem: NSMenuItem? var currentViewController: () -> NSViewController? - let accountManager: AccountManager + let accountManager: AccountManaging private var _purchaseManager: Any? @available(macOS 12.0, *) @@ -50,7 +50,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, - accountManager: AccountManager) { + accountManager: AccountManaging) { self.currentEnvironment = currentEnvironment self.updateEnvironment = updateEnvironment self.isInternalTestingEnabled = isInternalTestingEnabled diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 32d7a4914d..797dd73c35 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -35,7 +35,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { lazy var sheetModel: SubscriptionAccessModel = makeSubscriptionAccessModel() - private let accountManager: AccountManager + private let accountManager: AccountManaging private let openURLHandler: (URL) -> Void public let userEventHandler: (UserEvent) -> Void private let sheetActionHandler: SubscriptionAccessActionHandlers @@ -88,7 +88,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { public init(openURLHandler: @escaping (URL) -> Void, userEventHandler: @escaping (UserEvent) -> Void, sheetActionHandler: SubscriptionAccessActionHandlers, - accountManager: AccountManager) { + accountManager: AccountManaging) { self.accountManager = accountManager self.openURLHandler = openURLHandler self.userEventHandler = userEventHandler @@ -284,7 +284,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { } @MainActor - private func updateAllEntitlement(with cachePolicy: AccountManager.CachePolicy) async { + private func updateAllEntitlement(with cachePolicy: AccountManaging.CachePolicy) async { switch await self.accountManager.hasEntitlement(for: .networkProtection, cachePolicy: cachePolicy) { case let .success(result): hasAccessToVPN = result diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index d26cddb0ac..084a95b489 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -27,9 +27,9 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { public var emailLabel: String { UserText.email } public var emailDescription: String { hasEmail ? UserText.shareModalHasEmailDescription : UserText.shareModalNoEmailDescription } public var emailButtonTitle: String { hasEmail ? UserText.manageEmailButton : UserText.addEmailButton } - let accountManager: AccountManager + let accountManager: AccountManaging - public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, accountManager: AccountManager) { + public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, accountManager: AccountManaging) { self.actionHandlers = actionHandlers self.email = email self.accountManager = accountManager diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift index b253352d07..e860b5a97a 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -22,14 +22,14 @@ import SwiftUI public final class SubscriptionAccessViewController: NSViewController { - private let accountManager: AccountManager + private let accountManager: AccountManaging private var actionHandlers: SubscriptionAccessActionHandlers public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public init(accountManager: AccountManager, actionHandlers: SubscriptionAccessActionHandlers) { + public init(accountManager: AccountManaging, actionHandlers: SubscriptionAccessActionHandlers) { self.accountManager = accountManager self.actionHandlers = actionHandlers super.init(nibName: nil, bundle: nil) diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index 86d644c415..98a19d71e6 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -36,7 +36,7 @@ final class MoreOptionsMenuTests: XCTestCase { passwordManagerCoordinator: passwordManagerCoordinator, networkProtectionFeatureVisibility: networkProtectionVisibilityMock, sharingMenu: NSMenu(), - internalUserDecider: internalUserDecider, + internalUserDecider: internalUserDecider, accountManager: accountManager) menu.actionDelegate = capturingActionDelegate return menu From 9ecc6f21179822e39791dca886909709eae9328c Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 11:21:11 +0100 Subject: [PATCH 05/41] alerts temporary restored --- .../SubscriptionAppStoreRestorer.swift | 49 +++++++++++++++++-- ...scriptionPagesUseSubscriptionFeature.swift | 8 +++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 073cec82a1..fb1d4523f1 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -26,6 +26,10 @@ import PixelKit struct SubscriptionAppStoreRestorer { let accountManager: AccountManaging + @MainActor + var window: NSWindow? { + WindowControllersManager.shared.lastKeyMainWindowController?.window + } public init(accountManager: AccountManaging) { self.accountManager = accountManager @@ -85,14 +89,53 @@ struct SubscriptionAppStoreRestorer { switch error { case .missingAccountOrTransactions: SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) -// await windowController.showSubscriptionNotFoundAlert() + await showSubscriptionNotFoundAlert() case .subscriptionExpired: SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionExpired) -// await windowController.showSubscriptionInactiveAlert() + await showSubscriptionInactiveAlert() case .pastTransactionAuthenticationError, .failedToObtainAccessToken, .failedToFetchAccountDetails, .failedToFetchSubscriptionDetails: SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) -// await windowController.showSomethingWentWrongAlert() + await showSomethingWentWrongAlert() } } } } + +@available(macOS 12.0, *) +extension SubscriptionAppStoreRestorer { + + /* + WARNING: DUPLICATED CODE + This code will be moved as part of https://app.asana.com/0/0/1207157941206686/f + */ + + // MARK: - UI interactions + + @MainActor + func showSomethingWentWrongAlert() { + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) + guard let window else { return } + + window.show(.somethingWentWrongAlert()) + } + + @MainActor + func showSubscriptionNotFoundAlert() { + guard let window else { return } + + window.show(.subscriptionNotFoundAlert(), firstButtonAction: { + WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) + }) + } + + @MainActor + func showSubscriptionInactiveAlert() { + guard let window else { return } + + window.show(.subscriptionInactiveAlert(), firstButtonAction: { + WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) + }) + } +} diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index f01389de26..5ff7d80494 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -433,6 +433,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { broker.push(method: method.rawValue, params: params, for: self, into: webView) } +} + +extension SubscriptionPagesUseSubscriptionFeature { + + /* + WARNING: + This code will be moved as part of https://app.asana.com/0/0/1207157941206686/f + */ // MARK: - UI interactions From 4fd5f93b3adb58874ac2484345a00bdc6b87ba87 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 12:10:31 +0100 Subject: [PATCH 06/41] swiftlint --- .../UserScripts/Subscription/SubscriptionAppStoreRestorer.swift | 2 +- .../Subscription/SubscriptionPagesUseSubscriptionFeature.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index fb1d4523f1..cb8a0a8da2 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -35,7 +35,7 @@ struct SubscriptionAppStoreRestorer { self.accountManager = accountManager } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length func restoreAppStoreSubscription(mainViewController: MainViewController, windowController: MainWindowController) async { let progressViewController = await ProgressViewController(title: UserText.restoringSubscriptionTitle) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift index 5ff7d80494..78b5956efc 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift @@ -436,7 +436,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } extension SubscriptionPagesUseSubscriptionFeature { - + /* WARNING: This code will be moved as part of https://app.asana.com/0/0/1207157941206686/f From 269f1afe5085a2e6909df5c33e2ca480046080b3 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 12:23:39 +0100 Subject: [PATCH 07/41] BSK from PR branch --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++---- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a50615934d..a65dc80067 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -4032,7 +4032,6 @@ F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; - F1915CAE2BE10B9200FBE25B /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; @@ -6412,7 +6411,6 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( - F1915CAE2BE10B9200FBE25B /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, @@ -12755,8 +12753,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 143.0.1; + branch = fcappelli/subscription_refactoring; + kind = branch; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a4c4905c48..17f419ab1e 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_refactoring", + "revision" : "a99a83cdbcfe62bda19548b1f5e8c3637c3586bc" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 73f0fd6477f4eda2fd8de9b52223d3ba841674f6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 12:28:29 +0100 Subject: [PATCH 08/41] another accountManager DI --- .../BothAppTargets/NetworkProtectionDebugMenu.swift | 2 +- DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 472b1f5275..1b66e309a9 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -228,7 +228,7 @@ final class NetworkProtectionDebugMenu: NSMenu { /// @objc func logFeedbackMetadataToConsole(_ sender: Any?) { Task { @MainActor in - let collector = DefaultVPNMetadataCollector() + let collector = DefaultVPNMetadataCollector(accountManager: AppDelegate.accountManager) let metadata = await collector.collectMetadata() print(metadata.toPrettyPrintedJSON()!) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index 6c57d1c604..d912f0e057 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -121,11 +121,13 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusReporter: NetworkProtectionStatusReporter private let ipcClient: TunnelControllerIPCClient private let defaults: UserDefaults + private let accountManager: AccountManager - init(defaults: UserDefaults = .netP) { + init(defaults: UserDefaults = .netP, + accountManager: AccountManager) { let ipcClient = TunnelControllerIPCClient() ipcClient.register() - + self.accountManager = accountManager self.ipcClient = ipcClient self.defaults = defaults @@ -302,9 +304,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { - let accountManager = AppDelegate.accountManager let hasVPNEntitlement = (try? await accountManager.hasEntitlement(for: .networkProtection).get()) ?? false - return .init( hasPrivacyProAccount: accountManager.isUserAuthenticated, hasVPNEntitlement: hasVPNEntitlement From dc8d9e6d821a20b55b90a200ab06f1ee36debce7 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 12:32:32 +0100 Subject: [PATCH 09/41] another AccountManager DI --- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 7 +++---- DuckDuckGoVPN/NetworkProtectionBouncer.swift | 8 ++++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index b19518730d..473cce8d40 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -29,6 +29,8 @@ import ServiceManagement import PixelKit import Subscription +private let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + @objc(Application) final class DuckDuckGoVPNApplication: NSApplication { private let _delegate = DuckDuckGoVPNAppDelegate() @@ -46,8 +48,6 @@ final class DuckDuckGoVPNApplication: NSApplication { self.delegate = _delegate #if DEBUG - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - if let token = accountManager.accessToken { os_log(.error, log: .networkProtection, "🟢 VPN Agent found token: %{public}d", token) } else { @@ -67,7 +67,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private static let recentThreshold: TimeInterval = 5.0 private let appLauncher = AppLauncher() - private let bouncer = NetworkProtectionBouncer() + private let bouncer = NetworkProtectionBouncer(accountManager: accountManager) private var cancellables = Set() @@ -359,7 +359,6 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private lazy var entitlementMonitor = NetworkProtectionEntitlementMonitor() private func setUpSubscriptionMonitoring() { - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) guard accountManager.isUserAuthenticated else { return } let entitlementsCheck = { await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) diff --git a/DuckDuckGoVPN/NetworkProtectionBouncer.swift b/DuckDuckGoVPN/NetworkProtectionBouncer.swift index 1db66857a8..537d7cadca 100644 --- a/DuckDuckGoVPN/NetworkProtectionBouncer.swift +++ b/DuckDuckGoVPN/NetworkProtectionBouncer.swift @@ -27,12 +27,16 @@ import Subscription /// final class NetworkProtectionBouncer { + let accountManager: AccountManager + + init(accountManager: AccountManager) { + self.accountManager = accountManager + } + /// Simply verifies that the VPN feature is enabled and if not, takes care of killing the /// current app. /// func requireAuthTokenOrKillApp(controller: TunnelController) async { - - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) guard !accountManager.isUserAuthenticated else { return } From 430c046da165d006ffc7d1ab2d7b6689a739231b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 12:57:43 +0100 Subject: [PATCH 10/41] more DI --- DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift | 2 +- DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift index fe8b8258e2..ff0b808128 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift @@ -40,7 +40,7 @@ final class VPNFeedbackFormViewController: NSViewController { private var cancellables = Set() init() { - self.viewModel = VPNFeedbackFormViewModel() + self.viewModel = VPNFeedbackFormViewModel(metadataCollector: DefaultVPNMetadataCollector(accountManager: AppDelegate.accountManager)) super.init(nibName: nil, bundle: nil) self.viewModel.delegate = self } diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift index 7363d0d817..70aeb3e110 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift @@ -67,7 +67,8 @@ final class VPNFeedbackFormViewModel: ObservableObject { private let metadataCollector: VPNMetadataCollector private let feedbackSender: VPNFeedbackSender - init(metadataCollector: VPNMetadataCollector = DefaultVPNMetadataCollector(), feedbackSender: VPNFeedbackSender = DefaultVPNFeedbackSender()) { + init(metadataCollector: VPNMetadataCollector, + feedbackSender: VPNFeedbackSender = DefaultVPNFeedbackSender()) { self.viewState = .feedbackPending self.selectedFeedbackCategory = .landingPage From 32c74f415c4815a8d4e827528733b1ad12c166c8 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 15:28:37 +0100 Subject: [PATCH 11/41] user script merged --- DuckDuckGo.xcodeproj/project.pbxproj | 6 - ...scriptionPagesUseSubscriptionFeature.swift | 493 ------------------ .../SubscriptionPagesUserScript.swift | 465 +++++++++++++++++ 3 files changed, 465 insertions(+), 499 deletions(-) delete mode 100644 DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a65dc80067..095b63b303 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2547,8 +2547,6 @@ F116A7C32BD1924B00F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C22BD1924B00F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C72BD1925500F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C62BD1925500F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C92BD1929000F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C82BD1929000F3FCF7 /* PixelKitTestingUtilities */; }; - F119CCC42BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; - F119CCC52BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; @@ -4028,7 +4026,6 @@ EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+VPNAgentConvenienceInitializers.swift"; sourceTree = ""; }; EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = ""; }; EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; - F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; @@ -8127,7 +8124,6 @@ isa = PBXGroup; children = ( 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */, - F119CCC32BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift */, F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */, F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */, ); @@ -10093,7 +10089,6 @@ 3706FC78293F65D500E42796 /* SecureVaultReporter.swift in Sources */, 3706FC79293F65D500E42796 /* NSImageExtensions.swift in Sources */, 3706FEBD293F6EFF00E42796 /* BWCommand.swift in Sources */, - F119CCC52BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, 3706FC7B293F65D500E42796 /* PasswordManagementViewController.swift in Sources */, 3706FC7C293F65D500E42796 /* ImportedBookmarks.swift in Sources */, 3706FC7D293F65D500E42796 /* NSMenuExtension.swift in Sources */, @@ -11131,7 +11126,6 @@ B6830961274CDE99004B46BB /* FireproofDomainsContainer.swift in Sources */, B687B7CC2947A1E9001DEA6F /* ExternalAppSchemeHandler.swift in Sources */, B65536AE2685E17200085A79 /* GeolocationService.swift in Sources */, - F119CCC42BE239CB0002B30E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, 4B02198925E05FAC00ED7DEA /* FireproofingURLExtensions.swift in Sources */, 3168506D2AF3AD1D009A2828 /* WaitlistViewControllerPresenter.swift in Sources */, 7B1E819E27C8874900FF0E60 /* ContentOverlayPopover.swift in Sources */, diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift deleted file mode 100644 index 78b5956efc..0000000000 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUseSubscriptionFeature.swift +++ /dev/null @@ -1,493 +0,0 @@ -// -// SubscriptionPagesUseSubscriptionFeature.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import BrowserServicesKit -import Common -import Combine -import Navigation -import WebKit -import UserScript -import Subscription -import SubscriptionUI -import PixelKit - -/// Use Subscription sub-feature -final class SubscriptionPagesUseSubscriptionFeature: Subfeature { - weak var broker: UserScriptMessageBroker? - var featureName = "useSubscription" - var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ - .exact(hostname: "duckduckgo.com"), - .exact(hostname: "abrown.duckduckgo.com") - ]) - - @MainActor - var window: NSWindow? { - WindowControllersManager.shared.lastKeyMainWindowController?.window - } - let accountManager: AccountManaging - let stripePurchaseFlow: StripePurchaseFlow - - public init(accountManager: AccountManaging) { - self.accountManager = accountManager - self.stripePurchaseFlow = StripePurchaseFlow(accountManager: accountManager) - } - - func with(broker: UserScriptMessageBroker) { - self.broker = broker - } - - struct Handlers { - static let getSubscription = "getSubscription" - static let setSubscription = "setSubscription" - static let backToSettings = "backToSettings" - static let getSubscriptionOptions = "getSubscriptionOptions" - static let subscriptionSelected = "subscriptionSelected" - static let activateSubscription = "activateSubscription" - static let featureSelected = "featureSelected" - static let completeStripePayment = "completeStripePayment" - // Pixels related events - static let subscriptionsMonthlyPriceClicked = "subscriptionsMonthlyPriceClicked" - static let subscriptionsYearlyPriceClicked = "subscriptionsYearlyPriceClicked" - static let subscriptionsUnknownPriceClicked = "subscriptionsUnknownPriceClicked" - static let subscriptionsAddEmailSuccess = "subscriptionsAddEmailSuccess" - static let subscriptionsWelcomeFaqClicked = "subscriptionsWelcomeFaqClicked" - } - - // swiftlint:disable:next cyclomatic_complexity - func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - switch methodName { - case Handlers.getSubscription: return getSubscription - case Handlers.setSubscription: return setSubscription - case Handlers.backToSettings: return backToSettings - case Handlers.getSubscriptionOptions: return getSubscriptionOptions - case Handlers.subscriptionSelected: return subscriptionSelected - case Handlers.activateSubscription: return activateSubscription - case Handlers.featureSelected: return featureSelected - case Handlers.completeStripePayment: return completeStripePayment - // Pixel related events - case Handlers.subscriptionsMonthlyPriceClicked: return subscriptionsMonthlyPriceClicked - case Handlers.subscriptionsYearlyPriceClicked: return subscriptionsYearlyPriceClicked - case Handlers.subscriptionsUnknownPriceClicked: return subscriptionsUnknownPriceClicked - case Handlers.subscriptionsAddEmailSuccess: return subscriptionsAddEmailSuccess - case Handlers.subscriptionsWelcomeFaqClicked: return subscriptionsWelcomeFaqClicked - default: - return nil - } - } - - struct Subscription: Encodable { - let token: String - } - - /// Values that the Frontend can use to determine the current state. - struct SubscriptionValues: Codable { - enum CodingKeys: String, CodingKey { - case token - } - let token: String - } - - func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let authToken = accountManager.authToken, accountManager.accessToken != nil { - return Subscription(token: authToken) - } else { - return Subscription(token: "") - } - } - - func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) - - guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") - return nil - } - - let authToken = subscriptionValues.token - if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAuthToken(token: authToken) - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) - } - - return nil - } - - func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = accountManager.accessToken, - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) - } - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) - } - - return nil - } - - func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard DefaultSubscriptionFeatureAvailability().isSubscriptionPurchaseAllowed else { return SubscriptionOptions.empty } - - if SubscriptionPurchaseEnvironment.current == .appStore { - if #available(macOS 12.0, *) { - let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) - switch await appStorePurchaseFlow.subscriptionOptions() { - case .success(let subscriptionOptions): - return subscriptionOptions - case .failure: - break - } - } - } else if SubscriptionPurchaseEnvironment.current == .stripe { - switch await stripePurchaseFlow.subscriptionOptions() { - case .success(let subscriptionOptions): - return subscriptionOptions - case .failure: - break - } - } - - return SubscriptionOptions.empty - } - - // swiftlint:disable:next function_body_length cyclomatic_complexity - func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndCount) - struct SubscriptionSelection: Decodable { - let id: String - } - - let message = original - - if SubscriptionPurchaseEnvironment.current == .appStore { - if #available(macOS 12.0, *) { - let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController - let progressViewController = await ProgressViewController(title: UserText.purchasingSubscriptionTitle) - - defer { - Task { - await mainViewController?.dismiss(progressViewController) - } - } - - guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) - return nil - } - - os_log(.info, log: .subscription, "[Purchase] Starting purchase for: %{public}s", subscriptionSelection.id) - - await mainViewController?.presentAsSheet(progressViewController) - - // Check for active subscriptions - if await PurchaseManager.hasActiveSubscription() { - PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) - os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") - SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) - await showSubscriptionFoundAlert(originalMessage: message) - return nil - } - - let emailAccessToken = try? EmailManager().getToken() - let purchaseTransactionJWS: String - let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) - - os_log(.info, log: .subscription, "[Purchase] Purchasing") - switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { - case .success(let transactionJWS): - purchaseTransactionJWS = transactionJWS - case .failure(let error): - switch error { - case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) - case .activeSubscriptionAlreadyPresent: - SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) - case .authenticatingWithTransactionFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) - case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) - case .purchaseFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) - case .cancelledByUser: - SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) - case .missingEntitlements: - SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) - } - - if error != .cancelledByUser { - await showSomethingWentWrongAlert() - } - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) - return nil - } - - await progressViewController.updateTitleText(UserText.completingPurchaseTitle) - - os_log(.info, log: .subscription, "[Purchase] Completing purchase") - - switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { - case .success(let purchaseUpdate): - os_log(.info, log: .subscription, "[Purchase] Purchase complete") - PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .unique) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) - case .failure(let error): - switch error { - case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) - case .activeSubscriptionAlreadyPresent: - SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) - case .authenticatingWithTransactionFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) - case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) - case .purchaseFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) - case .cancelledByUser: - SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) - case .missingEntitlements: - SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) - DispatchQueue.main.async { - NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) - } - return nil - } - - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) - } - } - } else if SubscriptionPurchaseEnvironment.current == .stripe { - let emailAccessToken = try? EmailManager().getToken() - - let result = await stripePurchaseFlow.prepareSubscriptionPurchase(emailAccessToken: emailAccessToken) - - switch result { - case .success(let success): - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: success) - case .failure(let error): - await showSomethingWentWrongAlert() - - switch error { - case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) - case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) - } - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) - } - } - - return nil - } - - func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseOfferPageEntry) - guard let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController, - let windowControllerManager = await WindowControllersManager.shared.lastKeyMainWindowController else { - return nil - } - let message = original - - let actionHandlers = SubscriptionAccessActionHandlers(restorePurchases: { - if #available(macOS 12.0, *) { - Task { @MainActor in - let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: self.accountManager) - await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) - message.webView?.reload() - } - } - }, openURLHandler: { url in - DispatchQueue.main.async { - WindowControllersManager.shared.showTab(with: .subscription(url)) - } - }, uiActionHandler: { event in - switch event { - case .activateAddEmailClick: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) - default: - break - } - }) - - let subscriptionAccessViewController = await SubscriptionAccessViewController(accountManager: accountManager, actionHandlers: actionHandlers) - await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.presentAsSheet(subscriptionAccessViewController) - - return nil - } - - func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - struct FeatureSelection: Codable { - let feature: String - } - - guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") - return nil - } - - guard let subscriptionFeatureName = SubscriptionFeatureName(rawValue: featureSelection.feature) else { - assertionFailure("SubscriptionPagesUserScript: feature name does not matches mapping") - return nil - } - - switch subscriptionFeatureName { - case .privateBrowsing: - NotificationCenter.default.post(name: .openPrivateBrowsing, object: self, userInfo: nil) - case .privateSearch: - NotificationCenter.default.post(name: .openPrivateSearch, object: self, userInfo: nil) - case .emailProtection: - NotificationCenter.default.post(name: .openEmailProtection, object: self, userInfo: nil) - case .appTrackingProtection: - NotificationCenter.default.post(name: .openAppTrackingProtection, object: self, userInfo: nil) - case .vpn: - PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .unique) - NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) - case .personalInformationRemoval: - PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .unique) - NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) - await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) - case .identityTheftRestoration: - PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) - await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) - } - - return nil - } - - func completeStripePayment(params: Any, original: WKScriptMessage) async throws -> Encodable? { - let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController - let progressViewController = await ProgressViewController(title: UserText.completingPurchaseTitle) - - await mainViewController?.presentAsSheet(progressViewController) - await stripePurchaseFlow.completeSubscriptionPurchase() - await mainViewController?.dismiss(progressViewController) - - PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) - return [String: String]() // cannot be nil, the web app expect something back before redirecting the user to the final page - } - - // MARK: Pixel related actions - - func subscriptionsMonthlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProOfferMonthlyPriceClick) - return nil - } - - func subscriptionsYearlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProOfferYearlyPriceClick) - return nil - } - - func subscriptionsUnknownPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - // Not used - return nil - } - - func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .unique) - return nil - } - - func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .unique) - return nil - } - - // MARK: Push actions - - enum SubscribeActionName: String { - case onPurchaseUpdate - } - - @MainActor - func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) async { - pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) - } - - func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { - guard let broker else { - assertionFailure("Cannot continue without broker instance") - return - } - - broker.push(method: method.rawValue, params: params, for: self, into: webView) - } -} - -extension SubscriptionPagesUseSubscriptionFeature { - - /* - WARNING: - This code will be moved as part of https://app.asana.com/0/0/1207157941206686/f - */ - - // MARK: - UI interactions - - @MainActor - func showSomethingWentWrongAlert() { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) - guard let window else { return } - - window.show(.somethingWentWrongAlert()) - } - - @MainActor - func showSubscriptionNotFoundAlert() { - guard let window else { return } - - window.show(.subscriptionNotFoundAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) - } - - @MainActor - func showSubscriptionInactiveAlert() { - guard let window else { return } - - window.show(.subscriptionInactiveAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) - }) - } - - @MainActor - func showSubscriptionFoundAlert(originalMessage: WKScriptMessage) { - guard let window else { return } - - window.show(.subscriptionFoundAlert(), firstButtonAction: { - if #available(macOS 12.0, *) { - Task { - let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: self.accountManager) - let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() - switch result { - case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) - case .failure: break - } - originalMessage.webView?.reload() - } - } - }) - } -} diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index c828b22456..e141ed51cf 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -72,3 +72,468 @@ extension SubscriptionPagesUserScript: WKScriptMessageHandler { // unsupported } } + +/// Use Subscription sub-feature +final class SubscriptionPagesUseSubscriptionFeature: Subfeature { + weak var broker: UserScriptMessageBroker? + var featureName = "useSubscription" + var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ + .exact(hostname: "duckduckgo.com"), + .exact(hostname: "abrown.duckduckgo.com") + ]) + + @MainActor + var window: NSWindow? { + WindowControllersManager.shared.lastKeyMainWindowController?.window + } + let accountManager: AccountManaging + let stripePurchaseFlow: StripePurchaseFlow + + public init(accountManager: AccountManaging) { + self.accountManager = accountManager + self.stripePurchaseFlow = StripePurchaseFlow(accountManager: accountManager) + } + + func with(broker: UserScriptMessageBroker) { + self.broker = broker + } + + struct Handlers { + static let getSubscription = "getSubscription" + static let setSubscription = "setSubscription" + static let backToSettings = "backToSettings" + static let getSubscriptionOptions = "getSubscriptionOptions" + static let subscriptionSelected = "subscriptionSelected" + static let activateSubscription = "activateSubscription" + static let featureSelected = "featureSelected" + static let completeStripePayment = "completeStripePayment" + // Pixels related events + static let subscriptionsMonthlyPriceClicked = "subscriptionsMonthlyPriceClicked" + static let subscriptionsYearlyPriceClicked = "subscriptionsYearlyPriceClicked" + static let subscriptionsUnknownPriceClicked = "subscriptionsUnknownPriceClicked" + static let subscriptionsAddEmailSuccess = "subscriptionsAddEmailSuccess" + static let subscriptionsWelcomeFaqClicked = "subscriptionsWelcomeFaqClicked" + } + + // swiftlint:disable:next cyclomatic_complexity + func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { + switch methodName { + case Handlers.getSubscription: return getSubscription + case Handlers.setSubscription: return setSubscription + case Handlers.backToSettings: return backToSettings + case Handlers.getSubscriptionOptions: return getSubscriptionOptions + case Handlers.subscriptionSelected: return subscriptionSelected + case Handlers.activateSubscription: return activateSubscription + case Handlers.featureSelected: return featureSelected + case Handlers.completeStripePayment: return completeStripePayment + // Pixel related events + case Handlers.subscriptionsMonthlyPriceClicked: return subscriptionsMonthlyPriceClicked + case Handlers.subscriptionsYearlyPriceClicked: return subscriptionsYearlyPriceClicked + case Handlers.subscriptionsUnknownPriceClicked: return subscriptionsUnknownPriceClicked + case Handlers.subscriptionsAddEmailSuccess: return subscriptionsAddEmailSuccess + case Handlers.subscriptionsWelcomeFaqClicked: return subscriptionsWelcomeFaqClicked + default: + return nil + } + } + + struct Subscription: Encodable { + let token: String + } + + /// Values that the Frontend can use to determine the current state. + struct SubscriptionValues: Codable { + enum CodingKeys: String, CodingKey { + case token + } + let token: String + } + + func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + if let authToken = accountManager.authToken, accountManager.accessToken != nil { + return Subscription(token: authToken) + } else { + return Subscription(token: "") + } + } + + func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) + + guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + return nil + } + + let authToken = subscriptionValues.token + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + + return nil + } + + func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { + if let accessToken = accountManager.accessToken, + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) + } + + return nil + } + + func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard DefaultSubscriptionFeatureAvailability().isSubscriptionPurchaseAllowed else { return SubscriptionOptions.empty } + + if SubscriptionPurchaseEnvironment.current == .appStore { + if #available(macOS 12.0, *) { + let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + switch await appStorePurchaseFlow.subscriptionOptions() { + case .success(let subscriptionOptions): + return subscriptionOptions + case .failure: + break + } + } + } else if SubscriptionPurchaseEnvironment.current == .stripe { + switch await stripePurchaseFlow.subscriptionOptions() { + case .success(let subscriptionOptions): + return subscriptionOptions + case .failure: + break + } + } + + return SubscriptionOptions.empty + } + + // swiftlint:disable:next function_body_length cyclomatic_complexity + func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + + PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndCount) + struct SubscriptionSelection: Decodable { + let id: String + } + + let message = original + + if SubscriptionPurchaseEnvironment.current == .appStore { + if #available(macOS 12.0, *) { + let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + let progressViewController = await ProgressViewController(title: UserText.purchasingSubscriptionTitle) + + defer { + Task { + await mainViewController?.dismiss(progressViewController) + } + } + + guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") + SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + return nil + } + + os_log(.info, log: .subscription, "[Purchase] Starting purchase for: %{public}s", subscriptionSelection.id) + + await mainViewController?.presentAsSheet(progressViewController) + + // Check for active subscriptions + if await PurchaseManager.hasActiveSubscription() { + PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) + os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") + SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) + await showSubscriptionFoundAlert(originalMessage: message) + return nil + } + + let emailAccessToken = try? EmailManager().getToken() + let purchaseTransactionJWS: String + let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + + os_log(.info, log: .subscription, "[Purchase] Purchasing") + switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + case .success(let transactionJWS): + purchaseTransactionJWS = transactionJWS + case .failure(let error): + switch error { + case .noProductsFound: + SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + case .activeSubscriptionAlreadyPresent: + SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) + case .authenticatingWithTransactionFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + case .accountCreationFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + case .purchaseFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) + case .cancelledByUser: + SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) + case .missingEntitlements: + SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) + } + + if error != .cancelledByUser { + await showSomethingWentWrongAlert() + } + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) + return nil + } + + await progressViewController.updateTitleText(UserText.completingPurchaseTitle) + + os_log(.info, log: .subscription, "[Purchase] Completing purchase") + + switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { + case .success(let purchaseUpdate): + os_log(.info, log: .subscription, "[Purchase] Purchase complete") + PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .unique) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) + case .failure(let error): + switch error { + case .noProductsFound: + SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + case .activeSubscriptionAlreadyPresent: + SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) + case .authenticatingWithTransactionFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + case .accountCreationFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + case .purchaseFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) + case .cancelledByUser: + SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) + case .missingEntitlements: + SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) + DispatchQueue.main.async { + NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) + } + return nil + } + + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) + } + } + } else if SubscriptionPurchaseEnvironment.current == .stripe { + let emailAccessToken = try? EmailManager().getToken() + + let result = await stripePurchaseFlow.prepareSubscriptionPurchase(emailAccessToken: emailAccessToken) + + switch result { + case .success(let success): + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: success) + case .failure(let error): + await showSomethingWentWrongAlert() + + switch error { + case .noProductsFound: + SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + case .accountCreationFailed: + SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + } + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) + } + } + + return nil + } + + func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseOfferPageEntry) + guard let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController, + let windowControllerManager = await WindowControllersManager.shared.lastKeyMainWindowController else { + return nil + } + let message = original + + let actionHandlers = SubscriptionAccessActionHandlers(restorePurchases: { + if #available(macOS 12.0, *) { + Task { @MainActor in + let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: self.accountManager) + await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) + message.webView?.reload() + } + } + }, openURLHandler: { url in + DispatchQueue.main.async { + WindowControllersManager.shared.showTab(with: .subscription(url)) + } + }, uiActionHandler: { event in + switch event { + case .activateAddEmailClick: + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) + default: + break + } + }) + + let subscriptionAccessViewController = await SubscriptionAccessViewController(accountManager: accountManager, actionHandlers: actionHandlers) + await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.presentAsSheet(subscriptionAccessViewController) + + return nil + } + + func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct FeatureSelection: Codable { + let feature: String + } + + guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") + return nil + } + + guard let subscriptionFeatureName = SubscriptionFeatureName(rawValue: featureSelection.feature) else { + assertionFailure("SubscriptionPagesUserScript: feature name does not matches mapping") + return nil + } + + switch subscriptionFeatureName { + case .privateBrowsing: + NotificationCenter.default.post(name: .openPrivateBrowsing, object: self, userInfo: nil) + case .privateSearch: + NotificationCenter.default.post(name: .openPrivateSearch, object: self, userInfo: nil) + case .emailProtection: + NotificationCenter.default.post(name: .openEmailProtection, object: self, userInfo: nil) + case .appTrackingProtection: + NotificationCenter.default.post(name: .openAppTrackingProtection, object: self, userInfo: nil) + case .vpn: + PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .unique) + NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) + case .personalInformationRemoval: + PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .unique) + NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) + await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) + case .identityTheftRestoration: + PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) + await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) + } + + return nil + } + + func completeStripePayment(params: Any, original: WKScriptMessage) async throws -> Encodable? { + let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + let progressViewController = await ProgressViewController(title: UserText.completingPurchaseTitle) + + await mainViewController?.presentAsSheet(progressViewController) + await stripePurchaseFlow.completeSubscriptionPurchase() + await mainViewController?.dismiss(progressViewController) + + PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) + return [String: String]() // cannot be nil, the web app expect something back before redirecting the user to the final page + } + + // MARK: Pixel related actions + + func subscriptionsMonthlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProOfferMonthlyPriceClick) + return nil + } + + func subscriptionsYearlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProOfferYearlyPriceClick) + return nil + } + + func subscriptionsUnknownPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + // Not used + return nil + } + + func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .unique) + return nil + } + + func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { + PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .unique) + return nil + } + + // MARK: Push actions + + enum SubscribeActionName: String { + case onPurchaseUpdate + } + + @MainActor + func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) async { + pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) + } + + func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { + guard let broker else { + assertionFailure("Cannot continue without broker instance") + return + } + + broker.push(method: method.rawValue, params: params, for: self, into: webView) + } +} + +extension SubscriptionPagesUseSubscriptionFeature { + + /* + WARNING: + This code will be moved as part of https://app.asana.com/0/0/1207157941206686/f + */ + + // MARK: - UI interactions + + @MainActor + func showSomethingWentWrongAlert() { + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) + guard let window else { return } + + window.show(.somethingWentWrongAlert()) + } + + @MainActor + func showSubscriptionNotFoundAlert() { + guard let window else { return } + + window.show(.subscriptionNotFoundAlert(), firstButtonAction: { + WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) + }) + } + + @MainActor + func showSubscriptionInactiveAlert() { + guard let window else { return } + + window.show(.subscriptionInactiveAlert(), firstButtonAction: { + WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) + }) + } + + @MainActor + func showSubscriptionFoundAlert(originalMessage: WKScriptMessage) { + guard let window else { return } + + window.show(.subscriptionFoundAlert(), firstButtonAction: { + if #available(macOS 12.0, *) { + Task { + let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: self.accountManager) + let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() + switch result { + case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) + case .failure: break + } + originalMessage.webView?.reload() + } + } + }) + } +} From a859720e88e35c22509a420cb89f6876f5e5fea5 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 6 May 2024 09:47:20 +0100 Subject: [PATCH 12/41] subscription service DI --- DuckDuckGo.xcodeproj/project.pbxproj | 2 ++ .../xcshareddata/swiftpm/Package.resolved | 9 --------- DuckDuckGo/Application/AppDelegate.swift | 9 +++++---- ...okenStore+SubscriptionTokenKeychainStorage.swift | 3 ++- .../Preferences/View/PreferencesRootView.swift | 2 +- .../Subscription/SubscriptionAppStoreRestorer.swift | 2 +- .../Subscription/SubscriptionErrorReporter.swift | 2 +- .../Subscription/SubscriptionPagesUserScript.swift | 6 ++---- DuckDuckGo/Tab/UserScripts/UserScripts.swift | 2 +- .../DebugMenu/DebugPurchaseModel.swift | 4 ++-- .../DebugMenu/DebugPurchaseViewController.swift | 4 ++-- .../DebugMenu/SubscriptionDebugMenu.swift | 13 ++++++++----- .../Preferences/PreferencesSubscriptionModel.swift | 13 ++++++++----- 13 files changed, 35 insertions(+), 36 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 095b63b303..9366669fdc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -4026,6 +4026,7 @@ EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+VPNAgentConvenienceInitializers.swift"; sourceTree = ""; }; EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = ""; }; EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; + F119CCC72BE53CE00002B30E /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; @@ -6408,6 +6409,7 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( + F119CCC72BE53CE00002B30E /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 17f419ab1e..a4c4905c48 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_refactoring", - "revision" : "a99a83cdbcfe62bda19548b1f5e8c3637c3586bc" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index c38eb0b5b2..0065d9533d 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -104,6 +104,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return firstLaunchDate >= Date.weekAgo } + let subscriptionPurchaseEnvironment: SubscriptionPurchaseEnvironment = SubscriptionPurchaseEnvironment(subscriptionService: <#T##SubscriptionService#>) public static let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) // swiftlint:disable:next function_body_length @@ -182,7 +183,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #if APPSTORE || !STRIPE SubscriptionPurchaseEnvironment.current = .appStore #else - SubscriptionPurchaseEnvironment.current = .stripe + subscriptionPurchaseEnvironment.current = .stripe #endif networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler(accountManager: AppDelegate.accountManager) @@ -264,11 +265,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let currentEnvironment = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: defaultEnvironment).wrappedValue - SubscriptionPurchaseEnvironment.currentServiceEnvironment = currentEnvironment + subscriptionPurchaseEnvironment.currentServiceEnvironment = currentEnvironment Task { if let token = AppDelegate.accountManager.accessToken { - _ = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) + _ = await subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) _ = await AppDelegate.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } } @@ -576,7 +577,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { Task { guard let token = AppDelegate.accountManager.accessToken else { return } - if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { + if case .success(let subscription) = await subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift index 497ce84c7b..adc069cb3d 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift @@ -21,7 +21,8 @@ import Subscription import NetworkProtection import Common -extension NetworkProtectionKeychainTokenStore: SubscriptionTokenStorage { +extension NetworkProtectionKeychainTokenStore: SubscriptionTokenStoring { + public func store(accessToken: String) throws { try store(accessToken) } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index efe249804a..07ac1e814b 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -167,7 +167,7 @@ enum Preferences { return } - let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: AppDelegate.accountManager) + let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: AppDelegate.accountManager, subscriptionErrorReporter: SubscriptionErrorReporter()) await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index cb8a0a8da2..24ed6a4680 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -49,7 +49,7 @@ struct SubscriptionAppStoreRestorer { mainViewController.presentAsSheet(progressViewController) } - let syncResult = await PurchaseManager.shared.syncAppleIDAccount() + let syncResult = await StorePurchaseManager.shared.syncAppleIDAccount() switch syncResult { case .success: diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift index 438997c0f7..874e5ab9cb 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift @@ -40,7 +40,7 @@ enum SubscriptionError: Error { struct SubscriptionErrorReporter { // swiftlint:disable:next cyclomatic_complexity - static func report(subscriptionActivationError: SubscriptionError) { + func report(subscriptionActivationError: SubscriptionError) { os_log(.error, log: .subscription, "Subscription purchase error: %{public}s", subscriptionActivationError.localizedDescription) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index 7f47605aeb..038bc208bf 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -31,9 +31,7 @@ public extension Notification.Name { static let subscriptionPageCloseAndOpenPreferences = Notification.Name("com.duckduckgo.subscriptionPage.CloseAndOpenPreferences") } -/// /// The user script that will be the broker for all subscription features -/// public final class SubscriptionPagesUserScript: NSObject, UserScript, UserScriptMessaging { public var source: String = "" @@ -66,7 +64,7 @@ extension SubscriptionPagesUserScript: WKScriptMessageHandlerWithReply { } } -// MARK: - Fallback for macOS 10.15 +/// Fallback for macOS 10.15 extension SubscriptionPagesUserScript: WKScriptMessageHandler { public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // unsupported @@ -245,7 +243,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await mainViewController?.presentAsSheet(progressViewController) // Check for active subscriptions - if await PurchaseManager.hasActiveSubscription() { + if await StorePurchaseManager.hasActiveSubscription() { PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index 93d6071fed..7766d1b9e0 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -93,7 +93,7 @@ final class UserScripts: UserScriptsProvider { } if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature(accountManager: AppDelegate.accountManager)) + subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature(accountManager: AppDelegate.accountManager, subscriptionErrorReporter: SubscriptionErrorReporter())) userScripts.append(subscriptionPagesUserScript) identityTheftRestorationPagesUserScript.registerSubfeature(delegate: IdentityTheftRestorationPagesFeature()) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift index f1628fa561..1ab31b95d2 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift @@ -23,12 +23,12 @@ import Subscription @available(macOS 12.0, *) public final class DebugPurchaseModel: ObservableObject { - var purchaseManager: PurchaseManager + var purchaseManager: StorePurchaseManager let appStorePurchaseFlow: AppStorePurchaseFlow @Published var subscriptions: [SubscriptionRowModel] - init(manager: PurchaseManager, + init(manager: StorePurchaseManager, subscriptions: [SubscriptionRowModel] = [], accountManager: AccountManaging) { self.purchaseManager = manager diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift index c9a0e55b84..bdbf1f4c6e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift @@ -25,7 +25,7 @@ import Subscription @available(macOS 12.0, *) public final class DebugPurchaseViewController: NSViewController { - private let manager: PurchaseManager + private let manager: StorePurchaseManager private let model: DebugPurchaseModel private var cancellables = Set() @@ -35,7 +35,7 @@ public final class DebugPurchaseViewController: NSViewController { } public init(accountManager: AccountManaging) { - manager = PurchaseManager.shared + manager = StorePurchaseManager.shared model = DebugPurchaseModel(manager: manager, accountManager: accountManager) super.init(nibName: nil, bundle: nil) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index c6282cbc46..20f7d1dffb 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -30,15 +30,16 @@ public final class SubscriptionDebugMenu: NSMenuItem { var currentViewController: () -> NSViewController? let accountManager: AccountManaging + let subscriptionService: SubscriptionService private var _purchaseManager: Any? @available(macOS 12.0, *) - fileprivate var purchaseManager: PurchaseManager { + fileprivate var purchaseManager: StorePurchaseManager { if _purchaseManager == nil { - _purchaseManager = PurchaseManager() + _purchaseManager = StorePurchaseManager() } // swiftlint:disable:next force_cast - return _purchaseManager as! PurchaseManager + return _purchaseManager as! StorePurchaseManager } required init(coder: NSCoder) { @@ -50,13 +51,15 @@ public final class SubscriptionDebugMenu: NSMenuItem { isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, - accountManager: AccountManaging) { + accountManager: AccountManaging, + subscriptionService: SubscriptionService) { self.currentEnvironment = currentEnvironment self.updateEnvironment = updateEnvironment self.isInternalTestingEnabled = isInternalTestingEnabled self.updateInternalTestingFlag = updateInternalTestingFlag self.currentViewController = currentViewController self.accountManager = accountManager + self.subscriptionService = subscriptionService super.init(title: "Subscription", action: nil, keyEquivalent: "") self.submenu = makeSubmenu() } @@ -216,7 +219,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { func getSubscriptionDetails() { Task { guard let token = accountManager.accessToken else { return } - switch await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { + switch await subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { case .success(let response): showAlert(title: "Subscription info", message: "\(response)") case .failure(let error): diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 797dd73c35..511a440a3e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -36,6 +36,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { lazy var sheetModel: SubscriptionAccessModel = makeSubscriptionAccessModel() private let accountManager: AccountManaging + private let subscriptionService: SubscriptionService private let openURLHandler: (URL) -> Void public let userEventHandler: (UserEvent) -> Void private let sheetActionHandler: SubscriptionAccessActionHandlers @@ -88,8 +89,10 @@ public final class PreferencesSubscriptionModel: ObservableObject { public init(openURLHandler: @escaping (URL) -> Void, userEventHandler: @escaping (UserEvent) -> Void, sheetActionHandler: SubscriptionAccessActionHandlers, - accountManager: AccountManaging) { + accountManager: AccountManaging, + subscriptionService: SubscriptionService) { self.accountManager = accountManager + self.subscriptionService = subscriptionService self.openURLHandler = openURLHandler self.userEventHandler = userEventHandler self.sheetActionHandler = sheetActionHandler @@ -186,7 +189,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { case .stripe: Task { guard let accessToken = accountManager.accessToken, let externalID = accountManager.externalID, - case let .success(response) = await SubscriptionService.getCustomerPortalURL(accessToken: accessToken, externalID: externalID) else { return } + case let .success(response) = await subscriptionService.getCustomerPortalURL(accessToken: accessToken, externalID: externalID) else { return } guard let customerPortalURL = URL(string: response.customerPortalUrl) else { return } openURLHandler(customerPortalURL) @@ -196,7 +199,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func confirmIfSignedInToSameAccount() async -> Bool { if #available(macOS 12.0, *) { - guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return false } + guard let lastTransactionJWSRepresentation = await StorePurchaseManager.mostRecentTransaction() else { return false } switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { case .success(let response): @@ -269,11 +272,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor private func updateSubscription(with cachePolicy: SubscriptionService.CachePolicy) async { guard let token = accountManager.accessToken else { - SubscriptionService.signOut() + subscriptionService.signOut() return } - switch await SubscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) { + switch await subscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) { case .success(let subscription): updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) subscriptionPlatform = subscription.platform From 6cb20e16da7abf3c77cda893f5a51fc6fd8dc6ff Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 6 May 2024 19:37:12 +0100 Subject: [PATCH 13/41] refactoring WIP --- DuckDuckGo/Application/AppDelegate.swift | 144 +++++++++++------- ...erProtectionSubscriptionEventHandler.swift | 12 +- ...rkProtectionSubscriptionEventHandler.swift | 18 ++- .../MacPacketTunnelProvider.swift | 17 ++- .../NetworkProtectionFeatureVisibility.swift | 25 ++- .../DebugMenu/DebugPurchaseModel.swift | 4 +- .../DebugPurchaseViewController.swift | 6 +- .../DebugMenu/SubscriptionDebugMenu.swift | 38 ++--- .../PreferencesSubscriptionModel.swift | 20 +-- .../ActivateSubscriptionAccessModel.swift | 16 +- .../Model/ShareSubscriptionAccessModel.swift | 17 ++- .../SubscriptionAccessViewController.swift | 4 +- .../Sources/SubscriptionUI/UserText.swift | 4 +- 13 files changed, 198 insertions(+), 127 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 0065d9533d..6e65edfb63 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -84,11 +84,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let bookmarksManager = LocalBookmarkManager.shared var privacyDashboardWindow: NSWindow? - // Needs to be lazy as indirectly depends on AppDelegate - private let networkProtectionSubscriptionEventHandler: NetworkProtectionSubscriptionEventHandler + private let accountManager: AccountManager + private let subscriptionManager: SubscriptionManager + private var networkProtectionSubscriptionEventHandler: NetworkProtectionSubscriptionEventHandler? #if DBP - private let dataBrokerProtectionSubscriptionEventHandler: DataBrokerProtectionSubscriptionEventHandler + private var dataBrokerProtectionSubscriptionEventHandler: DataBrokerProtectionSubscriptionEventHandler? #endif private var didFinishLaunching = false @@ -104,9 +105,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return firstLaunchDate >= Date.weekAgo } - let subscriptionPurchaseEnvironment: SubscriptionPurchaseEnvironment = SubscriptionPurchaseEnvironment(subscriptionService: <#T##SubscriptionService#>) - public static let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - // swiftlint:disable:next function_body_length override init() { do { @@ -180,24 +178,45 @@ final class AppDelegate: NSObject, NSApplicationDelegate { privacyConfigManager: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager ) - #if APPSTORE || !STRIPE - SubscriptionPurchaseEnvironment.current = .appStore - #else - subscriptionPurchaseEnvironment.current = .stripe - #endif + // Configure subscription - networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler(accountManager: AppDelegate.accountManager) -#if DBP - dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler(accountManager: AppDelegate.accountManager) -#endif - } - - static func configurePixelKit() { -#if DEBUG - Self.setUpPixelKit(dryRun: true) +#if APPSTORE || !STRIPE + let subscriptionPurchaseEnvironment: SubscriptionEnvironment.Platform = .appStore #else - Self.setUpPixelKit(dryRun: false) + let subscriptionPurchaseEnvironment: SubscriptionEnvironment.Platform = .stripe #endif + + // Load subscription environment and re-configure SubscriptionManager if needed + let serviceEnvironment = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: SubscriptionEnvironment.ServiceEnvironment.default).wrappedValue + + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults(suiteName: subscriptionAppGroup) ?? UserDefaults.standard, + key: UserDefaultsCacheKey.subscriptionEntitlements, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) + let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) + let subscriptionService = SubscriptionService(currentServiceEnvironment: serviceEnvironment) + let authService = AuthService(currentServiceEnvironment: serviceEnvironment) + accountManager = AccountManager(accessTokenStorage: accessTokenStorage, + entitlementsCache: entitlementsCache, + subscriptionService: subscriptionService, + authService: authService) + + + if #available(macOS 12.0, iOS 15.0, *) { + let storePurchaseManager = StorePurchaseManager() + subscriptionManager = SubscriptionManager(storePurchaseManager: storePurchaseManager, + accountManager: accountManager, + subscriptionService: subscriptionService, + authService: authService, + currentServiceEnvironment: serviceEnvironment, + current: subscriptionPurchaseEnvironment) + } else { + subscriptionManager = SubscriptionManager(accountManager: accountManager, + subscriptionService: subscriptionService, + authService: authService, + currentServiceEnvironment: serviceEnvironment, + current: subscriptionPurchaseEnvironment) + } } func applicationWillFinishLaunching(_ notification: Notification) { @@ -214,6 +233,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #endif appIconChanger = AppIconChanger(internalUserDecider: internalUserDecider) + + // Configure Event handlers + networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler(accountManagerDataSource: self) +#if DBP + dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler(accountManagerDataSource: self) +#endif } // swiftlint:disable:next function_body_length @@ -261,18 +286,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { startupSync() - let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.default - - let currentEnvironment = UserDefaultsWrapper(key: .subscriptionEnvironment, - defaultValue: defaultEnvironment).wrappedValue - subscriptionPurchaseEnvironment.currentServiceEnvironment = currentEnvironment - - Task { - if let token = AppDelegate.accountManager.accessToken { - _ = await subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) - _ = await AppDelegate.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) - } - } + subscriptionManager.loadInitialData() if [.normal, .uiTests].contains(NSApp.runType) { stateRestorationManager.applicationDidFinishLaunching() @@ -310,14 +324,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { UserDefaultsWrapper.clearRemovedKeys() - networkProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() + networkProtectionSubscriptionEventHandler?.registerForSubscriptionAccountManagerEvents() - let defaultNetworkProtectionVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) - NetworkProtectionAppEvents(featureVisibility: defaultNetworkProtectionVisibility).applicationDidFinishLaunching() + NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(accountManagerDataSource: self)).applicationDidFinishLaunching() UNUserNotificationCenter.current().delegate = self #if DBP - dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() + dataBrokerProtectionSubscriptionEventHandler?.registerForSubscriptionAccountManagerEvents() #endif #if DBP @@ -333,16 +346,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate { syncService?.initializeIfNeeded() syncService?.scheduler.notifyAppLifecycleEvent() - let defaultNetworkProtectionVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) - NetworkProtectionAppEvents(featureVisibility: defaultNetworkProtectionVisibility).applicationDidBecomeActive() - + NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(accountManagerDataSource: self)).applicationDidBecomeActive() #if DBP DataBrokerProtectionAppEvents().applicationDidBecomeActive() #endif AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.toggleProtectionsCounter.sendEventsIfNeeded() - updateSubscriptionStatus() + subscriptionManager.updateSubscriptionStatus { isActive in + if isActive { + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) + } + } } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -384,9 +399,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate { urlEventHandler.handleFiles(files) } - private func applyPreferredTheme() { - let appearancePreferences = AppearancePreferences() - appearancePreferences.updateUserInterfaceStyle() + // MARK: - PixelKit + + static func configurePixelKit() { +#if DEBUG + Self.setUpPixelKit(dryRun: true) +#else + Self.setUpPixelKit(dryRun: false) +#endif } private static func setUpPixelKit(dryRun: Bool) { @@ -413,6 +433,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } } + // MARK: - Theme + + private func applyPreferredTheme() { + let appearancePreferences = AppearancePreferences() + appearancePreferences.updateUserInterfaceStyle() + } + // MARK: - Sync private func startupSync() { @@ -572,20 +599,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { NSApplication.shared.reply(toApplicationShouldTerminate: true) } } - - func updateSubscriptionStatus() { - Task { - guard let token = AppDelegate.accountManager.accessToken else { return } - - if case .success(let subscription) = await subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { - if subscription.isActive { - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) - } - } - - _ = await AppDelegate.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) - } - } } extension AppDelegate: UNUserNotificationCenterDelegate { @@ -612,3 +625,18 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } } + +extension AppDelegate: AccountManagingDataSource { + + var accessToken: String? { + accountManager.accessToken + } + + func hasEntitlement(for entitlement: Entitlement.ProductName) async -> Result { + await accountManager.hasEntitlement(for: entitlement) + } + + var isUserAuthenticated: Bool { + accountManager.isUserAuthenticated + } +} diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index 34782e0da6..2d9e1519f6 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -24,14 +24,14 @@ import PixelKit final class DataBrokerProtectionSubscriptionEventHandler { - private let accountManager: AccountManaging + private weak var accountManagerDataSource: AccountManagingDataSource? private let authRepository: AuthenticationRepository private let featureDisabler: DataBrokerProtectionFeatureDisabling - init(accountManager: AccountManaging, + init(accountManagerDataSource: AccountManagingDataSource?, authRepository: AuthenticationRepository = KeychainAuthenticationData(), featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) { - self.accountManager = accountManager + self.accountManagerDataSource = accountManagerDataSource self.authRepository = authRepository self.featureDisabler = featureDisabler } @@ -42,7 +42,11 @@ final class DataBrokerProtectionSubscriptionEventHandler { } @objc private func handleAccountDidSignIn() { - guard let token = accountManager.accessToken else { + guard let accountManagerDataSource else { + assertionFailure("Missing accountManagerDataSource") + return + } + guard let token = accountManagerDataSource.accessToken else { PixelKit.fire(GeneralPixel.dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn) assertionFailure("[DBP Subscription] AccountManager signed in but token could not be retrieved") return diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 064458b5fc..1560268d62 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -25,17 +25,17 @@ import NetworkProtectionUI final class NetworkProtectionSubscriptionEventHandler { - private let accountManager: AccountManaging + private weak var accountManagerDataSource: AccountManagingDataSource? private let networkProtectionTokenStorage: NetworkProtectionTokenStore private let networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling private let userDefaults: UserDefaults private var cancellables = Set() - init(accountManager: AccountManaging, + init(accountManagerDataSource: AccountManagingDataSource?, networkProtectionTokenStorage: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), userDefaults: UserDefaults = .netP) { - self.accountManager = accountManager + self.accountManagerDataSource = accountManagerDataSource self.networkProtectionTokenStorage = networkProtectionTokenStorage self.networkProtectionFeatureDisabler = networkProtectionFeatureDisabler self.userDefaults = userDefaults @@ -45,7 +45,11 @@ final class NetworkProtectionSubscriptionEventHandler { private func subscribeToEntitlementChanges() { Task { - switch await accountManager.hasEntitlement(for: .networkProtection) { + guard let accountManagerDataSource else { + assertionFailure("Missing accountManagerDataSource") + return + } + switch await accountManagerDataSource.hasEntitlement(for: .networkProtection) { case .success(let hasEntitlements): Task { await handleEntitlementsChange(hasEntitlements: hasEntitlements) @@ -95,7 +99,11 @@ final class NetworkProtectionSubscriptionEventHandler { } @objc private func handleAccountDidSignIn() { - guard accountManager.accessToken != nil else { + guard let accountManagerDataSource else { + assertionFailure("Missing accountManagerDataSource") + return + } + guard accountManagerDataSource.accessToken != nil else { assertionFailure("[NetP Subscription] AccountManager signed in but token could not be retrieved") return } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 5623af1f0c..8b087b04dd 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -350,10 +350,19 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { isSubscriptionEnabled: isSubscriptionEnabled, accessTokenProvider: { nil } ) - - let accountManager = AccountManager(subscriptionAppGroup: Self.subscriptionsAppGroup, accessTokenStorage: tokenStore) - - SubscriptionPurchaseEnvironment.currentServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults(suiteName: Self.subscriptionsAppGroup) ?? UserDefaults.standard, + key: UserDefaultsCacheKey.subscriptionEntitlements, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) + + let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging + + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment) + let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment) + let accountManager = AccountManager(accessTokenStorage: tokenStore, + entitlementsCache: entitlementsCache, + subscriptionService: subscriptionService, + authService: authService) + let entitlementsCheck = { await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) } diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 0c6da7b31a..3358af9177 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -46,7 +46,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let privacyConfigurationManager: PrivacyConfigurationManaging private let defaults: UserDefaults - let accountManager: AccountManaging + private weak var accountManagerDataSource: AccountManagingDataSource? init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), @@ -54,14 +54,14 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { featureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), defaults: UserDefaults = .netP, log: OSLog = .networkProtection, - accountManager: AccountManaging) { + accountManagerDataSource: AccountManagingDataSource?) { self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionFeatureActivation = networkProtectionFeatureActivation self.featureDisabler = featureDisabler self.featureOverrides = featureOverrides self.defaults = defaults - self.accountManager = accountManager + self.accountManagerDataSource = accountManagerDataSource } var isInstalled: Bool { @@ -78,7 +78,12 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return false } - switch await accountManager.hasEntitlement(for: .networkProtection) { + guard let accountManagerDataSource else { + assertionFailure("Missing accountManagerDataSource") + return false + } + + switch await accountManagerDataSource.hasEntitlement(for: .networkProtection) { case .success(let hasEntitlement): return hasEntitlement case .failure(let error): @@ -96,7 +101,11 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return false } - return accountManager.isUserAuthenticated + guard let accountManagerDataSource else { + assertionFailure("Missing accountManagerDataSource") + return false + } + return accountManagerDataSource.isUserAuthenticated } /// We've had to add this method because accessing the singleton in app delegate is crashing the integration tests. @@ -108,7 +117,11 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { /// Returns whether the VPN should be uninstalled automatically. /// This is only true when the user is not an Easter Egg user, the waitlist test has ended, and the user is onboarded. func shouldUninstallAutomatically() -> Bool { - return subscriptionFeatureAvailability.isFeatureAvailable && !accountManager.isUserAuthenticated && LoginItem.vpnMenu.status.isInstalled + guard let accountManagerDataSource else { + assertionFailure("Missing accountManagerDataSource") + return false + } + return subscriptionFeatureAvailability.isFeatureAvailable && !accountManagerDataSource.isUserAuthenticated && LoginItem.vpnMenu.status.isInstalled } /// Whether the user is fully onboarded diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift index 1ab31b95d2..c25e2b486e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseModel.swift @@ -30,10 +30,10 @@ public final class DebugPurchaseModel: ObservableObject { init(manager: StorePurchaseManager, subscriptions: [SubscriptionRowModel] = [], - accountManager: AccountManaging) { + appStorePurchaseFlow: AppStorePurchaseFlow) { self.purchaseManager = manager self.subscriptions = subscriptions - self.appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + self.appStorePurchaseFlow = appStorePurchaseFlow } @MainActor diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift index bdbf1f4c6e..8cc9b5a07a 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/DebugPurchaseViewController.swift @@ -34,9 +34,9 @@ public final class DebugPurchaseViewController: NSViewController { fatalError("init(coder:) has not been implemented") } - public init(accountManager: AccountManaging) { - manager = StorePurchaseManager.shared - model = DebugPurchaseModel(manager: manager, accountManager: accountManager) + public init(storePurchaseManager: StorePurchaseManager, appStorePurchaseFlow: AppStorePurchaseFlow) { + manager = storePurchaseManager + model = DebugPurchaseModel(manager: manager, appStorePurchaseFlow: appStorePurchaseFlow) super.init(nibName: nil, bundle: nil) } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 20f7d1dffb..394494ad2d 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -29,8 +29,10 @@ public final class SubscriptionDebugMenu: NSMenuItem { private var purchasePlatformItem: NSMenuItem? var currentViewController: () -> NSViewController? - let accountManager: AccountManaging - let subscriptionService: SubscriptionService + let subscriptionManager: SubscriptionManager + var accountManager: AccountManaging { + subscriptionManager.accountManager + } private var _purchaseManager: Any? @available(macOS 12.0, *) @@ -51,15 +53,13 @@ public final class SubscriptionDebugMenu: NSMenuItem { isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, - accountManager: AccountManaging, - subscriptionService: SubscriptionService) { + subscriptionManager: SubscriptionManager) { self.currentEnvironment = currentEnvironment self.updateEnvironment = updateEnvironment self.isInternalTestingEnabled = isInternalTestingEnabled self.updateInternalTestingFlag = updateInternalTestingFlag self.currentViewController = currentViewController - self.accountManager = accountManager - self.subscriptionService = subscriptionService + self.subscriptionManager = subscriptionManager super.init(title: "Subscription", action: nil, keyEquivalent: "") self.submenu = makeSubmenu() } @@ -106,11 +106,8 @@ public final class SubscriptionDebugMenu: NSMenuItem { private func makePurchasePlatformSubmenu() -> NSMenu { let menu = NSMenu(title: "Select purchase platform:") - - let currentPlatform = SubscriptionPurchaseEnvironment.current - let appStoreItem = NSMenuItem(title: "App Store", action: #selector(setPlatformToAppStore), target: self) - if currentPlatform == .appStore { + if subscriptionManager.currentEnvironment.platform == .appStore { appStoreItem.state = .on appStoreItem.isEnabled = false appStoreItem.action = nil @@ -119,7 +116,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { menu.addItem(appStoreItem) let stripeItem = NSMenuItem(title: "Stripe", action: #selector(setPlatformToStripe), target: self) - if currentPlatform == .stripe { + if subscriptionManager.currentEnvironment.platform == .stripe { stripeItem.state = .on stripeItem.isEnabled = false stripeItem.action = nil @@ -179,8 +176,8 @@ public final class SubscriptionDebugMenu: NSMenuItem { func showAccountDetails() { let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" let message = accountManager.isUserAuthenticated ? ["AuthToken: \(accountManager.authToken ?? "")", - "AccessToken: \(accountManager.accessToken ?? "")", - "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil + "AccessToken: \(accountManager.accessToken ?? "")", + "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil showAlert(title: title, message: message) } @@ -188,7 +185,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { func validateToken() { Task { guard let token = accountManager.accessToken else { return } - switch await AuthService.validateToken(accessToken: token) { + switch await subscriptionManager.authService.validateToken(accessToken: token) { case .success(let response): showAlert(title: "Validate token", message: "\(response)") case .failure(let error): @@ -219,7 +216,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { func getSubscriptionDetails() { Task { guard let token = accountManager.accessToken else { return } - switch await subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { + switch await subscriptionManager.subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { case .success(let response): showAlert(title: "Subscription info", message: "\(response)") case .failure(let error): @@ -238,7 +235,10 @@ public final class SubscriptionDebugMenu: NSMenuItem { @IBAction func showPurchaseView(_ sender: Any?) { if #available(macOS 12.0, *) { - currentViewController()?.presentAsSheet(DebugPurchaseViewController(accountManager: accountManager)) + let storePurchaseManager = StorePurchaseManager() + let appStorePurchaseFlow = AppStorePurchaseFlow(subscriptionManager: subscriptionManager) + let vc = DebugPurchaseViewController(storePurchaseManager: storePurchaseManager, appStorePurchaseFlow: appStorePurchaseFlow) + currentViewController()?.presentAsSheet(vc) } } @@ -250,7 +250,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { askAndUpdatePlatform(to: .stripe) } - private func askAndUpdatePlatform(to newPlatform: SubscriptionPurchaseEnvironment.Environment) { + private func askAndUpdatePlatform(to newPlatform: SubscriptionEnvironment.Platform) { let alert = makeAlert(title: "Are you sure you want to change the purchase platform to \(newPlatform.rawValue.capitalized)", message: "This setting is not persisted between app runs. After restarting the app it returns to the default determined on app's distribution method.", buttonNames: ["Yes", "No"]) @@ -258,7 +258,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { guard case .alertFirstButtonReturn = response else { return } - SubscriptionPurchaseEnvironment.current = newPlatform +// subscriptionManager.currentEnvironment.platform = newPlatform // TODO: In debug menus set subscription environments in user defaults and restart the app every time the environment is changed refreshSubmenu() } @@ -297,7 +297,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { func restorePurchases(_ sender: Any?) { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: accountManager) + let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) await appStoreRestoreFlow.restoreAccountFromPastPurchase() } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 511a440a3e..02c5cd29e2 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -35,8 +35,10 @@ public final class PreferencesSubscriptionModel: ObservableObject { lazy var sheetModel: SubscriptionAccessModel = makeSubscriptionAccessModel() - private let accountManager: AccountManaging - private let subscriptionService: SubscriptionService + private let subscriptionManager: SubscriptionManager + private var accountManager: AccountManaging { + subscriptionManager.accountManager + } private let openURLHandler: (URL) -> Void public let userEventHandler: (UserEvent) -> Void private let sheetActionHandler: SubscriptionAccessActionHandlers @@ -89,10 +91,8 @@ public final class PreferencesSubscriptionModel: ObservableObject { public init(openURLHandler: @escaping (URL) -> Void, userEventHandler: @escaping (UserEvent) -> Void, sheetActionHandler: SubscriptionAccessActionHandlers, - accountManager: AccountManaging, - subscriptionService: SubscriptionService) { - self.accountManager = accountManager - self.subscriptionService = subscriptionService + subscriptionManager: SubscriptionManager) { + self.subscriptionManager = subscriptionManager self.openURLHandler = openURLHandler self.userEventHandler = userEventHandler self.sheetActionHandler = sheetActionHandler @@ -137,9 +137,9 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { if accountManager.isUserAuthenticated { - ShareSubscriptionAccessModel(actionHandlers: sheetActionHandler, email: accountManager.email, accountManager: accountManager) + ShareSubscriptionAccessModel(actionHandlers: sheetActionHandler, email: accountManager.email, subscriptionManager: subscriptionManager) } else { - ActivateSubscriptionAccessModel(actionHandlers: sheetActionHandler, shouldShowRestorePurchase: SubscriptionPurchaseEnvironment.current == .appStore) + ActivateSubscriptionAccessModel(actionHandlers: sheetActionHandler, subscriptionEnvironment: subscriptionManager.currentEnvironment) } } @@ -150,7 +150,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func purchaseAction() { - openURLHandler(.subscriptionPurchase) + openURLHandler(SubscriptionURL.purchase.subscriptionURL(environment: <#T##SubscriptionEnvironment.ServiceEnvironment#>)) } enum ChangePlanOrBillingAction { @@ -182,7 +182,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { } } - private func changePlanOrBilling(for environment: SubscriptionPurchaseEnvironment.Environment) { + private func changePlanOrBilling(for environment: SubscriptionEnvironment.Platform) { switch environment { case .appStore: NSWorkspace.shared.open(.manageSubscriptionsInAppStoreAppURL) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift index 3e14765641..005ad75b91 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift @@ -18,12 +18,13 @@ import Foundation import Subscription +import BrowserServicesKit public final class ActivateSubscriptionAccessModel: SubscriptionAccessModel, PurchaseRestoringSubscriptionAccessModel { - private var actionHandlers: SubscriptionAccessActionHandlers + private var actionHandlers: SubscriptionAccessActionHandlers public var title = UserText.activateModalTitle - public var description = UserText.activateModalDescription(platform: SubscriptionPurchaseEnvironment.current) + public let description: String public var email: String? public var emailLabel: String { UserText.email } @@ -34,13 +35,18 @@ public final class ActivateSubscriptionAccessModel: SubscriptionAccessModel, Pur public var restorePurchaseDescription = UserText.restorePurchaseDescription public var restorePurchaseButtonTitle = UserText.restorePurchaseButton - public init(actionHandlers: SubscriptionAccessActionHandlers, shouldShowRestorePurchase: Bool) { + let subscriptionEnvironment: SubscriptionEnvironment + + public init(actionHandlers: SubscriptionAccessActionHandlers, + subscriptionEnvironment: SubscriptionEnvironment) { self.actionHandlers = actionHandlers - self.shouldShowRestorePurchase = shouldShowRestorePurchase + self.shouldShowRestorePurchase = subscriptionEnvironment.platform == .appStore + self.subscriptionEnvironment = subscriptionEnvironment + self.description = UserText.activateModalDescription(platform: subscriptionEnvironment.platform) } public func handleEmailAction() { - actionHandlers.openURLHandler(.activateSubscriptionViaEmail) + actionHandlers.openURLHandler(SubscriptionURL.activateViaEmail.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment)) actionHandlers.uiActionHandler(.activateAddEmailClick) } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index 084a95b489..4866e2f385 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -20,25 +20,28 @@ import Foundation import Subscription public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { + public var title = UserText.shareModalTitle - public var description = UserText.shareModalDescription(platform: SubscriptionPurchaseEnvironment.current) + public let description: String private var actionHandlers: SubscriptionAccessActionHandlers public var email: String? public var emailLabel: String { UserText.email } public var emailDescription: String { hasEmail ? UserText.shareModalHasEmailDescription : UserText.shareModalNoEmailDescription } public var emailButtonTitle: String { hasEmail ? UserText.manageEmailButton : UserText.addEmailButton } - let accountManager: AccountManaging + private let subscriptionManager: SubscriptionManager - public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, accountManager: AccountManaging) { + public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, subscriptionManager: SubscriptionManager) { self.actionHandlers = actionHandlers self.email = email - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager + self.description = UserText.shareModalDescription(platform: subscriptionManager.currentEnvironment.platform) } private var hasEmail: Bool { !(email?.isEmpty ?? true) } public func handleEmailAction() { - let url: URL = hasEmail ? .manageSubscriptionEmail : .addEmailToSubscription + let type = hasEmail ? SubscriptionURL.manageEmail : SubscriptionURL.addEmail + let url: URL = type.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) if hasEmail { actionHandlers.uiActionHandler(.postSubscriptionAddEmailClick) @@ -47,9 +50,9 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { } Task { - if SubscriptionPurchaseEnvironment.current == .appStore { + if subscriptionManager.currentEnvironment.platform == .appStore { if #available(macOS 12.0, iOS 15.0, *) { - let appStoreAccountManagementFlow = AppStoreAccountManagementFlow(accountManager: accountManager) + let appStoreAccountManagementFlow = AppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift index e860b5a97a..7d0480abb9 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -55,9 +55,9 @@ public final class SubscriptionAccessViewController: NSViewController { private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { if accountManager.isUserAuthenticated { - ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: accountManager.email, accountManager: accountManager) + ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: accountManager.email, subscriptionManager: sub) } else { - ActivateSubscriptionAccessModel(actionHandlers: actionHandlers, shouldShowRestorePurchase: SubscriptionPurchaseEnvironment.current == .appStore) + ActivateSubscriptionAccessModel(actionHandlers: actionHandlers, subscriptionEnvironment: <#T##SubscriptionEnvironment#>) } } } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift index f917d58b71..ec45b575ca 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift @@ -101,7 +101,7 @@ enum UserText { // MARK: - Activate subscription modal static let activateModalTitle = NSLocalizedString("subscription.activate.modal.title", value: "Activate your subscription on this device", comment: "Activate subscription modal view title") - static func activateModalDescription(platform: SubscriptionPurchaseEnvironment.Environment) -> String { + static func activateModalDescription(platform: SubscriptionEnvironment.Platform) -> String { switch platform { case .appStore: NSLocalizedString("subscription.appstore.activate.modal.description", value: "Access your Privacy Pro subscription on this device via Apple ID or an email address.", comment: "Activate subscription modal view subtitle description") @@ -115,7 +115,7 @@ enum UserText { // MARK: - Share subscription modal static let shareModalTitle = NSLocalizedString("subscription.share.modal.title", value: "Use your subscription on other devices", comment: "Share subscription modal view title") - static func shareModalDescription(platform: SubscriptionPurchaseEnvironment.Environment) -> String { + static func shareModalDescription(platform: SubscriptionEnvironment.Platform) -> String { switch platform { case .appStore: NSLocalizedString("subscription.appstore.share.modal.description", value: "Access your subscription via Apple ID or by adding an email address.", comment: "Share subscription modal view subtitle description") From 159bf6cd3661bf426635cd88ba6a67926192640a Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 7 May 2024 13:27:10 +0100 Subject: [PATCH 14/41] more dismantling and rebuilding --- DuckDuckGo.xcodeproj/project.pbxproj | 28 ++-- DuckDuckGo/Application/AppDelegate.swift | 60 ++++---- DuckDuckGo/Application/URLEventHandler.swift | 98 +++++++------ ...erProtectionSubscriptionEventHandler.swift | 12 +- DuckDuckGo/Menus/MainMenu.swift | 10 +- DuckDuckGo/Menus/MainMenuActions.swift | 131 +++++++++++------- .../View/AddressBarTextField.swift | 9 +- .../NavigationBar/View/MoreOptionsMenu.swift | 6 +- .../View/NavigationBarViewController.swift | 14 +- ...rkProtection+ConvenienceInitializers.swift | 2 +- .../NetworkProtectionDebugMenu.swift | 2 +- .../NetworkProtectionNavBarButtonModel.swift | 2 +- .../NetworkProtectionTunnelController.swift | 9 +- ...NetworkProtectionIPCTunnelController.swift | 2 +- .../NetworkProtectionRemoteMessaging.swift | 2 +- ...rkProtectionSubscriptionEventHandler.swift | 18 +-- .../MacPacketTunnelProvider.swift | 9 +- .../Model/PreferencesSection.swift | 7 +- .../Model/PreferencesSidebarModel.swift | 4 +- .../Model/VPNPreferencesModel.swift | 2 +- .../View/PreferencesRootView.swift | 7 +- ...atureAvailability+DefaultInitializer.swift | 3 +- DuckDuckGo/Sync/SyncDebugMenu.swift | 2 +- DuckDuckGo/Tab/Model/TabContent.swift | 10 +- .../RedirectNavigationResponder.swift | 8 +- ...ntityTheftRestorationPagesUserScript.swift | 2 +- .../SubscriptionAppStoreRestorer.swift | 28 ++-- .../SubscriptionPagesUserScript.swift | 91 ++++++------ DuckDuckGo/Tab/UserScripts/UserScripts.swift | 2 +- .../VPNFeedbackFormViewController.swift | 2 +- .../VPNMetadataCollector.swift | 4 +- .../NetworkProtectionFeatureVisibility.swift | 26 +--- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 42 +++++- DuckDuckGoVPN/NetworkProtectionBouncer.swift | 4 +- .../PreferencesSubscriptionModel.swift | 21 ++- .../SubscriptionAccessViewController.swift | 13 +- 36 files changed, 387 insertions(+), 305 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index edfa17fae6..e9c8b3935d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1537,8 +1537,6 @@ 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BB108592A43375D000AB95F /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BB108582A43375D000AB95F /* PFMoveApplication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 7BBA7CE62BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; - 7BBA7CE72BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; 7BBD45B12A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */; }; 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */; }; 7BBE2B7B2B61663C00697445 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7BBE2B7A2B61663C00697445 /* NetworkProtectionProxy */; }; @@ -2552,6 +2550,8 @@ F116A7C32BD1924B00F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C22BD1924B00F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C72BD1925500F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C62BD1925500F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C92BD1929000F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C82BD1929000F3FCF7 /* PixelKitTestingUtilities */; }; + F118EA7D2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; + F118EA7E2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; @@ -3318,7 +3318,6 @@ 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionIPCTunnelController.swift; sourceTree = ""; }; 7BB108572A43375D000AB95F /* PFMoveApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = ""; }; 7BB108582A43375D000AB95F /* PFMoveApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = ""; }; - 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugUtilities.swift; sourceTree = ""; }; 7BD01C182AD8319C0088B32E /* IPCServiceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPCServiceManager.swift; sourceTree = ""; }; 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkExtensionController.swift; sourceTree = ""; }; @@ -4034,6 +4033,7 @@ EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+VPNAgentConvenienceInitializers.swift"; sourceTree = ""; }; EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = ""; }; EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; + F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; F119CCC72BE53CE00002B30E /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; @@ -4486,14 +4486,6 @@ path = Services; sourceTree = ""; }; - 1EA7B8D62B7E124E000330A4 /* Subscription */ = { - isa = PBXGroup; - children = ( - 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, - ); - path = Subscription; - sourceTree = ""; - }; 3169132B2BD2C7960051B46D /* ErrorView */ = { isa = PBXGroup; children = ( @@ -6528,7 +6520,7 @@ 4B677422255DBEB800025BD8 /* SmarterEncryption */, B68458AE25C7E75100DC17B6 /* StateRestoration */, B6A9E44E26142AF90067D1B9 /* Statistics */, - 1EA7B8D62B7E124E000330A4 /* Subscription */, + F118EA7B2BEA2B8700F77634 /* Subscription */, AACB8E7224A4C8BC005F2218 /* Suggestions */, 3775913429AB99DA00E26367 /* Sync */, AA86491B24D837DE001BABEE /* Tab */, @@ -8133,6 +8125,14 @@ path = JSAlert; sourceTree = ""; }; + F118EA7B2BEA2B8700F77634 /* Subscription */ = { + isa = PBXGroup; + children = ( + F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, + ); + path = Subscription; + sourceTree = ""; + }; F1B33DF92BAD9C83001128B3 /* Subscription */ = { isa = PBXGroup; children = ( @@ -9978,7 +9978,6 @@ 3706FC19293F65D500E42796 /* NSNotificationName+Favicons.swift in Sources */, 3706FC1A293F65D500E42796 /* PinningManager.swift in Sources */, 4B37EE782B4CFF3900A89A61 /* DataBrokerProtectionRemoteMessage.swift in Sources */, - 7BBA7CE72BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, 3706FC1B293F65D500E42796 /* TabCollectionViewModel+NSSecureCoding.swift in Sources */, 3706FC1D293F65D500E42796 /* EmailManagerRequestDelegate.swift in Sources */, 3706FC1E293F65D500E42796 /* ApplicationVersionReader.swift in Sources */, @@ -9987,6 +9986,7 @@ 1DDC85042B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */, 3706FC20293F65D500E42796 /* PreferencesAutofillView.swift in Sources */, 3706FC21293F65D500E42796 /* UserText+PasswordManager.swift in Sources */, + F118EA7E2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, 3706FC22293F65D500E42796 /* LoadingProgressView.swift in Sources */, 3706FC23293F65D500E42796 /* StatisticsStore.swift in Sources */, 1DDD3EBD2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift in Sources */, @@ -10927,7 +10927,6 @@ B68C92C1274E3EF4002AC6B0 /* PopUpWindow.swift in Sources */, AA5FA6A0275F948900DCE9C9 /* Favicons.xcdatamodeld in Sources */, 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemScheduler.swift in Sources */, - 7BBA7CE62BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, B684592225C93BE000DC17B6 /* Publisher.asVoid.swift in Sources */, 4B9DB01D2A983B24000927DB /* Waitlist.swift in Sources */, BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */, @@ -11439,6 +11438,7 @@ B6C00ECB292F839D009C73A6 /* AutofillTabExtension.swift in Sources */, B6E319382953446000DD3BCF /* Assertions.swift in Sources */, AAB549DF25DAB8F80058460B /* BookmarkViewModel.swift in Sources */, + F118EA7D2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, 85707F28276A34D900DC0649 /* DaxSpeech.swift in Sources */, 31F28C5328C8EECA00119F70 /* DuckURLSchemeHandler.swift in Sources */, AA13DCB4271480B0006D48D3 /* FirePopoverViewModel.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 6e65edfb63..77197ff94d 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -37,9 +37,17 @@ import Lottie import NetworkProtection import Subscription -@MainActor +// @MainActor final class AppDelegate: NSObject, NSApplicationDelegate { + /// To be used with very cautiously, this do not replace proper dependency injection + static var shared: AppDelegate { + guard let delegate = NSApplication.shared.delegate as? AppDelegate else { + fatalError("Could not get app delegate ") + } + return delegate + } + #if DEBUG let disableCVDisplayLinkLogs: Void = { // Disable CVDisplayLink logs @@ -85,7 +93,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { var privacyDashboardWindow: NSWindow? private let accountManager: AccountManager - private let subscriptionManager: SubscriptionManager + public let subscriptionManager: SubscriptionManager private var networkProtectionSubscriptionEventHandler: NetworkProtectionSubscriptionEventHandler? #if DBP @@ -178,7 +186,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { privacyConfigManager: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager ) - // Configure subscription + // MARK: - Configure Subscription #if APPSTORE || !STRIPE let subscriptionPurchaseEnvironment: SubscriptionEnvironment.Platform = .appStore @@ -201,7 +209,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { subscriptionService: subscriptionService, authService: authService) - if #available(macOS 12.0, iOS 15.0, *) { let storePurchaseManager = StorePurchaseManager() subscriptionManager = SubscriptionManager(storePurchaseManager: storePurchaseManager, @@ -235,9 +242,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { appIconChanger = AppIconChanger(internalUserDecider: internalUserDecider) // Configure Event handlers - networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler(accountManagerDataSource: self) + networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler(subscriptionManager: subscriptionManager) #if DBP - dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler(accountManagerDataSource: self) + dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler(subscriptionManager: subscriptionManager) #endif } @@ -326,7 +333,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { networkProtectionSubscriptionEventHandler?.registerForSubscriptionAccountManagerEvents() - NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(accountManagerDataSource: self)).applicationDidFinishLaunching() + NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: subscriptionManager)).applicationDidFinishLaunching() UNUserNotificationCenter.current().delegate = self #if DBP @@ -346,7 +353,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { syncService?.initializeIfNeeded() syncService?.scheduler.notifyAppLifecycleEvent() - NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(accountManagerDataSource: self)).applicationDidBecomeActive() + NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: subscriptionManager)).applicationDidBecomeActive() #if DBP DataBrokerProtectionAppEvents().applicationDidBecomeActive() #endif @@ -514,7 +521,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { switch response { case .alertSecondButtonReturn: alert.window.sheetParent?.endSheet(alert.window) - WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .sync) + DispatchQueue.main.async { + WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .sync) + } default: break } @@ -591,12 +600,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } private func setUpAutoClearHandler() { - autoClearHandler = AutoClearHandler(preferences: .shared, - fireViewModel: FireCoordinator.fireViewModel, - stateRestorationManager: stateRestorationManager) - autoClearHandler.handleAppLaunch() - autoClearHandler.onAutoClearCompleted = { - NSApplication.shared.reply(toApplicationShouldTerminate: true) + DispatchQueue.main.async { + self.autoClearHandler = AutoClearHandler(preferences: .shared, + fireViewModel: FireCoordinator.fireViewModel, + stateRestorationManager: self.stateRestorationManager) + self.autoClearHandler.handleAppLaunch() + self.autoClearHandler.onAutoClearCompleted = { + NSApplication.shared.reply(toApplicationShouldTerminate: true) + } } } } @@ -616,7 +627,9 @@ extension AppDelegate: UNUserNotificationCenterDelegate { #if DBP if response.notification.request.identifier == DataBrokerProtectionWaitlist.notificationIdentifier { - DataBrokerProtectionAppEvents().handleWaitlistInvitedNotification(source: .localPush) + DispatchQueue.main.async { + DataBrokerProtectionAppEvents().handleWaitlistInvitedNotification(source: .localPush) + } } #endif } @@ -625,18 +638,3 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } } - -extension AppDelegate: AccountManagingDataSource { - - var accessToken: String? { - accountManager.accessToken - } - - func hasEntitlement(for entitlement: Entitlement.ProductName) async -> Result { - await accountManager.hasEntitlement(for: entitlement) - } - - var isUserAuthenticated: Bool { - accountManager.isUserAuthenticated - } -} diff --git a/DuckDuckGo/Application/URLEventHandler.swift b/DuckDuckGo/Application/URLEventHandler.swift index 003cf5500b..1329811b82 100644 --- a/DuckDuckGo/Application/URLEventHandler.swift +++ b/DuckDuckGo/Application/URLEventHandler.swift @@ -20,6 +20,7 @@ import Common import Foundation import AppKit import PixelKit +import Subscription import NetworkProtectionUI @@ -27,10 +28,10 @@ import NetworkProtectionUI import DataBrokerProtection #endif -@MainActor +// @MainActor final class URLEventHandler { - private let handler: @MainActor (URL) -> Void + private let handler: (URL) -> Void private var didFinishLaunching = false private var urlsToOpen = [URL]() @@ -50,7 +51,9 @@ final class URLEventHandler { if !urlsToOpen.isEmpty { for url in urlsToOpen { - self.handler(url) + DispatchQueue.main.async { + self.handler(url) + } } self.urlsToOpen = [] @@ -96,7 +99,9 @@ final class URLEventHandler { private func handleURLs(_ urls: [URL]) { if didFinishLaunching { - urls.forEach { self.handler($0) } + urls.forEach { + self.handler($0) + } } else { self.urlsToOpen.append(contentsOf: urls) } @@ -113,54 +118,58 @@ final class URLEventHandler { } #endif - if url.isFileURL && url.pathExtension == WebKitDownloadTask.downloadExtension { - guard let mainViewController = { - if let mainWindowController = WindowControllersManager.shared.lastKeyMainWindowController { - return mainWindowController.mainViewController + DispatchQueue.main.async { + if url.isFileURL && url.pathExtension == WebKitDownloadTask.downloadExtension { + guard let mainViewController = { + if let mainWindowController = WindowControllersManager.shared.lastKeyMainWindowController { + return mainWindowController.mainViewController + } + return WindowsManager.openNewWindow(with: .newtab, source: .ui, isBurner: false)?.contentViewController as? MainViewController + }() else { return } + + if !mainViewController.navigationBarViewController.isDownloadsPopoverShown { + mainViewController.navigationBarViewController.toggleDownloadsPopover(keepButtonVisible: false) } - return WindowsManager.openNewWindow(with: .newtab, source: .ui, isBurner: false)?.contentViewController as? MainViewController - }() else { return } - if !mainViewController.navigationBarViewController.isDownloadsPopoverShown { - mainViewController.navigationBarViewController.toggleDownloadsPopover(keepButtonVisible: false) + return } - return - } - - if url.scheme?.isNetworkProtectionScheme == false && url.scheme?.isDataBrokerProtectionScheme == false { - WaitlistModalDismisser.dismissWaitlistModalViewControllerIfNecessary(url) - WindowControllersManager.shared.show(url: url, source: .appOpenUrl, newTab: true) + if url.scheme?.isNetworkProtectionScheme == false && url.scheme?.isDataBrokerProtectionScheme == false { + WaitlistModalDismisser.dismissWaitlistModalViewControllerIfNecessary(url) + WindowControllersManager.shared.show(url: url, source: .appOpenUrl, newTab: true) + } } } /// Handles NetP URLs - /// private static func handleNetworkProtectionURL(_ url: URL) { - switch url { - case AppLaunchCommand.showStatus.launchURL: - Task { - await WindowControllersManager.shared.showNetworkProtectionStatus() - } - case AppLaunchCommand.showSettings.launchURL: - WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn) - case AppLaunchCommand.shareFeedback.launchURL: - WindowControllersManager.shared.showShareFeedbackModal() - case AppLaunchCommand.justOpen.launchURL: - WindowControllersManager.shared.showMainWindow() - case AppLaunchCommand.showVPNLocations.launchURL: - WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn) - WindowControllersManager.shared.showLocationPickerSheet() - case AppLaunchCommand.showPrivacyPro.launchURL: - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) + DispatchQueue.main.async { + switch url { + case AppLaunchCommand.showStatus.launchURL: + Task { + await WindowControllersManager.shared.showNetworkProtectionStatus() + } + case AppLaunchCommand.showSettings.launchURL: + WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn) + case AppLaunchCommand.shareFeedback.launchURL: + WindowControllersManager.shared.showShareFeedbackModal() + case AppLaunchCommand.justOpen.launchURL: + WindowControllersManager.shared.showMainWindow() + case AppLaunchCommand.showVPNLocations.launchURL: + WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn) + WindowControllersManager.shared.showLocationPickerSheet() + case AppLaunchCommand.showPrivacyPro.launchURL: + let url = SubscriptionURL.purchase.subscriptionURL(environment: AppDelegate.shared.subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .subscription(url)) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) #if !APPSTORE && !DEBUG - case AppLaunchCommand.moveAppToApplications.launchURL: - // this should be run after NSApplication.shared is set - PFMoveToApplicationsFolderIfNecessary(false) + case AppLaunchCommand.moveAppToApplications.launchURL: + // this should be run after NSApplication.shared is set + PFMoveToApplicationsFolderIfNecessary(false) #endif - default: - return + default: + return + } } } @@ -171,15 +180,14 @@ final class URLEventHandler { switch url { case DataBrokerProtectionNotificationCommand.showDashboard.url: NotificationCenter.default.post(name: DataBrokerProtectionNotifications.shouldReloadUI, object: nil) - - WindowControllersManager.shared.showTab(with: .dataBrokerProtection) + DispatchQueue.main.async { + WindowControllersManager.shared.showTab(with: .dataBrokerProtection) + } default: return } } - #endif - } private extension String { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index 2d9e1519f6..31fc1592a2 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -24,14 +24,14 @@ import PixelKit final class DataBrokerProtectionSubscriptionEventHandler { - private weak var accountManagerDataSource: AccountManagingDataSource? + private let subscriptionManager: SubscriptionManager private let authRepository: AuthenticationRepository private let featureDisabler: DataBrokerProtectionFeatureDisabling - init(accountManagerDataSource: AccountManagingDataSource?, + init(subscriptionManager: SubscriptionManager, authRepository: AuthenticationRepository = KeychainAuthenticationData(), featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) { - self.accountManagerDataSource = accountManagerDataSource + self.subscriptionManager = subscriptionManager self.authRepository = authRepository self.featureDisabler = featureDisabler } @@ -42,11 +42,7 @@ final class DataBrokerProtectionSubscriptionEventHandler { } @objc private func handleAccountDidSignIn() { - guard let accountManagerDataSource else { - assertionFailure("Missing accountManagerDataSource") - return - } - guard let token = accountManagerDataSource.accessToken else { + guard let token = subscriptionManager.accountManager.accessToken else { PixelKit.fire(GeneralPixel.dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn) assertionFailure("[DBP Subscription] AccountManager signed in but token could not be retrieved") return diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 8101d5c0f0..544e90a58e 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -540,7 +540,7 @@ import SubscriptionUI toggleBookmarksShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .bookmarks) toggleDownloadsShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .downloads) - if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isVPNVisible() { + if DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager).isVPNVisible() { toggleNetworkProtectionShortcutMenuItem.isHidden = false toggleNetworkProtectionShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .networkProtection) } else { @@ -619,14 +619,14 @@ import SubscriptionUI NSMenuItem(title: "Trigger Fatal Error", action: #selector(MainViewController.triggerFatalError)) - let currentEnvironmentWrapper = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: SubscriptionPurchaseEnvironment.ServiceEnvironment.default) + let currentEnvironmentWrapper = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: SubscriptionEnvironment.ServiceEnvironment.default) let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false) SubscriptionDebugMenu(currentEnvironment: { currentEnvironmentWrapper.wrappedValue.rawValue }, updateEnvironment: { - guard let newEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: $0) else { return } + guard let newEnvironment = SubscriptionEnvironment.ServiceEnvironment(rawValue: $0) else { return } currentEnvironmentWrapper.wrappedValue = newEnvironment - SubscriptionPurchaseEnvironment.currentServiceEnvironment = newEnvironment +// SubscriptionPurchaseEnvironment.currentServiceEnvironment = newEnvironment // TODO: reimplement debug menu for change of environment VPNSettings(defaults: .netP).selectedEnvironment = newEnvironment == .staging ? .staging : .production }, isInternalTestingEnabled: { isInternalTestingWrapper.wrappedValue }, @@ -634,7 +634,7 @@ import SubscriptionUI currentViewController: { WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController }, - accountManager: AppDelegate.accountManager) + subscriptionManager: AppDelegate.shared.subscriptionManager) NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) } diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 935189c20c..1d257bd027 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -42,29 +42,41 @@ extension AppDelegate { // MARK: - File @objc func newWindow(_ sender: Any?) { - WindowsManager.openNewWindow() + DispatchQueue.main.async { + WindowsManager.openNewWindow() + } } @objc func newBurnerWindow(_ sender: Any?) { - WindowsManager.openNewWindow(burnerMode: BurnerMode(isBurner: true)) + DispatchQueue.main.async { + WindowsManager.openNewWindow(burnerMode: BurnerMode(isBurner: true)) + } } @objc func newTab(_ sender: Any?) { - WindowsManager.openNewWindow() + DispatchQueue.main.async { + WindowsManager.openNewWindow() + } } @objc func openLocation(_ sender: Any?) { - WindowsManager.openNewWindow() + DispatchQueue.main.async { + WindowsManager.openNewWindow() + } } @objc func closeAllWindows(_ sender: Any?) { - WindowsManager.closeWindows() + DispatchQueue.main.async { + WindowsManager.closeWindows() + } } // MARK: - History @objc func reopenLastClosedTab(_ sender: Any?) { - RecentlyClosedCoordinator.shared.reopenItem() + DispatchQueue.main.async { + RecentlyClosedCoordinator.shared.reopenItem() + } } @objc func recentlyClosedAction(_ sender: Any?) { @@ -73,8 +85,9 @@ extension AppDelegate { assertionFailure("Wrong represented object for recentlyClosedAction()") return } - - RecentlyClosedCoordinator.shared.reopenItem(cacheItem) + DispatchQueue.main.async { + RecentlyClosedCoordinator.shared.reopenItem(cacheItem) + } } @objc func openVisit(_ sender: NSMenuItem) { @@ -83,34 +96,41 @@ extension AppDelegate { assertionFailure("Wrong represented object") return } - - WindowsManager.openNewWindow(with: Tab(content: .contentFromURL(url, source: .historyEntry), shouldLoadInBackground: true)) + DispatchQueue.main.async { + WindowsManager.openNewWindow(with: Tab(content: .contentFromURL(url, source: .historyEntry), shouldLoadInBackground: true)) + } } @objc func clearAllHistory(_ sender: NSMenuItem) { - guard let window = WindowsManager.openNewWindow(with: Tab(content: .newtab)), - let windowController = window.windowController as? MainWindowController else { - assertionFailure("No reference to main window controller") - return - } + DispatchQueue.main.async { + guard let window = WindowsManager.openNewWindow(with: Tab(content: .newtab)), + let windowController = window.windowController as? MainWindowController else { + assertionFailure("No reference to main window controller") + return + } - windowController.mainViewController.clearAllHistory(sender) + windowController.mainViewController.clearAllHistory(sender) + } } @objc func clearThisHistory(_ sender: ClearThisHistoryMenuItem) { - guard let window = WindowsManager.openNewWindow(with: Tab(content: .newtab)), - let windowController = window.windowController as? MainWindowController else { - assertionFailure("No reference to main window controller") - return - } + DispatchQueue.main.async { + guard let window = WindowsManager.openNewWindow(with: Tab(content: .newtab)), + let windowController = window.windowController as? MainWindowController else { + assertionFailure("No reference to main window controller") + return + } - windowController.mainViewController.clearThisHistory(sender) + windowController.mainViewController.clearThisHistory(sender) + } } // MARK: - Window @objc func reopenAllWindowsFromLastSession(_ sender: Any?) { - _=stateRestorationManager.restoreLastSessionState(interactive: true) + DispatchQueue.main.async { + self.stateRestorationManager.restoreLastSessionState(interactive: true) + } } // MARK: - Help @@ -118,7 +138,9 @@ extension AppDelegate { #if FEEDBACK @objc func openFeedback(_ sender: Any?) { - FeedbackPresenter.presentFeedbackForm() + DispatchQueue.main.async { + FeedbackPresenter.presentFeedbackForm() + } } @objc func openReportBrokenSite(_ sender: Any?) { @@ -136,13 +158,15 @@ extension AppDelegate { display: true) privacyDashboardWindow = window - guard let parentWindowController = WindowControllersManager.shared.lastKeyMainWindowController, - let tabModel = parentWindowController.mainViewController.tabCollectionViewModel.selectedTabViewModel else { - assertionFailure("AppDelegate: Failed to present PrivacyDashboard") - return + DispatchQueue.main.async { + guard let parentWindowController = WindowControllersManager.shared.lastKeyMainWindowController, + let tabModel = parentWindowController.mainViewController.tabCollectionViewModel.selectedTabViewModel else { + assertionFailure("AppDelegate: Failed to present PrivacyDashboard") + return + } + privacyDashboardViewController.updateTabViewModel(tabModel) + parentWindowController.window?.beginSheet(window) { _ in } } - privacyDashboardViewController.updateTabViewModel(tabModel) - parentWindowController.window?.beginSheet(window) { _ in } } #endif @@ -158,22 +182,27 @@ extension AppDelegate { assertionFailure("Unexpected type of menuItem.representedObject: \(type(of: menuItem.representedObject))") return } - - let tab = Tab(content: .url(url, source: .bookmark), shouldLoadInBackground: true) - WindowsManager.openNewWindow(with: tab) + DispatchQueue.main.async { + let tab = Tab(content: .url(url, source: .bookmark), shouldLoadInBackground: true) + WindowsManager.openNewWindow(with: tab) + } } @objc func showManageBookmarks(_ sender: Any?) { - let tabCollection = TabCollection(tabs: [Tab(content: .bookmarks)]) - let tabCollectionViewModel = TabCollectionViewModel(tabCollection: tabCollection) + DispatchQueue.main.async { + let tabCollection = TabCollection(tabs: [Tab(content: .bookmarks)]) + let tabCollectionViewModel = TabCollectionViewModel(tabCollection: tabCollection) - WindowsManager.openNewWindow(with: tabCollectionViewModel) + WindowsManager.openNewWindow(with: tabCollectionViewModel) + } } @objc func openPreferences(_ sender: Any?) { - let tabCollection = TabCollection(tabs: [Tab(content: .anySettingsPane)]) - let tabCollectionViewModel = TabCollectionViewModel(tabCollection: tabCollection) - WindowsManager.openNewWindow(with: tabCollectionViewModel) + DispatchQueue.main.async { + let tabCollection = TabCollection(tabs: [Tab(content: .anySettingsPane)]) + let tabCollectionViewModel = TabCollectionViewModel(tabCollection: tabCollection) + WindowsManager.openNewWindow(with: tabCollectionViewModel) + } } @objc func openAbout(_ sender: Any?) { @@ -186,9 +215,12 @@ extension AppDelegate { } @objc func openImportBrowserDataWindow(_ sender: Any?) { - DataImportView().show() + DispatchQueue.main.async { + DataImportView().show() + } } + @MainActor @objc func openExportLogins(_ sender: Any?) { guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController, let window = windowController.window else { return } @@ -227,6 +259,7 @@ extension AppDelegate { } } + @MainActor @objc func openExportBookmarks(_ sender: Any?) { guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController, let window = windowController.window, @@ -250,16 +283,20 @@ extension AppDelegate { } @objc func fireButtonAction(_ sender: NSButton) { - FireCoordinator.fireButtonAction() + DispatchQueue.main.async { + FireCoordinator.fireButtonAction() + } } @objc func navigateToPrivateEmail(_ sender: Any?) { - guard let window = NSApplication.shared.keyWindow, - let windowController = window.windowController as? MainWindowController else { - assertionFailure("No reference to main window controller") - return + DispatchQueue.main.async { + guard let window = NSApplication.shared.keyWindow, + let windowController = window.windowController as? MainWindowController else { + assertionFailure("No reference to main window controller") + return + } + windowController.mainViewController.browserTabViewController.openNewTab(with: .url(URL.duckDuckGoEmailLogin, source: .ui)) } - windowController.mainViewController.browserTabViewController.openNewTab(with: .url(URL.duckDuckGoEmailLogin, source: .ui)) } } @@ -752,7 +789,7 @@ extension MainViewController { /// Clears the PrivacyPro state to make testing easier. /// private func clearPrivacyProState() { - AppDelegate.accountManager.signOut() + AppDelegate.shared.subscriptionManager.accountManager.signOut() resetThankYouModalChecks(nil) UserDefaults.netP.networkProtectionEntitlementsExpired = false diff --git a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift index 25cc50a039..fa99e160f9 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift @@ -24,6 +24,7 @@ import Suggestions import Subscription import BrowserServicesKit +// swiftlint:disable:next type_body_length final class AddressBarTextField: NSTextField { var tabCollectionViewModel: TabCollectionViewModel! { @@ -69,6 +70,10 @@ final class AddressBarTextField: NSTextField { // flag when updating the Value from `handleTextDidChange()` private var currentTextDidChangeEvent: TextDidChangeEventType = .none + var subscriptionEnvironment: SubscriptionEnvironment { + AppDelegate.shared.subscriptionManager.currentEnvironment + } + // MARK: - Lifecycle override func awakeFromNib() { @@ -351,7 +356,9 @@ final class AddressBarTextField: NSTextField { #endif if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - if providedUrl.isChild(of: URL.subscriptionBaseURL) || providedUrl.isChild(of: URL.identityTheftRestoration) { + let baseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment) + let identityTheftRestorationURL = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment) + if providedUrl.isChild(of: baseURL) || providedUrl.isChild(of: identityTheftRestorationURL) { self.updateValue(selectedTabViewModel: nil, addressBarString: nil) // reset self.window?.makeFirstResponder(nil) return diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 391dce9a4e..b908ab39ed 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -67,7 +67,7 @@ final class MoreOptionsMenu: NSMenu { init(tabCollectionViewModel: TabCollectionViewModel, emailManager: EmailManager = EmailManager(), passwordManagerCoordinator: PasswordManagerCoordinator, - networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), + networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), sharingMenu: NSMenu? = nil, internalUserDecider: InternalUserDecider, accountManager: AccountManaging) { @@ -388,7 +388,9 @@ final class MoreOptionsMenu: NSMenu { } private func makeInactiveSubscriptionItems() -> [NSMenuItem] { - let shouldHidePrivacyProDueToNoProducts = SubscriptionPurchaseEnvironment.current == .appStore && SubscriptionPurchaseEnvironment.canPurchase == false + let subscriptionManager = AppDelegate.shared.subscriptionManager + let platform = subscriptionManager.currentEnvironment.platform + let shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false if shouldHidePrivacyProDueToNoProducts { return [] } diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 7e9866bc5f..a47bb75ddc 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -71,6 +71,10 @@ final class NavigationBarViewController: NSViewController { return progressView }() + private var subscriptionManager: SubscriptionManager { + AppDelegate.shared.subscriptionManager + } + var addressBarViewController: AddressBarViewController? private var tabCollectionViewModel: TabCollectionViewModel @@ -269,7 +273,7 @@ final class NavigationBarViewController: NSViewController { let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: PasswordManagerCoordinator.shared, internalUserDecider: internalUserDecider, - accountManager: AppDelegate.accountManager) + accountManager: subscriptionManager.accountManager) menu.actionDelegate = self let location = NSPoint(x: -menu.size.width + sender.bounds.width, y: sender.bounds.height + 4) menu.popUp(positioning: nil, at: location, in: sender) @@ -898,7 +902,7 @@ extension NavigationBarViewController: NSMenuDelegate { let isPopUpWindow = view.window?.isPopUpWindow ?? false - if !isPopUpWindow && DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isVPNVisible() { + if !isPopUpWindow && DefaultNetworkProtectionVisibility(subscriptionManager: subscriptionManager).isVPNVisible() { let networkProtectionTitle = LocalPinningManager.shared.shortcutTitle(for: .networkProtection) menu.addItem(withTitle: networkProtectionTitle, action: #selector(toggleNetworkProtectionPanelPinning), keyEquivalent: "N") } @@ -1036,12 +1040,14 @@ extension NavigationBarViewController: OptionsButtonMenuDelegate { } func optionsButtonMenuRequestedSubscriptionPurchasePage(_ menu: NSMenu) { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + let url = SubscriptionURL.purchase.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) } func optionsButtonMenuRequestedIdentityTheftRestoration(_ menu: NSMenu) { - WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) + let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .identityTheftRestoration(url)) } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index 48d7dd163c..ec2c971098 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -54,7 +54,7 @@ extension NetworkProtectionKeychainTokenStore { } convenience init(isSubscriptionEnabled: Bool) { - let accessTokenProvider: () -> String? = { AppDelegate.accountManager.accessToken } + let accessTokenProvider: () -> String? = { AppDelegate.shared.subscriptionManager.accountManager.accessToken } self.init(keychainType: .default, errorEvents: .networkProtectionAppDebugEvents, isSubscriptionEnabled: isSubscriptionEnabled, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 1b66e309a9..250fbef89f 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -228,7 +228,7 @@ final class NetworkProtectionDebugMenu: NSMenu { /// @objc func logFeedbackMetadataToConsole(_ sender: Any?) { Task { @MainActor in - let collector = DefaultVPNMetadataCollector(accountManager: AppDelegate.accountManager) + let collector = DefaultVPNMetadataCollector(accountManager: AppDelegate.shared.subscriptionManager.accountManager) let metadata = await collector.collectMetadata() print(metadata.toPrettyPrintedJSON()!) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index b6b0b44e82..1859f2ec2e 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -71,7 +71,7 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { init(popoverManager: NetPPopoverManager, pinningManager: PinningManager = LocalPinningManager.shared, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), statusReporter: NetworkProtectionStatusReporter, iconProvider: IconProvider = NavigationBarIconProvider()) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index ed20ffef1d..151ca04234 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -74,7 +74,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr // MARK: - Subscriptions - private let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + private let accessTokenStorage: SubscriptionTokenKeychainStorage // MARK: - Debug Options Support @@ -164,7 +164,8 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr defaults: UserDefaults, tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), notificationCenter: NotificationCenter = .default, - logger: NetworkProtectionLogger = DefaultNetworkProtectionLogger()) { + logger: NetworkProtectionLogger = DefaultNetworkProtectionLogger(), + accessTokenStorage: SubscriptionTokenKeychainStorage) { self.logger = logger self.networkExtensionBundleID = networkExtensionBundleID @@ -173,6 +174,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr self.settings = settings self.defaults = defaults self.tokenStore = tokenStore + self.accessTokenStorage = accessTokenStorage // = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(Bundle.main.appGroup(bundle: .subs)))) subscribeToSettingsChanges() subscribeToStatusChanges() @@ -793,7 +795,8 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } private func fetchAuthToken() throws -> NSString? { - if let accessToken = accountManager.accessToken { + + if let accessToken = try? accessTokenStorage.getAccessToken() { os_log(.error, log: .networkProtection, "🟢 TunnelController found token: %{public}d", accessToken) return Self.adaptAccessTokenForVPN(accessToken) as NSString? } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift index 06f120e264..b2afaf55c9 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -53,7 +53,7 @@ final class NetworkProtectionIPCTunnelController { private let pixelKit: PixelFiring? private let errorRecorder: VPNOperationErrorRecorder - init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), + init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), loginItemsManager: LoginItemsManaging = LoginItemsManager(), ipcClient: NetworkProtectionIPCClient, pixelKit: PixelFiring? = PixelKit.shared, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift index 5e6b98ab96..b81c2b880e 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift @@ -55,7 +55,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.networkProtection(), waitlistStorage: WaitlistStorage = WaitlistKeychainStore(waitlistIdentifier: "networkprotection", keychainAppGroup: Bundle.main.appGroup(bundle: .netP)), waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), - networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), + networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), minimumRefreshInterval: TimeInterval, userDefaults: UserDefaults = .standard ) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 1560268d62..35da244dcc 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -25,17 +25,17 @@ import NetworkProtectionUI final class NetworkProtectionSubscriptionEventHandler { - private weak var accountManagerDataSource: AccountManagingDataSource? + private let subscriptionManager: SubscriptionManager private let networkProtectionTokenStorage: NetworkProtectionTokenStore private let networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling private let userDefaults: UserDefaults private var cancellables = Set() - init(accountManagerDataSource: AccountManagingDataSource?, + init(subscriptionManager: SubscriptionManager, networkProtectionTokenStorage: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), userDefaults: UserDefaults = .netP) { - self.accountManagerDataSource = accountManagerDataSource + self.subscriptionManager = subscriptionManager self.networkProtectionTokenStorage = networkProtectionTokenStorage self.networkProtectionFeatureDisabler = networkProtectionFeatureDisabler self.userDefaults = userDefaults @@ -45,11 +45,7 @@ final class NetworkProtectionSubscriptionEventHandler { private func subscribeToEntitlementChanges() { Task { - guard let accountManagerDataSource else { - assertionFailure("Missing accountManagerDataSource") - return - } - switch await accountManagerDataSource.hasEntitlement(for: .networkProtection) { + switch await subscriptionManager.accountManager.hasEntitlement(for: .networkProtection) { case .success(let hasEntitlements): Task { await handleEntitlementsChange(hasEntitlements: hasEntitlements) @@ -99,11 +95,7 @@ final class NetworkProtectionSubscriptionEventHandler { } @objc private func handleAccountDidSignIn() { - guard let accountManagerDataSource else { - assertionFailure("Missing accountManagerDataSource") - return - } - guard accountManagerDataSource.accessToken != nil else { + guard subscriptionManager.accountManager.accessToken != nil else { assertionFailure("[NetP Subscription] AccountManager signed in but token could not be retrieved") return } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 8b087b04dd..a6e590bc36 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -339,11 +339,11 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { NetworkProtectionLastVersionRunStore(userDefaults: defaults).lastExtensionVersionRun = AppVersion.shared.versionAndBuildNumber + // MARK: - Configure Subscription let settings = VPNSettings(defaults: defaults) - let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) + let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter) let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore) - let notificationsPresenter = NetworkProtectionNotificationsPresenterFactory().make(settings: settings, defaults: defaults) let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: Bundle.keychainType, serviceName: Self.tokenServiceName, errorEvents: debugEvents, @@ -362,11 +362,14 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { entitlementsCache: entitlementsCache, subscriptionService: subscriptionService, authService: authService) - + let entitlementsCheck = { await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) } + let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter) + let notificationsPresenter = NetworkProtectionNotificationsPresenterFactory().make(settings: settings, defaults: defaults) + super.init(notificationsPresenter: notificationsPresenter, tunnelHealthStore: tunnelHealthStore, controllerErrorStore: controllerErrorStore, diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 9dee680552..f31ee447fc 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -61,10 +61,11 @@ struct PreferencesSection: Hashable, Identifiable { ] if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { + let subscriptionManager = AppDelegate.shared.subscriptionManager + let platform = subscriptionManager.currentEnvironment.platform + var shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false - var shouldHidePrivacyProDueToNoProducts = SubscriptionPurchaseEnvironment.current == .appStore && SubscriptionPurchaseEnvironment.canPurchase == false - - if AppDelegate.accountManager.isUserAuthenticated { + if subscriptionManager.accountManager.isUserAuthenticated { shouldHidePrivacyProDueToNoProducts = false } diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index f85a5338be..48ba1073e6 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -42,7 +42,7 @@ final class PreferencesSidebarModel: ObservableObject { tabSwitcherTabs: [Tab.TabContent], privacyConfigurationManager: PrivacyConfigurationManaging, syncService: DDGSyncing, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager) ) { self.loadSections = loadSections self.tabSwitcherTabs = tabSwitcherTabs @@ -77,7 +77,7 @@ final class PreferencesSidebarModel: ObservableObject { tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, syncService: DDGSyncing, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager), + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), includeDuckPlayer: Bool, userDefaults: UserDefaults = .netP ) { diff --git a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift index 3c9bf99887..c24547c50c 100644 --- a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift @@ -59,7 +59,7 @@ final class VPNPreferencesModel: ObservableObject { private var onboardingStatus: OnboardingStatus { didSet { - showUninstallVPN = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isInstalled + showUninstallVPN = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager).isInstalled } } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index 7ce12a12df..cd96fee009 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -137,7 +137,8 @@ enum Preferences { WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .openITR: PixelKit.fire(PrivacyProPixel.privacyProIdentityRestorationSettings) - WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) + let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: AppDelegate.shared.subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .identityTheftRestoration(url)) case .iHaveASubscriptionClick: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseClick) case .activateAddEmailClick: @@ -168,7 +169,7 @@ enum Preferences { return } - let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: AppDelegate.accountManager, subscriptionErrorReporter: SubscriptionErrorReporter()) + let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(subscriptionManager: AppDelegate.shared.subscriptionManager) await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) } } @@ -179,7 +180,7 @@ enum Preferences { return PreferencesSubscriptionModel(openURLHandler: openURL, userEventHandler: handleUIEvent, sheetActionHandler: sheetActionHandler, - accountManager: AppDelegate.accountManager) + subscriptionManager: AppDelegate.shared.subscriptionManager) } } } diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift b/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift index 73e6407c28..854c783f02 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift @@ -21,8 +21,9 @@ import Subscription import BrowserServicesKit extension DefaultSubscriptionFeatureAvailability { + convenience init() { self.init(privacyConfigurationManager: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager, - purchasePlatform: SubscriptionPurchaseEnvironment.current) + subscriptionEnvironment: AppDelegate.shared.subscriptionManager.currentEnvironment) } } diff --git a/DuckDuckGo/Sync/SyncDebugMenu.swift b/DuckDuckGo/Sync/SyncDebugMenu.swift index c5ff802e84..db74c0125d 100644 --- a/DuckDuckGo/Sync/SyncDebugMenu.swift +++ b/DuckDuckGo/Sync/SyncDebugMenu.swift @@ -16,7 +16,7 @@ // limitations under the License. // -import Foundation +import AppKit import DDGSync import Bookmarks diff --git a/DuckDuckGo/Tab/Model/TabContent.swift b/DuckDuckGo/Tab/Model/TabContent.swift index 369a036ba2..3412445e97 100644 --- a/DuckDuckGo/Tab/Model/TabContent.swift +++ b/DuckDuckGo/Tab/Model/TabContent.swift @@ -117,12 +117,16 @@ extension TabContent { } if let url { - if url.isChild(of: URL.subscriptionBaseURL) { - if SubscriptionPurchaseEnvironment.currentServiceEnvironment == .staging, url.getParameter(named: "environment") == nil { + let subscriptionManager = AppDelegate.shared.subscriptionManager + let environment = subscriptionManager.currentEnvironment.serviceEnvironment + let subscriptionBaseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let identityTheftRestorationURL = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + if url.isChild(of: subscriptionBaseURL) { + if environment == .staging, url.getParameter(named: "environment") == nil { return .subscription(url.appendingParameter(name: "environment", value: "staging")) } return .subscription(url) - } else if url.isChild(of: URL.identityTheftRestoration) { + } else if url.isChild(of: identityTheftRestorationURL) { return .identityTheftRestoration(url) } } diff --git a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift index d06cb5f3d3..7c89c962d5 100644 --- a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift +++ b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift @@ -38,10 +38,12 @@ struct RedirectNavigationResponder: NavigationResponder { if url.pathComponents == URL.privacyPro.pathComponents { let isFeatureAvailable = DefaultSubscriptionFeatureAvailability().isFeatureAvailable - let shouldHidePrivacyProDueToNoProducts = SubscriptionPurchaseEnvironment.current == .appStore && SubscriptionPurchaseEnvironment.canPurchase == false + let subscriptionManager = AppDelegate.shared.subscriptionManager + let platform = subscriptionManager.currentEnvironment.platform + let shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false let isPurchasePageRedirectActive = isFeatureAvailable && !shouldHidePrivacyProDueToNoProducts - - return isPurchasePageRedirectActive ? URL.subscriptionBaseURL : nil + let url = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + return isPurchasePageRedirectActive ? url : nil } return nil diff --git a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift index 8b63cc4b14..9f675d8413 100644 --- a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift @@ -92,7 +92,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = AppDelegate.accountManager.accessToken { + if let accessToken = AppDelegate.shared.subscriptionManager.accountManager.accessToken { return ["token": accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 24ed6a4680..2b7e20ce8a 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -25,14 +25,12 @@ import PixelKit @available(macOS 12.0, *) struct SubscriptionAppStoreRestorer { - let accountManager: AccountManaging - @MainActor - var window: NSWindow? { - WindowControllersManager.shared.lastKeyMainWindowController?.window - } + private let subscriptionManager: SubscriptionManager + @MainActor var window: NSWindow? { WindowControllersManager.shared.lastKeyMainWindowController?.window } + let subscriptionErrorReporter = SubscriptionErrorReporter() - public init(accountManager: AccountManaging) { - self.accountManager = accountManager + public init(subscriptionManager: SubscriptionManager) { + self.subscriptionManager = subscriptionManager } // swiftlint:disable:next cyclomatic_complexity function_body_length @@ -49,7 +47,7 @@ struct SubscriptionAppStoreRestorer { mainViewController.presentAsSheet(progressViewController) } - let syncResult = await StorePurchaseManager.shared.syncAppleIDAccount() + let syncResult = await subscriptionManager.getStorePurchaseManager().syncAppleIDAccount() switch syncResult { case .success: @@ -73,7 +71,7 @@ struct SubscriptionAppStoreRestorer { } } - let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: accountManager) + let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { @@ -88,13 +86,13 @@ struct SubscriptionAppStoreRestorer { switch error { case .missingAccountOrTransactions: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + subscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) await showSubscriptionNotFoundAlert() case .subscriptionExpired: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionExpired) + subscriptionErrorReporter.report(subscriptionActivationError: .subscriptionExpired) await showSubscriptionInactiveAlert() case .pastTransactionAuthenticationError, .failedToObtainAccessToken, .failedToFetchAccountDetails, .failedToFetchSubscriptionDetails: - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + subscriptionErrorReporter.report(subscriptionActivationError: .generalError) await showSomethingWentWrongAlert() } } @@ -124,7 +122,8 @@ extension SubscriptionAppStoreRestorer { guard let window else { return } window.show(.subscriptionNotFoundAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } @@ -134,7 +133,8 @@ extension SubscriptionAppStoreRestorer { guard let window else { return } window.show(.subscriptionInactiveAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index 038bc208bf..884afccbf3 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -84,12 +84,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { var window: NSWindow? { WindowControllersManager.shared.lastKeyMainWindowController?.window } - let accountManager: AccountManaging + let subscriptionManager: SubscriptionManager + var accountManager: AccountManaging { subscriptionManager.accountManager } + var subscriptionPlatform: SubscriptionEnvironment.Platform { subscriptionManager.currentEnvironment.platform } + let stripePurchaseFlow: StripePurchaseFlow + let subscriptionErrorReporter = SubscriptionErrorReporter() - public init(accountManager: AccountManaging) { - self.accountManager = accountManager - self.stripePurchaseFlow = StripePurchaseFlow(accountManager: accountManager) + public init(subscriptionManager: SubscriptionManager) { + self.subscriptionManager = subscriptionManager + self.stripePurchaseFlow = StripePurchaseFlow(subscriptionManager: subscriptionManager) } func with(broker: UserScriptMessageBroker) { @@ -189,17 +193,13 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard DefaultSubscriptionFeatureAvailability().isSubscriptionPurchaseAllowed else { return SubscriptionOptions.empty } - if SubscriptionPurchaseEnvironment.current == .appStore { + switch subscriptionPlatform { + case .appStore: if #available(macOS 12.0, *) { - let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) - switch await appStorePurchaseFlow.subscriptionOptions() { - case .success(let subscriptionOptions): - return subscriptionOptions - case .failure: - break - } + let appStorePurchaseFlow = AppStorePurchaseFlow(subscriptionManager: subscriptionManager) + return await subscriptionManager.getStorePurchaseManager().subscriptionOptions() } - } else if SubscriptionPurchaseEnvironment.current == .stripe { + case .stripe: switch await stripePurchaseFlow.subscriptionOptions() { case .success(let subscriptionOptions): return subscriptionOptions @@ -221,7 +221,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { let message = original - if SubscriptionPurchaseEnvironment.current == .appStore { + if subscriptionManager.currentEnvironment.platform == .appStore { if #available(macOS 12.0, *) { let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController let progressViewController = await ProgressViewController(title: UserText.purchasingSubscriptionTitle) @@ -234,7 +234,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + subscriptionErrorReporter.report(subscriptionActivationError: .generalError) return nil } @@ -243,17 +243,17 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await mainViewController?.presentAsSheet(progressViewController) // Check for active subscriptions - if await StorePurchaseManager.hasActiveSubscription() { + if await subscriptionManager.getStorePurchaseManager().hasActiveSubscription() { PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") - SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) + subscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) await showSubscriptionFoundAlert(originalMessage: message) return nil } let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String - let appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + let appStorePurchaseFlow = AppStorePurchaseFlow(subscriptionManager: subscriptionManager) os_log(.info, log: .subscription, "[Purchase] Purchasing") switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { @@ -262,19 +262,21 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { case .failure(let error): switch error { case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + subscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) case .activeSubscriptionAlreadyPresent: - SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) + subscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) case .authenticatingWithTransactionFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + subscriptionErrorReporter.report(subscriptionActivationError: .generalError) case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + subscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) case .purchaseFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) + subscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) case .cancelledByUser: - SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) + subscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) case .missingEntitlements: - SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) + subscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) + case .internalError: + assertionFailure("Internal error") } if error != .cancelledByUser { @@ -297,29 +299,31 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { case .failure(let error): switch error { case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + subscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) case .activeSubscriptionAlreadyPresent: - SubscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) + subscriptionErrorReporter.report(subscriptionActivationError: .activeSubscriptionAlreadyPresent) case .authenticatingWithTransactionFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .generalError) + subscriptionErrorReporter.report(subscriptionActivationError: .generalError) case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + subscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) case .purchaseFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) + subscriptionErrorReporter.report(subscriptionActivationError: .purchaseFailed) case .cancelledByUser: - SubscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) + subscriptionErrorReporter.report(subscriptionActivationError: .cancelledByUser) case .missingEntitlements: - SubscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) + subscriptionErrorReporter.report(subscriptionActivationError: .missingEntitlements) DispatchQueue.main.async { NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) } return nil + case .internalError: + assertionFailure("Internal error") } await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) } } - } else if SubscriptionPurchaseEnvironment.current == .stripe { + } else if subscriptionPlatform == .stripe { let emailAccessToken = try? EmailManager().getToken() let result = await stripePurchaseFlow.prepareSubscriptionPurchase(emailAccessToken: emailAccessToken) @@ -332,9 +336,9 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch error { case .noProductsFound: - SubscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) + subscriptionErrorReporter.report(subscriptionActivationError: .subscriptionNotFound) case .accountCreationFailed: - SubscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) + subscriptionErrorReporter.report(subscriptionActivationError: .accountCreationFailed) } await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) } @@ -355,7 +359,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { let actionHandlers = SubscriptionAccessActionHandlers(restorePurchases: { if #available(macOS 12.0, *) { Task { @MainActor in - let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(accountManager: self.accountManager) + let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(subscriptionManager: self.subscriptionManager) await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) message.webView?.reload() } @@ -373,7 +377,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } }) - let subscriptionAccessViewController = await SubscriptionAccessViewController(accountManager: accountManager, actionHandlers: actionHandlers) + let subscriptionAccessViewController = await SubscriptionAccessViewController(subscriptionManager: subscriptionManager, actionHandlers: actionHandlers) await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.presentAsSheet(subscriptionAccessViewController) return nil @@ -412,7 +416,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .identityTheftRestoration: PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) - await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) + let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(url)) } return nil @@ -458,7 +463,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).accessToken { + if let accessToken = accountManager.accessToken { return ["token": accessToken] } else { return [String: String]() @@ -508,7 +513,8 @@ extension SubscriptionPagesUseSubscriptionFeature { guard let window else { return } window.show(.subscriptionNotFoundAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } @@ -518,7 +524,8 @@ extension SubscriptionPagesUseSubscriptionFeature { guard let window else { return } window.show(.subscriptionInactiveAlert(), firstButtonAction: { - WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } @@ -530,7 +537,7 @@ extension SubscriptionPagesUseSubscriptionFeature { window.show(.subscriptionFoundAlert(), firstButtonAction: { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: self.accountManager) + let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: self.subscriptionManager) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index 7766d1b9e0..386af268d5 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -93,7 +93,7 @@ final class UserScripts: UserScriptsProvider { } if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature(accountManager: AppDelegate.accountManager, subscriptionErrorReporter: SubscriptionErrorReporter())) + subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature(subscriptionManager: AppDelegate.shared.subscriptionManager)) userScripts.append(subscriptionPagesUserScript) identityTheftRestorationPagesUserScript.registerSubfeature(delegate: IdentityTheftRestorationPagesFeature()) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift index ff0b808128..818977afe5 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift @@ -40,7 +40,7 @@ final class VPNFeedbackFormViewController: NSViewController { private var cancellables = Set() init() { - self.viewModel = VPNFeedbackFormViewModel(metadataCollector: DefaultVPNMetadataCollector(accountManager: AppDelegate.accountManager)) + self.viewModel = VPNFeedbackFormViewModel(metadataCollector: DefaultVPNMetadataCollector(accountManager: AppDelegate.shared.subscriptionManager.accountManager)) super.init(nibName: nil, bundle: nil) self.viewModel.delegate = self } diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index d912f0e057..188c6f7479 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -121,10 +121,10 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusReporter: NetworkProtectionStatusReporter private let ipcClient: TunnelControllerIPCClient private let defaults: UserDefaults - private let accountManager: AccountManager + private let accountManager: AccountManaging init(defaults: UserDefaults = .netP, - accountManager: AccountManager) { + accountManager: AccountManaging) { let ipcClient = TunnelControllerIPCClient() ipcClient.register() self.accountManager = accountManager diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 3358af9177..7fe6a8a49a 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -46,7 +46,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let privacyConfigurationManager: PrivacyConfigurationManaging private let defaults: UserDefaults - private weak var accountManagerDataSource: AccountManagingDataSource? + private let subscriptionManager: SubscriptionManager init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), @@ -54,14 +54,14 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { featureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), defaults: UserDefaults = .netP, log: OSLog = .networkProtection, - accountManagerDataSource: AccountManagingDataSource?) { + subscriptionManager: SubscriptionManager) { self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionFeatureActivation = networkProtectionFeatureActivation self.featureDisabler = featureDisabler self.featureOverrides = featureOverrides self.defaults = defaults - self.accountManagerDataSource = accountManagerDataSource + self.subscriptionManager = subscriptionManager } var isInstalled: Bool { @@ -78,12 +78,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return false } - guard let accountManagerDataSource else { - assertionFailure("Missing accountManagerDataSource") - return false - } - - switch await accountManagerDataSource.hasEntitlement(for: .networkProtection) { + switch await subscriptionManager.accountManager.hasEntitlement(for: .networkProtection) { case .success(let hasEntitlement): return hasEntitlement case .failure(let error): @@ -100,12 +95,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { guard subscriptionFeatureAvailability.isFeatureAvailable else { return false } - - guard let accountManagerDataSource else { - assertionFailure("Missing accountManagerDataSource") - return false - } - return accountManagerDataSource.isUserAuthenticated + return subscriptionManager.accountManager.isUserAuthenticated } /// We've had to add this method because accessing the singleton in app delegate is crashing the integration tests. @@ -117,11 +107,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { /// Returns whether the VPN should be uninstalled automatically. /// This is only true when the user is not an Easter Egg user, the waitlist test has ended, and the user is onboarded. func shouldUninstallAutomatically() -> Bool { - guard let accountManagerDataSource else { - assertionFailure("Missing accountManagerDataSource") - return false - } - return subscriptionFeatureAvailability.isFeatureAvailable && !accountManagerDataSource.isUserAuthenticated && LoginItem.vpnMenu.status.isInstalled + return subscriptionFeatureAvailability.isFeatureAvailable && !subscriptionManager.accountManager.isUserAuthenticated && LoginItem.vpnMenu.status.isInstalled } /// Whether the user is fully onboarded diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 473cce8d40..a40ad54316 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -29,11 +29,11 @@ import ServiceManagement import PixelKit import Subscription -private let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - @objc(Application) final class DuckDuckGoVPNApplication: NSApplication { - private let _delegate = DuckDuckGoVPNAppDelegate() + + public let accountManager: AccountManaging + private let _delegate: DuckDuckGoVPNAppDelegate override init() { os_log(.error, log: .networkProtection, "🟢 Status Bar Agent starting: %{public}d", NSRunningApplication.current.processIdentifier) @@ -44,6 +44,23 @@ final class DuckDuckGoVPNApplication: NSApplication { exit(0) } + // MARK: - Configure Subscription + let settings = VPNSettings(defaults: UserDefaults.netP) + let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment) + let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment) + let subscriptionsAppGroup = Bundle.main.appGroup(bundle: .subs) + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults(suiteName: subscriptionsAppGroup) ?? UserDefaults.standard, + key: UserDefaultsCacheKey.subscriptionEntitlements, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) + let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionsAppGroup))) + accountManager = AccountManager(accessTokenStorage: accessTokenStorage, + entitlementsCache: entitlementsCache, + subscriptionService: subscriptionService, + authService: authService) + + _delegate = DuckDuckGoVPNAppDelegate(bouncer: NetworkProtectionBouncer(accountManager: accountManager), accountManager: accountManager) + super.init() self.delegate = _delegate @@ -67,7 +84,14 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private static let recentThreshold: TimeInterval = 5.0 private let appLauncher = AppLauncher() - private let bouncer = NetworkProtectionBouncer(accountManager: accountManager) + private let bouncer: NetworkProtectionBouncer + private let accountManager: AccountManaging + + public init(bouncer: NetworkProtectionBouncer, + accountManager: AccountManaging) { + self.bouncer = bouncer + self.accountManager = accountManager + } private var cancellables = Set() @@ -150,7 +174,8 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { networkExtensionBundleID: tunnelExtensionBundleID, networkExtensionController: networkExtensionController, settings: tunnelSettings, - defaults: userDefaults) + defaults: userDefaults, + accessTokenStorage: SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(Bundle.main.appGroup(bundle: .subs))))) /// An IPC server that provides access to the tunnel controller. /// @@ -269,8 +294,11 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { @MainActor func applicationDidFinishLaunching(_ aNotification: Notification) { + APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) - SubscriptionPurchaseEnvironment.currentServiceEnvironment = tunnelSettings.selectedEnvironment == .production ? .production : .staging + + // let currentServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment = tunnelSettings.selectedEnvironment == .production ? .production : .staging + // TODO: set SubscriptionEnvironment.ServiceEnvironment across extensions and VPN os_log("DuckDuckGoVPN started", log: .networkProtectionLoginItemLog, type: .info) @@ -361,7 +389,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private func setUpSubscriptionMonitoring() { guard accountManager.isUserAuthenticated else { return } let entitlementsCheck = { - await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + await self.accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) } Task { diff --git a/DuckDuckGoVPN/NetworkProtectionBouncer.swift b/DuckDuckGoVPN/NetworkProtectionBouncer.swift index 537d7cadca..e9acf969b5 100644 --- a/DuckDuckGoVPN/NetworkProtectionBouncer.swift +++ b/DuckDuckGoVPN/NetworkProtectionBouncer.swift @@ -27,9 +27,9 @@ import Subscription /// final class NetworkProtectionBouncer { - let accountManager: AccountManager + let accountManager: AccountManaging - init(accountManager: AccountManager) { + init(accountManager: AccountManaging) { self.accountManager = accountManager } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 02c5cd29e2..0c0292a647 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -150,7 +150,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func purchaseAction() { - openURLHandler(SubscriptionURL.purchase.subscriptionURL(environment: <#T##SubscriptionEnvironment.ServiceEnvironment#>)) + openURLHandler(SubscriptionURL.purchase.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) } enum ChangePlanOrBillingAction { @@ -185,11 +185,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func changePlanOrBilling(for environment: SubscriptionEnvironment.Platform) { switch environment { case .appStore: - NSWorkspace.shared.open(.manageSubscriptionsInAppStoreAppURL) + NSWorkspace.shared.open(SubscriptionURL.manageSubscriptionsInAppStore.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) case .stripe: Task { guard let accessToken = accountManager.accessToken, let externalID = accountManager.externalID, - case let .success(response) = await subscriptionService.getCustomerPortalURL(accessToken: accessToken, externalID: externalID) else { return } + case let .success(response) = await subscriptionManager.subscriptionService.getCustomerPortalURL(accessToken: accessToken, externalID: externalID) else { return } guard let customerPortalURL = URL(string: response.customerPortalUrl) else { return } openURLHandler(customerPortalURL) @@ -199,9 +199,8 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func confirmIfSignedInToSameAccount() async -> Bool { if #available(macOS 12.0, *) { - guard let lastTransactionJWSRepresentation = await StorePurchaseManager.mostRecentTransaction() else { return false } - - switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { + guard let lastTransactionJWSRepresentation = await subscriptionManager.getStorePurchaseManager().mostRecentTransaction() else { return false } + switch await subscriptionManager.authService.storeLogin(signature: lastTransactionJWSRepresentation) { case .success(let response): return response.externalID == accountManager.externalID case .failure: @@ -235,15 +234,15 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func openFAQ() { - openURLHandler(.subscriptionFAQ) + openURLHandler( SubscriptionURL.FAQ.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) } @MainActor func refreshSubscriptionPendingState() { - if SubscriptionPurchaseEnvironment.current == .appStore { + if subscriptionManager.currentEnvironment.platform == .appStore { if #available(macOS 12.0, *) { Task { - let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: accountManager) + let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) await appStoreRestoreFlow.restoreAccountFromPastPurchase() fetchAndUpdateSubscriptionDetails() } @@ -272,11 +271,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor private func updateSubscription(with cachePolicy: SubscriptionService.CachePolicy) async { guard let token = accountManager.accessToken else { - subscriptionService.signOut() + subscriptionManager.subscriptionService.signOut() return } - switch await subscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) { + switch await subscriptionManager.subscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) { case .success(let subscription): updateDescription(for: subscription.expiresOrRenewsAt, status: subscription.status, period: subscription.billingPeriod) subscriptionPlatform = subscription.platform diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift index 7d0480abb9..6e7a4adb58 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -22,15 +22,16 @@ import SwiftUI public final class SubscriptionAccessViewController: NSViewController { - private let accountManager: AccountManaging + private let subscriptionManager: SubscriptionManager private var actionHandlers: SubscriptionAccessActionHandlers public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public init(accountManager: AccountManaging, actionHandlers: SubscriptionAccessActionHandlers) { - self.accountManager = accountManager + public init(subscriptionManager: SubscriptionManager, + actionHandlers: SubscriptionAccessActionHandlers) { + self.subscriptionManager = subscriptionManager self.actionHandlers = actionHandlers super.init(nibName: nil, bundle: nil) } @@ -54,10 +55,10 @@ public final class SubscriptionAccessViewController: NSViewController { } private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { - if accountManager.isUserAuthenticated { - ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: accountManager.email, subscriptionManager: sub) + if subscriptionManager.accountManager.isUserAuthenticated { + ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: subscriptionManager.accountManager.email, subscriptionManager: subscriptionManager) } else { - ActivateSubscriptionAccessModel(actionHandlers: actionHandlers, subscriptionEnvironment: <#T##SubscriptionEnvironment#>) + ActivateSubscriptionAccessModel(actionHandlers: actionHandlers, subscriptionEnvironment: subscriptionManager.currentEnvironment) } } } From a9efc504601c48c5c10c9cd2246912a27e542f2a Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 7 May 2024 17:48:00 +0100 Subject: [PATCH 15/41] it builds! --- DuckDuckGo/Application/AppDelegate.swift | 8 -------- DuckDuckGo/Application/Application.swift | 12 +++++++----- DuckDuckGo/Application/URLEventHandler.swift | 2 +- DuckDuckGo/Menus/MainMenu.swift | 4 ++-- DuckDuckGo/Menus/MainMenuActions.swift | 2 +- .../NavigationBar/View/AddressBarTextField.swift | 2 +- DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift | 4 ++-- .../View/NavigationBarViewController.swift | 3 ++- .../NetworkProtection+ConvenienceInitializers.swift | 2 +- .../BothAppTargets/NetworkProtectionDebugMenu.swift | 2 +- .../NetworkProtectionNavBarButtonModel.swift | 2 +- .../NetworkProtectionIPCTunnelController.swift | 2 +- .../NetworkProtectionRemoteMessaging.swift | 2 +- .../Preferences/Model/PreferencesSection.swift | 2 +- .../Preferences/Model/PreferencesSidebarModel.swift | 4 ++-- .../Preferences/Model/VPNPreferencesModel.swift | 2 +- .../Preferences/View/PreferencesRootView.swift | 6 +++--- .../Preferences/View/PreferencesViewController.swift | 4 +++- ...ptionFeatureAvailability+DefaultInitializer.swift | 3 ++- DuckDuckGo/Tab/Model/TabContent.swift | 2 +- .../Tab/Navigation/RedirectNavigationResponder.swift | 2 +- .../IdentityTheftRestorationPagesUserScript.swift | 2 +- .../Subscription/SubscriptionPagesUserScript.swift | 1 - DuckDuckGo/Tab/UserScripts/UserScripts.swift | 2 +- .../VPNFeedbackFormViewController.swift | 2 +- .../Model/ActivateSubscriptionAccessModel.swift | 1 - 26 files changed, 38 insertions(+), 42 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 77197ff94d..e7696e4c2e 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -40,14 +40,6 @@ import Subscription // @MainActor final class AppDelegate: NSObject, NSApplicationDelegate { - /// To be used with very cautiously, this do not replace proper dependency injection - static var shared: AppDelegate { - guard let delegate = NSApplication.shared.delegate as? AppDelegate else { - fatalError("Could not get app delegate ") - } - return delegate - } - #if DEBUG let disableCVDisplayLinkLogs: Void = { // Disable CVDisplayLink logs diff --git a/DuckDuckGo/Application/Application.swift b/DuckDuckGo/Application/Application.swift index 2099e4aef8..73045c9ca9 100644 --- a/DuckDuckGo/Application/Application.swift +++ b/DuckDuckGo/Application/Application.swift @@ -23,16 +23,18 @@ import Foundation final class Application: NSApplication { private let copyHandler = CopyHandler() - private var _delegate: AppDelegate! +// private var _delegate: AppDelegate! + public static var appDelegate: AppDelegate! override init() { super.init() - _delegate = AppDelegate() - self.delegate = _delegate + let delegate = AppDelegate() + self.delegate = delegate + Application.appDelegate = delegate - let mainMenu = MainMenu(featureFlagger: _delegate.featureFlagger, - bookmarkManager: _delegate.bookmarksManager, + let mainMenu = MainMenu(featureFlagger: delegate.featureFlagger, + bookmarkManager: delegate.bookmarksManager, faviconManager: FaviconManager.shared, copyHandler: copyHandler) self.mainMenu = mainMenu diff --git a/DuckDuckGo/Application/URLEventHandler.swift b/DuckDuckGo/Application/URLEventHandler.swift index 1329811b82..9f42802cbd 100644 --- a/DuckDuckGo/Application/URLEventHandler.swift +++ b/DuckDuckGo/Application/URLEventHandler.swift @@ -159,7 +159,7 @@ final class URLEventHandler { WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn) WindowControllersManager.shared.showLocationPickerSheet() case AppLaunchCommand.showPrivacyPro.launchURL: - let url = SubscriptionURL.purchase.subscriptionURL(environment: AppDelegate.shared.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = SubscriptionURL.purchase.subscriptionURL(environment: Application.appDelegate.subscriptionManager.currentEnvironment.serviceEnvironment) WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) #if !APPSTORE && !DEBUG diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 544e90a58e..d58b9f2e0e 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -540,7 +540,7 @@ import SubscriptionUI toggleBookmarksShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .bookmarks) toggleDownloadsShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .downloads) - if DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager).isVPNVisible() { + if DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager).isVPNVisible() { toggleNetworkProtectionShortcutMenuItem.isHidden = false toggleNetworkProtectionShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .networkProtection) } else { @@ -634,7 +634,7 @@ import SubscriptionUI currentViewController: { WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController }, - subscriptionManager: AppDelegate.shared.subscriptionManager) + subscriptionManager: Application.appDelegate.subscriptionManager) NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) } diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 1d257bd027..4ff1f9791b 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -789,7 +789,7 @@ extension MainViewController { /// Clears the PrivacyPro state to make testing easier. /// private func clearPrivacyProState() { - AppDelegate.shared.subscriptionManager.accountManager.signOut() + Application.appDelegate.subscriptionManager.accountManager.signOut() resetThankYouModalChecks(nil) UserDefaults.netP.networkProtectionEntitlementsExpired = false diff --git a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift index fa99e160f9..e3980bf339 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift @@ -71,7 +71,7 @@ final class AddressBarTextField: NSTextField { private var currentTextDidChangeEvent: TextDidChangeEventType = .none var subscriptionEnvironment: SubscriptionEnvironment { - AppDelegate.shared.subscriptionManager.currentEnvironment + Application.appDelegate.subscriptionManager.currentEnvironment } // MARK: - Lifecycle diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index b908ab39ed..9918531249 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -67,7 +67,7 @@ final class MoreOptionsMenu: NSMenu { init(tabCollectionViewModel: TabCollectionViewModel, emailManager: EmailManager = EmailManager(), passwordManagerCoordinator: PasswordManagerCoordinator, - networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), + networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility, sharingMenu: NSMenu? = nil, internalUserDecider: InternalUserDecider, accountManager: AccountManaging) { @@ -388,7 +388,7 @@ final class MoreOptionsMenu: NSMenu { } private func makeInactiveSubscriptionItems() -> [NSMenuItem] { - let subscriptionManager = AppDelegate.shared.subscriptionManager + let subscriptionManager = Application.appDelegate.subscriptionManager let platform = subscriptionManager.currentEnvironment.platform let shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false if shouldHidePrivacyProDueToNoProducts { diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index a47bb75ddc..9af982da42 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -72,7 +72,7 @@ final class NavigationBarViewController: NSViewController { }() private var subscriptionManager: SubscriptionManager { - AppDelegate.shared.subscriptionManager + Application.appDelegate.subscriptionManager } var addressBarViewController: AddressBarViewController? @@ -272,6 +272,7 @@ final class NavigationBarViewController: NSViewController { let internalUserDecider = NSApp.delegateTyped.internalUserDecider let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: PasswordManagerCoordinator.shared, + networkProtectionFeatureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), internalUserDecider: internalUserDecider, accountManager: subscriptionManager.accountManager) menu.actionDelegate = self diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index ec2c971098..ec17fc8ca4 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -54,7 +54,7 @@ extension NetworkProtectionKeychainTokenStore { } convenience init(isSubscriptionEnabled: Bool) { - let accessTokenProvider: () -> String? = { AppDelegate.shared.subscriptionManager.accountManager.accessToken } + let accessTokenProvider: () -> String? = { Application.appDelegate.subscriptionManager.accountManager.accessToken } self.init(keychainType: .default, errorEvents: .networkProtectionAppDebugEvents, isSubscriptionEnabled: isSubscriptionEnabled, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 250fbef89f..afcb4a0b57 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -228,7 +228,7 @@ final class NetworkProtectionDebugMenu: NSMenu { /// @objc func logFeedbackMetadataToConsole(_ sender: Any?) { Task { @MainActor in - let collector = DefaultVPNMetadataCollector(accountManager: AppDelegate.shared.subscriptionManager.accountManager) + let collector = DefaultVPNMetadataCollector(accountManager: Application.appDelegate.subscriptionManager.accountManager) let metadata = await collector.collectMetadata() print(metadata.toPrettyPrintedJSON()!) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index 1859f2ec2e..de2dd586dd 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -71,7 +71,7 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { init(popoverManager: NetPPopoverManager, pinningManager: PinningManager = LocalPinningManager.shared, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), statusReporter: NetworkProtectionStatusReporter, iconProvider: IconProvider = NavigationBarIconProvider()) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift index b2afaf55c9..028b3ba4cc 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -53,7 +53,7 @@ final class NetworkProtectionIPCTunnelController { private let pixelKit: PixelFiring? private let errorRecorder: VPNOperationErrorRecorder - init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), + init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), loginItemsManager: LoginItemsManaging = LoginItemsManager(), ipcClient: NetworkProtectionIPCClient, pixelKit: PixelFiring? = PixelKit.shared, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift index b81c2b880e..aef4e46516 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift @@ -55,7 +55,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.networkProtection(), waitlistStorage: WaitlistStorage = WaitlistKeychainStore(waitlistIdentifier: "networkprotection", keychainAppGroup: Bundle.main.appGroup(bundle: .netP)), waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), - networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), + networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), minimumRefreshInterval: TimeInterval, userDefaults: UserDefaults = .standard ) { diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index f31ee447fc..684209208e 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -61,7 +61,7 @@ struct PreferencesSection: Hashable, Identifiable { ] if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - let subscriptionManager = AppDelegate.shared.subscriptionManager + let subscriptionManager = Application.appDelegate.subscriptionManager let platform = subscriptionManager.currentEnvironment.platform var shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index 48ba1073e6..db5a483437 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -42,7 +42,7 @@ final class PreferencesSidebarModel: ObservableObject { tabSwitcherTabs: [Tab.TabContent], privacyConfigurationManager: PrivacyConfigurationManaging, syncService: DDGSyncing, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager) + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager) ) { self.loadSections = loadSections self.tabSwitcherTabs = tabSwitcherTabs @@ -77,7 +77,7 @@ final class PreferencesSidebarModel: ObservableObject { tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, syncService: DDGSyncing, - vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager), + vpnVisibility: NetworkProtectionFeatureVisibility, includeDuckPlayer: Bool, userDefaults: UserDefaults = .netP ) { diff --git a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift index c24547c50c..dbc0072b71 100644 --- a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift @@ -59,7 +59,7 @@ final class VPNPreferencesModel: ObservableObject { private var onboardingStatus: OnboardingStatus { didSet { - showUninstallVPN = DefaultNetworkProtectionVisibility(subscriptionManager: AppDelegate.shared.subscriptionManager).isInstalled + showUninstallVPN = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager).isInstalled } } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index cd96fee009..ba4f085d45 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -137,7 +137,7 @@ enum Preferences { WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .openITR: PixelKit.fire(PrivacyProPixel.privacyProIdentityRestorationSettings) - let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: AppDelegate.shared.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: Application.appDelegate.subscriptionManager.currentEnvironment.serviceEnvironment) WindowControllersManager.shared.showTab(with: .identityTheftRestoration(url)) case .iHaveASubscriptionClick: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseClick) @@ -169,7 +169,7 @@ enum Preferences { return } - let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(subscriptionManager: AppDelegate.shared.subscriptionManager) + let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(subscriptionManager: Application.appDelegate.subscriptionManager) await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager) } } @@ -180,7 +180,7 @@ enum Preferences { return PreferencesSubscriptionModel(openURLHandler: openURL, userEventHandler: handleUIEvent, sheetActionHandler: sheetActionHandler, - subscriptionManager: AppDelegate.shared.subscriptionManager) + subscriptionManager: Application.appDelegate.subscriptionManager) } } } diff --git a/DuckDuckGo/Preferences/View/PreferencesViewController.swift b/DuckDuckGo/Preferences/View/PreferencesViewController.swift index 37d63c320f..967bc49768 100644 --- a/DuckDuckGo/Preferences/View/PreferencesViewController.swift +++ b/DuckDuckGo/Preferences/View/PreferencesViewController.swift @@ -34,7 +34,9 @@ final class PreferencesViewController: NSViewController { private var bitwardenManager: BWManagement = BWManager.shared init(syncService: DDGSyncing, duckPlayer: DuckPlayer = DuckPlayer.shared) { - model = PreferencesSidebarModel(syncService: syncService, includeDuckPlayer: duckPlayer.isAvailable) + model = PreferencesSidebarModel(syncService: syncService, + vpnVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), + includeDuckPlayer: duckPlayer.isAvailable) super.init(nibName: nil, bundle: nil) } diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift b/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift index 854c783f02..9be69a6886 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift @@ -17,6 +17,7 @@ // import Foundation +import AppKit import Subscription import BrowserServicesKit @@ -24,6 +25,6 @@ extension DefaultSubscriptionFeatureAvailability { convenience init() { self.init(privacyConfigurationManager: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager, - subscriptionEnvironment: AppDelegate.shared.subscriptionManager.currentEnvironment) + subscriptionEnvironment: Application.appDelegate.subscriptionManager.currentEnvironment) } } diff --git a/DuckDuckGo/Tab/Model/TabContent.swift b/DuckDuckGo/Tab/Model/TabContent.swift index 3412445e97..43db82985c 100644 --- a/DuckDuckGo/Tab/Model/TabContent.swift +++ b/DuckDuckGo/Tab/Model/TabContent.swift @@ -117,7 +117,7 @@ extension TabContent { } if let url { - let subscriptionManager = AppDelegate.shared.subscriptionManager + let subscriptionManager = Application.appDelegate.subscriptionManager let environment = subscriptionManager.currentEnvironment.serviceEnvironment let subscriptionBaseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) let identityTheftRestorationURL = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) diff --git a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift index 7c89c962d5..a6cf094e53 100644 --- a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift +++ b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift @@ -38,7 +38,7 @@ struct RedirectNavigationResponder: NavigationResponder { if url.pathComponents == URL.privacyPro.pathComponents { let isFeatureAvailable = DefaultSubscriptionFeatureAvailability().isFeatureAvailable - let subscriptionManager = AppDelegate.shared.subscriptionManager + let subscriptionManager = Application.appDelegate.subscriptionManager let platform = subscriptionManager.currentEnvironment.platform let shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false let isPurchasePageRedirectActive = isFeatureAvailable && !shouldHidePrivacyProDueToNoProducts diff --git a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift index 9f675d8413..a1ad9e3f9d 100644 --- a/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/IdentityTheftRestorationPagesUserScript.swift @@ -92,7 +92,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = AppDelegate.shared.subscriptionManager.accountManager.accessToken { + if let accessToken = await Application.appDelegate.subscriptionManager.accountManager.accessToken { return ["token": accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index 884afccbf3..d647b50545 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -196,7 +196,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch subscriptionPlatform { case .appStore: if #available(macOS 12.0, *) { - let appStorePurchaseFlow = AppStorePurchaseFlow(subscriptionManager: subscriptionManager) return await subscriptionManager.getStorePurchaseManager().subscriptionOptions() } case .stripe: diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index 386af268d5..2668a5fde6 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -93,7 +93,7 @@ final class UserScripts: UserScriptsProvider { } if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature(subscriptionManager: AppDelegate.shared.subscriptionManager)) + subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature(subscriptionManager: Application.appDelegate.subscriptionManager)) userScripts.append(subscriptionPagesUserScript) identityTheftRestorationPagesUserScript.registerSubfeature(delegate: IdentityTheftRestorationPagesFeature()) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift index 818977afe5..590d958513 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift @@ -40,7 +40,7 @@ final class VPNFeedbackFormViewController: NSViewController { private var cancellables = Set() init() { - self.viewModel = VPNFeedbackFormViewModel(metadataCollector: DefaultVPNMetadataCollector(accountManager: AppDelegate.shared.subscriptionManager.accountManager)) + self.viewModel = VPNFeedbackFormViewModel(metadataCollector: DefaultVPNMetadataCollector(accountManager: Application.appDelegate.subscriptionManager.accountManager)) super.init(nibName: nil, bundle: nil) self.viewModel.delegate = self } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift index 005ad75b91..ba98b21821 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift @@ -18,7 +18,6 @@ import Foundation import Subscription -import BrowserServicesKit public final class ActivateSubscriptionAccessModel: SubscriptionAccessModel, PurchaseRestoringSubscriptionAccessModel { From 41b1ad37b820cd163e454998f93e0644d926d826 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 8 May 2024 15:50:02 +0100 Subject: [PATCH 16/41] unit tests improved using AccountManagerMock --- .../DuckDuckGo Privacy Browser.xcscheme | 3 ++ DuckDuckGo/Common/Logging/Logging.swift | 42 +++++++++---------- UnitTests/Menus/MoreOptionsMenuTests.swift | 39 +++++++++++++---- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index 7198d6f122..3afb1cb16d 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -154,6 +154,9 @@ + + diff --git a/DuckDuckGo/Common/Logging/Logging.swift b/DuckDuckGo/Common/Logging/Logging.swift index 3740e0e223..68237f1c85 100644 --- a/DuckDuckGo/Common/Logging/Logging.swift +++ b/DuckDuckGo/Common/Logging/Logging.swift @@ -52,27 +52,27 @@ extension OSLog { } } - @OSLogWrapper(.atb) static var atb - @OSLogWrapper(.config) static var config - @OSLogWrapper(.downloads) static var downloads - @OSLogWrapper(.fire) static var fire - @OSLogWrapper(.dataImportExport) static var dataImportExport - @OSLogWrapper(.pixel) static var pixel - @OSLogWrapper(.contentBlocking) static var contentBlocking - @OSLogWrapper(.httpsUpgrade) static var httpsUpgrade - @OSLogWrapper(.favicons) static var favicons - @OSLogWrapper(.autoLock) static var autoLock - @OSLogWrapper(.tabLazyLoading) static var tabLazyLoading - @OSLogWrapper(.autoconsent) static var autoconsent - @OSLogWrapper(.bookmarks) static var bookmarks - @OSLogWrapper(.attribution) static var attribution - @OSLogWrapper(.bitwarden) static var bitwarden - @OSLogWrapper(.navigation) static var navigation - @OSLogWrapper(.duckPlayer) static var duckPlayer - @OSLogWrapper(.tabSnapshots) static var tabSnapshots - @OSLogWrapper(.sync) static var sync - @OSLogWrapper(.networkProtection) static var networkProtection - @OSLogWrapper(.dbp) static var dbp + @OSLogWrapper(AppCategories.atb) static var atb + @OSLogWrapper(AppCategories.config) static var config + @OSLogWrapper(AppCategories.downloads) static var downloads + @OSLogWrapper(AppCategories.fire) static var fire + @OSLogWrapper(AppCategories.dataImportExport) static var dataImportExport + @OSLogWrapper(AppCategories.pixel) static var pixel + @OSLogWrapper(AppCategories.contentBlocking) static var contentBlocking + @OSLogWrapper(AppCategories.httpsUpgrade) static var httpsUpgrade + @OSLogWrapper(AppCategories.favicons) static var favicons + @OSLogWrapper(AppCategories.autoLock) static var autoLock + @OSLogWrapper(AppCategories.tabLazyLoading) static var tabLazyLoading + @OSLogWrapper(AppCategories.autoconsent) static var autoconsent + @OSLogWrapper(AppCategories.bookmarks) static var bookmarks + @OSLogWrapper(AppCategories.attribution) static var attribution + @OSLogWrapper(AppCategories.bitwarden) static var bitwarden + @OSLogWrapper(AppCategories.navigation) static var navigation + @OSLogWrapper(AppCategories.duckPlayer) static var duckPlayer + @OSLogWrapper(AppCategories.tabSnapshots) static var tabSnapshots + @OSLogWrapper(AppCategories.sync) static var sync + @OSLogWrapper(AppCategories.networkProtection) static var networkProtection + @OSLogWrapper(AppCategories.dbp) static var dbp // Debug->Logging categories will only be enabled for one day @UserDefaultsWrapper(key: .loggingEnabledDate, defaultValue: .distantPast) diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index e389cc2163..b2024f0422 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -31,9 +31,7 @@ final class MoreOptionsMenuTests: XCTestCase { var passwordManagerCoordinator: PasswordManagerCoordinator! var capturingActionDelegate: CapturingOptionsButtonMenuDelegate! var accountManager: AccountManagerMock! - @MainActor var moreOptionsMenu: MoreOptionsMenu! - var internalUserDecider: InternalUserDeciderMock! var networkProtectionVisibilityMock: NetworkProtectionVisibilityMock! @@ -67,7 +65,7 @@ final class MoreOptionsMenuTests: XCTestCase { } @MainActor - func testThatMoreOptionMenuHasTheExpectedItems() { + func testThatMoreOptionMenuHasTheExpectedItemsAuthenticated() { moreOptionsMenu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: passwordManagerCoordinator, networkProtectionFeatureVisibility: NetworkProtectionVisibilityMock(isInstalled: false, visible: true), @@ -89,23 +87,47 @@ final class MoreOptionsMenuTests: XCTestCase { XCTAssertTrue(moreOptionsMenu.items[11].isSeparatorItem) XCTAssertEqual(moreOptionsMenu.items[12].title, UserText.emailOptionsMenuItem) - accountManager.isUserAuthenticated = true XCTAssertTrue(moreOptionsMenu.items[13].isSeparatorItem) XCTAssertTrue(moreOptionsMenu.items[14].title.hasPrefix(UserText.networkProtection)) XCTAssertTrue(moreOptionsMenu.items[15].title.hasPrefix(UserText.identityTheftRestorationOptionsMenuItem)) XCTAssertTrue(moreOptionsMenu.items[16].isSeparatorItem) XCTAssertEqual(moreOptionsMenu.items[17].title, UserText.settings) + } + + @MainActor + func testThatMoreOptionMenuHasTheExpectedItemsNotAuthenticated() { + + accountManager = AccountManagerMock(isUserAuthenticated: false) + moreOptionsMenu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, + passwordManagerCoordinator: passwordManagerCoordinator, + networkProtectionFeatureVisibility: NetworkProtectionVisibilityMock(isInstalled: false, visible: true), + sharingMenu: NSMenu(), + internalUserDecider: internalUserDecider, + accountManager: accountManager) + + XCTAssertEqual(moreOptionsMenu.items[0].title, UserText.sendFeedback) + XCTAssertTrue(moreOptionsMenu.items[1].isSeparatorItem) + XCTAssertEqual(moreOptionsMenu.items[2].title, UserText.plusButtonNewTabMenuItem) + XCTAssertEqual(moreOptionsMenu.items[3].title, UserText.newWindowMenuItem) + XCTAssertEqual(moreOptionsMenu.items[4].title, UserText.newBurnerWindowMenuItem) + XCTAssertTrue(moreOptionsMenu.items[5].isSeparatorItem) + XCTAssertEqual(moreOptionsMenu.items[6].title, UserText.zoom) + XCTAssertTrue(moreOptionsMenu.items[7].isSeparatorItem) + XCTAssertEqual(moreOptionsMenu.items[8].title, UserText.bookmarks) + XCTAssertEqual(moreOptionsMenu.items[9].title, UserText.downloads) + XCTAssertEqual(moreOptionsMenu.items[10].title, UserText.passwordManagement) + XCTAssertTrue(moreOptionsMenu.items[11].isSeparatorItem) + XCTAssertEqual(moreOptionsMenu.items[12].title, UserText.emailOptionsMenuItem) - accountManager.isUserAuthenticated = false XCTAssertTrue(moreOptionsMenu.items[13].isSeparatorItem) XCTAssertTrue(moreOptionsMenu.items[14].title.hasPrefix(UserText.networkProtection)) XCTAssertTrue(moreOptionsMenu.items[15].isSeparatorItem) XCTAssertEqual(moreOptionsMenu.items[16].title, UserText.settings) } - } // MARK: Zoom + @MainActor func testWhenClickingDefaultZoomInZoomSubmenuThenTheActionDelegateIsAlerted() { guard let zoomSubmenu = moreOptionsMenu.zoomMenuItem.submenu else { XCTFail("No zoom submenu available") @@ -119,9 +141,8 @@ final class MoreOptionsMenuTests: XCTestCase { } // MARK: Preferences - func testWhenClickingOnPreferenceMenuItemThenTheActionDelegateIsAlerted() { - moreOptionsMenu.performActionForItem(at: moreOptionsMenu(GeneralPixel.items.count - 1) + moreOptionsMenu.performActionForItem(at: moreOptionsMenu.items.count - 1) XCTAssertTrue(capturingActionDelegate.optionsButtonMenuRequestedPreferencesCalled) } @@ -129,7 +150,7 @@ final class MoreOptionsMenuTests: XCTestCase { func testWhenClickingOnBookmarkAllTabsMenuItemThenTheActionDelegateIsAlerted() throws { // GIVEN - let bookmarksMenu = try XCTUnwrap(moreOptionsMenu(GeneralPixel.item(at: 8)?.submenu) + let bookmarksMenu = try XCTUnwrap(moreOptionsMenu.item(at: 8)?.submenu) let bookmarkAllTabsIndex = try XCTUnwrap(bookmarksMenu.indexOfItem(withTitle: UserText.bookmarkAllTabs)) let bookmarkAllTabsMenuItem = try XCTUnwrap(bookmarksMenu.items[bookmarkAllTabsIndex]) bookmarkAllTabsMenuItem.isEnabled = true From 7a76e2f5b194d839f33246d329202e13c3b69e92 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 8 May 2024 16:28:32 +0100 Subject: [PATCH 17/41] SubscriptionManaging --- DuckDuckGo/Application/AppDelegate.swift | 2 +- .../DBP/DataBrokerProtectionSubscriptionEventHandler.swift | 4 ++-- .../NavigationBar/View/NavigationBarViewController.swift | 2 +- .../NetworkProtectionSubscriptionEventHandler.swift | 4 ++-- .../Subscription/SubscriptionAppStoreRestorer.swift | 6 +++--- .../Subscription/SubscriptionPagesUserScript.swift | 4 ++-- .../Waitlist/NetworkProtectionFeatureVisibility.swift | 4 ++-- .../SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift | 4 ++-- .../Preferences/PreferencesSubscriptionModel.swift | 4 ++-- .../Model/ShareSubscriptionAccessModel.swift | 4 ++-- .../SubscriptionAccessViewController.swift | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 7605b4f767..6ffcc54337 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -85,7 +85,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { var privacyDashboardWindow: NSWindow? private let accountManager: AccountManager - public let subscriptionManager: SubscriptionManager + public let subscriptionManager: SubscriptionManaging private var networkProtectionSubscriptionEventHandler: NetworkProtectionSubscriptionEventHandler? #if DBP diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index 31fc1592a2..7a970deb76 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -24,11 +24,11 @@ import PixelKit final class DataBrokerProtectionSubscriptionEventHandler { - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: SubscriptionManaging private let authRepository: AuthenticationRepository private let featureDisabler: DataBrokerProtectionFeatureDisabling - init(subscriptionManager: SubscriptionManager, + init(subscriptionManager: SubscriptionManaging, authRepository: AuthenticationRepository = KeychainAuthenticationData(), featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) { self.subscriptionManager = subscriptionManager diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 9af982da42..2c2775d6e6 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -71,7 +71,7 @@ final class NavigationBarViewController: NSViewController { return progressView }() - private var subscriptionManager: SubscriptionManager { + private var subscriptionManager: SubscriptionManaging { Application.appDelegate.subscriptionManager } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift index 35da244dcc..9f38cc4168 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionSubscriptionEventHandler.swift @@ -25,13 +25,13 @@ import NetworkProtectionUI final class NetworkProtectionSubscriptionEventHandler { - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: SubscriptionManaging private let networkProtectionTokenStorage: NetworkProtectionTokenStore private let networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling private let userDefaults: UserDefaults private var cancellables = Set() - init(subscriptionManager: SubscriptionManager, + init(subscriptionManager: SubscriptionManaging, networkProtectionTokenStorage: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), userDefaults: UserDefaults = .netP) { diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 2b7e20ce8a..5f55d9e441 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -16,7 +16,7 @@ // limitations under the License. // -import Foundation +import AppKit import Subscription import SubscriptionUI import enum StoreKit.StoreKitError @@ -25,11 +25,11 @@ import PixelKit @available(macOS 12.0, *) struct SubscriptionAppStoreRestorer { - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: SubscriptionManaging @MainActor var window: NSWindow? { WindowControllersManager.shared.lastKeyMainWindowController?.window } let subscriptionErrorReporter = SubscriptionErrorReporter() - public init(subscriptionManager: SubscriptionManager) { + public init(subscriptionManager: SubscriptionManaging) { self.subscriptionManager = subscriptionManager } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index d647b50545..d9fa34bb88 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -84,14 +84,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { var window: NSWindow? { WindowControllersManager.shared.lastKeyMainWindowController?.window } - let subscriptionManager: SubscriptionManager + let subscriptionManager: SubscriptionManaging var accountManager: AccountManaging { subscriptionManager.accountManager } var subscriptionPlatform: SubscriptionEnvironment.Platform { subscriptionManager.currentEnvironment.platform } let stripePurchaseFlow: StripePurchaseFlow let subscriptionErrorReporter = SubscriptionErrorReporter() - public init(subscriptionManager: SubscriptionManager) { + public init(subscriptionManager: SubscriptionManaging) { self.subscriptionManager = subscriptionManager self.stripePurchaseFlow = StripePurchaseFlow(subscriptionManager: subscriptionManager) } diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 7fe6a8a49a..7a67e5f3c5 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -46,7 +46,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation private let privacyConfigurationManager: PrivacyConfigurationManaging private let defaults: UserDefaults - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: SubscriptionManaging init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), @@ -54,7 +54,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { featureDisabler: NetworkProtectionFeatureDisabling = NetworkProtectionFeatureDisabler(), defaults: UserDefaults = .netP, log: OSLog = .networkProtection, - subscriptionManager: SubscriptionManager) { + subscriptionManager: SubscriptionManaging) { self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionFeatureActivation = networkProtectionFeatureActivation diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 394494ad2d..42aa641f23 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -29,7 +29,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { private var purchasePlatformItem: NSMenuItem? var currentViewController: () -> NSViewController? - let subscriptionManager: SubscriptionManager + let subscriptionManager: SubscriptionManaging var accountManager: AccountManaging { subscriptionManager.accountManager } @@ -53,7 +53,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, - subscriptionManager: SubscriptionManager) { + subscriptionManager: SubscriptionManaging) { self.currentEnvironment = currentEnvironment self.updateEnvironment = updateEnvironment self.isInternalTestingEnabled = isInternalTestingEnabled diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 0c0292a647..a6b8f05e32 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -35,7 +35,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { lazy var sheetModel: SubscriptionAccessModel = makeSubscriptionAccessModel() - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: SubscriptionManaging private var accountManager: AccountManaging { subscriptionManager.accountManager } @@ -91,7 +91,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { public init(openURLHandler: @escaping (URL) -> Void, userEventHandler: @escaping (UserEvent) -> Void, sheetActionHandler: SubscriptionAccessActionHandlers, - subscriptionManager: SubscriptionManager) { + subscriptionManager: SubscriptionManaging) { self.subscriptionManager = subscriptionManager self.openURLHandler = openURLHandler self.userEventHandler = userEventHandler diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index 4866e2f385..768e428192 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -28,9 +28,9 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { public var emailLabel: String { UserText.email } public var emailDescription: String { hasEmail ? UserText.shareModalHasEmailDescription : UserText.shareModalNoEmailDescription } public var emailButtonTitle: String { hasEmail ? UserText.manageEmailButton : UserText.addEmailButton } - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: SubscriptionManaging - public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, subscriptionManager: SubscriptionManager) { + public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?, subscriptionManager: SubscriptionManaging) { self.actionHandlers = actionHandlers self.email = email self.subscriptionManager = subscriptionManager diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift index 6e7a4adb58..494415e13f 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -22,14 +22,14 @@ import SwiftUI public final class SubscriptionAccessViewController: NSViewController { - private let subscriptionManager: SubscriptionManager + private let subscriptionManager: SubscriptionManaging private var actionHandlers: SubscriptionAccessActionHandlers public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public init(subscriptionManager: SubscriptionManager, + public init(subscriptionManager: SubscriptionManaging, actionHandlers: SubscriptionAccessActionHandlers) { self.subscriptionManager = subscriptionManager self.actionHandlers = actionHandlers From e7851bd6989f1ed03c9a18c2d9ea6a04a67c9a5b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 9 May 2024 11:51:23 +0100 Subject: [PATCH 18/41] subscriptionenvironment saved and loaded from userdefaults --- DuckDuckGo/Application/AppDelegate.swift | 30 +++----- DuckDuckGo/Menus/MainMenu.swift | 31 ++++---- .../DebugMenu/SubscriptionDebugMenu.swift | 72 +++++++++++-------- 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 6ffcc54337..1f8954b181 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -84,7 +84,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let bookmarksManager = LocalBookmarkManager.shared var privacyDashboardWindow: NSWindow? - private let accountManager: AccountManager + private var accountManager: AccountManaging { + subscriptionManager.accountManager + } public let subscriptionManager: SubscriptionManaging private var networkProtectionSubscriptionEventHandler: NetworkProtectionSubscriptionEventHandler? @@ -179,24 +181,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate { ) // MARK: - Configure Subscription - -#if APPSTORE || !STRIPE - let subscriptionPurchaseEnvironment: SubscriptionEnvironment.Platform = .appStore -#else - let subscriptionPurchaseEnvironment: SubscriptionEnvironment.Platform = .stripe -#endif - - // Load subscription environment and re-configure SubscriptionManager if needed - let serviceEnvironment = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: SubscriptionEnvironment.ServiceEnvironment.default).wrappedValue - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults(suiteName: subscriptionAppGroup) ?? UserDefaults.standard, + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let subscriptionService = SubscriptionService(currentServiceEnvironment: serviceEnvironment) - let authService = AuthService(currentServiceEnvironment: serviceEnvironment) - accountManager = AccountManager(accessTokenStorage: accessTokenStorage, + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let accountManager = AccountManager(accessTokenStorage: accessTokenStorage, entitlementsCache: entitlementsCache, subscriptionService: subscriptionService, authService: authService) @@ -207,14 +201,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { accountManager: accountManager, subscriptionService: subscriptionService, authService: authService, - currentServiceEnvironment: serviceEnvironment, - current: subscriptionPurchaseEnvironment) + subscriptionEnvironment: subscriptionEnvironment) } else { subscriptionManager = SubscriptionManager(accountManager: accountManager, subscriptionService: subscriptionService, authService: authService, - currentServiceEnvironment: serviceEnvironment, - current: subscriptionPurchaseEnvironment) + subscriptionEnvironment: subscriptionEnvironment) } } diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index d58b9f2e0e..4f9f31f018 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -619,21 +619,26 @@ import SubscriptionUI NSMenuItem(title: "Trigger Fatal Error", action: #selector(MainViewController.triggerFatalError)) - let currentEnvironmentWrapper = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: SubscriptionEnvironment.ServiceEnvironment.default) - let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false) - - SubscriptionDebugMenu(currentEnvironment: { currentEnvironmentWrapper.wrappedValue.rawValue }, - updateEnvironment: { - guard let newEnvironment = SubscriptionEnvironment.ServiceEnvironment(rawValue: $0) else { return } - currentEnvironmentWrapper.wrappedValue = newEnvironment -// SubscriptionPurchaseEnvironment.currentServiceEnvironment = newEnvironment // TODO: reimplement debug menu for change of environment - VPNSettings(defaults: .netP).selectedEnvironment = newEnvironment == .staging ? .staging : .production - }, + let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false) // TODO: Is this still used?? + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + + var currentEnvironment: SubscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + let updateServiceEnvironment: (SubscriptionEnvironment.ServiceEnvironment) -> Void = { env in + currentEnvironment.serviceEnvironment = env + SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) + } + let updatePurchasingPlatform: (SubscriptionEnvironment.Platform) -> Void = { platform in + currentEnvironment.platform = platform + SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) + } + + SubscriptionDebugMenu(currentEnvironment: currentEnvironment, + updateServiceEnvironment: updateServiceEnvironment, + updatePurchasingPlatform: updatePurchasingPlatform, isInternalTestingEnabled: { isInternalTestingWrapper.wrappedValue }, updateInternalTestingFlag: { isInternalTestingWrapper.wrappedValue = $0 }, - currentViewController: { - WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController - }, + currentViewController: { WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController }, subscriptionManager: Application.appDelegate.subscriptionManager) NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index 42aa641f23..e78b58ba8e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -21,8 +21,10 @@ import Subscription public final class SubscriptionDebugMenu: NSMenuItem { - var currentEnvironment: () -> String - var updateEnvironment: (String) -> Void + var currentEnvironment: SubscriptionEnvironment + var updateServiceEnvironment: (SubscriptionEnvironment.ServiceEnvironment) -> Void + var updatePurchasingPlatform: (SubscriptionEnvironment.Platform) -> Void + var isInternalTestingEnabled: () -> Bool var updateInternalTestingFlag: (Bool) -> Void @@ -48,14 +50,16 @@ public final class SubscriptionDebugMenu: NSMenuItem { fatalError("init(coder:) has not been implemented") } - public init(currentEnvironment: @escaping () -> String, - updateEnvironment: @escaping (String) -> Void, + public init(currentEnvironment: SubscriptionEnvironment, + updateServiceEnvironment: @escaping (SubscriptionEnvironment.ServiceEnvironment) -> Void, + updatePurchasingPlatform: @escaping (SubscriptionEnvironment.Platform) -> Void, isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, subscriptionManager: SubscriptionManaging) { self.currentEnvironment = currentEnvironment - self.updateEnvironment = updateEnvironment + self.updateServiceEnvironment = updateServiceEnvironment + self.updatePurchasingPlatform = updatePurchasingPlatform self.isInternalTestingEnabled = isInternalTestingEnabled self.updateInternalTestingFlag = updateInternalTestingFlag self.currentViewController = currentViewController @@ -107,7 +111,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { private func makePurchasePlatformSubmenu() -> NSMenu { let menu = NSMenu(title: "Select purchase platform:") let appStoreItem = NSMenuItem(title: "App Store", action: #selector(setPlatformToAppStore), target: self) - if subscriptionManager.currentEnvironment.platform == .appStore { + if currentEnvironment.platform == .appStore { appStoreItem.state = .on appStoreItem.isEnabled = false appStoreItem.action = nil @@ -116,7 +120,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { menu.addItem(appStoreItem) let stripeItem = NSMenuItem(title: "Stripe", action: #selector(setPlatformToStripe), target: self) - if subscriptionManager.currentEnvironment.platform == .stripe { + if currentEnvironment.platform == .stripe { stripeItem.state = .on stripeItem.isEnabled = false stripeItem.action = nil @@ -126,7 +130,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { menu.addItem(.separator()) - let disclaimerItem = NSMenuItem(title: "⚠️ Change not persisted between app restarts", action: nil, target: nil) + let disclaimerItem = NSMenuItem(title: "⚠️ App restart required! The changes are persistent", action: nil, target: nil) menu.addItem(disclaimerItem) return menu @@ -135,11 +139,10 @@ public final class SubscriptionDebugMenu: NSMenuItem { private func makeEnvironmentSubmenu() -> NSMenu { let menu = NSMenu(title: "Select environment:") - let currentEnvironment = currentEnvironment() - let stagingItem = NSMenuItem(title: "Staging", action: #selector(setEnvironmentToStaging), target: self) - stagingItem.state = currentEnvironment == "staging" ? .on : .off - if currentEnvironment == "staging" { + let isStaging = currentEnvironment.serviceEnvironment == .staging + stagingItem.state = isStaging ? .on : .off + if isStaging { stagingItem.isEnabled = false stagingItem.action = nil stagingItem.target = nil @@ -147,14 +150,18 @@ public final class SubscriptionDebugMenu: NSMenuItem { menu.addItem(stagingItem) let productionItem = NSMenuItem(title: "Production", action: #selector(setEnvironmentToProduction), target: self) - productionItem.state = currentEnvironment == "production" ? .on : .off - if currentEnvironment == "production" { + let isProduction = currentEnvironment.serviceEnvironment == .production + productionItem.state = isProduction ? .on : .off + if isProduction { productionItem.isEnabled = false productionItem.action = nil productionItem.target = nil } menu.addItem(productionItem) + let disclaimerItem = NSMenuItem(title: "⚠️ App restart required! The changes are persistent", action: nil, target: nil) + menu.addItem(disclaimerItem) + return menu } @@ -242,6 +249,8 @@ public final class SubscriptionDebugMenu: NSMenuItem { } } + // MARK: - Platform + @IBAction func setPlatformToAppStore(_ sender: Any?) { askAndUpdatePlatform(to: .appStore) } @@ -252,37 +261,44 @@ public final class SubscriptionDebugMenu: NSMenuItem { private func askAndUpdatePlatform(to newPlatform: SubscriptionEnvironment.Platform) { let alert = makeAlert(title: "Are you sure you want to change the purchase platform to \(newPlatform.rawValue.capitalized)", - message: "This setting is not persisted between app runs. After restarting the app it returns to the default determined on app's distribution method.", + message: "This setting IS persisted between app runs. This action will close the app, do you want to proceed?", buttonNames: ["Yes", "No"]) let response = alert.runModal() - guard case .alertFirstButtonReturn = response else { return } - -// subscriptionManager.currentEnvironment.platform = newPlatform // TODO: In debug menus set subscription environments in user defaults and restart the app every time the environment is changed - - refreshSubmenu() + updatePurchasingPlatform(newPlatform) + closeTheApp() } + // MARK: - Environment + @IBAction func setEnvironmentToStaging(_ sender: Any?) { - askAndUpdateEnvironment(to: "staging") + askAndUpdateServiceEnvironment(to: SubscriptionEnvironment.ServiceEnvironment.staging) } @IBAction func setEnvironmentToProduction(_ sender: Any?) { - askAndUpdateEnvironment(to: "production") + askAndUpdateServiceEnvironment(to: SubscriptionEnvironment.ServiceEnvironment.production) } - private func askAndUpdateEnvironment(to newEnvironmentString: String) { - let alert = makeAlert(title: "Are you sure you want to change the environment to \(newEnvironmentString.capitalized)", - message: "Please make sure you have manually removed your current active Subscription and reset all related features. \nYou may also need to change environment of related features.", + private func askAndUpdateServiceEnvironment(to newServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment) { + let alert = makeAlert(title: "Are you sure you want to change the environment to \(newServiceEnvironment.description.capitalized)", + message: """ + Please make sure you have manually removed your current active Subscription and reset all related features. + You may also need to change environment of related features. + This setting IS persisted between app runs. This action will close the app, do you want to proceed? + """, buttonNames: ["Yes", "No"]) let response = alert.runModal() - guard case .alertFirstButtonReturn = response else { return } + updateServiceEnvironment(newServiceEnvironment) + closeTheApp() + } - updateEnvironment(newEnvironmentString) - refreshSubmenu() + func closeTheApp() { + NSApp.terminate(self) } + // MARK: - + @objc func postDidSignInNotification(_ sender: Any?) { NotificationCenter.default.post(name: .accountDidSignIn, object: self, userInfo: nil) From 96339421247a16ff13c5078213f9f89c4e793a52 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 9 May 2024 12:16:32 +0100 Subject: [PATCH 19/41] subscription environment propagated across app and extensions --- .../MacPacketTunnelProvider.swift | 13 +++++++------ DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 18 ++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index a6e590bc36..e827b07944 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -341,6 +341,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - Configure Subscription let settings = VPNSettings(defaults: defaults) + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter) let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore) @@ -350,14 +352,13 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { isSubscriptionEnabled: isSubscriptionEnabled, accessTokenProvider: { nil } ) - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults(suiteName: Self.subscriptionsAppGroup) ?? UserDefaults.standard, + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - - let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging - - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment) - let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment) +// let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) // TODO: the subscription environment should be matching to the VPNSettings environment, what should we do? + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let accountManager = AccountManager(accessTokenStorage: tokenStore, entitlementsCache: entitlementsCache, subscriptionService: subscriptionService, diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index a40ad54316..5e2b50ad46 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -46,14 +46,16 @@ final class DuckDuckGoVPNApplication: NSApplication { // MARK: - Configure Subscription let settings = VPNSettings(defaults: UserDefaults.netP) - let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment) - let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment) - let subscriptionsAppGroup = Bundle.main.appGroup(bundle: .subs) - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults(suiteName: subscriptionsAppGroup) ?? UserDefaults.standard, + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + // let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) // TODO: the subscription environment should be matching to the VPNSettings environment, what should we do? + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionsAppGroup))) + let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) accountManager = AccountManager(accessTokenStorage: accessTokenStorage, entitlementsCache: entitlementsCache, subscriptionService: subscriptionService, @@ -296,10 +298,6 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) - - // let currentServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment = tunnelSettings.selectedEnvironment == .production ? .production : .staging - // TODO: set SubscriptionEnvironment.ServiceEnvironment across extensions and VPN - os_log("DuckDuckGoVPN started", log: .networkProtectionLoginItemLog, type: .info) setupMenuVisibility() From d385fa0021dce6aa28b996150dd8a7f76312e2eb Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 9 May 2024 12:29:02 +0100 Subject: [PATCH 20/41] BSK points now to the right branch --- DuckDuckGo.xcodeproj/project.pbxproj | 4 +--- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9a361f2bec..ab27c58579 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -4045,7 +4045,6 @@ EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; - F119CCC72BE53CE00002B30E /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; @@ -6430,7 +6429,6 @@ AA585D75248FD31100E9A3E2 = { isa = PBXGroup; children = ( - F119CCC72BE53CE00002B30E /* BrowserServicesKit */, 378B5886295CF2A4002C0CC0 /* Configuration */, 378E279C2970217400FCADA2 /* LocalPackages */, 7BB108552A43375D000AB95F /* LocalThirdParty */, @@ -12803,7 +12801,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - branch = fcappelli/subscription_refactoring; + branch = fcappelli/subscription_refactoring_2; kind = branch; }; }; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a4c4905c48..5ea97a19d5 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_refactoring_2", + "revision" : "3e26d5b590d09fc9d45ef05c4a7898162433de12" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 84df605b1a67844660b5a30b04730470c40b74e6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 May 2024 12:43:20 +0100 Subject: [PATCH 21/41] lint and BSK update, VPN env aligned with subscription env --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Application/AppDelegate.swift | 10 ++++++++++ .../View/NavigationBarViewController.swift | 2 +- .../MacPacketTunnelProvider.swift | 12 ++++++++++-- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 12 ++++++++++-- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 42028afba2..3382a61e95 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_refactoring_2", - "revision" : "ee49b912e40f9330a58f835c9ecd529543fd5530" + "revision" : "2fb70be3b1e98f535192101b44b8f2ffb8437d1d" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 2a1b24887d..8284acd6c1 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -184,6 +184,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + + // The VPN environment is forced to match the subscription environment + let settings = VPNSettings(defaults: UserDefaults.netP) + switch subscriptionEnvironment.serviceEnvironment { + case .production: + settings.selectedEnvironment = .production + case .staging: + settings.selectedEnvironment = .staging + } + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 2c2775d6e6..dc4b5dbbf7 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -272,7 +272,7 @@ final class NavigationBarViewController: NSViewController { let internalUserDecider = NSApp.delegateTyped.internalUserDecider let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: PasswordManagerCoordinator.shared, - networkProtectionFeatureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), + networkProtectionFeatureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), internalUserDecider: internalUserDecider, accountManager: subscriptionManager.accountManager) menu.actionDelegate = self diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index e827b07944..0d4bba1d16 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -355,8 +355,16 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) -// let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) // TODO: the subscription environment should be matching to the VPNSettings environment, what should we do? + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + + // The VPN environment is forced to match the subscription environment + switch subscriptionEnvironment.serviceEnvironment { + case .production: + settings.selectedEnvironment = .production + case .staging: + settings.selectedEnvironment = .staging + } + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let accountManager = AccountManager(accessTokenStorage: tokenStore, diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 5e2b50ad46..fce2953ff5 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -48,8 +48,16 @@ final class DuckDuckGoVPNApplication: NSApplication { let settings = VPNSettings(defaults: UserDefaults.netP) let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - // let subscriptionEnvironment: SubscriptionEnvironment.ServiceEnvironment = settings.selectedEnvironment == .production ? .production : .staging - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) // TODO: the subscription environment should be matching to the VPNSettings environment, what should we do? + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + + // The VPN environment is forced to match the subscription environment + switch subscriptionEnvironment.serviceEnvironment { + case .production: + settings.selectedEnvironment = .production + case .staging: + settings.selectedEnvironment = .staging + } + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, From a9a9265caa912532a687abbdaad1ef1b2392d8f6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 May 2024 14:25:06 +0100 Subject: [PATCH 22/41] VPN env now follows the Subscription env changes --- DuckDuckGo/Application/AppDelegate.swift | 10 ---------- DuckDuckGo/Menus/MainMenu.swift | 9 +++++++++ .../MacPacketTunnelProvider.swift | 9 --------- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 9 --------- 4 files changed, 9 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 8284acd6c1..2a1b24887d 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -184,16 +184,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - - // The VPN environment is forced to match the subscription environment - let settings = VPNSettings(defaults: UserDefaults.netP) - switch subscriptionEnvironment.serviceEnvironment { - case .production: - settings.selectedEnvironment = .production - case .staging: - settings.selectedEnvironment = .staging - } - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index ebdebb7f53..58e42ea4d1 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -628,6 +628,15 @@ import SubscriptionUI let updateServiceEnvironment: (SubscriptionEnvironment.ServiceEnvironment) -> Void = { env in currentEnvironment.serviceEnvironment = env SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) + + // The VPN environment is forced to match the Subscription environment + let settings = VPNSettings(defaults: .netP) + switch env { + case .production: + settings.selectedEnvironment = .production + case .staging: + settings.selectedEnvironment = .staging + } } let updatePurchasingPlatform: (SubscriptionEnvironment.Platform) -> Void = { platform in currentEnvironment.platform = platform diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 0d4bba1d16..5b9d16ea53 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -356,15 +356,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - - // The VPN environment is forced to match the subscription environment - switch subscriptionEnvironment.serviceEnvironment { - case .production: - settings.selectedEnvironment = .production - case .staging: - settings.selectedEnvironment = .staging - } - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let accountManager = AccountManager(accessTokenStorage: tokenStore, diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index fce2953ff5..7af92a624b 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -49,15 +49,6 @@ final class DuckDuckGoVPNApplication: NSApplication { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - - // The VPN environment is forced to match the subscription environment - switch subscriptionEnvironment.serviceEnvironment { - case .production: - settings.selectedEnvironment = .production - case .staging: - settings.selectedEnvironment = .staging - } - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, From c5a1b69ab76986a3b275d7d31830b0291a5e11e6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 11:36:18 +0100 Subject: [PATCH 23/41] lint --- DuckDuckGo/Menus/MainMenu.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 58e42ea4d1..99a9362e10 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -620,7 +620,7 @@ import SubscriptionUI NSMenuItem(title: "Trigger Fatal Error", action: #selector(MainViewController.triggerFatalError)) - let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false) // TODO: Is this still used?? + let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false) let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! From 0943e0bce2e0db26e1d545a1ef12bbed2ffe8c1e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 13:22:32 +0100 Subject: [PATCH 24/41] SubscriptionEnvironment Default moved to main app --- DuckDuckGo.xcodeproj/project.pbxproj | 70 ++++--------------- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Menus/MainMenu.swift | 2 +- .../SubscriptionEnvironment+Default.swift | 48 +++++++++++++ 4 files changed, 64 insertions(+), 58 deletions(-) create mode 100644 DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4b0225b0cb..1e912288b4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2570,14 +2570,6 @@ F118EA7E2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; F118EA852BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; F118EA862BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; - F118EA882BEBBC1B00F77634 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA872BEBBC1B00F77634 /* Subscription */; }; - F118EA8A2BEBBC1B00F77634 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA892BEBBC1B00F77634 /* SubscriptionTestingUtilities */; }; - F118EA8C2BEBBC2400F77634 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA8B2BEBBC2400F77634 /* Subscription */; }; - F118EA8E2BEBBC2400F77634 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA8D2BEBBC2400F77634 /* SubscriptionTestingUtilities */; }; - F118EA902BEBBC3200F77634 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA8F2BEBBC3200F77634 /* Subscription */; }; - F118EA922BEBBC3200F77634 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA912BEBBC3200F77634 /* SubscriptionTestingUtilities */; }; - F118EA942BEBBC3900F77634 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA932BEBBC3900F77634 /* Subscription */; }; - F118EA962BEBBC3900F77634 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F118EA952BEBBC3900F77634 /* SubscriptionTestingUtilities */; }; F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; @@ -2610,6 +2602,12 @@ F1DF95E42BD1807C0045E591 /* Crashes in Frameworks */ = {isa = PBXBuildFile; productRef = 537FC71EA5115A983FAF3170 /* Crashes */; }; F1DF95E52BD1807C0045E591 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = DC3F73D49B2D44464AFEFCD8 /* Subscription */; }; F1DF95E72BD188B60045E591 /* LoginItems in Frameworks */ = {isa = PBXBuildFile; productRef = F1DF95E62BD188B60045E591 /* LoginItems */; }; + F1FDC9292BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC92A2BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC92B2BF4DFEC006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC92C2BF4DFED006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC92D2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC92E2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; F41D174125CB131900472416 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; F44C130225C2DA0400426E3E /* NSAppearanceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */; }; F4A6198C283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */; }; @@ -4075,6 +4073,7 @@ F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; + F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; F41D174025CB131900472416 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = ""; }; F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearanceExtension.swift; sourceTree = ""; }; F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScopeFeatureFlagging.swift; sourceTree = ""; }; @@ -4129,8 +4128,6 @@ 3706FE88293F661700E42796 /* OHHTTPStubs in Frameworks */, F116A7C72BD1925500F3FCF7 /* PixelKitTestingUtilities in Frameworks */, B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */, - F118EA922BEBBC3200F77634 /* SubscriptionTestingUtilities in Frameworks */, - F118EA902BEBBC3200F77634 /* Subscription in Frameworks */, 3706FE89293F661700E42796 /* OHHTTPStubsSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4139,9 +4136,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F118EA962BEBBC3900F77634 /* SubscriptionTestingUtilities in Frameworks */, B65CD8D12B316E0C00A595BB /* SnapshotTesting in Frameworks */, - F118EA942BEBBC3900F77634 /* Subscription in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4159,8 +4154,6 @@ F116A7C92BD1929000F3FCF7 /* PixelKitTestingUtilities in Frameworks */, B65CD8CD2B316DFC00A595BB /* SnapshotTesting in Frameworks */, B6AE39F329374AEC00C37AA4 /* OHHTTPStubs in Frameworks */, - F118EA8E2BEBBC2400F77634 /* SubscriptionTestingUtilities in Frameworks */, - F118EA8C2BEBBC2400F77634 /* Subscription in Frameworks */, B6AE39F529374AEC00C37AA4 /* OHHTTPStubsSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4329,8 +4322,6 @@ B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */, F116A7C32BD1924B00F3FCF7 /* PixelKitTestingUtilities in Frameworks */, B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */, - F118EA8A2BEBBC1B00F77634 /* SubscriptionTestingUtilities in Frameworks */, - F118EA882BEBBC1B00F77634 /* Subscription in Frameworks */, B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8195,6 +8186,7 @@ isa = PBXGroup; children = ( F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, + F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */, ); path = Subscription; sourceTree = ""; @@ -8290,8 +8282,6 @@ 3706FDD8293F661700E42796 /* OHHTTPStubsSwift */, B65CD8CE2B316E0200A595BB /* SnapshotTesting */, F116A7C62BD1925500F3FCF7 /* PixelKitTestingUtilities */, - F118EA8F2BEBBC3200F77634 /* Subscription */, - F118EA912BEBBC3200F77634 /* SubscriptionTestingUtilities */, ); productName = DuckDuckGoTests; productReference = 3706FE99293F661700E42796 /* Unit Tests App Store.xctest */; @@ -8314,8 +8304,6 @@ name = "Integration Tests App Store"; packageProductDependencies = ( B65CD8D02B316E0C00A595BB /* SnapshotTesting */, - F118EA932BEBBC3900F77634 /* Subscription */, - F118EA952BEBBC3900F77634 /* SubscriptionTestingUtilities */, ); productName = "Integration Tests"; productReference = 3706FEB2293F662100E42796 /* Integration Tests App Store.xctest */; @@ -8359,8 +8347,6 @@ B6AE39F429374AEC00C37AA4 /* OHHTTPStubsSwift */, B65CD8CC2B316DFC00A595BB /* SnapshotTesting */, F116A7C82BD1929000F3FCF7 /* PixelKitTestingUtilities */, - F118EA8B2BEBBC2400F77634 /* Subscription */, - F118EA8D2BEBBC2400F77634 /* SubscriptionTestingUtilities */, ); productName = "Integration Tests"; productReference = 4B1AD89D25FC27E200261379 /* Integration Tests.xctest */; @@ -8691,8 +8677,6 @@ B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */, B65CD8CA2B316DF100A595BB /* SnapshotTesting */, F116A7C22BD1924B00F3FCF7 /* PixelKitTestingUtilities */, - F118EA872BEBBC1B00F77634 /* Subscription */, - F118EA892BEBBC1B00F77634 /* SubscriptionTestingUtilities */, ); productName = DuckDuckGoTests; productReference = AA585D90248FD31400E9A3E2 /* Unit Tests.xctest */; @@ -9730,6 +9714,7 @@ 3706FB43293F65D500E42796 /* DuckPlayer.swift in Sources */, 3706FB44293F65D500E42796 /* Favicon.swift in Sources */, 3706FB45293F65D500E42796 /* SuggestionContainerViewModel.swift in Sources */, + F1FDC92A2BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 3706FB46293F65D500E42796 /* FirePopoverWrapperViewController.swift in Sources */, 3706FB47293F65D500E42796 /* NSPasteboardItemExtension.swift in Sources */, 3706FB48293F65D500E42796 /* AutofillPreferencesModel.swift in Sources */, @@ -10616,6 +10601,7 @@ B65DA5F42A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, EE66418D2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */, EEBCA0C72BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, + F1FDC92C2BF4DFED006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 7B2E52252A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift in Sources */, B602E8232A1E260E006D261F /* Bundle+NetworkProtectionExtensions.swift in Sources */, 4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */, @@ -10655,6 +10641,7 @@ 7B4D8A232BDA857300852966 /* VPNOperationErrorRecorder.swift in Sources */, 7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */, 7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, + F1FDC92D2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */, BDA764842BC49E3F00D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, @@ -10692,6 +10679,7 @@ 7B4D8A242BDA857300852966 /* VPNOperationErrorRecorder.swift in Sources */, 4BF0E5152AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 7BFE95592A9DF2AF0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, + F1FDC92E2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, BDA764852BC49E4000D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, @@ -10746,6 +10734,7 @@ 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B4D60AC2A0C804B00BCD287 /* OptionalExtension.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, + F1FDC92B2BF4DFEC006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -11485,6 +11474,7 @@ AAC82C60258B6CB5009B6B42 /* TabPreviewWindowController.swift in Sources */, AAC5E4E425D6BA9C007F5990 /* NSSizeExtension.swift in Sources */, AA6820EB25503D6A005ED0D5 /* Fire.swift in Sources */, + F1FDC9292BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 3158B1492B0BF73000AF130C /* DBPHomeViewController.swift in Sources */, 9F56CFA92B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */, 37445F9C2A1569F00029F789 /* SyncBookmarksAdapter.swift in Sources */, @@ -13479,38 +13469,6 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = PixelKitTestingUtilities; }; - F118EA872BEBBC1B00F77634 /* Subscription */ = { - isa = XCSwiftPackageProductDependency; - productName = Subscription; - }; - F118EA892BEBBC1B00F77634 /* SubscriptionTestingUtilities */ = { - isa = XCSwiftPackageProductDependency; - productName = SubscriptionTestingUtilities; - }; - F118EA8B2BEBBC2400F77634 /* Subscription */ = { - isa = XCSwiftPackageProductDependency; - productName = Subscription; - }; - F118EA8D2BEBBC2400F77634 /* SubscriptionTestingUtilities */ = { - isa = XCSwiftPackageProductDependency; - productName = SubscriptionTestingUtilities; - }; - F118EA8F2BEBBC3200F77634 /* Subscription */ = { - isa = XCSwiftPackageProductDependency; - productName = Subscription; - }; - F118EA912BEBBC3200F77634 /* SubscriptionTestingUtilities */ = { - isa = XCSwiftPackageProductDependency; - productName = SubscriptionTestingUtilities; - }; - F118EA932BEBBC3900F77634 /* Subscription */ = { - isa = XCSwiftPackageProductDependency; - productName = Subscription; - }; - F118EA952BEBBC3900F77634 /* SubscriptionTestingUtilities */ = { - isa = XCSwiftPackageProductDependency; - productName = SubscriptionTestingUtilities; - }; F198C7112BD18A28000BF24D /* PixelKit */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4a030bca1d..37eb9e8f9a 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_refactoring_2", - "revision" : "c6ec025644e9ebf9c06a5477dc2859c6f0cc7ced" + "revision" : "823c356b7a5c047e75ae685d84364ca3d3b07abb" } }, { diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 99a9362e10..9e56d9b7fc 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -624,7 +624,7 @@ import SubscriptionUI let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - var currentEnvironment: SubscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + var currentEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) let updateServiceEnvironment: (SubscriptionEnvironment.ServiceEnvironment) -> Void = { env in currentEnvironment.serviceEnvironment = env SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) diff --git a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift new file mode 100644 index 0000000000..0f665ca4e4 --- /dev/null +++ b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift @@ -0,0 +1,48 @@ +// +// SubscriptionEnvironment+Default.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Subscription + +extension SubscriptionEnvironment { + + public static var `default`: SubscriptionEnvironment { +#if APPSTORE || !STRIPE + let platform: SubscriptionEnvironment.Platform = .appStore +#else + let platform: SubscriptionEnvironment.Platform = .stripe +#endif + +#if ALPHA || DEBUG + let environment: SubscriptionEnvironment.ServiceEnvironment = .staging +#else + let environment: SubscriptionEnvironment.ServiceEnvironment = .production +#endif + return SubscriptionEnvironment(serviceEnvironment: environment, platform: platform) + } +} + +extension SubscriptionManager { + + static public func getSavedOrDefaultEnvironment(userDefaults: UserDefaults) -> SubscriptionEnvironment { + if let savedEnvironment = loadEnvironmentFrom(userDefaults: userDefaults) { + return savedEnvironment + } + return SubscriptionEnvironment.default + } +} From 5b45f8f457e4de73231e4024949d35da419d7bd0 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 16:27:26 +0100 Subject: [PATCH 25/41] Platform renamed PurchasePlatform --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Menus/MainMenu.swift | 4 ++-- DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift | 2 +- DuckDuckGo/Preferences/Model/PreferencesSection.swift | 2 +- ...riptionFeatureAvailability+DefaultInitializer.swift | 2 +- .../Subscription/SubscriptionEnvironment+Default.swift | 6 +++--- .../Tab/Navigation/RedirectNavigationResponder.swift | 2 +- .../Subscription/SubscriptionPagesUserScript.swift | 4 ++-- .../DebugMenu/SubscriptionDebugMenu.swift | 10 +++++----- .../Preferences/PreferencesSubscriptionModel.swift | 4 ++-- .../Model/ActivateSubscriptionAccessModel.swift | 4 ++-- .../Model/ShareSubscriptionAccessModel.swift | 4 ++-- .../Sources/SubscriptionUI/UserText.swift | 4 ++-- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 37eb9e8f9a..6446163bd5 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_refactoring_2", - "revision" : "823c356b7a5c047e75ae685d84364ca3d3b07abb" + "revision" : "a3a0614439f3e2ea0ca7cec60ebbc835b4bac920" } }, { diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 9e56d9b7fc..a45a310dd8 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -638,8 +638,8 @@ import SubscriptionUI settings.selectedEnvironment = .staging } } - let updatePurchasingPlatform: (SubscriptionEnvironment.Platform) -> Void = { platform in - currentEnvironment.platform = platform + let updatePurchasingPlatform: (SubscriptionEnvironment.PurchasePlatform) -> Void = { platform in + currentEnvironment.purchasePlatform = platform SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) } diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 0d14369145..3752e3100e 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -389,7 +389,7 @@ final class MoreOptionsMenu: NSMenu { private func makeInactiveSubscriptionItems() -> [NSMenuItem] { let subscriptionManager = Application.appDelegate.subscriptionManager - let platform = subscriptionManager.currentEnvironment.platform + let platform = subscriptionManager.currentEnvironment.purchasePlatform let shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false if shouldHidePrivacyProDueToNoProducts { return [] diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index bbe5f19649..7a69e10868 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -62,7 +62,7 @@ struct PreferencesSection: Hashable, Identifiable { if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { let subscriptionManager = Application.appDelegate.subscriptionManager - let platform = subscriptionManager.currentEnvironment.platform + let platform = subscriptionManager.currentEnvironment.purchasePlatform var shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false if subscriptionManager.accountManager.isUserAuthenticated { diff --git a/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift b/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift index e9cb26ac1c..9a3312c941 100644 --- a/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift +++ b/DuckDuckGo/Subscription/DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift @@ -25,6 +25,6 @@ extension DefaultSubscriptionFeatureAvailability { convenience init() { self.init(privacyConfigurationManager: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager, - subscriptionPlatform: Application.appDelegate.subscriptionManager.currentEnvironment.platform) + subscriptionPlatform: Application.appDelegate.subscriptionManager.currentEnvironment.purchasePlatform) } } diff --git a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift index 0f665ca4e4..fdb1353662 100644 --- a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift +++ b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift @@ -23,9 +23,9 @@ extension SubscriptionEnvironment { public static var `default`: SubscriptionEnvironment { #if APPSTORE || !STRIPE - let platform: SubscriptionEnvironment.Platform = .appStore + let platform: SubscriptionEnvironment.PurchasePlatform = .appStore #else - let platform: SubscriptionEnvironment.Platform = .stripe + let platform: SubscriptionEnvironment.PurchasePlatform = .stripe #endif #if ALPHA || DEBUG @@ -33,7 +33,7 @@ extension SubscriptionEnvironment { #else let environment: SubscriptionEnvironment.ServiceEnvironment = .production #endif - return SubscriptionEnvironment(serviceEnvironment: environment, platform: platform) + return SubscriptionEnvironment(serviceEnvironment: environment, purchasePlatform: platform) } } diff --git a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift index a6cf094e53..fbe704c5e9 100644 --- a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift +++ b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift @@ -39,7 +39,7 @@ struct RedirectNavigationResponder: NavigationResponder { if url.pathComponents == URL.privacyPro.pathComponents { let isFeatureAvailable = DefaultSubscriptionFeatureAvailability().isFeatureAvailable let subscriptionManager = Application.appDelegate.subscriptionManager - let platform = subscriptionManager.currentEnvironment.platform + let platform = subscriptionManager.currentEnvironment.purchasePlatform let shouldHidePrivacyProDueToNoProducts = platform == .appStore && subscriptionManager.canPurchase == false let isPurchasePageRedirectActive = isFeatureAvailable && !shouldHidePrivacyProDueToNoProducts let url = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index d9fa34bb88..c5192520ba 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -86,7 +86,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } let subscriptionManager: SubscriptionManaging var accountManager: AccountManaging { subscriptionManager.accountManager } - var subscriptionPlatform: SubscriptionEnvironment.Platform { subscriptionManager.currentEnvironment.platform } + var subscriptionPlatform: SubscriptionEnvironment.PurchasePlatform { subscriptionManager.currentEnvironment.purchasePlatform } let stripePurchaseFlow: StripePurchaseFlow let subscriptionErrorReporter = SubscriptionErrorReporter() @@ -220,7 +220,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { let message = original - if subscriptionManager.currentEnvironment.platform == .appStore { + if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, *) { let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController let progressViewController = await ProgressViewController(title: UserText.purchasingSubscriptionTitle) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift index e78b58ba8e..fd16741a1d 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift @@ -23,7 +23,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { var currentEnvironment: SubscriptionEnvironment var updateServiceEnvironment: (SubscriptionEnvironment.ServiceEnvironment) -> Void - var updatePurchasingPlatform: (SubscriptionEnvironment.Platform) -> Void + var updatePurchasingPlatform: (SubscriptionEnvironment.PurchasePlatform) -> Void var isInternalTestingEnabled: () -> Bool var updateInternalTestingFlag: (Bool) -> Void @@ -52,7 +52,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { public init(currentEnvironment: SubscriptionEnvironment, updateServiceEnvironment: @escaping (SubscriptionEnvironment.ServiceEnvironment) -> Void, - updatePurchasingPlatform: @escaping (SubscriptionEnvironment.Platform) -> Void, + updatePurchasingPlatform: @escaping (SubscriptionEnvironment.PurchasePlatform) -> Void, isInternalTestingEnabled: @escaping () -> Bool, updateInternalTestingFlag: @escaping (Bool) -> Void, currentViewController: @escaping () -> NSViewController?, @@ -111,7 +111,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { private func makePurchasePlatformSubmenu() -> NSMenu { let menu = NSMenu(title: "Select purchase platform:") let appStoreItem = NSMenuItem(title: "App Store", action: #selector(setPlatformToAppStore), target: self) - if currentEnvironment.platform == .appStore { + if currentEnvironment.purchasePlatform == .appStore { appStoreItem.state = .on appStoreItem.isEnabled = false appStoreItem.action = nil @@ -120,7 +120,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { menu.addItem(appStoreItem) let stripeItem = NSMenuItem(title: "Stripe", action: #selector(setPlatformToStripe), target: self) - if currentEnvironment.platform == .stripe { + if currentEnvironment.purchasePlatform == .stripe { stripeItem.state = .on stripeItem.isEnabled = false stripeItem.action = nil @@ -259,7 +259,7 @@ public final class SubscriptionDebugMenu: NSMenuItem { askAndUpdatePlatform(to: .stripe) } - private func askAndUpdatePlatform(to newPlatform: SubscriptionEnvironment.Platform) { + private func askAndUpdatePlatform(to newPlatform: SubscriptionEnvironment.PurchasePlatform) { let alert = makeAlert(title: "Are you sure you want to change the purchase platform to \(newPlatform.rawValue.capitalized)", message: "This setting IS persisted between app runs. This action will close the app, do you want to proceed?", buttonNames: ["Yes", "No"]) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index a6b8f05e32..78cc51846c 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -182,7 +182,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { } } - private func changePlanOrBilling(for environment: SubscriptionEnvironment.Platform) { + private func changePlanOrBilling(for environment: SubscriptionEnvironment.PurchasePlatform) { switch environment { case .appStore: NSWorkspace.shared.open(SubscriptionURL.manageSubscriptionsInAppStore.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) @@ -239,7 +239,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func refreshSubscriptionPendingState() { - if subscriptionManager.currentEnvironment.platform == .appStore { + if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, *) { Task { let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift index ba98b21821..e8ab91c642 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift @@ -39,9 +39,9 @@ public final class ActivateSubscriptionAccessModel: SubscriptionAccessModel, Pur public init(actionHandlers: SubscriptionAccessActionHandlers, subscriptionEnvironment: SubscriptionEnvironment) { self.actionHandlers = actionHandlers - self.shouldShowRestorePurchase = subscriptionEnvironment.platform == .appStore + self.shouldShowRestorePurchase = subscriptionEnvironment.purchasePlatform == .appStore self.subscriptionEnvironment = subscriptionEnvironment - self.description = UserText.activateModalDescription(platform: subscriptionEnvironment.platform) + self.description = UserText.activateModalDescription(platform: subscriptionEnvironment.purchasePlatform) } public func handleEmailAction() { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index 768e428192..def20d0bc1 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -34,7 +34,7 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { self.actionHandlers = actionHandlers self.email = email self.subscriptionManager = subscriptionManager - self.description = UserText.shareModalDescription(platform: subscriptionManager.currentEnvironment.platform) + self.description = UserText.shareModalDescription(platform: subscriptionManager.currentEnvironment.purchasePlatform) } private var hasEmail: Bool { !(email?.isEmpty ?? true) } @@ -50,7 +50,7 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { } Task { - if subscriptionManager.currentEnvironment.platform == .appStore { + if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { if #available(macOS 12.0, iOS 15.0, *) { let appStoreAccountManagementFlow = AppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift index ec45b575ca..5e470bf2f8 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift @@ -101,7 +101,7 @@ enum UserText { // MARK: - Activate subscription modal static let activateModalTitle = NSLocalizedString("subscription.activate.modal.title", value: "Activate your subscription on this device", comment: "Activate subscription modal view title") - static func activateModalDescription(platform: SubscriptionEnvironment.Platform) -> String { + static func activateModalDescription(platform: SubscriptionEnvironment.PurchasePlatform) -> String { switch platform { case .appStore: NSLocalizedString("subscription.appstore.activate.modal.description", value: "Access your Privacy Pro subscription on this device via Apple ID or an email address.", comment: "Activate subscription modal view subtitle description") @@ -115,7 +115,7 @@ enum UserText { // MARK: - Share subscription modal static let shareModalTitle = NSLocalizedString("subscription.share.modal.title", value: "Use your subscription on other devices", comment: "Share subscription modal view title") - static func shareModalDescription(platform: SubscriptionEnvironment.Platform) -> String { + static func shareModalDescription(platform: SubscriptionEnvironment.PurchasePlatform) -> String { switch platform { case .appStore: NSLocalizedString("subscription.appstore.share.modal.description", value: "Access your subscription via Apple ID or by adding an email address.", comment: "Share subscription modal view subtitle description") From b6faadc87e2ea1c4af5f227789e8de838c0984b1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 18:05:59 +0100 Subject: [PATCH 26/41] VPNSettings environment is now aligned with subscription environment --- DuckDuckGo.xcodeproj/project.pbxproj | 14 ++++++++ .../xcshareddata/swiftpm/Package.resolved | 4 +-- DuckDuckGo/Application/AppDelegate.swift | 5 +++ DuckDuckGo/Menus/MainMenu.swift | 9 ++--- ...rkProtection+ConvenienceInitializers.swift | 6 ++-- .../NetworkProtectionDebugMenu.swift | 4 ++- ...etworkProtectionNavBarPopoverManager.swift | 2 +- .../VPNLocation/VPNLocationViewModel.swift | 2 +- .../VPNSettings+Environment.swift | 34 +++++++++++++++++++ DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 19 ++++++----- 10 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 DuckDuckGo/Subscription/VPNSettings+Environment.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1e912288b4..295c717b4b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2608,6 +2608,12 @@ F1FDC92C2BF4DFED006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; F1FDC92D2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; F1FDC92E2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC9382BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; + F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; + F1FDC93A2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; + F1FDC93B2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; + F1FDC93C2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; + F1FDC93D2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F41D174125CB131900472416 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; F44C130225C2DA0400426E3E /* NSAppearanceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */; }; F4A6198C283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */; }; @@ -4074,6 +4080,7 @@ F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; + F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VPNSettings+Environment.swift"; sourceTree = ""; }; F41D174025CB131900472416 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = ""; }; F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearanceExtension.swift; sourceTree = ""; }; F4A6198B283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScopeFeatureFlagging.swift; sourceTree = ""; }; @@ -8185,6 +8192,7 @@ F118EA7B2BEA2B8700F77634 /* Subscription */ = { isa = PBXGroup; children = ( + F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */, ); @@ -9706,6 +9714,7 @@ 3706FB3D293F65D500E42796 /* FocusRingView.swift in Sources */, 3706FB3E293F65D500E42796 /* BookmarksBarViewModel.swift in Sources */, 3706FB3F293F65D500E42796 /* NSPopUpButtonView.swift in Sources */, + F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */, 3706FB40293F65D500E42796 /* ContextualMenu.swift in Sources */, 3706FB41293F65D500E42796 /* NavigationBarViewController.swift in Sources */, @@ -10607,6 +10616,7 @@ 4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */, B602E8192A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, 4B2D06322A11C1D300DE1F49 /* NSApplicationExtension.swift in Sources */, + F1FDC93B2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 4B2D06332A11C1E300DE1F49 /* OptionalExtension.swift in Sources */, 4BF0E50B2AD2552200FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B41EDA12B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift in Sources */, @@ -10651,6 +10661,7 @@ 4BF0E5072AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 7BA7CC442AD11E490042E5CE /* UserText.swift in Sources */, 4BF0E5142AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, + F1FDC93C2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 7BA7CC422AD11E420042E5CE /* NetworkProtectionBouncer.swift in Sources */, B65DA5F12A77D2BC00CBEE8D /* BundleExtension.swift in Sources */, ); @@ -10689,6 +10700,7 @@ 7BA7CC552AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, 7BA7CC3D2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, 4BA7C4DA2B3F639800AFE511 /* NetworkProtectionTunnelController.swift in Sources */, + F1FDC93D2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 7BA7CC432AD11E480042E5CE /* UserText.swift in Sources */, 7BA7CC542AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */, ); @@ -10735,6 +10747,7 @@ 4B4D60AC2A0C804B00BCD287 /* OptionalExtension.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, F1FDC92B2BF4DFEC006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, + F1FDC93A2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -11405,6 +11418,7 @@ 858A797F26A79EAA00A75A42 /* UserText+PasswordManager.swift in Sources */, B693954E26F04BEB0015B914 /* LoadingProgressView.swift in Sources */, B69B503C2726A12500758A2B /* StatisticsStore.swift in Sources */, + F1FDC9382BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 3158B14A2B0BF74300AF130C /* DataBrokerProtectionDebugMenu.swift in Sources */, 4BBDEE9128FC14760092FAA6 /* BWInstallationService.swift in Sources */, 7B4D8A212BDA857300852966 /* VPNOperationErrorRecorder.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 97deaa6303..6446163bd5 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "bb8e7e62104ed6506c7bfd3ef7aa4aca3686ed4f", - "version" : "5.15.0" + "revision" : "1bb3bc5eb565735051f342a87b5405d4374876c7", + "version" : "5.12.0" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 924dff179e..6eac679ecd 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -89,6 +89,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { subscriptionManager.accountManager } public let subscriptionManager: SubscriptionManaging + public let vpnSettings = VPNSettings(defaults: .netP) + private var networkProtectionSubscriptionEventHandler: NetworkProtectionSubscriptionEventHandler? #if DBP private var dataBrokerProtectionSubscriptionEventHandler: DataBrokerProtectionSubscriptionEventHandler? @@ -184,6 +186,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + + vpnSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index a45a310dd8..bb46395d7f 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -630,13 +630,8 @@ import SubscriptionUI SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) // The VPN environment is forced to match the Subscription environment - let settings = VPNSettings(defaults: .netP) - switch env { - case .production: - settings.selectedEnvironment = .production - case .staging: - settings.selectedEnvironment = .staging - } + let settings = Application.appDelegate.vpnSettings + settings.alignTo(subscriptionEnvironment: currentEnvironment) } let updatePurchasingPlatform: (SubscriptionEnvironment.PurchasePlatform) -> Void = { platform in currentEnvironment.purchasePlatform = platform diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index ec17fc8ca4..f48a2c97fb 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -27,7 +27,7 @@ extension NetworkProtectionDeviceManager { @MainActor static func create() -> NetworkProtectionDeviceManager { - let settings = VPNSettings(defaults: .netP) + let settings = Application.appDelegate.vpnSettings let keyStore = NetworkProtectionKeychainKeyStore() let tokenStore = NetworkProtectionKeychainTokenStore() return NetworkProtectionDeviceManager(environment: settings.selectedEnvironment, @@ -40,7 +40,7 @@ extension NetworkProtectionDeviceManager { extension NetworkProtectionCodeRedemptionCoordinator { convenience init() { - let settings = VPNSettings(defaults: .netP) + let settings = Application.appDelegate.vpnSettings self.init(environment: settings.selectedEnvironment, tokenStore: NetworkProtectionKeychainTokenStore(), errorEvents: .networkProtectionAppDebugEvents, @@ -71,7 +71,7 @@ extension NetworkProtectionKeychainKeyStore { extension NetworkProtectionLocationListCompositeRepository { convenience init() { - let settings = VPNSettings(defaults: .netP) + let settings = Application.appDelegate.vpnSettings self.init( environment: settings.selectedEnvironment, tokenStore: NetworkProtectionKeychainTokenStore(), diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 6bd3ff3ee4..930c5ee6bf 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -168,7 +168,9 @@ final class NetworkProtectionDebugMenu: NSMenu { // MARK: - Tunnel Settings - private let settings = VPNSettings(defaults: .netP) + private var settings: VPNSettings { + Application.appDelegate.vpnSettings + } // MARK: - Debug Logic diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index 82cd98ab95..b372b5f13a 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -73,7 +73,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { ) let onboardingStatusPublisher = UserDefaults.netP.networkProtectionOnboardingStatusPublisher - _ = VPNSettings(defaults: .netP) +// _ = VPNSettings(defaults: .netP) let appLauncher = AppLauncher(appBundleURL: Bundle.main.bundleURL) let popover = NetworkProtectionPopover(controller: controller, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift index e2e26d0e67..3f9f0f86e4 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift @@ -190,7 +190,7 @@ extension VPNLocationViewModel { let locationListRepository = NetworkProtectionLocationListCompositeRepository() self.init( locationListRepository: locationListRepository, - settings: VPNSettings(defaults: .netP) + settings: Application.appDelegate.vpnSettings ) } } diff --git a/DuckDuckGo/Subscription/VPNSettings+Environment.swift b/DuckDuckGo/Subscription/VPNSettings+Environment.swift new file mode 100644 index 0000000000..4e046637ed --- /dev/null +++ b/DuckDuckGo/Subscription/VPNSettings+Environment.swift @@ -0,0 +1,34 @@ +// +// VPNSettings+Environment.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NetworkProtection +import Subscription + +public extension VPNSettings { + + /// Align VPN environment to the Subscription environment + func alignTo(subscriptionEnvironment: SubscriptionEnvironment) { + switch subscriptionEnvironment.serviceEnvironment { + case .production: + self.selectedEnvironment = .production + case .staging: + self.selectedEnvironment = .staging + } + } +} diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 7af92a624b..d4ae0b41bd 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -45,7 +45,6 @@ final class DuckDuckGoVPNApplication: NSApplication { } // MARK: - Configure Subscription - let settings = VPNSettings(defaults: UserDefaults.netP) let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) @@ -56,12 +55,13 @@ final class DuckDuckGoVPNApplication: NSApplication { settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) accountManager = AccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionService: subscriptionService, - authService: authService) - - _delegate = DuckDuckGoVPNAppDelegate(bouncer: NetworkProtectionBouncer(accountManager: accountManager), accountManager: accountManager) + entitlementsCache: entitlementsCache, + subscriptionService: subscriptionService, + authService: authService) + _delegate = DuckDuckGoVPNAppDelegate(bouncer: NetworkProtectionBouncer(accountManager: accountManager), + accountManager: accountManager, + subscriptionEnvironment: subscriptionEnvironment) super.init() self.delegate = _delegate @@ -89,9 +89,12 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private let accountManager: AccountManaging public init(bouncer: NetworkProtectionBouncer, - accountManager: AccountManaging) { + accountManager: AccountManaging, + subscriptionEnvironment: SubscriptionEnvironment) { self.bouncer = bouncer self.accountManager = accountManager + self.tunnelSettings = VPNSettings(defaults: .netP) + self.tunnelSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) } private var cancellables = Set() @@ -114,7 +117,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { #endif } - private lazy var tunnelSettings = VPNSettings(defaults: .netP) + private let tunnelSettings: VPNSettings private lazy var userDefaults = UserDefaults.netP private lazy var proxySettings = TransparentProxySettings(defaults: .netP) From d5e6f54374093baca2150e79bb97e95ef9e92f2d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 18:31:28 +0100 Subject: [PATCH 27/41] BSK updated --- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6446163bd5..267da7c741 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_refactoring_2", - "revision" : "a3a0614439f3e2ea0ca7cec60ebbc835b4bac920" + "revision" : "91191b597f623203b4e5d9ca9ba52e6f3bfcff8f" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "1bb3bc5eb565735051f342a87b5405d4374876c7", - "version" : "5.12.0" + "revision" : "bb8e7e62104ed6506c7bfd3ef7aa4aca3686ed4f", + "version" : "5.15.0" } }, { From e02eb69b33018e451cd17538f26d25cb88eb9808 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 11:25:21 +0100 Subject: [PATCH 28/41] lint and unit tests fixed --- DuckDuckGo.xcodeproj/project.pbxproj | 32 +++++++++++++++++++ .../SubscriptionRedirectManager.swift | 15 +++++---- DuckDuckGo/Tab/Model/Tab+Navigation.swift | 10 +++++- .../RedirectNavigationResponder.swift | 2 +- .../SubscriptionRedirectManagerTests.swift | 12 ++++--- 5 files changed, 59 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0b5ec16be2..4f1fcc32a0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2622,6 +2622,10 @@ F1DA51972BF6083A00CF29FA /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; F1DA51982BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; F1DA51992BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + F1DA51A32BF6114200CF29FA /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F1DA51A22BF6114200CF29FA /* Subscription */; }; + F1DA51A52BF6114200CF29FA /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F1DA51A42BF6114200CF29FA /* SubscriptionTestingUtilities */; }; + F1DA51A72BF6114B00CF29FA /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F1DA51A62BF6114B00CF29FA /* Subscription */; }; + F1DA51A92BF6114C00CF29FA /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F1DA51A82BF6114C00CF29FA /* SubscriptionTestingUtilities */; }; F1DF95E32BD1807C0045E591 /* Crashes in Frameworks */ = {isa = PBXBuildFile; productRef = 08D4923DC968236E22E373E2 /* Crashes */; }; F1DF95E42BD1807C0045E591 /* Crashes in Frameworks */ = {isa = PBXBuildFile; productRef = 537FC71EA5115A983FAF3170 /* Crashes */; }; F1DF95E52BD1807C0045E591 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = DC3F73D49B2D44464AFEFCD8 /* Subscription */; }; @@ -4165,6 +4169,8 @@ 3706FE88293F661700E42796 /* OHHTTPStubs in Frameworks */, F116A7C72BD1925500F3FCF7 /* PixelKitTestingUtilities in Frameworks */, B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */, + F1DA51A52BF6114200CF29FA /* SubscriptionTestingUtilities in Frameworks */, + F1DA51A32BF6114200CF29FA /* Subscription in Frameworks */, 3706FE89293F661700E42796 /* OHHTTPStubsSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4359,6 +4365,8 @@ B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */, F116A7C32BD1924B00F3FCF7 /* PixelKitTestingUtilities in Frameworks */, B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */, + F1DA51A92BF6114C00CF29FA /* SubscriptionTestingUtilities in Frameworks */, + F1DA51A72BF6114B00CF29FA /* Subscription in Frameworks */, B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8350,6 +8358,8 @@ 3706FDD8293F661700E42796 /* OHHTTPStubsSwift */, B65CD8CE2B316E0200A595BB /* SnapshotTesting */, F116A7C62BD1925500F3FCF7 /* PixelKitTestingUtilities */, + F1DA51A22BF6114200CF29FA /* Subscription */, + F1DA51A42BF6114200CF29FA /* SubscriptionTestingUtilities */, ); productName = DuckDuckGoTests; productReference = 3706FE99293F661700E42796 /* Unit Tests App Store.xctest */; @@ -8745,6 +8755,8 @@ B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */, B65CD8CA2B316DF100A595BB /* SnapshotTesting */, F116A7C22BD1924B00F3FCF7 /* PixelKitTestingUtilities */, + F1DA51A62BF6114B00CF29FA /* Subscription */, + F1DA51A82BF6114C00CF29FA /* SubscriptionTestingUtilities */, ); productName = DuckDuckGoTests; productReference = AA585D90248FD31400E9A3E2 /* Unit Tests.xctest */; @@ -13617,6 +13629,26 @@ package = F1D43AF12B98E47800BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */; productName = BareBonesBrowserKit; }; + F1DA51A22BF6114200CF29FA /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; + F1DA51A42BF6114200CF29FA /* SubscriptionTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = SubscriptionTestingUtilities; + }; + F1DA51A62BF6114B00CF29FA /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; + F1DA51A82BF6114C00CF29FA /* SubscriptionTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = SubscriptionTestingUtilities; + }; F1DF95E62BD188B60045E591 /* LoginItems */ = { isa = XCSwiftPackageProductDependency; productName = LoginItems; diff --git a/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift b/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift index d259691620..d2d584f0ef 100644 --- a/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift +++ b/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift @@ -25,14 +25,17 @@ protocol SubscriptionRedirectManager: AnyObject { } final class PrivacyProSubscriptionRedirectManager: SubscriptionRedirectManager { - + private let featureAvailabiltyProvider: () -> Bool - private let subscriptionManager: SubscriptionManaging + private let subscriptionEnvironment: SubscriptionEnvironment + private let canPurchase: () -> Bool init(featureAvailabiltyProvider: @escaping @autoclosure () -> Bool = DefaultSubscriptionFeatureAvailability().isFeatureAvailable, - subscriptionManager: SubscriptionManaging) { + subscriptionEnvironment: SubscriptionEnvironment, + canPurchase: @escaping () -> Bool) { self.featureAvailabiltyProvider = featureAvailabiltyProvider - self.subscriptionManager = subscriptionManager + self.subscriptionEnvironment = subscriptionEnvironment + self.canPurchase = canPurchase } func redirectURL(for url: URL) -> URL? { @@ -40,10 +43,10 @@ final class PrivacyProSubscriptionRedirectManager: SubscriptionRedirectManager { if url.pathComponents == URL.privacyPro.pathComponents { let isFeatureAvailable = featureAvailabiltyProvider() - let shouldHidePrivacyProDueToNoProducts = subscriptionManager.currentEnvironment.purchasePlatform == .appStore && subscriptionManager.canPurchase == false + let shouldHidePrivacyProDueToNoProducts = subscriptionEnvironment.purchasePlatform == .appStore && canPurchase() == false let isPurchasePageRedirectActive = isFeatureAvailable && !shouldHidePrivacyProDueToNoProducts // Redirect the `/pro` URL to `/subscriptions` URL. If there are any query items in the original URL it appends to the `/subscriptions` URL. - let baseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let baseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment) return isPurchasePageRedirectActive ? baseURL.addingQueryItems(from: url) : nil } diff --git a/DuckDuckGo/Tab/Model/Tab+Navigation.swift b/DuckDuckGo/Tab/Model/Tab+Navigation.swift index 2b86d68118..b35225a2e2 100644 --- a/DuckDuckGo/Tab/Model/Tab+Navigation.swift +++ b/DuckDuckGo/Tab/Model/Tab+Navigation.swift @@ -71,7 +71,7 @@ extension Tab: NavigationResponder { // add extra headers to SERP requests .struct(SerpHeadersNavigationResponder()), - .struct(RedirectNavigationResponder()), + .struct(redirectNavigationResponder), // ensure Content Blocking Rules are applied before navigation .weak(nullable: self.contentBlockingAndSurrogates), @@ -107,4 +107,12 @@ extension Tab: NavigationResponder { } } + var redirectNavigationResponder: RedirectNavigationResponder { + let subscriptionManager = Application.appDelegate.subscriptionManager + let redirectManager = PrivacyProSubscriptionRedirectManager(subscriptionEnvironment: subscriptionManager.currentEnvironment, canPurchase: { + subscriptionManager.canPurchase + }) + return RedirectNavigationResponder(redirectManager: redirectManager) + } + } diff --git a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift index 00db1a239d..5e57ccab46 100644 --- a/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift +++ b/DuckDuckGo/Tab/Navigation/RedirectNavigationResponder.swift @@ -23,7 +23,7 @@ struct RedirectNavigationResponder: NavigationResponder { private let redirectManager: SubscriptionRedirectManager - init(redirectManager: SubscriptionRedirectManager = PrivacyProSubscriptionRedirectManager(subscriptionManager: Application.appDelegate.subscriptionManager)) { + init(redirectManager: SubscriptionRedirectManager) { self.redirectManager = redirectManager } diff --git a/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift b/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift index 814abc7a42..d739d34819 100644 --- a/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift +++ b/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift @@ -18,6 +18,7 @@ import XCTest import Subscription +import SubscriptionTestingUtilities @testable import DuckDuckGo_Privacy_Browser final class SubscriptionRedirectManagerTests: XCTestCase { @@ -25,8 +26,10 @@ final class SubscriptionRedirectManagerTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() - sut = PrivacyProSubscriptionRedirectManager(featureAvailabiltyProvider: true) - SubscriptionPurchaseEnvironment.canPurchase = true + sut = PrivacyProSubscriptionRedirectManager(featureAvailabiltyProvider: true, + subscriptionEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, + purchasePlatform: .appStore), + canPurchase: { true }) } override func tearDownWithError() throws { @@ -37,7 +40,8 @@ final class SubscriptionRedirectManagerTests: XCTestCase { func testWhenURLIsPrivacyProAndHasOriginQueryParameterThenRedirectToSubscriptionBaseURLAndAppendQueryParameter() throws { // GIVEN let url = try XCTUnwrap(URL(string: "https://www.duckduckgo.com/pro?origin=test")) - let expectedURL = URL.subscriptionBaseURL.appending(percentEncodedQueryItem: .init(name: "origin", value: "test")) + let baseURL = SubscriptionURL.baseURL.subscriptionURL(environment: .production) + let expectedURL = baseURL.appending(percentEncodedQueryItem: .init(name: "origin", value: "test")) // WHEN let result = sut.redirectURL(for: url) @@ -49,7 +53,7 @@ final class SubscriptionRedirectManagerTests: XCTestCase { func testWhenURLIsPrivacyProAndDoesNotHaveOriginQueryParameterThenRedirectToSubscriptionBaseURL() throws { // GIVEN let url = try XCTUnwrap(URL(string: "https://www.duckduckgo.com/pro")) - let expectedURL = URL.subscriptionBaseURL + let expectedURL = SubscriptionURL.baseURL.subscriptionURL(environment: .production) // WHEN let result = sut.redirectURL(for: url) From 605bfcf835d1984680dbc2fc371c9fd33650b809 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 14:35:01 +0100 Subject: [PATCH 29/41] BSK update and renaming --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../Preferences/PreferencesSubscriptionModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 09d23d7b4d..b3113f8832 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_refactoring_2", - "revision" : "46ef62f6dffcc7911913aa0ff5bae470c5b512c6" + "revision" : "dae75b94bb5e707ba945e2fc948a9b5f3a9aca03" } }, { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 78cc51846c..0f22c281ab 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -234,7 +234,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func openFAQ() { - openURLHandler( SubscriptionURL.FAQ.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) + openURLHandler( SubscriptionURL.faq.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) } @MainActor From ea24db9cc0b3a7053f4397c9fbb6dc2b94a984b7 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 17:25:22 +0100 Subject: [PATCH 30/41] various improvements suggested in the PR --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Application/URLEventHandler.swift | 2 +- .../NavigationBar/View/AddressBarTextField.swift | 10 +++++----- .../View/NavigationBarViewController.swift | 4 ++-- .../Preferences/View/PreferencesRootView.swift | 2 +- .../Subscription/SubscriptionRedirectManager.swift | 4 +++- DuckDuckGo/Tab/Model/Tab+Navigation.swift | 4 +++- DuckDuckGo/Tab/Model/TabContent.swift | 4 ++-- .../Subscription/SubscriptionAppStoreRestorer.swift | 6 +++--- .../Subscription/SubscriptionPagesUserScript.swift | 10 +++++----- .../Preferences/PreferencesSubscriptionModel.swift | 10 +++++----- .../Model/ActivateSubscriptionAccessModel.swift | 12 ++++++------ .../Model/ShareSubscriptionAccessModel.swift | 2 +- .../SubscriptionAccessViewController.swift | 2 +- .../SubscriptionRedirectManagerTests.swift | 3 ++- UnitTests/TabBar/View/TabBarViewItemTests.swift | 3 +-- 16 files changed, 42 insertions(+), 38 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b3113f8832..74b262fa14 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_refactoring_2", - "revision" : "dae75b94bb5e707ba945e2fc948a9b5f3a9aca03" + "revision" : "0335dcb7559e12f3ab7b77951d04400b4771b66d" } }, { diff --git a/DuckDuckGo/Application/URLEventHandler.swift b/DuckDuckGo/Application/URLEventHandler.swift index 9f42802cbd..723fbc2123 100644 --- a/DuckDuckGo/Application/URLEventHandler.swift +++ b/DuckDuckGo/Application/URLEventHandler.swift @@ -159,7 +159,7 @@ final class URLEventHandler { WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn) WindowControllersManager.shared.showLocationPickerSheet() case AppLaunchCommand.showPrivacyPro.launchURL: - let url = SubscriptionURL.purchase.subscriptionURL(environment: Application.appDelegate.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = Application.appDelegate.subscriptionManager.url(for: .purchase) WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) #if !APPSTORE && !DEBUG diff --git a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift index a5ad51a8dc..6461fb5429 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift @@ -71,9 +71,9 @@ final class AddressBarTextField: NSTextField { // flag when updating the Value from `handleTextDidChange()` private var currentTextDidChangeEvent: TextDidChangeEventType = .none - var subscriptionEnvironment: SubscriptionEnvironment { - Application.appDelegate.subscriptionManager.currentEnvironment - } +// var subscriptionEnvironment: SubscriptionEnvironment { +// Application.appDelegate.subscriptionManager.currentEnvironment +// } // MARK: - Lifecycle @@ -376,8 +376,8 @@ final class AddressBarTextField: NSTextField { #endif if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - let baseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment) - let identityTheftRestorationURL = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment) + let baseURL = Application.appDelegate.subscriptionManager.url(for: .baseURL) + let identityTheftRestorationURL = Application.appDelegate.subscriptionManager.url(for: .identityTheftRestoration) if providedUrl.isChild(of: baseURL) || providedUrl.isChild(of: identityTheftRestorationURL) { self.updateValue(selectedTabViewModel: nil, addressBarString: nil) // reset self.window?.makeFirstResponder(nil) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 40380bc8e1..cbc10a2d8b 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -1034,13 +1034,13 @@ extension NavigationBarViewController: OptionsButtonMenuDelegate { } func optionsButtonMenuRequestedSubscriptionPurchasePage(_ menu: NSMenu) { - let url = SubscriptionURL.purchase.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let url = subscriptionManager.url(for: .purchase) WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) } func optionsButtonMenuRequestedIdentityTheftRestoration(_ menu: NSMenu) { - let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let url = subscriptionManager.url(for: .identityTheftRestoration) WindowControllersManager.shared.showTab(with: .identityTheftRestoration(url)) } } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index ba4f085d45..1145d2c915 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -137,7 +137,7 @@ enum Preferences { WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .openITR: PixelKit.fire(PrivacyProPixel.privacyProIdentityRestorationSettings) - let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: Application.appDelegate.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = Application.appDelegate.subscriptionManager.url(for: .identityTheftRestoration) WindowControllersManager.shared.showTab(with: .identityTheftRestoration(url)) case .iHaveASubscriptionClick: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseClick) diff --git a/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift b/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift index d2d584f0ef..2e9c3d1f62 100644 --- a/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift +++ b/DuckDuckGo/Subscription/SubscriptionRedirectManager.swift @@ -29,13 +29,16 @@ final class PrivacyProSubscriptionRedirectManager: SubscriptionRedirectManager { private let featureAvailabiltyProvider: () -> Bool private let subscriptionEnvironment: SubscriptionEnvironment private let canPurchase: () -> Bool + private let baseURL: URL init(featureAvailabiltyProvider: @escaping @autoclosure () -> Bool = DefaultSubscriptionFeatureAvailability().isFeatureAvailable, subscriptionEnvironment: SubscriptionEnvironment, + baseURL: URL, canPurchase: @escaping () -> Bool) { self.featureAvailabiltyProvider = featureAvailabiltyProvider self.subscriptionEnvironment = subscriptionEnvironment self.canPurchase = canPurchase + self.baseURL = baseURL } func redirectURL(for url: URL) -> URL? { @@ -46,7 +49,6 @@ final class PrivacyProSubscriptionRedirectManager: SubscriptionRedirectManager { let shouldHidePrivacyProDueToNoProducts = subscriptionEnvironment.purchasePlatform == .appStore && canPurchase() == false let isPurchasePageRedirectActive = isFeatureAvailable && !shouldHidePrivacyProDueToNoProducts // Redirect the `/pro` URL to `/subscriptions` URL. If there are any query items in the original URL it appends to the `/subscriptions` URL. - let baseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment) return isPurchasePageRedirectActive ? baseURL.addingQueryItems(from: url) : nil } diff --git a/DuckDuckGo/Tab/Model/Tab+Navigation.swift b/DuckDuckGo/Tab/Model/Tab+Navigation.swift index b35225a2e2..5d5ac5606f 100644 --- a/DuckDuckGo/Tab/Model/Tab+Navigation.swift +++ b/DuckDuckGo/Tab/Model/Tab+Navigation.swift @@ -109,7 +109,9 @@ extension Tab: NavigationResponder { var redirectNavigationResponder: RedirectNavigationResponder { let subscriptionManager = Application.appDelegate.subscriptionManager - let redirectManager = PrivacyProSubscriptionRedirectManager(subscriptionEnvironment: subscriptionManager.currentEnvironment, canPurchase: { + let redirectManager = PrivacyProSubscriptionRedirectManager(subscriptionEnvironment: subscriptionManager.currentEnvironment, + baseURL: subscriptionManager.url(for: .baseURL), + canPurchase: { subscriptionManager.canPurchase }) return RedirectNavigationResponder(redirectManager: redirectManager) diff --git a/DuckDuckGo/Tab/Model/TabContent.swift b/DuckDuckGo/Tab/Model/TabContent.swift index 43db82985c..b213cc2ba5 100644 --- a/DuckDuckGo/Tab/Model/TabContent.swift +++ b/DuckDuckGo/Tab/Model/TabContent.swift @@ -119,8 +119,8 @@ extension TabContent { if let url { let subscriptionManager = Application.appDelegate.subscriptionManager let environment = subscriptionManager.currentEnvironment.serviceEnvironment - let subscriptionBaseURL = SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) - let identityTheftRestorationURL = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let subscriptionBaseURL = subscriptionManager.url(for: .baseURL) + let identityTheftRestorationURL = subscriptionManager.url(for: .identityTheftRestoration) if url.isChild(of: subscriptionBaseURL) { if environment == .staging, url.getParameter(named: "environment") == nil { return .subscription(url.appendingParameter(name: "environment", value: "staging")) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 5f55d9e441..68a02e6372 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -47,7 +47,7 @@ struct SubscriptionAppStoreRestorer { mainViewController.presentAsSheet(progressViewController) } - let syncResult = await subscriptionManager.getStorePurchaseManager().syncAppleIDAccount() + let syncResult = await subscriptionManager.storePurchaseManager().syncAppleIDAccount() switch syncResult { case .success: @@ -122,7 +122,7 @@ extension SubscriptionAppStoreRestorer { guard let window else { return } window.show(.subscriptionNotFoundAlert(), firstButtonAction: { - let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = subscriptionManager.url(for: .purchase) WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) @@ -133,7 +133,7 @@ extension SubscriptionAppStoreRestorer { guard let window else { return } window.show(.subscriptionInactiveAlert(), firstButtonAction: { - let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = subscriptionManager.url(for: .purchase) WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index d3ac7e72a3..32f819fead 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -199,7 +199,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch subscriptionPlatform { case .appStore: if #available(macOS 12.0, *) { - return await subscriptionManager.getStorePurchaseManager().subscriptionOptions() + return await subscriptionManager.storePurchaseManager().subscriptionOptions() } case .stripe: switch await stripePurchaseFlow.subscriptionOptions() { @@ -246,7 +246,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await mainViewController?.presentAsSheet(progressViewController) // Check for active subscriptions - if await subscriptionManager.getStorePurchaseManager().hasActiveSubscription() { + if await subscriptionManager.storePurchaseManager().hasActiveSubscription() { PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") subscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) @@ -420,7 +420,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .identityTheftRestoration: PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) - let url = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let url = subscriptionManager.url(for: .identityTheftRestoration) await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(url)) } @@ -524,7 +524,7 @@ extension SubscriptionPagesUseSubscriptionFeature { guard let window else { return } window.show(.subscriptionNotFoundAlert(), firstButtonAction: { - let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = self.subscriptionManager.url(for: .purchase) WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) @@ -535,7 +535,7 @@ extension SubscriptionPagesUseSubscriptionFeature { guard let window else { return } window.show(.subscriptionInactiveAlert(), firstButtonAction: { - let url = SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionManager.currentEnvironment.serviceEnvironment) + let url = self.subscriptionManager.url(for: .purchase) WindowControllersManager.shared.showTab(with: .subscription(url)) PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index 0f22c281ab..406d632e8d 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -139,7 +139,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { if accountManager.isUserAuthenticated { ShareSubscriptionAccessModel(actionHandlers: sheetActionHandler, email: accountManager.email, subscriptionManager: subscriptionManager) } else { - ActivateSubscriptionAccessModel(actionHandlers: sheetActionHandler, subscriptionEnvironment: subscriptionManager.currentEnvironment) + ActivateSubscriptionAccessModel(actionHandlers: sheetActionHandler, subscriptionManager: subscriptionManager) } } @@ -150,7 +150,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func purchaseAction() { - openURLHandler(SubscriptionURL.purchase.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) + openURLHandler(subscriptionManager.url(for: .purchase)) } enum ChangePlanOrBillingAction { @@ -185,7 +185,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func changePlanOrBilling(for environment: SubscriptionEnvironment.PurchasePlatform) { switch environment { case .appStore: - NSWorkspace.shared.open(SubscriptionURL.manageSubscriptionsInAppStore.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) + NSWorkspace.shared.open(subscriptionManager.url(for: .manageSubscriptionsInAppStore)) case .stripe: Task { guard let accessToken = accountManager.accessToken, let externalID = accountManager.externalID, @@ -199,7 +199,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { private func confirmIfSignedInToSameAccount() async -> Bool { if #available(macOS 12.0, *) { - guard let lastTransactionJWSRepresentation = await subscriptionManager.getStorePurchaseManager().mostRecentTransaction() else { return false } + guard let lastTransactionJWSRepresentation = await subscriptionManager.storePurchaseManager().mostRecentTransaction() else { return false } switch await subscriptionManager.authService.storeLogin(signature: lastTransactionJWSRepresentation) { case .success(let response): return response.externalID == accountManager.externalID @@ -234,7 +234,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { @MainActor func openFAQ() { - openURLHandler( SubscriptionURL.faq.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment)) + openURLHandler(subscriptionManager.url(for: .faq)) } @MainActor diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift index e8ab91c642..5d7517bb58 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift @@ -34,18 +34,18 @@ public final class ActivateSubscriptionAccessModel: SubscriptionAccessModel, Pur public var restorePurchaseDescription = UserText.restorePurchaseDescription public var restorePurchaseButtonTitle = UserText.restorePurchaseButton - let subscriptionEnvironment: SubscriptionEnvironment + let subscriptionManager: SubscriptionManaging public init(actionHandlers: SubscriptionAccessActionHandlers, - subscriptionEnvironment: SubscriptionEnvironment) { + subscriptionManager: SubscriptionManaging) { self.actionHandlers = actionHandlers - self.shouldShowRestorePurchase = subscriptionEnvironment.purchasePlatform == .appStore - self.subscriptionEnvironment = subscriptionEnvironment - self.description = UserText.activateModalDescription(platform: subscriptionEnvironment.purchasePlatform) + self.shouldShowRestorePurchase = subscriptionManager.currentEnvironment.purchasePlatform == .appStore + self.subscriptionManager = subscriptionManager + self.description = UserText.activateModalDescription(platform: subscriptionManager.currentEnvironment.purchasePlatform) } public func handleEmailAction() { - actionHandlers.openURLHandler(SubscriptionURL.activateViaEmail.subscriptionURL(environment: subscriptionEnvironment.serviceEnvironment)) + actionHandlers.openURLHandler(subscriptionManager.url(for: .activateViaEmail)) actionHandlers.uiActionHandler(.activateAddEmailClick) } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index def20d0bc1..e7b2eae2d7 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -41,7 +41,7 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { public func handleEmailAction() { let type = hasEmail ? SubscriptionURL.manageEmail : SubscriptionURL.addEmail - let url: URL = type.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let url: URL = subscriptionManager.url(for: type) if hasEmail { actionHandlers.uiActionHandler(.postSubscriptionAddEmailClick) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift index 494415e13f..f08c95cfb0 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -58,7 +58,7 @@ public final class SubscriptionAccessViewController: NSViewController { if subscriptionManager.accountManager.isUserAuthenticated { ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: subscriptionManager.accountManager.email, subscriptionManager: subscriptionManager) } else { - ActivateSubscriptionAccessModel(actionHandlers: actionHandlers, subscriptionEnvironment: subscriptionManager.currentEnvironment) + ActivateSubscriptionAccessModel(actionHandlers: actionHandlers, subscriptionManager: subscriptionManager) } } } diff --git a/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift b/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift index d739d34819..3f7d7da067 100644 --- a/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift +++ b/UnitTests/Subscriptions/SubscriptionRedirectManagerTests.swift @@ -17,7 +17,7 @@ // import XCTest -import Subscription +@testable import Subscription import SubscriptionTestingUtilities @testable import DuckDuckGo_Privacy_Browser @@ -29,6 +29,7 @@ final class SubscriptionRedirectManagerTests: XCTestCase { sut = PrivacyProSubscriptionRedirectManager(featureAvailabiltyProvider: true, subscriptionEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore), + baseURL: SubscriptionURL.baseURL.subscriptionURL(environment: .production), canPurchase: { true }) } diff --git a/UnitTests/TabBar/View/TabBarViewItemTests.swift b/UnitTests/TabBar/View/TabBarViewItemTests.swift index 3bda7382fd..ea46a83f31 100644 --- a/UnitTests/TabBar/View/TabBarViewItemTests.swift +++ b/UnitTests/TabBar/View/TabBarViewItemTests.swift @@ -17,8 +17,7 @@ // import XCTest -import Subscription - +@testable import Subscription @testable import DuckDuckGo_Privacy_Browser @MainActor From 39e91bd1fd469d601124401262a687907f9ecc95 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 17:39:40 +0100 Subject: [PATCH 31/41] lint --- .../Model/ShareSubscriptionAccessModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index e7b2eae2d7..0e849daf0a 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -41,7 +41,7 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { public func handleEmailAction() { let type = hasEmail ? SubscriptionURL.manageEmail : SubscriptionURL.addEmail - let url: URL = subscriptionManager.url(for: type) + let url: URL = subscriptionManager.url(for: type) if hasEmail { actionHandlers.uiActionHandler(.postSubscriptionAddEmailClick) From baa806bbb1110d6067b83b48622bb3e5ad563092 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 17:48:04 +0100 Subject: [PATCH 32/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 74b262fa14..6f00193ecc 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_refactoring_2", - "revision" : "0335dcb7559e12f3ab7b77951d04400b4771b66d" + "revision" : "3819577dbab2f3b90ba61a9141ac3efc869be982" } }, { From e33bdb35daa3e3c6820e3cb45615b008ccac940c Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 17 May 2024 15:23:14 +0100 Subject: [PATCH 33/41] BSK update --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0e48fb9a5a..af560d069c 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_refactoring_2", - "revision" : "73ee685cfabf54635c45d00d270edc90124e0f7d" + "revision" : "dc996ee2c8a8d3a9476ed4776dc570ee7b9ce845" } }, { From 8e5719f9dfd13ec233cfefb5bb699bd997273268 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 20 May 2024 17:45:36 +0200 Subject: [PATCH 34/41] lint --- .../DuckDuckGoDBPBackgroundAgentAppDelegate.swift | 3 ++- .../DataBrokerProtectionSubscriptionManaging.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index f625506918..cbd643e974 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -31,6 +31,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { private let _delegate: DuckDuckGoDBPBackgroundAgentAppDelegate private let subscriptionManager: SubscriptionManaging + // swiftlint:disable:next function_body_length override init() { os_log(.error, log: .dbpBackgroundAgent, "🟢 DBP background Agent starting: %{public}d", NSRunningApplication.current.processIdentifier) @@ -129,7 +130,7 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele setupStatusBarMenu() - //Update environment + // Update environment settings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift index 17a34ebc8c..c2035d4c06 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Authentication/DataBrokerProtectionSubscriptionManaging.swift @@ -27,7 +27,7 @@ public protocol DataBrokerProtectionSubscriptionManaging { } public final class DataBrokerProtectionSubscriptionManager: DataBrokerProtectionSubscriptionManaging { - + let subscriptionManager: SubscriptionManaging public var isUserAuthenticated: Bool { From 71577f9d4c4bdbb8b6205c8f7895674ba650c157 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 20 May 2024 19:21:34 +0200 Subject: [PATCH 35/41] DBP and VPN environments aligned with Subscription, related debug menus updated --- DuckDuckGo.xcodeproj/project.pbxproj | 24 +++++++- DuckDuckGo/Application/AppDelegate.swift | 40 +++---------- .../DBP/DataBrokerProtectionDebugMenu.swift | 25 +++----- .../DBP/DataBrokerProtectionManager.swift | 2 +- .../View/NavigationBarViewController.swift | 2 +- .../NetworkProtectionDebugMenu.swift | 38 ++----------- ...etworkProtectionNavBarPopoverManager.swift | 2 +- .../MacPacketTunnelProvider.swift | 12 +++- ...BrokerProtectionSettings+Environment.swift | 0 ...riptionManager+StandardConfiguration.swift | 57 +++++++++++++++++++ .../VPNMetadataCollector.swift | 13 ++++- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 33 +---------- .../Model/ShareSubscriptionAccessModel.swift | 4 +- 13 files changed, 132 insertions(+), 120 deletions(-) rename {DuckDuckGoDBPBackgroundAgent => DuckDuckGo/Subscription}/DataBrokerProtectionSettings+Environment.swift (100%) create mode 100644 DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2d90e019a1..103afd3462 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2618,6 +2618,16 @@ F1D042922BFB9FD800A31506 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; + F1D042992BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D0429A2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D0429B2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D0429C2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D0429D2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D0429E2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D0429F2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D042A02BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; + F1D042A12BFBB4DD00A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; + F1D042A22BFBB4DD00A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; F1D43AEE2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */; }; F1D43AEF2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */; }; F1D43AF32B98E47800BAB743 /* BareBonesBrowserKit in Frameworks */ = {isa = PBXBuildFile; productRef = F1D43AF22B98E47800BAB743 /* BareBonesBrowserKit */; }; @@ -4130,6 +4140,7 @@ F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataBrokerProtectionSettings+Environment.swift"; sourceTree = ""; }; + F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionManager+StandardConfiguration.swift"; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionAttributionPixelHandler.swift; sourceTree = ""; }; F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionRedirectManager.swift; sourceTree = ""; }; @@ -6358,7 +6369,6 @@ children = ( 9D9AE9152AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift */, 9D9AE9282AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift */, - F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */, 7BD01C182AD8319C0088B32E /* IPCServiceManager.swift */, 7B0099782B65013800FE7C31 /* BrowserWindowManager.swift */, 9D9AE92B2AAB84FF0026E7DC /* DBPMocks.swift */, @@ -8284,9 +8294,11 @@ F118EA7B2BEA2B8700F77634 /* Subscription */ = { isa = PBXGroup; children = ( + F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */, F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */, F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */, + F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */, F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */, F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, ); @@ -9611,6 +9623,7 @@ 3706FAA2293F65D500E42796 /* MainWindow.swift in Sources */, 3707C727294B5D2900682A9F /* WKWebView+SessionState.swift in Sources */, 4B44FEF42B1FEF5A000619D8 /* FocusableTextEditor.swift in Sources */, + F1D042A22BFBB4DD00A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, 3706FAA3293F65D500E42796 /* CrashReportPromptViewController.swift in Sources */, 3706FAA4293F65D500E42796 /* ContextMenuManager.swift in Sources */, 31AA6B982B960BA50025014E /* DataBrokerProtectionLoginItemPixels.swift in Sources */, @@ -9769,6 +9782,7 @@ 85774B042A71CDD000DE0561 /* BlockMenuItem.swift in Sources */, 3706FB19293F65D500E42796 /* FireViewController.swift in Sources */, B6E3E55C2BC0041A00A41922 /* DownloadListStoreMock.swift in Sources */, + F1D0429A2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, 4B4D60D42A0C84F700BCD287 /* UserText+NetworkProtection.swift in Sources */, 3707C71F294B5D2900682A9F /* WKUserContentControllerExtension.swift in Sources */, 3706FB1A293F65D500E42796 /* OutlineSeparatorViewCell.swift in Sources */, @@ -10736,6 +10750,7 @@ 4B2537722A11BF8B00610219 /* main.swift in Sources */, EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */, 4B2D06292A11C0C900DE1F49 /* Bundle+VPN.swift in Sources */, + F1D0429C2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, F1DA51972BF6083A00CF29FA /* PrivacyProPixel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -10754,6 +10769,7 @@ 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, 7BA7CC4C2AD11EC70042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */, + F1D0429D2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, B6F92BAC2A6937B3002ABA6B /* OptionalExtension.swift in Sources */, 7B8DB31A2B504D7500EC16DA /* VPNAppEventsHandler.swift in Sources */, 7BA7CC532AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */, @@ -10796,6 +10812,7 @@ B6F92BAD2A6937B5002ABA6B /* OptionalExtension.swift in Sources */, 4BA7C4D92B3F61FB00AFE511 /* BundleExtension.swift in Sources */, EEC589DC2A4F1CE800BCD60C /* AppLauncher.swift in Sources */, + F1D0429E2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, 7BA7CC3F2AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, 4B0EF7292B5780EB009D6481 /* VPNAppEventsHandler.swift in Sources */, 7BA7CC412AD11E420042E5CE /* NetworkProtectionBouncer.swift in Sources */, @@ -10856,6 +10873,7 @@ F1DA51962BF6083700CF29FA /* PrivacyProPixel.swift in Sources */, EEBCA0C62BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, 4B4D60A12A0B2D6100BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */, + F1D0429B2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, B602E8182A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, B65DA5F52A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, 4B4D60892A0B2A1C00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */, @@ -10928,6 +10946,7 @@ 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, 7BD01C192AD8319C0088B32E /* IPCServiceManager.swift in Sources */, 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, + F1D0429F2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0E2BED317300AE679F /* BundleExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -10946,6 +10965,7 @@ 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, 315A023D2B64216B00BFA577 /* IPCServiceManager.swift in Sources */, 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, + F1D042A02BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, 31ECDA0F2BED317300AE679F /* BundleExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -10961,6 +10981,7 @@ 4B9DB0412A983B24000927DB /* WaitlistDialogView.swift in Sources */, 37D2377A287EB8CA00BCE03B /* TabIndex.swift in Sources */, B60C6F8D29B200AB007BFAA8 /* SavePanelAccessoryView.swift in Sources */, + F1D042992BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, 37534CA3281132CB002621E7 /* TabLazyLoaderDataSource.swift in Sources */, 3158B15C2B0BF76D00AF130C /* DataBrokerProtectionAppEvents.swift in Sources */, 4B723E0E26B0006300E14D75 /* LoginImport.swift in Sources */, @@ -11111,6 +11132,7 @@ 1DFAB51D2A8982A600A0F7F6 /* SetExtension.swift in Sources */, 315AA07028CA5CC800200030 /* YoutubePlayerNavigationHandler.swift in Sources */, 37AFCE9227DB8CAD00471A10 /* PreferencesAboutView.swift in Sources */, + F1D042A12BFBB4DD00A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, 4B2F565C2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */, 9826B0A02747DF3D0092F683 /* ContentBlocking.swift in Sources */, 4B379C2227BDBA29008A968E /* LocalAuthenticationService.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 2e93e4d4ad..f0811eca7f 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -37,6 +37,7 @@ import Lottie import NetworkProtection import Subscription import NetworkProtectionIPC +import DataBrokerProtection // @MainActor final class AppDelegate: NSObject, NSApplicationDelegate { @@ -182,37 +183,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate { privacyConfigManager: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager ) - // MARK: - Configure Subscription - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - let subscriptionEnvironment = SubscriptionManager.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 = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = AccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionService: subscriptionService, - authService: authService) - - if #available(macOS 12.0, *) { - let storePurchaseManager = StorePurchaseManager() - subscriptionManager = SubscriptionManager(storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - subscriptionService: subscriptionService, - authService: authService, - subscriptionEnvironment: subscriptionEnvironment) - } else { - subscriptionManager = SubscriptionManager(accountManager: accountManager, - subscriptionService: subscriptionService, - authService: authService, - subscriptionEnvironment: subscriptionEnvironment) - } + // Configure Subscription + subscriptionManager = SubscriptionManager() + + // Update VPN environment and match the Subscription environment + vpnSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) + + // Update DBP environment and match the Subscription environment + DataBrokerProtectionSettings().alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) } func applicationWillFinishLaunching(_ notification: Notification) { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift index a552bf4bc6..51029dc0f3 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift @@ -316,7 +316,7 @@ final class DataBrokerProtectionDebugMenu: NSMenu { } @objc private func runCustomJSON() { - let authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager() + let authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(subscriptionManager: Application.appDelegate.subscriptionManager) let viewController = DataBrokerRunCustomJSONViewController(authenticationManager: authenticationManager) let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 500, height: 400), styleMask: [.titled, .closable, .miniaturizable, .resizable], @@ -375,25 +375,13 @@ final class DataBrokerProtectionDebugMenu: NSMenu { settings.showInMenuBar.toggle() } - @objc func setSelectedEnvironment(_ menuItem: NSMenuItem) { - let title = menuItem.title - let selectedEnvironment: DataBrokerProtectionSettings.SelectedEnvironment - - if title == EnvironmentTitle.staging.rawValue { - selectedEnvironment = .staging - } else { - selectedEnvironment = .production - } - - settings.selectedEnvironment = selectedEnvironment - } - // MARK: - Utility Functions private func populateDataBrokerProtectionEnvironmentListMenuItems() { environmentMenu.items = [ - NSMenuItem(title: EnvironmentTitle.production.rawValue, action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), - NSMenuItem(title: EnvironmentTitle.staging.rawValue, action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), + NSMenuItem(title: "⚠️ The environment can be set in the Subscription > Environment menu", action: nil, target: nil), + NSMenuItem(title: EnvironmentTitle.production.rawValue, action: nil, target: nil, keyEquivalent: ""), + NSMenuItem(title: EnvironmentTitle.staging.rawValue, action: nil, target: nil, keyEquivalent: ""), ] } @@ -453,9 +441,10 @@ final class DataBrokerProtectionDebugMenu: NSMenu { private func updateEnvironmentMenu() { let selectedEnvironment = settings.selectedEnvironment + guard environmentMenu.items.count == 3 else { return } - environmentMenu.items.first?.state = selectedEnvironment == .production ? .on: .off - environmentMenu.items.last?.state = selectedEnvironment == .staging ? .on: .off + environmentMenu.items[1].state = selectedEnvironment == .production ? .on: .off + environmentMenu.items[2].state = selectedEnvironment == .staging ? .on: .off } private func updateShowStatusMenuIconMenu() { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift index 9ecde48a3e..b295a59cdd 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift @@ -54,7 +54,7 @@ public final class DataBrokerProtectionManager { }() private init() { - self.authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager() + self.authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(subscriptionManager: Application.appDelegate.subscriptionManager) } public func isUserAuthenticated() -> Bool { diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 4cc239477d..02368d3191 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -273,7 +273,7 @@ final class NavigationBarViewController: NSViewController { let internalUserDecider = NSApp.delegateTyped.internalUserDecider let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: PasswordManagerCoordinator.shared, - networkProtectionFeatureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), + networkProtectionFeatureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: subscriptionManager), internalUserDecider: internalUserDecider, accountManager: subscriptionManager.accountManager) menu.actionDelegate = self diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 930c5ee6bf..3cd6f11e63 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -321,8 +321,9 @@ final class NetworkProtectionDebugMenu: NSMenu { private func populateNetworkProtectionEnvironmentListMenuItems() { environmentMenu.items = [ - NSMenuItem(title: "Production", action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), - NSMenuItem(title: "Staging", action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), + NSMenuItem(title: "⚠️ The environment can be set in the Subscription > Environment menu", action: nil, target: nil), + NSMenuItem(title: "Production", action: nil, target: nil, keyEquivalent: ""), + NSMenuItem(title: "Staging", action: nil, target: nil, keyEquivalent: ""), ] } @@ -419,15 +420,10 @@ final class NetworkProtectionDebugMenu: NSMenu { private func updateEnvironmentMenu() { let selectedEnvironment = settings.selectedEnvironment + guard environmentMenu.items.count == 3 else { return } - switch selectedEnvironment { - case .production: - environmentMenu.items.first?.state = .on - environmentMenu.items.last?.state = .off - case .staging: - environmentMenu.items.first?.state = .off - environmentMenu.items.last?.state = .on - } + environmentMenu.items[1].state = selectedEnvironment == .production ? .on: .off + environmentMenu.items[2].state = selectedEnvironment == .staging ? .on: .off } private func updatePreferredServerMenu() { @@ -514,28 +510,6 @@ final class NetworkProtectionDebugMenu: NSMenu { } } - // MARK: Environment - - @objc func setSelectedEnvironment(_ menuItem: NSMenuItem) { - let title = menuItem.title - let selectedEnvironment: VPNSettings.SelectedEnvironment - - if title == "Staging" { - selectedEnvironment = .staging - } else { - selectedEnvironment = .production - } - - settings.selectedEnvironment = selectedEnvironment - - Task { - _ = try await NetworkProtectionDeviceManager.create().refreshServerList() - try? await populateNetworkProtectionServerListMenuItems() - - settings.selectedServer = .automatic - } - } - // MARK: - Exclusions private let dbpBackgroundAppIdentifier = Bundle.main.dbpBackgroundAgentBundleId diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index b372b5f13a..82cd98ab95 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -73,7 +73,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { ) let onboardingStatusPublisher = UserDefaults.netP.networkProtectionOnboardingStatusPublisher -// _ = VPNSettings(defaults: .netP) + _ = VPNSettings(defaults: .netP) let appLauncher = AppLauncher(appBundleURL: Bundle.main.bundleURL) let popover = NetworkProtectionPopover(controller: controller, diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 5b9d16ea53..5ae338625f 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -143,6 +143,12 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { #endif let settings = VPNSettings(defaults: defaults) + // Update the VPN environment and match the Subscription environment + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) + switch event { case .userBecameActive: PixelKit.fire( @@ -340,7 +346,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { NetworkProtectionLastVersionRunStore(userDefaults: defaults).lastExtensionVersionRun = AppVersion.shared.versionAndBuildNumber // MARK: - Configure Subscription - let settings = VPNSettings(defaults: defaults) let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() @@ -363,6 +368,11 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { subscriptionService: subscriptionService, authService: authService) + let settings = VPNSettings(defaults: defaults) + + // Update the VPN environment and match the Subscription environment + settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) + let entitlementsCheck = { await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) } diff --git a/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionSettings+Environment.swift b/DuckDuckGo/Subscription/DataBrokerProtectionSettings+Environment.swift similarity index 100% rename from DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionSettings+Environment.swift rename to DuckDuckGo/Subscription/DataBrokerProtectionSettings+Environment.swift diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift new file mode 100644 index 0000000000..552b8e28b7 --- /dev/null +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -0,0 +1,57 @@ +// +// SubscriptionManager+StandardConfiguration.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Subscription +import Common + +extension SubscriptionManager { + + // Init the SubscriptionManager using the standard dependencies and configuration, to be used only in the dependencies tree root + public convenience init() { + // MARK: - Configure Subscription + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, + key: UserDefaultsCacheKey.subscriptionEntitlements, + settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) + let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) + let accountManager = AccountManager(accessTokenStorage: accessTokenStorage, + entitlementsCache: entitlementsCache, + subscriptionService: subscriptionService, + authService: authService) + + if #available(macOS 12.0, *) { + let storePurchaseManager = StorePurchaseManager() + self.init(storePurchaseManager: storePurchaseManager, + accountManager: accountManager, + subscriptionService: subscriptionService, + authService: authService, + subscriptionEnvironment: subscriptionEnvironment) + } else { + self.init(accountManager: accountManager, + subscriptionService: subscriptionService, + authService: authService, + subscriptionEnvironment: subscriptionEnvironment) + } + } +} diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index 188c6f7479..e69c0c4a41 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -122,6 +122,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let ipcClient: TunnelControllerIPCClient private let defaults: UserDefaults private let accountManager: AccountManaging + private let settings: VPNSettings init(defaults: UserDefaults = .netP, accountManager: AccountManaging) { @@ -143,6 +144,16 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { // Force refresh just in case. A refresh is requested when the IPC client is created, but distributed notifications don't guarantee delivery // so we'll play it safe and add one more attempt. self.statusReporter.forceRefresh() + + self.settings = VPNSettings(defaults: defaults) + updateSettings() + } + + func updateSettings() { + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) } @MainActor @@ -289,8 +300,6 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectVPNSettingsState() -> VPNMetadata.VPNSettingsState { - let settings = VPNSettings(defaults: defaults) - return .init( connectOnLoginEnabled: settings.connectOnLogin, includeAllNetworksEnabled: settings.includeAllNetworks, diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index cbd643e974..ad3b9edc7c 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -31,7 +31,6 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { private let _delegate: DuckDuckGoDBPBackgroundAgentAppDelegate private let subscriptionManager: SubscriptionManaging - // swiftlint:disable:next function_body_length override init() { os_log(.error, log: .dbpBackgroundAgent, "🟢 DBP background Agent starting: %{public}d", NSRunningApplication.current.processIdentifier) @@ -68,34 +67,8 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { exit(0) } - // MARK: - Configure Subscription - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, - key: UserDefaultsCacheKey.subscriptionEntitlements, - settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) - let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) - let accountManager = AccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionService: subscriptionService, - authService: authService) - - if #available(macOS 12.0, *) { - let storePurchaseManager = StorePurchaseManager() - subscriptionManager = SubscriptionManager(storePurchaseManager: storePurchaseManager, - accountManager: accountManager, - subscriptionService: subscriptionService, - authService: authService, - subscriptionEnvironment: subscriptionEnvironment) - } else { - subscriptionManager = SubscriptionManager(accountManager: accountManager, - subscriptionService: subscriptionService, - authService: authService, - subscriptionEnvironment: subscriptionEnvironment) - } + // Configure Subscription + subscriptionManager = SubscriptionManager() _delegate = DuckDuckGoDBPBackgroundAgentAppDelegate(subscriptionManager: subscriptionManager) @@ -130,7 +103,7 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele setupStatusBarMenu() - // Update environment + // Aligning the environment with the Subscription one settings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) } diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift index 0e849daf0a..d67abee49c 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -41,7 +41,7 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { public func handleEmailAction() { let type = hasEmail ? SubscriptionURL.manageEmail : SubscriptionURL.addEmail - let url: URL = subscriptionManager.url(for: type) + let mailURL: URL = subscriptionManager.url(for: type) if hasEmail { actionHandlers.uiActionHandler(.postSubscriptionAddEmailClick) @@ -58,7 +58,7 @@ public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { } DispatchQueue.main.async { - self.actionHandlers.openURLHandler(url) + self.actionHandlers.openURLHandler(mailURL) } } } From d46895200c8cfd4e1a81db87982b55f8d1d4378f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 21 May 2024 10:53:41 +0200 Subject: [PATCH 36/41] align removed from debug menu --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Application/AppDelegate.swift | 2 +- DuckDuckGo/Menus/MainMenu.swift | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 64bb4e3ec8..981f226c05 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_refactoring_2", - "revision" : "e826efd545e3b2d83ec2c69a1f615761f28a4f8c" + "revision" : "874ae4269db821797742655e134e72199c2813c8" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index f0811eca7f..6481891cea 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -185,7 +185,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { // Configure Subscription subscriptionManager = SubscriptionManager() - + // Update VPN environment and match the Subscription environment vpnSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index bb46395d7f..3a41e65525 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -628,10 +628,6 @@ import SubscriptionUI let updateServiceEnvironment: (SubscriptionEnvironment.ServiceEnvironment) -> Void = { env in currentEnvironment.serviceEnvironment = env SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) - - // The VPN environment is forced to match the Subscription environment - let settings = Application.appDelegate.vpnSettings - settings.alignTo(subscriptionEnvironment: currentEnvironment) } let updatePurchasingPlatform: (SubscriptionEnvironment.PurchasePlatform) -> Void = { platform in currentEnvironment.purchasePlatform = platform From c2784b29b96debc190d3fe265ad16921fdb6cda9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 21 May 2024 11:10:03 +0200 Subject: [PATCH 37/41] lint and DI --- DuckDuckGo/Application/AppDelegate.swift | 2 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 6481891cea..f0811eca7f 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -185,7 +185,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { // Configure Subscription subscriptionManager = SubscriptionManager() - + // Update VPN environment and match the Subscription environment vpnSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment) diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index dc7a6ff39c..de78d86b57 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -61,6 +61,7 @@ final class DuckDuckGoVPNApplication: NSApplication { _delegate = DuckDuckGoVPNAppDelegate(bouncer: NetworkProtectionBouncer(accountManager: accountManager), accountManager: accountManager, + accessTokenStorage: accessTokenStorage, subscriptionEnvironment: subscriptionEnvironment) super.init() @@ -124,12 +125,15 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private let appLauncher = AppLauncher() private let bouncer: NetworkProtectionBouncer private let accountManager: AccountManaging + private let accessTokenStorage: SubscriptionTokenKeychainStorage public init(bouncer: NetworkProtectionBouncer, accountManager: AccountManaging, + accessTokenStorage: SubscriptionTokenKeychainStorage, subscriptionEnvironment: SubscriptionEnvironment) { self.bouncer = bouncer self.accountManager = accountManager + self.accessTokenStorage = accessTokenStorage self.tunnelSettings = VPNSettings(defaults: .netP) self.tunnelSettings.alignTo(subscriptionEnvironment: subscriptionEnvironment) } @@ -216,7 +220,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { networkExtensionController: networkExtensionController, settings: tunnelSettings, defaults: userDefaults, - accessTokenStorage: SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(Bundle.main.appGroup(bundle: .subs))))) + accessTokenStorage: accessTokenStorage) /// An IPC server that provides access to the tunnel controller. /// From 9d33fed927b98c895b784aff1e06b47b91aec459 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 21 May 2024 12:46:10 +0200 Subject: [PATCH 38/41] net ext crash fixed and vpn+subscription environments aligned --- .../MacPacketTunnelProvider.swift | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 5ae338625f..50547581f6 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -141,14 +141,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { #else let defaults = UserDefaults.netP #endif - let settings = VPNSettings(defaults: defaults) - - // Update the VPN environment and match the Subscription environment - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) - settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) - switch event { case .userBecameActive: PixelKit.fire( @@ -344,10 +336,10 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { #endif NetworkProtectionLastVersionRunStore(userDefaults: defaults).lastExtensionVersionRun = AppVersion.shared.versionAndBuildNumber + let settings = VPNSettings(defaults: defaults) // MARK: - Configure Subscription - let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) - let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let subscriptionUserDefaults = UserDefaults(suiteName: MacPacketTunnelProvider.subscriptionsAppGroup)! let notificationCenter: NetworkProtectionNotificationCenter = DistributedNotificationCenter.default() let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter) let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore) @@ -360,7 +352,15 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: subscriptionUserDefaults, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - let subscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + // Align Subscription environment to the VPN environment + var subscriptionEnvironment = SubscriptionEnvironment.default + switch settings.selectedEnvironment { + case .production: + subscriptionEnvironment.serviceEnvironment = .production + case .staging: + subscriptionEnvironment.serviceEnvironment = .staging + } + let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let accountManager = AccountManager(accessTokenStorage: tokenStore, @@ -368,11 +368,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { subscriptionService: subscriptionService, authService: authService) - let settings = VPNSettings(defaults: defaults) - - // Update the VPN environment and match the Subscription environment - settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) - let entitlementsCheck = { await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) } From ca1eb393f88f1cb0bb294319c8797f192be86e3f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 21 May 2024 12:47:53 +0200 Subject: [PATCH 39/41] lint --- .../NetworkExtensionTargets/MacPacketTunnelProvider.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 50547581f6..8b4fd7288d 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -325,8 +325,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - Initialization - @MainActor - @objc public init() { + // swiftlint:disable:next function_body_length + @MainActor @objc public init() { let isSubscriptionEnabled = false #if NETP_SYSTEM_EXTENSION From 2fe2a22eb6a16eebc4978e922e495fbfe1ebd69d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 23 May 2024 11:15:25 +0100 Subject: [PATCH 40/41] BSK v146.0.0 --- DuckDuckGo.xcodeproj/project.pbxproj | 13 +++---------- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f4c1f200d8..dcd4c9923c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10939,10 +10939,7 @@ files = ( 31A83FB72BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, - 9D9AE92C2AAB84FF0026E7DC /* DBPMocks.swift in Sources */, F1D042912BFB9FD700A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 7B0099792B65013800FE7C31 /* BrowserWindowManager.swift in Sources */, - 9D9AE9292AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */, 9D9AE91D2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -10957,10 +10954,7 @@ files = ( 31A83FB82BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, - 7B1459542B7D437200047F2C /* BrowserWindowManager.swift in Sources */, F1D042922BFB9FD800A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 9D9AE92D2AAB84FF0026E7DC /* DBPMocks.swift in Sources */, - 9D9AE92A2AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */, 9D9AE91E2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -11162,8 +11156,7 @@ AA5FA6A0275F948900DCE9C9 /* Favicons.xcdatamodeld in Sources */, 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 9F6434612BEC82B700D2D8A0 /* AttributionPixelHandler.swift in Sources */, - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemScheduler.swift in Sources */, - 7BBA7CE62BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, + 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, B684592225C93BE000DC17B6 /* Publisher.asVoid.swift in Sources */, 4B9DB01D2A983B24000927DB /* Waitlist.swift in Sources */, BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */, @@ -13034,8 +13027,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - branch = fcappelli/subscription_refactoring_2; - kind = branch; + kind = exactVersion; + version = 146.0.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 981f226c05..7c7e84edb5 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" : { - "branch" : "fcappelli/subscription_refactoring_2", - "revision" : "874ae4269db821797742655e134e72199c2813c8" + "revision" : "b01a7ba359b650f0c5c3ab00a756e298b1ae650c", + "version" : "146.0.0" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { - "revision" : "46989693916f56d1186bd59ac15124caef896560", - "version" : "1.3.1" + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" } }, { diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index b199cd1256..203c5dda21 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "145.3.3"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "146.0.0"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), ], diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 6bdacb0bff..9b10b4534f 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,7 +31,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "145.3.3"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "146.0.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index cd969a3ce6..210d28971a 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "145.3.3"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "146.0.0"), .package(path: "../SwiftUIExtensions") ], targets: [ From 39ee321988562156732deb528c0060115fef4502 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 23 May 2024 11:25:32 +0100 Subject: [PATCH 41/41] build warnings removed --- DuckDuckGo.xcodeproj/project.pbxproj | 77 ++++++++++++---------------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 499d9ca6b6..08e3fe56ea 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -169,7 +169,6 @@ 3158B1492B0BF73000AF130C /* DBPHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */; }; 3158B14A2B0BF74300AF130C /* DataBrokerProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316850712AF3AD58009A2828 /* DataBrokerProtectionDebugMenu.swift */; }; 3158B14D2B0BF74D00AF130C /* DataBrokerProtectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */; }; - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; 3158B1532B0BF75700AF130C /* LoginItem+DataBrokerProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */; }; 3158B1562B0BF75D00AF130C /* DataBrokerProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C5FFB82AF64D120008A79F /* DataBrokerProtectionFeatureVisibility.swift */; }; 3158B1592B0BF76400AF130C /* DataBrokerProtectionFeatureDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3199C6F82AF94F5B002A7BA1 /* DataBrokerProtectionFeatureDisabler.swift */; }; @@ -221,7 +220,6 @@ 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */; }; 31EF1E802B63FFA800E6DB17 /* DBPHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */; }; 31EF1E812B63FFB800E6DB17 /* DataBrokerProtectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */; }; - 31EF1E822B63FFC200E6DB17 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; 31EF1E832B63FFCA00E6DB17 /* LoginItem+DataBrokerProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */; }; 31EF1E842B63FFD100E6DB17 /* DataBrokerProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316850712AF3AD58009A2828 /* DataBrokerProtectionDebugMenu.swift */; }; 31F28C4F28C8EEC500119F70 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; @@ -2611,10 +2609,18 @@ F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; F1B33DF72BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; + F1C70D792BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */; }; + F1C70D7A2BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */; }; + F1C70D7C2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1C70D7D2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1C70D7E2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1C70D7F2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1C70D802BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1C70D812BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1C70D822BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1C70D832BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; F1D0428E2BFB9F9C00A31506 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F1D0428D2BFB9F9C00A31506 /* Subscription */; }; F1D042902BFB9FA300A31506 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F1D0428F2BFB9FA300A31506 /* Subscription */; }; - F1D042912BFB9FD700A31506 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; - F1D042922BFB9FD800A31506 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; F1D042992BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */; }; @@ -2655,12 +2661,6 @@ F1DF95E42BD1807C0045E591 /* Crashes in Frameworks */ = {isa = PBXBuildFile; productRef = 537FC71EA5115A983FAF3170 /* Crashes */; }; F1DF95E52BD1807C0045E591 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = DC3F73D49B2D44464AFEFCD8 /* Subscription */; }; F1DF95E72BD188B60045E591 /* LoginItems in Frameworks */ = {isa = PBXBuildFile; productRef = F1DF95E62BD188B60045E591 /* LoginItems */; }; - F1FDC9292BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; - F1FDC92A2BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; - F1FDC92B2BF4DFEC006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; - F1FDC92C2BF4DFED006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; - F1FDC92D2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; - F1FDC92E2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */; }; F1FDC9382BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; F1FDC93A2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */; }; @@ -3392,7 +3392,6 @@ 7B4D8A202BDA857300852966 /* VPNOperationErrorRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNOperationErrorRecorder.swift; sourceTree = ""; }; 7B5291882A1697680022E406 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7B5291892A169BC90022E406 /* DeveloperID.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DeveloperID.xcconfig; sourceTree = ""; }; - 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionLoginItemInterface.swift; sourceTree = ""; }; 7B6EC5E42AE2D8AF004FE6DF /* DuckDuckGoDBPAgentAppStore.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DuckDuckGoDBPAgentAppStore.xcconfig; sourceTree = ""; }; 7B6EC5E52AE2D8AF004FE6DF /* DuckDuckGoDBPAgent.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DuckDuckGoDBPAgent.xcconfig; sourceTree = ""; }; 7B76E6852AD5D77600186A84 /* XPCHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = XPCHelper; sourceTree = ""; }; @@ -4138,12 +4137,13 @@ F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; + F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionLoginItemInterface.swift; sourceTree = ""; }; + F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataBrokerProtectionSettings+Environment.swift"; sourceTree = ""; }; F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionManager+StandardConfiguration.swift"; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionAttributionPixelHandler.swift; sourceTree = ""; }; F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionRedirectManager.swift; sourceTree = ""; }; - F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VPNSettings+Environment.swift"; sourceTree = ""; }; F41D174025CB131900472416 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = ""; }; F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearanceExtension.swift; sourceTree = ""; }; @@ -4625,21 +4625,21 @@ 3192EC862A4DCF0E001E97A5 /* DBP */ = { isa = PBXGroup; children = ( - 3169132B2BD2C7960051B46D /* ErrorView */, - 316913222BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift */, + 316913252BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift */, + 3199C6FC2AF97367002A7BA1 /* DataBrokerProtectionAppEvents.swift */, 316850712AF3AD58009A2828 /* DataBrokerProtectionDebugMenu.swift */, - 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */, - 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */, - 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */, - 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */, - 31C5FFB82AF64D120008A79F /* DataBrokerProtectionFeatureVisibility.swift */, + BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */, 3199C6F82AF94F5B002A7BA1 /* DataBrokerProtectionFeatureDisabler.swift */, - 3199C6FC2AF97367002A7BA1 /* DataBrokerProtectionAppEvents.swift */, + 31C5FFB82AF64D120008A79F /* DataBrokerProtectionFeatureVisibility.swift */, + F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */, 31AA6B962B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift */, - BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */, + 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */, + 316913222BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift */, BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */, + 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */, + 3169132B2BD2C7960051B46D /* ErrorView */, + 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */, 4B37EE652B4CFC9500A89A61 /* RemoteMessaging */, - 316913252BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift */, ); path = DBP; sourceTree = ""; @@ -8292,10 +8292,10 @@ F118EA7B2BEA2B8700F77634 /* Subscription */ = { isa = PBXGroup; children = ( + F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */, F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */, F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */, - F1FDC9282BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift */, F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */, F1DA51852BF607D200CF29FA /* SubscriptionRedirectManager.swift */, F1FDC9372BF51F41006B1435 /* VPNSettings+Environment.swift */, @@ -9836,7 +9836,6 @@ 3706FB43293F65D500E42796 /* DuckPlayer.swift in Sources */, 3706FB44293F65D500E42796 /* Favicon.swift in Sources */, 3706FB45293F65D500E42796 /* SuggestionContainerViewModel.swift in Sources */, - F1FDC92A2BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 3706FB46293F65D500E42796 /* FirePopoverWrapperViewController.swift in Sources */, 3706FB47293F65D500E42796 /* NSPasteboardItemExtension.swift in Sources */, 3706FB48293F65D500E42796 /* AutofillPreferencesModel.swift in Sources */, @@ -9893,6 +9892,7 @@ 7B7FCD102BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift in Sources */, 3706FB6C293F65D500E42796 /* FaviconStore.swift in Sources */, 3706FB6D293F65D500E42796 /* SuggestionListCharacteristics.swift in Sources */, + F1C70D7A2BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 377D801F2AB48191002AF251 /* FavoritesDisplayModeSyncHandler.swift in Sources */, 3706FB6F293F65D500E42796 /* BookmarkListViewController.swift in Sources */, 3706FB70293F65D500E42796 /* SecureVaultLoginImporter.swift in Sources */, @@ -10185,6 +10185,7 @@ 4B9DB03F2A983B24000927DB /* JoinWaitlistView.swift in Sources */, 987799F22999993C005D8EB6 /* LegacyBookmarkStore.swift in Sources */, 37A6A8F72AFCCA59008580A3 /* FaviconsFetcherOnboardingViewController.swift in Sources */, + F1C70D7D2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 3706FC2C293F65D500E42796 /* BookmarkHTMLReader.swift in Sources */, 3706FC2D293F65D500E42796 /* Tab+NSSecureCoding.swift in Sources */, 3706FC2E293F65D500E42796 /* NSNotificationName+EmailManager.swift in Sources */, @@ -10201,7 +10202,6 @@ 3706FC34293F65D500E42796 /* PermissionAuthorizationViewController.swift in Sources */, 3706FC35293F65D500E42796 /* BookmarkNode.swift in Sources */, B6ABD0CB2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */, - 31EF1E822B63FFC200E6DB17 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, B6B140892ABDBCC1004F8E85 /* HoverTrackingArea.swift in Sources */, 3706FC36293F65D500E42796 /* LongPressButton.swift in Sources */, 3706FC37293F65D500E42796 /* CoreDataStore.swift in Sources */, @@ -10735,7 +10735,6 @@ EE66418D2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */, EEBCA0C72BD7CE2C004DF19C /* VPNFailureRecoveryPixel.swift in Sources */, F1DA51932BF6081D00CF29FA /* AttributionPixelHandler.swift in Sources */, - F1FDC92C2BF4DFED006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 7B2E52252A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift in Sources */, F1DA51892BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, B602E8232A1E260E006D261F /* Bundle+NetworkProtectionExtensions.swift in Sources */, @@ -10749,6 +10748,7 @@ 7B0099822B65C6B300FE7C31 /* MacTransparentProxyProvider.swift in Sources */, B65DA5F32A77D3C700CBEE8D /* UserDefaultsWrapper.swift in Sources */, 4B2537722A11BF8B00610219 /* main.swift in Sources */, + F1C70D7F2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */, 4B2D06292A11C0C900DE1F49 /* Bundle+VPN.swift in Sources */, F1D0429C2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, @@ -10782,7 +10782,7 @@ 7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */, 7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, F1DA51982BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */, - F1FDC92D2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, + F1C70D802BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */, BDA764842BC49E3F00D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, @@ -10825,7 +10825,7 @@ 4BF0E5152AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 7BFE95592A9DF2AF0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, F1DA51992BF6083B00CF29FA /* PrivacyProPixel.swift in Sources */, - F1FDC92E2BF4E001006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, + F1C70D812BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, BDA764852BC49E4000D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, @@ -10878,6 +10878,7 @@ B602E8182A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, B65DA5F52A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, 4B4D60892A0B2A1C00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */, + F1C70D7E2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 4B4D60A02A0B2D5B00BCD287 /* Bundle+VPN.swift in Sources */, 4B4D60AD2A0C807300BCD287 /* NSApplicationExtension.swift in Sources */, F1DA51922BF6081C00CF29FA /* AttributionPixelHandler.swift in Sources */, @@ -10885,7 +10886,6 @@ 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B4D60AC2A0C804B00BCD287 /* OptionalExtension.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, - F1FDC92B2BF4DFEC006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, F1DA51882BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, F1FDC93A2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, ); @@ -10939,11 +10939,7 @@ files = ( 31A83FB72BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, - F1D042912BFB9FD700A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 9D9AE92C2AAB84FF0026E7DC /* DBPMocks.swift in Sources */, - F1D042912BFB9FD700A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 7B0099792B65013800FE7C31 /* BrowserWindowManager.swift in Sources */, - 9D9AE9292AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */, + F1C70D822BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 9D9AE91D2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -10958,11 +10954,7 @@ files = ( 31A83FB82BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, - F1D042922BFB9FD800A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 7B1459542B7D437200047F2C /* BrowserWindowManager.swift in Sources */, - F1D042922BFB9FD800A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 9D9AE92D2AAB84FF0026E7DC /* DBPMocks.swift in Sources */, - 9D9AE92A2AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */, + F1C70D832BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 9D9AE91E2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -11109,6 +11101,7 @@ 1E7E2E9029029A2A00C01B54 /* ContentBlockingRulesUpdateObserver.swift in Sources */, 4B8AC93926B48A5100879451 /* FirefoxLoginReader.swift in Sources */, F18826902BC0105800D9AC4F /* PixelDataRecord.swift in Sources */, + F1C70D792BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, F18826912BC0105800D9AC4F /* PixelDataStore.swift in Sources */, B69B503E2726A12500758A2B /* AtbParser.swift in Sources */, 37F19A6528E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift in Sources */, @@ -11162,11 +11155,7 @@ 85707F26276A335700DC0649 /* Onboarding.swift in Sources */, B68C92C1274E3EF4002AC6B0 /* PopUpWindow.swift in Sources */, AA5FA6A0275F948900DCE9C9 /* Favicons.xcdatamodeld in Sources */, - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 9F6434612BEC82B700D2D8A0 /* AttributionPixelHandler.swift in Sources */, - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemScheduler.swift in Sources */, - 7BBA7CE62BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, B684592225C93BE000DC17B6 /* Publisher.asVoid.swift in Sources */, 4B9DB01D2A983B24000927DB /* Waitlist.swift in Sources */, BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */, @@ -11540,6 +11529,7 @@ 4BCF15D72ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift in Sources */, C168B9AC2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */, 9FA173E72B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */, + F1C70D7C2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, D64A5FF82AEA5C2B00B6D6E7 /* HomeButtonMenuFactory.swift in Sources */, 37A6A8F62AFCCA59008580A3 /* FaviconsFetcherOnboardingViewController.swift in Sources */, AA3F895324C18AD500628DDE /* SuggestionViewModel.swift in Sources */, @@ -11648,7 +11638,6 @@ AAC82C60258B6CB5009B6B42 /* TabPreviewWindowController.swift in Sources */, AAC5E4E425D6BA9C007F5990 /* NSSizeExtension.swift in Sources */, AA6820EB25503D6A005ED0D5 /* Fire.swift in Sources */, - F1FDC9292BF4DF48006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, 3158B1492B0BF73000AF130C /* DBPHomeViewController.swift in Sources */, 9F56CFA92B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */, 37445F9C2A1569F00029F789 /* SyncBookmarksAdapter.swift in Sources */,