From 140680f0859daa41d603e9edea67c9d83435d306 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 2 May 2024 15:11:30 +0100 Subject: [PATCH 01/39] Accountmanager injected and abstracted --- DuckDuckGo.xcodeproj/project.pbxproj | 2 ++ .../xcshareddata/swiftpm/Package.resolved | 9 ------- DuckDuckGo/AppDelegate+Waitlists.swift | 2 +- DuckDuckGo/AppDelegate.swift | 19 +++++++------ .../DefaultNetworkProtectionVisibility.swift | 27 +++++++++++++++++-- .../Feedback/VPNMetadataCollector.swift | 2 +- DuckDuckGo/MainViewController+Segues.swift | 2 +- DuckDuckGo/MainViewController.swift | 2 +- ...orkProtectionConvenienceInitialisers.swift | 7 ++--- ...NetworkProtectionDebugViewController.swift | 18 +++++++------ .../NetworkProtectionFeatureVisibility.swift | 20 +------------- DuckDuckGo/NetworkProtectionRootView.swift | 2 +- ...rotectionVisibilityForTunnelProvider.swift | 25 ++++++++++++++++- DuckDuckGo/RootDebugViewController.swift | 1 + DuckDuckGo/SettingsViewModel.swift | 15 ++++++----- ...IdentityTheftRestorationPagesFeature.swift | 9 +++++-- ...scriptionPagesUseSubscriptionFeature.swift | 25 ++++++++++------- .../SubscriptionContainerViewModel.swift | 17 ++++++------ .../SubscriptionEmailViewModel.swift | 4 +-- .../ViewModel/SubscriptionITPViewModel.swift | 11 ++++---- .../SubscriptionRestoreViewModel.swift | 10 ++++--- .../SubscriptionSettingsViewModel.swift | 6 ++--- .../Views/SubscriptionContainerView.swift | 9 +++---- .../Views/SubscriptionITPView.swift | 2 +- .../Views/SubscriptionSettingsView.swift | 2 +- .../SubscriptionDebugViewController.swift | 6 ++--- ...etworkProtectionPacketTunnelProvider.swift | 17 ++++++------ 27 files changed, 153 insertions(+), 118 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 123d3bafb1..6805ef3e40 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2491,6 +2491,7 @@ F1134ECF1F40EBE200B73467 /* JsonTestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonTestDataLoader.swift; sourceTree = ""; }; F1134ED41F40F15800B73467 /* StatisticsUserDefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticsUserDefaultsTests.swift; sourceTree = ""; }; F114C55A1E66EB020018F95F /* NibLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoading.swift; sourceTree = ""; }; + F119CCC62BE397850002B30E /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F130D7391E5776C500C45811 /* OmniBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarDelegate.swift; sourceTree = ""; }; F1386BA31E6846C40062FC3C /* TabDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabDelegate.swift; sourceTree = ""; }; F13B4BBF1F180D8A00814661 /* TabsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsModel.swift; sourceTree = ""; }; @@ -3436,6 +3437,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F119CCC62BE397850002B30E /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 91c730f4fa..bba9105e7c 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" : "7c41d69a93bbe80639fb7489e2018e5957ac2b5c", - "version" : "143.0.0" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index 967b830348..8d61ecf882 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -45,7 +45,7 @@ extension AppDelegate { #if NETWORK_PROTECTION private func checkNetworkProtectionWaitlist() { - let accessController = NetworkProtectionAccessController() +// let accessController = NetworkProtectionAccessController() VPNWaitlist.shared.fetchInviteCodeIfAvailable { [weak self] error in guard error == nil else { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index cb3ca23c43..426f612754 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -69,7 +69,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults - lazy var vpnFeatureVisibility = DefaultNetworkProtectionVisibility() + lazy var vpnFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) #endif private var autoClear: AutoClear? @@ -93,6 +93,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @UserDefaultsWrapper(key: .privacyProEnvironment, defaultValue: SubscriptionPurchaseEnvironment.ServiceEnvironment.default.description) private var privacyProEnvironment: String + public static let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { @@ -514,7 +516,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func stopTunnelAndShowThankYouMessagingIfNeeded() { - if AccountManager().isUserAuthenticated { + if AppDelegate.accountManager.isUserAuthenticated { tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true return } @@ -523,7 +525,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Task { await self.stopAndRemoveVPN(with: "thank-you-dialog") } - } else if vpnFeatureVisibility.isPrivacyProLaunched() && !AccountManager().isUserAuthenticated { + } else if vpnFeatureVisibility.isPrivacyProLaunched() && !AppDelegate.accountManager.isUserAuthenticated { Task { await self.stopAndRemoveVPN(with: "subscription-check") } @@ -549,9 +551,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func updateSubscriptionStatus() { Task { - let accountManager = AccountManager() - - guard let token = accountManager.accessToken else { return } + guard let token = AppDelegate.accountManager.accessToken else { return } if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { @@ -560,7 +560,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - _ = await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) + _ = await AppDelegate.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } } @@ -858,7 +858,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION if shortcutItem.type == ShortcutKey.openVPNSettings { - let visibility = DefaultNetworkProtectionVisibility() + let visibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) if visibility.shouldShowVPNShortcut() { presentNetworkProtectionStatusSettingsModal() } @@ -971,8 +971,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { #if NETWORK_PROTECTION if NetworkProtectionNotificationIdentifier(rawValue: identifier) != nil { Task { - let accountManager = AccountManager() - if case .success(let hasEntitlements) = await accountManager.hasEntitlement(for: .networkProtection), + if case .success(let hasEntitlements) = await AppDelegate.accountManager.hasEntitlement(for: .networkProtection), hasEntitlements { presentNetworkProtectionStatusSettingsModal() } diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index e71f0ead81..35c71c9d8b 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -24,6 +24,7 @@ import BrowserServicesKit import Waitlist import NetworkProtection import Core +import Subscription struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let privacyConfigurationManager: PrivacyConfigurationManaging @@ -31,23 +32,29 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let networkProtectionAccessManager: NetworkProtectionAccess? private let featureFlagger: FeatureFlagger private let userDefaults: UserDefaults + private let accountManager: AccountManaging init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, networkProtectionTokenStore: NetworkProtectionTokenStore? = NetworkProtectionKeychainTokenStore(), networkProtectionAccessManager: NetworkProtectionAccess? = NetworkProtectionAccessController(), featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger, - userDefaults: UserDefaults = .networkProtectionGroupDefaults) { + userDefaults: UserDefaults = .networkProtectionGroupDefaults, + accountManager: AccountManaging) { + self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionTokenStore = networkProtectionTokenStore self.networkProtectionAccessManager = networkProtectionAccessManager self.featureFlagger = featureFlagger self.userDefaults = userDefaults + self.accountManager = accountManager } /// A lite version with fewer dependencies /// We need this to run shouldMonitorEntitlement() check inside the token store static func forTokenStore() -> DefaultNetworkProtectionVisibility { - DefaultNetworkProtectionVisibility(networkProtectionTokenStore: nil, networkProtectionAccessManager: nil) + DefaultNetworkProtectionVisibility(networkProtectionTokenStore: nil, + networkProtectionAccessManager: nil, + accountManager: AppDelegate.accountManager) } func isWaitlistBetaActive() -> Bool { @@ -89,6 +96,22 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { func shouldMonitorEntitlement() -> Bool { isPrivacyProLaunched() } + + func shouldShowThankYouMessaging() -> Bool { + isPrivacyProLaunched() && isWaitlistUser() + } + + func shouldKeepVPNAccessViaWaitlist() -> Bool { + !isPrivacyProLaunched() && isWaitlistBetaActive() && isWaitlistUser() + } + + func shouldShowVPNShortcut() -> Bool { + if isPrivacyProLaunched() { + return accountManager.isUserAuthenticated + } else { + return shouldKeepVPNAccessViaWaitlist() + } + } } #endif diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index 755b8ba95a..196aa0968a 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -278,7 +278,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { enableSource: .init(from: accessManager.networkProtectionAccessType()), betaParticipant: accessType == .waitlistInvited, hasToken: hasToken, - subscriptionActive: AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).isUserAuthenticated + subscriptionActive: AppDelegate.accountManager.isUserAuthenticated ) } } diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index a52fa424c7..d44327e55e 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -248,7 +248,7 @@ extension MainViewController { tabManager: tabManager) let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, - accountManager: AccountManager(), + accountManager: AppDelegate.accountManager, deepLink: deepLinkTarget) Pixel.fire(pixel: .settingsPresented, diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 2e1c9f8e02..2ee24eea32 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1417,7 +1417,7 @@ class MainViewController: UIViewController { @objc private func onEntitlementsChange(_ notification: Notification) { Task { - guard case .success(false) = await AccountManager().hasEntitlement(for: .networkProtection) else { return } + guard case .success(false) = await AppDelegate.accountManager.hasEntitlement(for: .networkProtection) else { return } let controller = NetworkProtectionTunnelController() diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index dcc76a9ac6..f9a0594015 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -57,12 +57,13 @@ extension ConnectionServerInfoObserverThroughSession { } extension NetworkProtectionKeychainTokenStore { + convenience init() { let featureVisibility = DefaultNetworkProtectionVisibility.forTokenStore() let isSubscriptionEnabled = featureVisibility.isPrivacyProLaunched() let accessTokenProvider: () -> String? = { if featureVisibility.shouldMonitorEntitlement() { - return { AccountManager().accessToken } + return { AppDelegate.accountManager.accessToken } } return { nil } }() @@ -83,7 +84,7 @@ extension NetworkProtectionCodeRedemptionCoordinator { tokenStore: NetworkProtectionKeychainTokenStore(), isManualCodeRedemptionFlow: isManualCodeRedemptionFlow, errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: DefaultNetworkProtectionVisibility().isPrivacyProLaunched() + isSubscriptionEnabled: DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isPrivacyProLaunched() ) } } @@ -104,7 +105,7 @@ extension NetworkProtectionLocationListCompositeRepository { environment: settings.selectedEnvironment, tokenStore: NetworkProtectionKeychainTokenStore(), errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: DefaultNetworkProtectionVisibility().isPrivacyProLaunched() + isSubscriptionEnabled: DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isPrivacyProLaunched() ) } } diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 837d962442..4b56f259c6 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -35,7 +35,6 @@ import NetworkExtension import NetworkProtection import Subscription - // swiftlint:disable:next type_body_length final class NetworkProtectionDebugViewController: UITableViewController { private let titles = [ @@ -138,21 +137,24 @@ final class NetworkProtectionDebugViewController: UITableViewController { private var connectionTestResults: [ConnectionTestResult] = [] private var connectionTestResultError: String? private let connectionTestQueue = DispatchQueue(label: "com.duckduckgo.ios.vpnDebugConnectionTestQueue") + private let accountManager: AccountManaging // MARK: Lifecycle - init?(coder: NSCoder, - tokenStore: NetworkProtectionTokenStore, - debugFeatures: NetworkProtectionDebugFeatures = NetworkProtectionDebugFeatures()) { - + required init?(coder: NSCoder, + tokenStore: NetworkProtectionTokenStore, + debugFeatures: NetworkProtectionDebugFeatures = NetworkProtectionDebugFeatures(), + accountManager: AccountManaging) { + self.debugFeatures = debugFeatures self.tokenStore = tokenStore + self.accountManager = accountManager super.init(coder: coder) } required convenience init?(coder: NSCoder) { - self.init(coder: coder, tokenStore: NetworkProtectionKeychainTokenStore()) + self.init(coder: coder, tokenStore: NetworkProtectionKeychainTokenStore(), accountManager: AppDelegate.accountManager) } override func viewWillAppear(_ animated: Bool) { @@ -642,7 +644,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { cell.textLabel?.text = "Subscription Override: N/A" } case .debugInfo: - let vpnVisibility = DefaultNetworkProtectionVisibility() + let vpnVisibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) cell.textLabel?.font = .monospacedSystemFont(ofSize: 13.0, weight: .regular) cell.textLabel?.text = """ @@ -692,7 +694,7 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") if let subscriptionOverrideEnabled = defaults.subscriptionOverrideEnabled { if subscriptionOverrideEnabled { defaults.subscriptionOverrideEnabled = false - AccountManager().signOut() + accountManager.signOut() } else { defaults.resetsubscriptionOverrideEnabled() } diff --git a/DuckDuckGo/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/NetworkProtectionFeatureVisibility.swift index b0d1be1cce..8ee0e79ac4 100644 --- a/DuckDuckGo/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/NetworkProtectionFeatureVisibility.swift @@ -21,6 +21,7 @@ import Foundation import Subscription public protocol NetworkProtectionFeatureVisibility { + func isWaitlistBetaActive() -> Bool func isWaitlistUser() -> Bool func isPrivacyProLaunched() -> Bool @@ -40,22 +41,3 @@ public protocol NetworkProtectionFeatureVisibility { /// Whether to show VPN shortcut on the homescreen func shouldShowVPNShortcut() -> Bool } - -public extension NetworkProtectionFeatureVisibility { - func shouldShowThankYouMessaging() -> Bool { - isPrivacyProLaunched() && isWaitlistUser() - } - - func shouldKeepVPNAccessViaWaitlist() -> Bool { - !isPrivacyProLaunched() && isWaitlistBetaActive() && isWaitlistUser() - } - - func shouldShowVPNShortcut() -> Bool { - if isPrivacyProLaunched() { - let accountManager = AccountManager() - return accountManager.isUserAuthenticated - } else { - return shouldKeepVPNAccessViaWaitlist() - } - } -} diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 4fd5c58998..0582f9199d 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -32,7 +32,7 @@ struct NetworkProtectionRootView: View { redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator(isManualCodeRedemptionFlow: true), completion: inviteCompletion ) - if DefaultNetworkProtectionVisibility().isPrivacyProLaunched() { + if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isPrivacyProLaunched() { NetworkProtectionStatusView( statusModel: NetworkProtectionStatusViewModel() ) diff --git a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift index dce270a1a5..88bf3b3856 100644 --- a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift @@ -23,6 +23,13 @@ import Foundation import Subscription struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVisibility { + + private let accountManager: AccountManaging + + init(accountManager: AccountManaging) { + self.accountManager = accountManager + } + func isWaitlistBetaActive() -> Bool { preconditionFailure("Does not apply to Tunnel Provider") } @@ -32,12 +39,28 @@ struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVis } func isPrivacyProLaunched() -> Bool { - AccountManager().isUserAuthenticated + accountManager.isUserAuthenticated } func shouldMonitorEntitlement() -> Bool { isPrivacyProLaunched() } + + func shouldShowThankYouMessaging() -> Bool { + isPrivacyProLaunched() && isWaitlistUser() + } + + func shouldKeepVPNAccessViaWaitlist() -> Bool { + !isPrivacyProLaunched() && isWaitlistBetaActive() && isWaitlistUser() + } + + func shouldShowVPNShortcut() -> Bool { + if isPrivacyProLaunched() { + return accountManager.isUserAuthenticated + } else { + return shouldKeepVPNAccessViaWaitlist() + } + } } #endif diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index d83856a29b..bf352c03a0 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -27,6 +27,7 @@ import Common import Configuration import Persistence import DDGSync +import NetworkProtection class RootDebugViewController: UITableViewController { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index a69d774972..1399fd36b7 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -46,7 +46,7 @@ final class SettingsViewModel: ObservableObject { var emailManager: EmailManager { EmailManager() } // Subscription Dependencies - private var accountManager: AccountManager + private var accountManager: AccountManaging private var signOutObserver: Any? #if NETWORK_PROTECTION @@ -384,7 +384,7 @@ final class SettingsViewModel: ObservableObject { // MARK: Default Init init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider, - accountManager: AccountManager, + accountManager: AccountManaging, voiceSearchHelper: VoiceSearchHelperProtocol = AppDependencyProvider.shared.voiceSearchHelper, variantManager: VariantManager = AppDependencyProvider.shared.variantManager, deepLink: SettingsDeepLinkSection? = nil) { @@ -447,7 +447,7 @@ extension SettingsViewModel { var enabled = false #if NETWORK_PROTECTION if #available(iOS 15, *) { - enabled = DefaultNetworkProtectionVisibility().shouldKeepVPNAccessViaWaitlist() + enabled = DefaultNetworkProtectionVisibility(accountManager: accountManager).shouldKeepVPNAccessViaWaitlist() } #endif return SettingsState.NetworkProtection(enabled: enabled, status: "") @@ -486,7 +486,7 @@ extension SettingsViewModel { #if NETWORK_PROTECTION private func updateNetPStatus(connectionStatus: ConnectionStatus) { - if DefaultNetworkProtectionVisibility().isPrivacyProLaunched() { + if DefaultNetworkProtectionVisibility(accountManager: accountManager).isPrivacyProLaunched() { switch connectionStatus { case .connected: self.state.networkProtection.status = UserText.netPCellConnected @@ -765,7 +765,7 @@ extension SettingsViewModel { // Check entitlements and update state let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] for entitlement in entitlements { - if case .success = await AccountManager().hasEntitlement(for: entitlement) { + if case .success = await accountManager.hasEntitlement(for: entitlement) { switch entitlement { case .identityTheftRestoration: self.state.subscription.entitlements.append(.identityTheftRestoration) @@ -800,7 +800,7 @@ extension SettingsViewModel { @available(iOS 15.0, *) private func signOutUser() { - AccountManager().signOut() + accountManager.signOut() setupSubscriptionPurchaseOptions() } @@ -825,7 +825,8 @@ extension SettingsViewModel { @available(iOS 15.0, *) func restoreAccountPurchase() async { DispatchQueue.main.async { self.state.subscription.isRestoring = true } - 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: DispatchQueue.main.async { diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index 1f1e1b7a37..5ee23bddb5 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -43,7 +43,12 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { static let getAccessToken = "getAccessToken" } - + private let accountManager: AccountManaging + + init(accountManager: AccountManaging) { + self.accountManager = accountManager + } + weak var broker: UserScriptMessageBroker? var featureName: String = Constants.featureName @@ -67,7 +72,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = AccountManager().accessToken { + if let accessToken = accountManager.accessToken { return [Constants.token: accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 2a282230c4..d355820e34 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -90,6 +90,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec generalError } + private let accountManager: AccountManaging + private let appStorePurchaseFlow: AppStorePurchaseFlow + + init(accountManager: AccountManaging) { + self.accountManager = accountManager + self.appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + } + // Transaction Status and errors are observed from ViewModels to handle errors in the UI @Published private(set) var transactionStatus: SubscriptionTransactionStatus = .idle @Published private(set) var transactionError: UseSubscriptionError? @@ -168,14 +176,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // MARK: Broker Methods (Called from WebView via UserScripts) func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { - let authToken = AccountManager().authToken ?? Constants.empty + let authToken = accountManager.authToken ?? Constants.empty return [Constants.token: authToken] } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { resetSubscriptionFlow() - switch await AppStorePurchaseFlow.subscriptionOptions() { + switch await appStorePurchaseFlow.subscriptionOptions() { case .success(let subscriptionOptions): if AppDependencyProvider.shared.subscriptionFeatureAvailability.isSubscriptionPurchaseAllowed { return subscriptionOptions @@ -219,9 +227,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String - switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, - emailAccessToken: emailAccessToken, - subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { + switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, + emailAccessToken: emailAccessToken) { case .success(let transactionJWS): purchaseTransactionJWS = transactionJWS @@ -244,8 +251,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } setTransactionStatus(.polling) - switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, - subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { + switch await appStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { case .success(let purchaseUpdate): DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseSuccess) UniquePixel.fire(pixel: .privacyProSubscriptionActivated) @@ -268,7 +274,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } let authToken = subscriptionValues.token - let accountManager = AccountManager() if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { accountManager.storeAuthToken(token: authToken) @@ -306,7 +311,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func backToSettings(params: Any, original: WKScriptMessage) async -> Encodable? { - let accountManager = AccountManager() if let accessToken = accountManager.accessToken, case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { switch await SubscriptionService.getSubscription(accessToken: accessToken) { @@ -374,7 +378,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func restoreAccountFromAppStorePurchase() async throws { setTransactionStatus(.restoring) - 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: setTransactionStatus(.idle) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift index a30d527090..747190fc86 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift @@ -19,25 +19,24 @@ import Foundation import Combine +import Subscription @available(iOS 15.0, *) final class SubscriptionContainerViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature - + let flow: SubscriptionFlowViewModel let restore: SubscriptionRestoreViewModel let email: SubscriptionEmailViewModel - - - init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), - subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature()) { - self.userScript = userScript - self.subFeature = subFeature + + init(accountManager: AccountManaging) { + self.userScript = SubscriptionPagesUserScript() + self.subFeature = SubscriptionPagesUseSubscriptionFeature(accountManager: accountManager) self.flow = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) - self.restore = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) - self.email = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) + self.restore = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature, accountManager: accountManager) + self.email = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature, accountManager: accountManager) } deinit { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 0a53d953e0..f5723d5abe 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -26,7 +26,7 @@ import Subscription @available(iOS 15.0, *) final class SubscriptionEmailViewModel: ObservableObject { - let accountManager: AccountManager + let accountManager: AccountManaging let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature @@ -77,7 +77,7 @@ final class SubscriptionEmailViewModel: ObservableObject { init(userScript: SubscriptionPagesUserScript, subFeature: SubscriptionPagesUseSubscriptionFeature, - accountManager: AccountManager = AccountManager()) { + accountManager: AccountManaging) { self.userScript = userScript self.subFeature = subFeature self.accountManager = accountManager diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index a9ac238d38..fc03f0f201 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -60,12 +60,11 @@ final class SubscriptionITPViewModel: ObservableObject { private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? - - init(userScript: IdentityTheftRestorationPagesUserScript = IdentityTheftRestorationPagesUserScript(), - subFeature: IdentityTheftRestorationPagesFeature = IdentityTheftRestorationPagesFeature()) { - self.userScript = userScript - self.subFeature = subFeature - + + init(accountManager: AccountManager) { + self.userScript = IdentityTheftRestorationPagesUserScript() + self.subFeature = IdentityTheftRestorationPagesFeature(accountManager: accountManager) + let webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: Self.allowedDomains, contentBlocking: false) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index f025776f7e..3d68efdfc2 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -29,7 +29,8 @@ final class SubscriptionRestoreViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature let purchaseManager: PurchaseManager - let accountManager: AccountManager + let accountManager: AccountManaging + let appStoreAccountManagementFlow: AppStoreAccountManagementFlow private var cancellables = Set() @@ -59,12 +60,13 @@ final class SubscriptionRestoreViewModel: ObservableObject { init(userScript: SubscriptionPagesUserScript, subFeature: SubscriptionPagesUseSubscriptionFeature, purchaseManager: PurchaseManager = PurchaseManager.shared, - accountManager: AccountManager = AccountManager(), + accountManager: AccountManaging, isAddingDevice: Bool = false) { self.userScript = userScript self.subFeature = subFeature self.purchaseManager = purchaseManager self.accountManager = accountManager + self.appStoreAccountManagementFlow = AppStoreAccountManagementFlow(accountManager: accountManager) self.state.isAddingDevice = false } @@ -84,10 +86,10 @@ final class SubscriptionRestoreViewModel: ObservableObject { private func cleanUp() { cancellables.removeAll() } - + private func refreshToken() async { if state.isAddingDevice { - await AppStoreAccountManagementFlow.refreshAuthTokenIfNeeded(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded() } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 06e9af978c..c95548e2cd 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -26,7 +26,7 @@ import Core @available(iOS 15.0, *) final class SubscriptionSettingsViewModel: ObservableObject { - let accountManager: AccountManager + let accountManager: AccountManaging private var subscriptionUpdateTimer: Timer? private var signOutObserver: Any? private var subscriptionInfo: SubscriptionService.GetSubscriptionResponse? @@ -56,7 +56,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { @Published private(set) var state = State() - init(accountManager: AccountManager = AccountManager()) { + init(accountManager: AccountManaging) { self.accountManager = accountManager setupSubscriptionUpdater() setupNotificationObservers() @@ -130,7 +130,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func removeSubscription() { - AccountManager().signOut() + accountManager.signOut() _ = ActionMessageView() ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, presentationLocation: .withoutBottomBar) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift index 57e5822f37..b981f3858c 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift @@ -35,12 +35,11 @@ struct SubscriptionContainerView: View { private let restoreViewModel: SubscriptionRestoreViewModel private let emailViewModel: SubscriptionEmailViewModel - init(currentView: CurrentView, - viewModel: SubscriptionContainerViewModel = SubscriptionContainerViewModel()) { + init(currentView: CurrentView) { _currentViewState = State(initialValue: currentView) - self.viewModel = viewModel - let userScript = viewModel.userScript - let subFeature = viewModel.subFeature + self.viewModel = SubscriptionContainerViewModel(accountManager: AppDelegate.accountManager) +// let userScript = viewModel.userScript +// let subFeature = viewModel.subFeature flowViewModel = viewModel.flow restoreViewModel = viewModel.restore emailViewModel = viewModel.email diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index 649284eda8..a9d4c6fcea 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -36,7 +36,7 @@ struct SubscriptionActivityViewController: UIViewControllerRepresentable { struct SubscriptionITPView: View { @Environment(\.dismiss) var dismiss - @StateObject var viewModel = SubscriptionITPViewModel() + @StateObject var viewModel = SubscriptionITPViewModel(accountManager: AppDelegate.accountManager) @State private var shouldShowNavigationBar = false @State private var isShowingActivityView = false diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index 9d57d27765..1bff6b95fa 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -26,7 +26,7 @@ import Core struct SubscriptionSettingsView: View { @Environment(\.dismiss) var dismiss - @StateObject var viewModel = SubscriptionSettingsViewModel() + @StateObject var viewModel = SubscriptionSettingsViewModel(accountManager: AppDelegate.accountManager) @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator @State var isShowingStripeView = false diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 718d7a0b97..17238c4e72 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -29,7 +29,7 @@ import NetworkProtection @available(iOS 15.0, *) final class SubscriptionDebugViewController: UITableViewController { - private let accountManager = AccountManager() + private let accountManager: AccountManaging = AppDelegate.accountManager fileprivate var purchaseManager: PurchaseManager = PurchaseManager.shared @UserDefaultsWrapper(key: .privacyProEnvironment, defaultValue: SubscriptionPurchaseEnvironment.ServiceEnvironment.default.description) @@ -271,7 +271,7 @@ final class SubscriptionDebugViewController: UITableViewController { } let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] for entitlement in entitlements { - if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement, cachePolicy: .reloadIgnoringLocalCacheData) { + if case let .success(result) = await accountManager.hasEntitlement(for: entitlement, cachePolicy: .reloadIgnoringLocalCacheData) { let resultSummary = "Entitlement check for \(entitlement.rawValue): \(result)" results.append(resultSummary) print(resultSummary) @@ -284,7 +284,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func setEnvironment(_ environment: SubscriptionPurchaseEnvironment.ServiceEnvironment) { if environment.description != privacyProEnvironment { - AccountManager().signOut() + accountManager.signOut() // Update Subscription environment privacyProEnvironment = environment.rawValue diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 7eff1b1b18..e404747664 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -34,6 +34,7 @@ import Subscription final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { private var cancellables = Set() + private static let accountManager: AccountManaging = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) // MARK: - PacketTunnelProvider.Event reporting @@ -253,13 +254,13 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } @objc init() { - let featureVisibility = NetworkProtectionVisibilityForTunnelProvider() + let featureVisibility = NetworkProtectionVisibilityForTunnelProvider(accountManager: NetworkProtectionPacketTunnelProvider.accountManager) let isSubscriptionEnabled = featureVisibility.isPrivacyProLaunched() let accessTokenProvider: () -> String? = { - if featureVisibility.shouldMonitorEntitlement() { - return { AccountManager().accessToken } - } - return { nil } + if featureVisibility.shouldMonitorEntitlement() { + return { NetworkProtectionPacketTunnelProvider.accountManager.accessToken } + } + return { nil } }() let tokenStore = NetworkProtectionKeychainTokenStore( keychainType: .dataProtection(.unspecified), @@ -335,7 +336,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } private static func entitlementCheck() async -> Result { - guard NetworkProtectionVisibilityForTunnelProvider().shouldMonitorEntitlement() else { + let accountManager = NetworkProtectionPacketTunnelProvider.accountManager + guard NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager).shouldMonitorEntitlement() else { return .success(true) } @@ -343,8 +345,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging } - let result = await AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - .hasEntitlement(for: .networkProtection) + let result = await accountManager.hasEntitlement(for: .networkProtection) switch result { case .success(let hasEntitlement): return .success(hasEntitlement) From 199844e54cf3d8da9a196dea91bad0636d4a4856 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 2 May 2024 15:12:22 +0100 Subject: [PATCH 02/39] .profraw ignored --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d460333497..6977dc0ec9 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ Configuration/ExternalDeveloper.xcconfig scripts/assets DuckDuckGoTests/NetworkProtectionVPNLocationViewModelTests.swift*.plist +*.profraw From df39472ce31cec075d9e49829f71332b23ef3288 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 3 May 2024 14:44:34 +0100 Subject: [PATCH 03/39] BSK as branch --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1fb2bc912f..683b0ed9ca 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9715,8 +9715,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 143.0.1; + branch = fcappelli/subscription_refactoring; + kind = branch; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { From 24d28b1c7a2da13b423016425b5f2d364b50195e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 May 2024 09:24:47 +0100 Subject: [PATCH 04/39] DI #1 --- DuckDuckGo.xcodeproj/project.pbxproj | 7 ++ DuckDuckGo/AppDelegate.swift | 98 +++++++++++++------ DuckDuckGo/AppDependencyProvider.swift | 4 +- .../DefaultNetworkProtectionVisibility.swift | 8 +- DuckDuckGo/MainViewController+Segues.swift | 2 +- DuckDuckGo/MainViewController.swift | 2 +- DuckDuckGo/NetworkProtectionRootView.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 35 ++++--- ...scriptionPagesUseSubscriptionFeature.swift | 27 ++--- .../ViewModel/SubscriptionFlowViewModel.swift | 33 +++---- .../SubscriptionRestoreViewModel.swift | 14 +-- .../SubscriptionSettingsViewModel.swift | 18 ++-- .../Views/SubscriptionSettingsView.swift | 2 +- .../SubscriptionDebugViewController.swift | 19 ++-- DuckDuckGo/UserDefaultsCacheKey.swift | 6 +- 15 files changed, 162 insertions(+), 115 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 64c4c3d0d5..d2935926de 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -865,6 +865,7 @@ F1564F032B7B915F00D454A6 /* AppDelegate+SKAD4.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */; }; F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */; }; F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */; }; + F15E9F3C2BECFCFD00DEFDDE /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15E9F3B2BECFCFD00DEFDDE /* SubscriptionTestingUtilities */; }; F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C141E57336D00DEDCAF /* TabManager.swift */; }; F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */; }; @@ -2653,6 +2654,7 @@ F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, EEFAB4672A73C230008A38E4 /* NetworkProtectionTestUtils in Frameworks */, 4BE67B072B96B9B0007335F7 /* Common in Frameworks */, + F15E9F3C2BECFCFD00DEFDDE /* SubscriptionTestingUtilities in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5511,6 +5513,7 @@ F115ED9B2B4EFC8E001A0453 /* TestUtils */, 4BE67B042B96B9AB007335F7 /* ContentBlocking */, 4BE67B062B96B9B0007335F7 /* Common */, + F15E9F3B2BECFCFD00DEFDDE /* SubscriptionTestingUtilities */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -9965,6 +9968,10 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = TestUtils; }; + F15E9F3B2BECFCFD00DEFDDE /* SubscriptionTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + productName = SubscriptionTestingUtilities; + }; F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */ = { isa = XCSwiftPackageProductDependency; package = F1D43AF82B99C1D300BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */; diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 426f612754..b74f844ed9 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -47,6 +47,13 @@ import WebKit class AppDelegate: UIResponder, UIApplicationDelegate { // swiftlint:enable type_body_length + static func appDelegate() -> AppDelegate { + guard let delegate = UIApplication.shared.delegate as? AppDelegate else { + fatalError("could not get app delegate ") + } + return delegate + } + private static let ShowKeyboardOnLaunchThreshold = TimeInterval(20) private struct ShortcutKey { @@ -69,7 +76,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults - lazy var vpnFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) + let vpnFeatureVisibility: DefaultNetworkProtectionVisibility #endif private var autoClear: AutoClear? @@ -89,11 +96,36 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? - - @UserDefaultsWrapper(key: .privacyProEnvironment, defaultValue: SubscriptionPurchaseEnvironment.ServiceEnvironment.default.description) - private var privacyProEnvironment: String + public let subscriptionManager: SubscriptionManaging? + private var accountManager: AccountManaging? { + subscriptionManager?.accountManager + } - public static let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + override 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(iOS 15.0, *) { + subscriptionManager = SubscriptionManager(storePurchaseManager: StorePurchaseManager(), + accountManager: accountManager, + subscriptionService: subscriptionService, + authService: authService, + subscriptionEnvironment: subscriptionEnvironment) + } + + vpnFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) + } // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { @@ -319,8 +351,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { widgetRefreshModel.beginObservingVPNStatus() NetworkProtectionAccessController().refreshNetworkProtectionAccess() #endif - - setupSubscriptionsEnvironment() + +// setupSubscriptionsEnvironment() if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { clearDebugWaitlistState() @@ -333,6 +365,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } + @available(iOS 15.0, *) + public func getSubscriptionManager() -> SubscriptionManaging { + subscriptionManager! + } + private func prepareTabsModel(previewsSource: TabPreviewsSource = TabPreviewsSource(), appSettings: AppSettings = AppDependencyProvider.shared.appSettings, isDesktop: Bool = UIDevice.current.userInterfaceIdiom == .pad) -> TabsModel { @@ -425,20 +462,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - - private func setupSubscriptionsEnvironment() { - Task { - #if ALPHA || DEBUG - let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.staging - #else - let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.production - #endif - let environment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: privacyProEnvironment) ?? defaultEnvironment - SubscriptionPurchaseEnvironment.currentServiceEnvironment = environment - VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = (environment == .production) ? .production : .staging - SubscriptionPurchaseEnvironment.current = .appStore - } - } +// private func setupSubscriptionsEnvironment() { +// Task { +// #if ALPHA || DEBUG +// let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.staging +// #else +// let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.production +// #endif +// let environment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: privacyProEnvironment) ?? defaultEnvironment +// SubscriptionPurchaseEnvironment.currentServiceEnvironment = environment +// VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = (environment == .production) ? .production : .staging +// SubscriptionPurchaseEnvironment.current = .appStore +// } +// } private func reportAdAttribution() { guard AdAttributionPixelReporter.isAdAttributionReportingEnabled else { return } @@ -516,7 +552,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func stopTunnelAndShowThankYouMessagingIfNeeded() { - if AppDelegate.accountManager.isUserAuthenticated { + + if let accountManager, + accountManager.isUserAuthenticated { tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true return } @@ -525,7 +563,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Task { await self.stopAndRemoveVPN(with: "thank-you-dialog") } - } else if vpnFeatureVisibility.isPrivacyProLaunched() && !AppDelegate.accountManager.isUserAuthenticated { + } else if let accountManager, + vpnFeatureVisibility.isPrivacyProLaunched() && !accountManager.isUserAuthenticated { Task { await self.stopAndRemoveVPN(with: "subscription-check") } @@ -551,16 +590,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func updateSubscriptionStatus() { Task { - guard let token = AppDelegate.accountManager.accessToken else { return } + guard let token = accountManager?.accessToken else { return } - if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, - cachePolicy: .reloadIgnoringLocalCacheData) { + if case .success(let subscription) = await subscriptionManager?.subscriptionService.getSubscription(accessToken: token, + cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { DailyPixel.fire(pixel: .privacyProSubscriptionActive) } } - - _ = await AppDelegate.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) + await accountManager?.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } } @@ -858,7 +896,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION if shortcutItem.type == ShortcutKey.openVPNSettings { - let visibility = DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager) + let visibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) if visibility.shouldShowVPNShortcut() { presentNetworkProtectionStatusSettingsModal() } @@ -971,7 +1009,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { #if NETWORK_PROTECTION if NetworkProtectionNotificationIdentifier(rawValue: identifier) != nil { Task { - if case .success(let hasEntitlements) = await AppDelegate.accountManager.hasEntitlement(for: .networkProtection), + if case .success(let hasEntitlements) = await accountManager?.hasEntitlement(for: .networkProtection), hasEntitlements { presentNetworkProtectionStatusSettingsModal() } diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 09281657e9..da72ff4bb7 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -72,9 +72,9 @@ class AppDependencyProvider: DependencyProvider { let toggleProtectionsCounter: ToggleProtectionsCounter = ContentBlocking.shared.privacyConfigurationManager.toggleProtectionsCounter let userBehaviorMonitor = UserBehaviorMonitor() - + let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, - purchasePlatform: .appStore) + subscriptionPlatform: .appStore) } diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index 35c71c9d8b..e2409161c1 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -32,14 +32,14 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { private let networkProtectionAccessManager: NetworkProtectionAccess? private let featureFlagger: FeatureFlagger private let userDefaults: UserDefaults - private let accountManager: AccountManaging + private let accountManager: AccountManaging? init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, networkProtectionTokenStore: NetworkProtectionTokenStore? = NetworkProtectionKeychainTokenStore(), networkProtectionAccessManager: NetworkProtectionAccess? = NetworkProtectionAccessController(), featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger, userDefaults: UserDefaults = .networkProtectionGroupDefaults, - accountManager: AccountManaging) { + accountManager: AccountManaging?) { self.privacyConfigurationManager = privacyConfigurationManager self.networkProtectionTokenStore = networkProtectionTokenStore @@ -54,7 +54,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { static func forTokenStore() -> DefaultNetworkProtectionVisibility { DefaultNetworkProtectionVisibility(networkProtectionTokenStore: nil, networkProtectionAccessManager: nil, - accountManager: AppDelegate.accountManager) + accountManager: AppDelegate.appDelegate().getSubscriptionManager().accountManager) } func isWaitlistBetaActive() -> Bool { @@ -107,7 +107,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { func shouldShowVPNShortcut() -> Bool { if isPrivacyProLaunched() { - return accountManager.isUserAuthenticated + return accountManager?.isUserAuthenticated ?? false } else { return shouldKeepVPNAccessViaWaitlist() } diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index d44327e55e..3123011ea3 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -248,7 +248,7 @@ extension MainViewController { tabManager: tabManager) let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, - accountManager: AppDelegate.accountManager, + subscriptionManager: AppDelegate.appDelegate().subscriptionManager, deepLink: deepLinkTarget) Pixel.fire(pixel: .settingsPresented, diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 2ee24eea32..b4a009a76a 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1417,7 +1417,7 @@ class MainViewController: UIViewController { @objc private func onEntitlementsChange(_ notification: Notification) { Task { - guard case .success(false) = await AppDelegate.accountManager.hasEntitlement(for: .networkProtection) else { return } + guard case .success(false) = await AppDelegate.appDelegate().subscriptionManager?.accountManager.hasEntitlement(for: .networkProtection) else { return } let controller = NetworkProtectionTunnelController() diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 0582f9199d..4a0ec4a53b 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -32,7 +32,7 @@ struct NetworkProtectionRootView: View { redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator(isManualCodeRedemptionFlow: true), completion: inviteCompletion ) - if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isPrivacyProLaunched() { + if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.appDelegate().subscriptionManager?.accountManager).isPrivacyProLaunched() { NetworkProtectionStatusView( statusModel: NetworkProtectionStatusViewModel() ) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 4cdfdf3a66..2bc968604e 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -34,7 +34,6 @@ import NetworkProtection // swiftlint:disable type_body_length final class SettingsViewModel: ObservableObject { - // Dependencies private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings private(set) var privacyStore = PrivacyUserDefaults() @@ -46,11 +45,14 @@ final class SettingsViewModel: ObservableObject { var emailManager: EmailManager { EmailManager() } // Subscription Dependencies - private var subscriptionAccountManager: AccountManager + private let subscriptionManager: SubscriptionManaging? private var subscriptionSignOutObserver: Any? + private enum UserDefaultsCacheKey: String, UserDefaultsCacheKeyStore { + case subscriptionState = "com.duckduckgo.ios.subscription.state" + } // Used to cache the lasts subscription state for up to a week - private var subscriptionStateCache = UserDefaultsCache(key: UserDefaultsCacheKey.subscriptionState, + private let subscriptionStateCache = UserDefaultsCache(key: UserDefaultsCacheKey.subscriptionState, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .days(7))) #if NETWORK_PROTECTION @@ -388,13 +390,13 @@ final class SettingsViewModel: ObservableObject { // MARK: Default Init init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider, - accountManager: AccountManager, + subscriptionManager: SubscriptionManaging?, voiceSearchHelper: VoiceSearchHelperProtocol = AppDependencyProvider.shared.voiceSearchHelper, variantManager: VariantManager = AppDependencyProvider.shared.variantManager, deepLink: SettingsDeepLinkSection? = nil) { self.state = SettingsState.defaults self.legacyViewProvider = legacyViewProvider - self.subscriptionAccountManager = accountManager + self.subscriptionManager = subscriptionManager self.voiceSearchHelper = voiceSearchHelper self.deepLinkTarget = deepLink @@ -444,14 +446,13 @@ extension SettingsViewModel { setupSubscribers() Task { await setupSubscriptionEnvironment() } - } private func getNetworkProtectionState() -> SettingsState.NetworkProtection { var enabled = false #if NETWORK_PROTECTION - if #available(iOS 15, *) { - enabled = DefaultNetworkProtectionVisibility(accountManager: subscriptionAccountManager).shouldKeepVPNAccessViaWaitlist() + if #available(iOS 15, *), let subscriptionManager { + enabled = DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).shouldKeepVPNAccessViaWaitlist() } #endif return SettingsState.NetworkProtection(enabled: enabled, status: "") @@ -490,7 +491,8 @@ extension SettingsViewModel { #if NETWORK_PROTECTION private func updateNetPStatus(connectionStatus: ConnectionStatus) { - if DefaultNetworkProtectionVisibility(accountManager: subscriptionAccountManager).isPrivacyProLaunched() { + if let subscriptionManager, + DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).isPrivacyProLaunched() { switch connectionStatus { case .connected: self.state.networkProtection.status = UserText.netPCellConnected @@ -742,7 +744,11 @@ extension SettingsViewModel { @MainActor private func setupSubscriptionEnvironment() async { - + + guard let subscriptionManager else { + return + } + // If there's cached data use it by default if let cachedSubscription = subscriptionStateCache.get() { state.subscription = cachedSubscription @@ -755,15 +761,15 @@ extension SettingsViewModel { state.subscription.enabled = AppDependencyProvider.shared.subscriptionFeatureAvailability.isFeatureAvailable // Update if can purchase based on App Store product availability - state.subscription.canPurchase = SubscriptionPurchaseEnvironment.canPurchase + state.subscription.canPurchase = subscriptionManager.canPurchase // Active subscription check - guard let token = subscriptionAccountManager.accessToken else { + guard let token = subscriptionManager.accountManager.accessToken else { subscriptionStateCache.set(state.subscription) // Sync cache return } - let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token) + let subscriptionResult = await subscriptionManager.subscriptionService.getSubscription(accessToken: token) switch subscriptionResult { case .success(let subscription): @@ -797,7 +803,6 @@ extension SettingsViewModel { case .failure: break - } // Sync Cache @@ -821,7 +826,7 @@ extension SettingsViewModel { @available(iOS 15.0, *) func restoreAccountPurchase() async { DispatchQueue.main.async { self.state.subscription.isRestoring = true } - let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: subscriptionAccountManager) + let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 8697c42058..8588e6964f 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -91,12 +91,15 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec generalError } - private let accountManager: AccountManaging + private let subscriptionManager: SubscriptionManaging + private var accountManager: AccountManaging { + subscriptionManager.accountManager + } private let appStorePurchaseFlow: AppStorePurchaseFlow - init(accountManager: AccountManaging) { - self.accountManager = accountManager - self.appStorePurchaseFlow = AppStorePurchaseFlow(accountManager: accountManager) + init(subscriptionManager: SubscriptionManaging) { + self.subscriptionManager = subscriptionManager + self.appStorePurchaseFlow = AppStorePurchaseFlow(subscriptionManager: subscriptionManager) } // Transaction Status and errors are observed from ViewModels to handle errors in the UI @@ -185,15 +188,13 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { resetSubscriptionFlow() - - switch await appStorePurchaseFlow.subscriptionOptions() { - case .success(let subscriptionOptions): + if let subscriptionOptions = await subscriptionManager.getStorePurchaseManager().subscriptionOptions() { if AppDependencyProvider.shared.subscriptionFeatureAvailability.isSubscriptionPurchaseAllowed { return subscriptionOptions } else { return SubscriptionOptions.empty } - case .failure: + } else { os_log("Failed to obtain subscription options", log: .subscription, type: .error) setTransactionError(.failedToGetSubscriptionOptions) return nil @@ -220,7 +221,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Check for active subscriptions - if await PurchaseManager.hasActiveSubscription() { + if await subscriptionManager.getStorePurchaseManager().hasActiveSubscription() { setTransactionError(.hasActiveSubscription) Pixel.fire(pixel: .privacyProRestoreAfterPurchaseAttempt) setTransactionStatus(.idle) @@ -277,8 +278,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Clear subscription Cache - SubscriptionService.signOut() - + subscriptionManager.subscriptionService.signOut() + let authToken = subscriptionValues.token if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { @@ -320,7 +321,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func backToSettings(params: Any, original: WKScriptMessage) async -> Encodable? { if let accessToken = accountManager.accessToken, case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - switch await SubscriptionService.getSubscription(accessToken: accessToken) { + switch await subscriptionManager.subscriptionService.getSubscription(accessToken: accessToken) { case .success: accountManager.storeAccount(token: accessToken, @@ -393,7 +394,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func restoreAccountFromAppStorePurchase() async throws { setTransactionStatus(.restoring) - let appStoreRestoreFlow = AppStoreRestoreFlow(accountManager: accountManager) + let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 5aaf7ff3ce..e1c2160c73 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -24,16 +24,17 @@ import Core import Subscription @available(iOS 15.0, *) -// swiftlint:disable type_body_length +// swiftlint:disable:next type_body_length final class SubscriptionFlowViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature - let purchaseManager: PurchaseManager var webViewModel: AsyncHeadlessWebViewViewModel - - var purchaseURL = URL.subscriptionPurchase - + let subscriptionManager: SubscriptionManaging + var subscriptionServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment { + subscriptionManager.currentEnvironment.serviceEnvironment + } + private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? private var urlCancellable: AnyCancellable? @@ -71,11 +72,11 @@ final class SubscriptionFlowViewModel: ObservableObject { init(userScript: SubscriptionPagesUserScript, subFeature: SubscriptionPagesUseSubscriptionFeature, - purchaseManager: PurchaseManager = PurchaseManager.shared, + subscriptionManager: SubscriptionManaging, selectedFeature: SettingsViewModel.SettingsDeepLinkSection? = nil) { self.userScript = userScript self.subFeature = subFeature - self.purchaseManager = purchaseManager + self.subscriptionManager = subscriptionManager self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, subFeature: subFeature, settings: webViewSettings) @@ -233,9 +234,8 @@ final class SubscriptionFlowViewModel: ObservableObject { strongSelf.state.canNavigateBack = false guard let currentURL = self?.webViewModel.url else { return } Task { await strongSelf.setTransactionStatus(.idle) } - if currentURL.forComparison() == URL.addEmailToSubscription.forComparison() || - currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() || - currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() { + if currentURL.forComparison() == SubscriptionURL.addEmail.subscriptionURL(environment: strongSelf.subscriptionServiceEnvironment).forComparison() || + currentURL.forComparison() == SubscriptionURL.addEmailToSubscriptionSuccess.subscriptionURL(environment: strongSelf.subscriptionServiceEnvironment).forComparison() { strongSelf.state.viewTitle = UserText.subscriptionRestoreAddEmailTitle } else { strongSelf.state.viewTitle = UserText.subscriptionTitle @@ -243,11 +243,11 @@ final class SubscriptionFlowViewModel: ObservableObject { } } - + private func backButtonForURL(currentURL: URL) -> Bool { - return currentURL.forComparison() != URL.subscriptionBaseURL.forComparison() && - currentURL.forComparison() != URL.subscriptionActivateSuccess.forComparison() && - currentURL.forComparison() != URL.subscriptionPurchase.forComparison() + return currentURL.forComparison() != SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() && + currentURL.forComparison() != SubscriptionURL.activateSuccess.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() && + currentURL.forComparison() != SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() } private func cleanUp() { @@ -310,8 +310,8 @@ final class SubscriptionFlowViewModel: ObservableObject { DispatchQueue.main.async { self.resetState() } - if webViewModel.url != URL.subscriptionPurchase.forComparison() { - self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL) + if webViewModel.url != SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() { + self.webViewModel.navigationCoordinator.navigateTo(url: SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionServiceEnvironment)) } await self.setupTransactionObserver() await self.setupWebViewObservers() @@ -346,4 +346,3 @@ final class SubscriptionFlowViewModel: ObservableObject { } } -// swiftlint:enable type_body_length diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index f087c88834..bc84853d1e 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -28,8 +28,10 @@ final class SubscriptionRestoreViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature - let purchaseManager: PurchaseManager - let accountManager: AccountManaging + let subscriptionManager: SubscriptionManaging + var accountManager: AccountManaging { + subscriptionManager.accountManager + } let appStoreAccountManagementFlow: AppStoreAccountManagementFlow private var cancellables = Set() @@ -59,14 +61,12 @@ final class SubscriptionRestoreViewModel: ObservableObject { init(userScript: SubscriptionPagesUserScript, subFeature: SubscriptionPagesUseSubscriptionFeature, - purchaseManager: PurchaseManager = PurchaseManager.shared, - accountManager: AccountManaging, + subscriptionManager: SubscriptionManaging, isAddingDevice: Bool = false) { self.userScript = userScript self.subFeature = subFeature - self.purchaseManager = purchaseManager - self.accountManager = accountManager - self.appStoreAccountManagementFlow = AppStoreAccountManagementFlow(accountManager: accountManager) + self.subscriptionManager = subscriptionManager + self.appStoreAccountManagementFlow = AppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) self.state.isAddingDevice = false } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index fa53bb48df..fdfe03fee0 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -26,7 +26,7 @@ import Core @available(iOS 15.0, *) final class SubscriptionSettingsViewModel: ObservableObject { - let accountManager: AccountManaging + private let subscriptionManager: SubscriptionManaging private var subscriptionUpdateTimer: Timer? private var signOutObserver: Any? @@ -39,7 +39,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { var shouldDismissView: Bool = false var isShowingGoogleView: Bool = false var isShowingFAQView: Bool = false - var subscriptionInfo: SubscriptionService.GetSubscriptionResponse? + var subscriptionInfo: Subscription? var isLoadingSubscriptionInfo: Bool = false // Used to display stripe WebUI @@ -50,7 +50,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { var isShowingConnectionError: Bool = false // Used to display the FAQ WebUI - var FAQViewModel: SubscriptionExternalLinkViewModel = SubscriptionExternalLinkViewModel(url: URL.subscriptionFAQ) + let subscriptionFAQURL = SubscriptionURL.FAQ.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + var FAQViewModel: SubscriptionExternalLinkViewModel = SubscriptionExternalLinkViewModel(url: subscriptionFAQURL) } // Publish the currently selected feature @@ -60,8 +61,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { @Published private(set) var state = State() - init(accountManager: AccountManaging) { - self.accountManager = accountManager + init(subscriptionManager: SubscriptionManaging = AppDelegate.appDelegate().getSubscriptionManager()) { + self.subscriptionManager = subscriptionManager setupSubscriptionUpdater() setupNotificationObservers() } @@ -76,11 +77,12 @@ final class SubscriptionSettingsViewModel: ObservableObject { self.fetchAndUpdateSubscriptionDetails(cachePolicy: .returnCacheDataElseLoad) } - private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionService.CachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool = true) { + private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionService.CachePolicy = .returnCacheDataElseLoad, + loadingIndicator: Bool = true) { Task { if loadingIndicator { displayLoader(true) } - guard let token = self.accountManager.accessToken else { return } - let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) + guard let token = self.subscriptionManager.accountManager.accessToken else { return } + let subscriptionResult = await self.subscriptionManager.subscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) switch subscriptionResult { case .success(let subscription): DispatchQueue.main.async { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index d5cc159c71..ff26ee052b 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -26,7 +26,7 @@ import Core struct SubscriptionSettingsView: View { @Environment(\.dismiss) var dismiss - @StateObject var viewModel = SubscriptionSettingsViewModel(accountManager: AppDelegate.accountManager) + @StateObject var viewModel = SubscriptionSettingsViewModel() @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator var viewPlans: (() -> Void)? diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 17238c4e72..bd67b8ac38 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -29,12 +29,8 @@ import NetworkProtection @available(iOS 15.0, *) final class SubscriptionDebugViewController: UITableViewController { - private let accountManager: AccountManaging = AppDelegate.accountManager - fileprivate var purchaseManager: PurchaseManager = PurchaseManager.shared - - @UserDefaultsWrapper(key: .privacyProEnvironment, defaultValue: SubscriptionPurchaseEnvironment.ServiceEnvironment.default.description) - private var privacyProEnvironment: String - + private let subscriptionManager: SubscriptionManaging + private let titles = [ Sections.authorization: "Authentication", Sections.subscription: "Subscription", @@ -69,7 +65,6 @@ final class SubscriptionDebugViewController: UITableViewController { case staging case production } - override func numberOfSections(in tableView: UITableView) -> Int { return Sections.allCases.count @@ -125,15 +120,15 @@ final class SubscriptionDebugViewController: UITableViewController { } case .environment: - let staging = SubscriptionPurchaseEnvironment.ServiceEnvironment.staging - let prod = SubscriptionPurchaseEnvironment.ServiceEnvironment.production + let staging = SubscriptionEnvironment.ServiceEnvironment.staging + let prod = SubscriptionEnvironment.ServiceEnvironment.production switch EnvironmentRows(rawValue: indexPath.row) { case .staging: cell.textLabel?.text = "Staging" - cell.accessoryType = SubscriptionPurchaseEnvironment.currentServiceEnvironment == staging ? .checkmark : .none + cell.accessoryType = SubscriptionEnvironment.currentServiceEnvironment == staging ? .checkmark : .none case .production: cell.textLabel?.text = "Production" - cell.accessoryType = SubscriptionPurchaseEnvironment.currentServiceEnvironment == prod ? .checkmark : .none + cell.accessoryType = SubscriptionEnvironment.currentServiceEnvironment == prod ? .checkmark : .none case .none: break } @@ -281,7 +276,7 @@ final class SubscriptionDebugViewController: UITableViewController { } } - private func setEnvironment(_ environment: SubscriptionPurchaseEnvironment.ServiceEnvironment) { + private func setEnvironment(_ environment: SubscriptionEnvironment.ServiceEnvironment) { if environment.description != privacyProEnvironment { accountManager.signOut() diff --git a/DuckDuckGo/UserDefaultsCacheKey.swift b/DuckDuckGo/UserDefaultsCacheKey.swift index 48af208768..f3af33833f 100644 --- a/DuckDuckGo/UserDefaultsCacheKey.swift +++ b/DuckDuckGo/UserDefaultsCacheKey.swift @@ -19,6 +19,6 @@ import Common -public enum UserDefaultsCacheKey: String, UserDefaultsCacheKeyStore { - case subscriptionState = "com.duckduckgo.ios.subscription.state" -} +// public enum UserDefaultsCacheKey: String, UserDefaultsCacheKeyStore { +// case subscriptionState = "com.duckduckgo.ios.subscription.state" +// } From ffc80dffaf2a682e6e9ed42e2496015a50849e2b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 May 2024 11:58:05 +0100 Subject: [PATCH 05/39] DI #2 + lint --- Core/AccountManager+AppGroup.swift | 27 ------ DuckDuckGo.xcodeproj/project.pbxproj | 16 +--- DuckDuckGo/AppDelegate.swift | 20 ++--- .../DefaultNetworkProtectionVisibility.swift | 2 +- .../Feedback/VPNMetadataCollector.swift | 2 +- DuckDuckGo/MainViewController.swift | 3 +- ...orkProtectionConvenienceInitialisers.swift | 9 +- ...NetworkProtectionDebugViewController.swift | 3 +- DuckDuckGo/NetworkProtectionRootView.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 16 ++-- .../SubscriptionManageriOS14.swift | 41 +++++++++ ...scriptionPagesUseSubscriptionFeature.swift | 2 +- .../SubscriptionContainerViewModel.swift | 10 +-- .../SubscriptionEmailViewModel.swift | 32 ++++--- .../ViewModel/SubscriptionFlowViewModel.swift | 11 ++- .../ViewModel/SubscriptionITPViewModel.swift | 10 ++- .../SubscriptionSettingsViewModel.swift | 30 ++++--- .../Views/SubscriptionContainerView.swift | 2 +- .../Views/SubscriptionITPView.swift | 2 +- .../SubscriptionDebugViewController.swift | 89 ++++++++++++------- DuckDuckGo/TabURLInterceptor.swift | 13 +-- DuckDuckGo/TabViewController.swift | 2 +- ...etworkProtectionPacketTunnelProvider.swift | 36 +++++--- 23 files changed, 223 insertions(+), 157 deletions(-) delete mode 100644 Core/AccountManager+AppGroup.swift create mode 100644 DuckDuckGo/Subscription/SubscriptionManageriOS14.swift diff --git a/Core/AccountManager+AppGroup.swift b/Core/AccountManager+AppGroup.swift deleted file mode 100644 index 5e6ae4f057..0000000000 --- a/Core/AccountManager+AppGroup.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// AccountManager+AppGroup.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Subscription - -public extension AccountManager { - convenience init() { - self.init(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - } -} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d2935926de..f0aa794b5c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -790,7 +790,6 @@ D6FEB8B12B7498A300C3615F /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FEB8B02B7498A300C3615F /* HeadlessWebView.swift */; }; D6FEB8B32B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FEB8B22B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift */; }; D6FEB8B52B74994000C3615F /* HeadlessWebViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FEB8B42B74994000C3615F /* HeadlessWebViewCoordinator.swift */; }; - D6FF22482BC95F0B008E7BCC /* AccountManager+AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FF22472BC95F0B008E7BCC /* AccountManager+AppGroup.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -866,6 +865,7 @@ F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */; }; F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */; }; F15E9F3C2BECFCFD00DEFDDE /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15E9F3B2BECFCFD00DEFDDE /* SubscriptionTestingUtilities */; }; + F15E9F3E2BEE128200DEFDDE /* SubscriptionManageriOS14.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */; }; F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C141E57336D00DEDCAF /* TabManager.swift */; }; F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */; }; @@ -2415,7 +2415,6 @@ D6FEB8B02B7498A300C3615F /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; D6FEB8B22B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebViewNavCoordinator.swift; sourceTree = ""; }; D6FEB8B42B74994000C3615F /* HeadlessWebViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebViewCoordinator.swift; sourceTree = ""; }; - D6FF22472BC95F0B008E7BCC /* AccountManager+AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountManager+AppGroup.swift"; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -2519,6 +2518,7 @@ F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate+SKAD4.swift"; sourceTree = ""; }; F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = ""; }; F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteViewController.swift; sourceTree = ""; }; + F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionManageriOS14.swift; sourceTree = ""; }; F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherViewController.swift; sourceTree = ""; }; F1617C141E57336D00DEDCAF /* TabManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherDelegate.swift; sourceTree = ""; }; @@ -4339,6 +4339,7 @@ isa = PBXGroup; children = ( D60170BB2BA32DD6001911B5 /* Subscription.swift */, + F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */, D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */, D664C7952B289AA000CBFA76 /* Subscription.storekit */, D664C7932B289AA000CBFA76 /* ViewModel */, @@ -4452,14 +4453,6 @@ name = Model; sourceTree = ""; }; - D6FF22462BC95EF9008E7BCC /* Subscription */ = { - isa = PBXGroup; - children = ( - D6FF22472BC95F0B008E7BCC /* AccountManager+AppGroup.swift */, - ); - name = Subscription; - sourceTree = ""; - }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -4848,7 +4841,6 @@ F143C2E51E4A4CD400CFDE3A /* Core */ = { isa = PBXGroup; children = ( - D6FF22462BC95EF9008E7BCC /* Subscription */, F1CE42A71ECA0A520074A8DF /* Bookmarks */, 837774491F8E1ECE00E17A29 /* ContentBlocker */, F143C2E61E4A4CD400CFDE3A /* Core.h */, @@ -6256,6 +6248,7 @@ 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, 8540BD5423D8D5080057FDD2 /* PreserveLoginsAlert.swift in Sources */, + F15E9F3E2BEE128200DEFDDE /* SubscriptionManageriOS14.swift in Sources */, 1E87615928A1517200C7C5CE /* PrivacyDashboardViewController.swift in Sources */, EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */, 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, @@ -6984,7 +6977,6 @@ 854858E32937BC550063610B /* CollectionExtension.swift in Sources */, 1E6A4D692984208800A371D3 /* LocaleExtension.swift in Sources */, 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */, - D6FF22482BC95F0B008E7BCC /* AccountManager+AppGroup.swift in Sources */, F1134EB01F40AC6300B73467 /* AtbParser.swift in Sources */, EE50052E29C369D300AE0773 /* FeatureFlag.swift in Sources */, BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index b74f844ed9..0c5ff1a09e 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -96,9 +96,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? - public let subscriptionManager: SubscriptionManaging? + public let subscriptionManager: SubscriptionManaging private var accountManager: AccountManaging? { - subscriptionManager?.accountManager + subscriptionManager.accountManager } override init() { @@ -122,6 +122,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { subscriptionService: subscriptionService, authService: authService, subscriptionEnvironment: subscriptionEnvironment) + } else { + // This is used just for iOS <15, it's a sort of mocked environment that will not be used. + subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) } vpnFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) @@ -352,8 +355,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { NetworkProtectionAccessController().refreshNetworkProtectionAccess() #endif -// setupSubscriptionsEnvironment() - if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { clearDebugWaitlistState() } @@ -365,11 +366,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } - @available(iOS 15.0, *) - public func getSubscriptionManager() -> SubscriptionManaging { - subscriptionManager! - } - private func prepareTabsModel(previewsSource: TabPreviewsSource = TabPreviewsSource(), appSettings: AppSettings = AppDependencyProvider.shared.appSettings, isDesktop: Bool = UIDevice.current.userInterfaceIdiom == .pad) -> TabsModel { @@ -591,9 +587,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func updateSubscriptionStatus() { Task { guard let token = accountManager?.accessToken else { return } - - if case .success(let subscription) = await subscriptionManager?.subscriptionService.getSubscription(accessToken: token, - cachePolicy: .reloadIgnoringLocalCacheData) { + var subscriptionService: SubscriptionService { subscriptionManager.subscriptionService } + if case .success(let subscription) = await subscriptionService.getSubscription(accessToken: token, + cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { DailyPixel.fire(pixel: .privacyProSubscriptionActive) } diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index e2409161c1..99303c5688 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -54,7 +54,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { static func forTokenStore() -> DefaultNetworkProtectionVisibility { DefaultNetworkProtectionVisibility(networkProtectionTokenStore: nil, networkProtectionAccessManager: nil, - accountManager: AppDelegate.appDelegate().getSubscriptionManager().accountManager) + accountManager: AppDelegate.appDelegate().subscriptionManager.accountManager) } func isWaitlistBetaActive() -> Bool { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index 196aa0968a..2e24afea68 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -278,7 +278,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { enableSource: .init(from: accessManager.networkProtectionAccessType()), betaParticipant: accessType == .waitlistInvited, hasToken: hasToken, - subscriptionActive: AppDelegate.accountManager.isUserAuthenticated + subscriptionActive: AppDelegate.appDelegate().subscriptionManager.accountManager.isUserAuthenticated ) } } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index b4a009a76a..d83e0d579e 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1417,7 +1417,8 @@ class MainViewController: UIViewController { @objc private func onEntitlementsChange(_ notification: Notification) { Task { - guard case .success(false) = await AppDelegate.appDelegate().subscriptionManager?.accountManager.hasEntitlement(for: .networkProtection) else { return } + let accountManager = AppDelegate.appDelegate().subscriptionManager.accountManager + guard case .success(false) = await accountManager.hasEntitlement(for: .networkProtection) else { return } let controller = NetworkProtectionTunnelController() diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index f9a0594015..2cbad059d8 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -63,7 +63,7 @@ extension NetworkProtectionKeychainTokenStore { let isSubscriptionEnabled = featureVisibility.isPrivacyProLaunched() let accessTokenProvider: () -> String? = { if featureVisibility.shouldMonitorEntitlement() { - return { AppDelegate.accountManager.accessToken } + return { AppDelegate.appDelegate().subscriptionManager.accountManager.accessToken } } return { nil } }() @@ -79,12 +79,14 @@ extension NetworkProtectionKeychainTokenStore { extension NetworkProtectionCodeRedemptionCoordinator { convenience init(isManualCodeRedemptionFlow: Bool = false) { let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + var subscriptionManager: SubscriptionManaging { AppDelegate.appDelegate().subscriptionManager } + let networkProtectionVisibility = DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager) self.init( environment: settings.selectedEnvironment, tokenStore: NetworkProtectionKeychainTokenStore(), isManualCodeRedemptionFlow: isManualCodeRedemptionFlow, errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isPrivacyProLaunched() + isSubscriptionEnabled: networkProtectionVisibility.isPrivacyProLaunched() ) } } @@ -101,11 +103,12 @@ extension NetworkProtectionVPNSettingsViewModel { extension NetworkProtectionLocationListCompositeRepository { convenience init() { let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + var subscriptionManager: SubscriptionManaging { AppDelegate.appDelegate().subscriptionManager } self.init( environment: settings.selectedEnvironment, tokenStore: NetworkProtectionKeychainTokenStore(), errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: DefaultNetworkProtectionVisibility(accountManager: AppDelegate.accountManager).isPrivacyProLaunched() + isSubscriptionEnabled: DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).isPrivacyProLaunched() ) } } diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 4b56f259c6..d84773a777 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -154,7 +154,8 @@ final class NetworkProtectionDebugViewController: UITableViewController { } required convenience init?(coder: NSCoder) { - self.init(coder: coder, tokenStore: NetworkProtectionKeychainTokenStore(), accountManager: AppDelegate.accountManager) + self.init(coder: coder, tokenStore: NetworkProtectionKeychainTokenStore(), + accountManager: AppDelegate.appDelegate().subscriptionManager.accountManager) } override func viewWillAppear(_ animated: Bool) { diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 4a0ec4a53b..3f7f3832bc 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -32,7 +32,7 @@ struct NetworkProtectionRootView: View { redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator(isManualCodeRedemptionFlow: true), completion: inviteCompletion ) - if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.appDelegate().subscriptionManager?.accountManager).isPrivacyProLaunched() { + if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.appDelegate().subscriptionManager.accountManager).isPrivacyProLaunched() { NetworkProtectionStatusView( statusModel: NetworkProtectionStatusViewModel() ) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 2bc968604e..9da9d3bdbf 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -45,7 +45,7 @@ final class SettingsViewModel: ObservableObject { var emailManager: EmailManager { EmailManager() } // Subscription Dependencies - private let subscriptionManager: SubscriptionManaging? + private let subscriptionManager: SubscriptionManaging private var subscriptionSignOutObserver: Any? private enum UserDefaultsCacheKey: String, UserDefaultsCacheKeyStore { @@ -390,7 +390,7 @@ final class SettingsViewModel: ObservableObject { // MARK: Default Init init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider, - subscriptionManager: SubscriptionManaging?, + subscriptionManager: SubscriptionManaging, voiceSearchHelper: VoiceSearchHelperProtocol = AppDependencyProvider.shared.voiceSearchHelper, variantManager: VariantManager = AppDependencyProvider.shared.variantManager, deepLink: SettingsDeepLinkSection? = nil) { @@ -451,7 +451,7 @@ extension SettingsViewModel { private func getNetworkProtectionState() -> SettingsState.NetworkProtection { var enabled = false #if NETWORK_PROTECTION - if #available(iOS 15, *), let subscriptionManager { + if #available(iOS 15, *) { enabled = DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).shouldKeepVPNAccessViaWaitlist() } #endif @@ -491,8 +491,7 @@ extension SettingsViewModel { #if NETWORK_PROTECTION private func updateNetPStatus(connectionStatus: ConnectionStatus) { - if let subscriptionManager, - DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).isPrivacyProLaunched() { + if DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).isPrivacyProLaunched() { switch connectionStatus { case .connected: self.state.networkProtection.status = UserText.netPCellConnected @@ -744,11 +743,6 @@ extension SettingsViewModel { @MainActor private func setupSubscriptionEnvironment() async { - - guard let subscriptionManager else { - return - } - // If there's cached data use it by default if let cachedSubscription = subscriptionStateCache.get() { state.subscription = cachedSubscription @@ -783,7 +777,7 @@ extension SettingsViewModel { // Check entitlements and update state let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] for entitlement in entitlements { - if case .success = await AccountManager().hasEntitlement(for: entitlement) { + if case .success = await subscriptionManager.accountManager.hasEntitlement(for: entitlement) { switch entitlement { case .identityTheftRestoration: self.state.subscription.entitlements.append(.identityTheftRestoration) diff --git a/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift new file mode 100644 index 0000000000..ce5ec6c4f7 --- /dev/null +++ b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift @@ -0,0 +1,41 @@ +// +// SubscriptionManageriOS14.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Subscription + +class SubscriptionManageriOS14: SubscriptionManaging { + + var accountManager: AccountManaging + var subscriptionService: SubscriptionService = SubscriptionService(currentServiceEnvironment: .production) + var authService: AuthService = AuthService(currentServiceEnvironment: .production) + + @available(iOS 15, *) + func getStorePurchaseManager() -> StorePurchaseManaging { + StorePurchaseManager() + } + var currentEnvironment: SubscriptionEnvironment = SubscriptionEnvironment.default + var canPurchase: Bool = false + func loadInitialData() {} + func updateSubscriptionStatus(completion: @escaping (Bool) -> Void) {} + + init(accountManager: AccountManaging) { + self.accountManager = accountManager + } +} diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 8588e6964f..854ea7b35f 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -340,7 +340,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - if let accessToken = AccountManager().accessToken { + if let accessToken = subscriptionManager.accountManager.accessToken { return [Constants.token: accessToken] } else { return [String: String]() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift index 747190fc86..15a38836fb 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift @@ -31,12 +31,12 @@ final class SubscriptionContainerViewModel: ObservableObject { let restore: SubscriptionRestoreViewModel let email: SubscriptionEmailViewModel - init(accountManager: AccountManaging) { + init(subscriptionManager: SubscriptionManaging) { self.userScript = SubscriptionPagesUserScript() - self.subFeature = SubscriptionPagesUseSubscriptionFeature(accountManager: accountManager) - self.flow = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) - self.restore = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature, accountManager: accountManager) - self.email = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature, accountManager: accountManager) + self.subFeature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: subscriptionManager) + self.flow = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature, subscriptionManager: subscriptionManager) + self.restore = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature, subscriptionManager: subscriptionManager) + self.email = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature, subscriptionManager: subscriptionManager) } deinit { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 93b1a67c16..45af64f6ee 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -26,16 +26,17 @@ import Subscription @available(iOS 15.0, *) final class SubscriptionEmailViewModel: ObservableObject { - let accountManager: AccountManaging + private let subscriptionManager: SubscriptionManaging let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature private var canGoBackCancellable: AnyCancellable? private var urlCancellable: AnyCancellable? - var emailURL = URL.activateSubscriptionViaEmail + private var emailURL: URL var webViewModel: AsyncHeadlessWebViewViewModel - + + enum SelectedFeature { case netP, dbp, itr, none } @@ -69,23 +70,31 @@ final class SubscriptionEmailViewModel: ObservableObject { } private var cancellables = Set() - + + var subscriptionServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment { + subscriptionManager.currentEnvironment.serviceEnvironment + } + var accountManager: AccountManaging { subscriptionManager.accountManager } + private var isWelcomePageOrSuccessPage: Bool { - webViewModel.url?.forComparison() == URL.subscriptionActivateSuccess.forComparison() || - webViewModel.url?.forComparison() == URL.subscriptionPurchase.forComparison() + let subscriptionActivateSuccessURL = SubscriptionURL.activateSuccess.subscriptionURL(environment: subscriptionServiceEnvironment) + let subscriptionPurchaseURL = SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment) + return webViewModel.url?.forComparison() == subscriptionActivateSuccessURL.forComparison() || + webViewModel.url?.forComparison() == subscriptionPurchaseURL.forComparison() } init(userScript: SubscriptionPagesUserScript, subFeature: SubscriptionPagesUseSubscriptionFeature, - accountManager: AccountManaging) { + subscriptionManager: SubscriptionManaging) { self.userScript = userScript self.subFeature = subFeature - self.accountManager = accountManager + self.subscriptionManager = subscriptionManager self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, subFeature: subFeature, settings: AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: Self.allowedDomains, contentBlocking: false)) + self.emailURL = SubscriptionURL.activateViaEmail.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) } @MainActor @@ -95,7 +104,8 @@ final class SubscriptionEmailViewModel: ObservableObject { } else { // If not in the Welcome page, dismiss the view, otherwise, assume we // came from Activation, so dismiss the entire stack - if webViewModel.url?.forComparison() != URL.subscriptionPurchase.forComparison() { + let subscriptionPurchaseURL = SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment) + if webViewModel.url?.forComparison() != subscriptionPurchaseURL.forComparison() { state.shouldDismissView = true } else { state.shouldPopToAppSettings = true @@ -123,7 +133,9 @@ final class SubscriptionEmailViewModel: ObservableObject { // If the user is Authenticated & not in the Welcome page if accountManager.isUserAuthenticated && !isWelcomePageOrSuccessPage { // If user is authenticated, we want to "Add or manage email" instead of activating - emailURL = accountManager.email == nil ? URL.addEmailToSubscription : URL.manageSubscriptionEmail + let addEmailToSubscriptionURL = SubscriptionURL.addEmail.subscriptionURL(environment: subscriptionServiceEnvironment) + let manageSubscriptionEmailURL = SubscriptionURL.manageEmail.subscriptionURL(environment: subscriptionServiceEnvironment) + emailURL = accountManager.email == nil ? addEmailToSubscriptionURL : manageSubscriptionEmailURL state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle // Also we assume subscription requires managing, and not activation diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index e1c2160c73..03d09fbb0d 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -234,8 +234,12 @@ final class SubscriptionFlowViewModel: ObservableObject { strongSelf.state.canNavigateBack = false guard let currentURL = self?.webViewModel.url else { return } Task { await strongSelf.setTransactionStatus(.idle) } - if currentURL.forComparison() == SubscriptionURL.addEmail.subscriptionURL(environment: strongSelf.subscriptionServiceEnvironment).forComparison() || - currentURL.forComparison() == SubscriptionURL.addEmailToSubscriptionSuccess.subscriptionURL(environment: strongSelf.subscriptionServiceEnvironment).forComparison() { + + let addEmailURL = SubscriptionURL.addEmail.subscriptionURL(environment: strongSelf.subscriptionServiceEnvironment) + let addEmailSuccessURL = SubscriptionURL.addEmailToSubscriptionSuccess.subscriptionURL(environment: + strongSelf.subscriptionServiceEnvironment) + if currentURL.forComparison() == addEmailURL.forComparison() || + currentURL.forComparison() == addEmailSuccessURL.forComparison() { strongSelf.state.viewTitle = UserText.subscriptionRestoreAddEmailTitle } else { strongSelf.state.viewTitle = UserText.subscriptionTitle @@ -311,7 +315,8 @@ final class SubscriptionFlowViewModel: ObservableObject { self.resetState() } if webViewModel.url != SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() { - self.webViewModel.navigationCoordinator.navigateTo(url: SubscriptionURL.purchase.subscriptionURL(environment: self.subscriptionServiceEnvironment)) + self.webViewModel.navigationCoordinator.navigateTo(url: SubscriptionURL.purchase.subscriptionURL(environment: + self.subscriptionServiceEnvironment)) } await self.setupTransactionObserver() await self.setupWebViewObservers() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index fc03f0f201..c0768394db 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -28,7 +28,7 @@ final class SubscriptionITPViewModel: ObservableObject { var userScript: IdentityTheftRestorationPagesUserScript? var subFeature: IdentityTheftRestorationPagesFeature? - var manageITPURL = URL.identityTheftRestoration + let manageITPURL: URL var viewTitle = UserText.settingsPProITRTitle enum Constants { @@ -38,7 +38,7 @@ final class SubscriptionITPViewModel: ObservableObject { } // State variables - var itpURL = URL.identityTheftRestoration + let itpURL: URL @Published var canNavigateBack: Bool = false @Published var isDownloadableContent: Bool = false @Published var activityItems: [Any] = [] @@ -61,9 +61,11 @@ final class SubscriptionITPViewModel: ObservableObject { private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? - init(accountManager: AccountManager) { + init(subscriptionManager: SubscriptionManaging) { + self.itpURL = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + self.manageITPURL = self.itpURL self.userScript = IdentityTheftRestorationPagesUserScript() - self.subFeature = IdentityTheftRestorationPagesFeature(accountManager: accountManager) + self.subFeature = IdentityTheftRestorationPagesFeature(accountManager: subscriptionManager.accountManager) let webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: Self.allowedDomains, diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index fdfe03fee0..9cc5a933bb 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -50,19 +50,25 @@ final class SubscriptionSettingsViewModel: ObservableObject { var isShowingConnectionError: Bool = false // Used to display the FAQ WebUI - let subscriptionFAQURL = SubscriptionURL.FAQ.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) - var FAQViewModel: SubscriptionExternalLinkViewModel = SubscriptionExternalLinkViewModel(url: subscriptionFAQURL) + var FAQViewModel: SubscriptionExternalLinkViewModel + + init(faqURL: URL) { + self.FAQViewModel = SubscriptionExternalLinkViewModel(url: faqURL) + } } // Publish the currently selected feature @Published var selectedFeature: SettingsViewModel.SettingsDeepLinkSection? // Read only View State - Should only be modified from the VM - @Published private(set) var state = State() - + @Published private(set) var state: State + - init(subscriptionManager: SubscriptionManaging = AppDelegate.appDelegate().getSubscriptionManager()) { + init(subscriptionManager: SubscriptionManaging = AppDelegate.appDelegate().subscriptionManager) { self.subscriptionManager = subscriptionManager + let subscriptionFAQURL = SubscriptionURL.FAQ.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + self.state = State(faqURL: subscriptionFAQURL) + setupSubscriptionUpdater() setupNotificationObservers() } @@ -76,7 +82,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { func onFirstAppear() { self.fetchAndUpdateSubscriptionDetails(cachePolicy: .returnCacheDataElseLoad) } - + private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionService.CachePolicy = .returnCacheDataElseLoad, loadingIndicator: Bool = true) { Task { @@ -156,7 +162,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func removeSubscription() { - accountManager.signOut() + subscriptionManager.accountManager.signOut() _ = ActionMessageView() ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, presentationLocation: .withoutBottomBar) @@ -198,7 +204,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { @MainActor private func manageAppleSubscription() async { if state.subscriptionInfo?.isActive ?? false { - let url = URL.manageSubscriptionsInAppStoreAppURL + let url = SubscriptionURL.manageSubscriptionsInAppStore.subscriptionURL(environment: + subscriptionManager.currentEnvironment.serviceEnvironment) if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { do { try await AppStore.showManageSubscriptions(in: windowScene) @@ -212,9 +219,10 @@ final class SubscriptionSettingsViewModel: ObservableObject { } private func manageStripeSubscription() async { - guard let token = accountManager.accessToken, let externalID = accountManager.externalID else { return } - let serviceResponse = await SubscriptionService.getCustomerPortalURL(accessToken: token, externalID: externalID) - + guard let token = subscriptionManager.accountManager.accessToken, + let externalID = subscriptionManager.accountManager.externalID else { return } + let serviceResponse = await subscriptionManager.subscriptionService.getCustomerPortalURL(accessToken: token, externalID: externalID) + // Get Stripe Customer Portal URL and update the model if case .success(let response) = serviceResponse { guard let url = URL(string: response.customerPortalUrl) else { return } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift index b981f3858c..4ee315c419 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift @@ -37,7 +37,7 @@ struct SubscriptionContainerView: View { init(currentView: CurrentView) { _currentViewState = State(initialValue: currentView) - self.viewModel = SubscriptionContainerViewModel(accountManager: AppDelegate.accountManager) + self.viewModel = SubscriptionContainerViewModel(subscriptionManager: AppDelegate.appDelegate().subscriptionManager) // let userScript = viewModel.userScript // let subFeature = viewModel.subFeature flowViewModel = viewModel.flow diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index a9d4c6fcea..bd1882ff7c 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -36,7 +36,7 @@ struct SubscriptionActivityViewController: UIViewControllerRepresentable { struct SubscriptionITPView: View { @Environment(\.dismiss) var dismiss - @StateObject var viewModel = SubscriptionITPViewModel(accountManager: AppDelegate.accountManager) + @StateObject var viewModel = SubscriptionITPViewModel(subscriptionManager: AppDelegate.appDelegate().subscriptionManager) @State private var shouldShowNavigationBar = false @State private var isShowingActivityView = false diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index bd67b8ac38..de3dd029ba 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -29,7 +29,9 @@ import NetworkProtection @available(iOS 15.0, *) final class SubscriptionDebugViewController: UITableViewController { - private let subscriptionManager: SubscriptionManaging + private var subscriptionManager: SubscriptionManaging { + AppDelegate.appDelegate().subscriptionManager + } private let titles = [ Sections.authorization: "Authentication", @@ -125,10 +127,10 @@ final class SubscriptionDebugViewController: UITableViewController { switch EnvironmentRows(rawValue: indexPath.row) { case .staging: cell.textLabel?.text = "Staging" - cell.accessoryType = SubscriptionEnvironment.currentServiceEnvironment == staging ? .checkmark : .none + cell.accessoryType = subscriptionManager.currentEnvironment.serviceEnvironment == staging ? .checkmark : .none case .production: cell.textLabel?.text = "Production" - cell.accessoryType = SubscriptionEnvironment.currentServiceEnvironment == prod ? .checkmark : .none + cell.accessoryType = subscriptionManager.currentEnvironment.serviceEnvironment == prod ? .checkmark : .none case .none: break } @@ -195,30 +197,30 @@ final class SubscriptionDebugViewController: UITableViewController { // MARK: Account Status Actions private func clearAuthData() { - accountManager.signOut() + subscriptionManager.accountManager.signOut() showAlert(title: "Data cleared!") } private func injectCredentials() { - accountManager.storeAccount(token: "a-fake-token", + subscriptionManager.accountManager.storeAccount(token: "a-fake-token", email: "a.fake@email.com", externalID: "666") showAccountDetails() } private func showAccountDetails() { - let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" - let message = accountManager.isUserAuthenticated ? - ["Service Environment: \(SubscriptionPurchaseEnvironment.currentServiceEnvironment.description)", - "AuthToken: \(accountManager.authToken ?? "")", - "AccessToken: \(accountManager.accessToken ?? "")", - "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil + let title = subscriptionManager.accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" + let message = subscriptionManager.accountManager.isUserAuthenticated ? + ["Service Environment: \(subscriptionManager.currentEnvironment.serviceEnvironment.description)", + "AuthToken: \(subscriptionManager.accountManager.authToken ?? "")", + "AccessToken: \(subscriptionManager.accountManager.accessToken ?? "")", + "Email: \(subscriptionManager.accountManager.email ?? "")"].joined(separator: "\n") : nil showAlert(title: title, message: message) } private func syncAppleIDAccount() { Task { - switch await purchaseManager.syncAppleIDAccount() { + switch await subscriptionManager.getStorePurchaseManager().syncAppleIDAccount() { case .success: showAlert(title: "Account synced!", message: "") case .failure(let error): @@ -229,11 +231,11 @@ final class SubscriptionDebugViewController: UITableViewController { private func validateToken() { Task { - guard let token = accountManager.accessToken else { + guard let token = subscriptionManager.accountManager.accessToken else { showAlert(title: "Not authenticated", message: "No authenticated user found! - Token not available") return } - switch await AuthService.validateToken(accessToken: token) { + switch await subscriptionManager.authService.validateToken(accessToken: token) { case .success(let response): showAlert(title: "Token details", message: "\(response)") case .failure(let error): @@ -244,11 +246,11 @@ final class SubscriptionDebugViewController: UITableViewController { private func getSubscription() { Task { - guard let token = accountManager.accessToken else { + guard let token = subscriptionManager.accountManager.accessToken else { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") 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): @@ -260,13 +262,14 @@ final class SubscriptionDebugViewController: UITableViewController { private func getEntitlements() { Task { var results: [String] = [] - guard accountManager.accessToken != nil else { + guard subscriptionManager.accountManager.accessToken != nil else { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") return } let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] for entitlement in entitlements { - if case let .success(result) = await accountManager.hasEntitlement(for: entitlement, cachePolicy: .reloadIgnoringLocalCacheData) { + if case let .success(result) = await subscriptionManager.accountManager.hasEntitlement(for: entitlement, + cachePolicy: .reloadIgnoringLocalCacheData) { let resultSummary = "Entitlement check for \(entitlement.rawValue): \(result)" results.append(resultSummary) print(resultSummary) @@ -277,22 +280,40 @@ final class SubscriptionDebugViewController: UITableViewController { } private func setEnvironment(_ environment: SubscriptionEnvironment.ServiceEnvironment) { - if environment.description != privacyProEnvironment { - - accountManager.signOut() - - // Update Subscription environment - privacyProEnvironment = environment.rawValue - SubscriptionPurchaseEnvironment.currentServiceEnvironment = environment - - // Update VPN Environment - VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = environment == .production - ? .production - : .staging - NetworkProtectionLocationListCompositeRepository.clearCache() - - tableView.reloadData() - } +// let fullEnv = SubscriptionEnvironment() +// SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) +// +// +// 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) +// } +// +// +// +// +// +// +// if environment.description != privacyProEnvironment { +// accountManager.signOut() +// +// // Update Subscription environment +// privacyProEnvironment = environment.rawValue +// SubscriptionPurchaseEnvironment.currentServiceEnvironment = environment +// +// // Update VPN Environment +// VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = environment == .production +// ? .production +// : .staging +// NetworkProtectionLocationListCompositeRepository.clearCache() +// +// tableView.reloadData() +// } } } diff --git a/DuckDuckGo/TabURLInterceptor.swift b/DuckDuckGo/TabURLInterceptor.swift index 600b18e2fb..03b65eaf17 100644 --- a/DuckDuckGo/TabURLInterceptor.swift +++ b/DuckDuckGo/TabURLInterceptor.swift @@ -37,6 +37,11 @@ protocol TabURLInterceptor { final class TabURLInterceptorDefault: TabURLInterceptor { + private let subscriptionManager: SubscriptionManaging + + init(subscriptionManager: SubscriptionManaging) { + self.subscriptionManager = subscriptionManager + } static let interceptedURLs: [InterceptedURLInfo] = [ InterceptedURLInfo(id: .privacyPro, path: "/pro") @@ -56,8 +61,7 @@ final class TabURLInterceptorDefault: TabURLInterceptor { return true } - return Self.handleURLInterception(url: matchingURL.id) - + return handleURLInterception(url: matchingURL.id) } } @@ -79,12 +83,11 @@ extension TabURLInterceptorDefault { return URLComponents(string: "\(URL.URLProtocol.https.scheme)\(noScheme)") } - private static func handleURLInterception(url: InterceptedURL) -> Bool { + private func handleURLInterception(url: InterceptedURL) -> Bool { switch url { - // Opens the Privacy Pro Subscription Purchase page (if user can purchase) case .privacyPro: - if SubscriptionPurchaseEnvironment.canPurchase { + if subscriptionManager.canPurchase { NotificationCenter.default.post(name: .urlInterceptPrivacyPro, object: nil) return false } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 494c86a8af..ca4ec1f7cd 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -127,7 +127,7 @@ class TabViewController: UIViewController { private var trackersInfoWorkItem: DispatchWorkItem? - private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault() + private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault(subscriptionManager: AppDelegate.appDelegate().subscriptionManager) private var currentlyLoadedURL: URL? #if NETWORK_PROTECTION diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index e404747664..dc5735b22c 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -34,7 +34,7 @@ import Subscription final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { private var cancellables = Set() - private static let accountManager: AccountManaging = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + private let accountManager: AccountManaging // MARK: - PacketTunnelProvider.Event reporting @@ -254,14 +254,28 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } @objc init() { - let featureVisibility = NetworkProtectionVisibilityForTunnelProvider(accountManager: NetworkProtectionPacketTunnelProvider.accountManager) + // 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) + self.accountManager = accountManager + let featureVisibility = NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager) let isSubscriptionEnabled = featureVisibility.isPrivacyProLaunched() let accessTokenProvider: () -> String? = { if featureVisibility.shouldMonitorEntitlement() { - return { NetworkProtectionPacketTunnelProvider.accountManager.accessToken } + return { accountManager.accessToken } } - return { nil } - }() + return { nil } }() let tokenStore = NetworkProtectionKeychainTokenStore( keychainType: .dataProtection(.unspecified), errorEvents: nil, @@ -288,7 +302,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { settings: settings, defaults: .networkProtectionGroupDefaults, isSubscriptionEnabled: isSubscriptionEnabled, - entitlementCheck: Self.entitlementCheck) + entitlementCheck: { return await Self.entitlementCheck(accountManager: accountManager) }) startMonitoringMemoryPressureEvents() observeServerChanges() APIRequest.Headers.setUserAgent(DefaultUserAgentManager.duckDuckGoUserAgent) @@ -335,15 +349,15 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { activationDateStore.updateLastActiveDate() } - private static func entitlementCheck() async -> Result { - let accountManager = NetworkProtectionPacketTunnelProvider.accountManager + private static func entitlementCheck(accountManager: AccountManaging) async -> Result { guard NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager).shouldMonitorEntitlement() else { return .success(true) } - if VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment == .staging { - SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging - } + // TODO: the subscription environment should be matching to the VPNSettings environment, what should we do? +// if VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment == .staging { +// SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging +// } let result = await accountManager.hasEntitlement(for: .networkProtection) switch result { From 982b388ccbca17e8a52b57cffd23ed4641ef0ac2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 May 2024 12:24:38 +0100 Subject: [PATCH 06/39] todo added --- DuckDuckGo/SubscriptionDebugViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index de3dd029ba..17bfc48a83 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -281,6 +281,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func setEnvironment(_ environment: SubscriptionEnvironment.ServiceEnvironment) { + // TODO: reimplement debug menu // let fullEnv = SubscriptionEnvironment() // SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) // From bfccc1161494b9d4c1a0520e75612f07a25d1113 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 May 2024 12:56:43 +0100 Subject: [PATCH 07/39] cleanup --- DuckDuckGo/AppDelegate+Waitlists.swift | 2 -- DuckDuckGo/AppDelegate.swift | 6 +++--- DuckDuckGo/NetworkProtectionDebugViewController.swift | 2 -- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index 8d61ecf882..64a49a176a 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -45,8 +45,6 @@ extension AppDelegate { #if NETWORK_PROTECTION private func checkNetworkProtectionWaitlist() { -// let accessController = NetworkProtectionAccessController() - VPNWaitlist.shared.fetchInviteCodeIfAvailable { [weak self] error in guard error == nil else { return diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 7284df3ef9..ff506e9028 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -76,7 +76,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults - let vpnFeatureVisibility: DefaultNetworkProtectionVisibility + var vpnFeatureVisibility: DefaultNetworkProtectionVisibility! #endif private var autoClear: AutoClear? @@ -126,8 +126,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // This is used just for iOS <15, it's a sort of mocked environment that will not be used. subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) } - - vpnFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) } // swiftlint:disable:next function_body_length cyclomatic_complexity @@ -136,6 +134,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // SKAD4 support updateSKAd(conversionValue: 1) + vpnFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) + #if targetEnvironment(simulator) if ProcessInfo.processInfo.environment["UITESTING"] == "true" { // Disable hardware keyboards. diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index d84773a777..0821f55bb7 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -718,10 +718,8 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") let accessController = NetworkProtectionAccessController() accessController.revokeNetworkProtectionAccess() } - } - extension NWConnection { var stateUpdateStream: AsyncStream { From dc71688495c22eefac5c7b0a777308d9c9b76094 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 10 May 2024 13:06:03 +0100 Subject: [PATCH 08/39] vpnsettings env restored --- DuckDuckGo/AppDelegate.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index ff506e9028..b126ee4458 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -126,6 +126,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // This is used just for iOS <15, it's a sort of mocked environment that will not be used. subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) } + let isProduction = (subscriptionEnvironment.serviceEnvironment == .production) + VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = isProduction ? .production : .staging } // swiftlint:disable:next function_body_length cyclomatic_complexity From c4300997d7e95321d34fa1ad123de11a9bef52e9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 13 May 2024 09:41:43 +0100 Subject: [PATCH 09/39] it builds! --- DuckDuckGo/AppDelegate+Waitlists.swift | 5 +- DuckDuckGo/AppDelegate.swift | 112 ++++-------------- DuckDuckGo/AppDependencyProvider.swift | 94 ++++++++++++++- .../DefaultNetworkProtectionVisibility.swift | 34 ++---- DuckDuckGo/Feedback/VPNFeedbackFormView.swift | 4 +- .../Feedback/VPNFeedbackFormViewModel.swift | 4 +- .../Feedback/VPNMetadataCollector.swift | 6 +- DuckDuckGo/MainViewController+Segues.swift | 2 +- DuckDuckGo/MainViewController.swift | 24 ++-- .../NetworkProtectionAccessController.swift | 30 +++-- ...orkProtectionConvenienceInitialisers.swift | 59 ++++----- ...NetworkProtectionDebugViewController.swift | 12 +- DuckDuckGo/NetworkProtectionInviteView.swift | 3 +- .../NetworkProtectionInviteViewModel.swift | 2 +- DuckDuckGo/NetworkProtectionRootView.swift | 23 ++-- .../NetworkProtectionRootViewModel.swift | 2 +- .../NetworkProtectionStatusViewModel.swift | 4 +- .../NetworkProtectionTunnelController.swift | 6 +- .../NetworkProtectionVPNLocationView.swift | 2 +- DuckDuckGo/RemoteMessaging.swift | 4 +- DuckDuckGo/SettingsViewModel.swift | 6 +- .../SubscriptionSettingsViewModel.swift | 2 +- .../Views/SubscriptionContainerView.swift | 2 +- .../Views/SubscriptionITPView.swift | 2 +- .../SubscriptionDebugViewController.swift | 2 +- DuckDuckGo/TabViewController.swift | 2 +- DuckDuckGo/VPNWaitlist.swift | 2 +- ...tworkProtectionAccessControllerTests.swift | 13 +- ...etworkProtectionPacketTunnelProvider.swift | 10 +- 29 files changed, 253 insertions(+), 220 deletions(-) diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index 64a49a176a..c96c77f5c2 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -37,7 +37,7 @@ extension AppDelegate { func checkWaitlists() { #if NETWORK_PROTECTION - if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { + if AppDependencyProvider.shared.vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { checkNetworkProtectionWaitlist() } #endif @@ -63,7 +63,8 @@ extension AppDelegate { func fetchVPNWaitlistAuthToken(inviteCode: String) { Task { do { - try await NetworkProtectionCodeRedemptionCoordinator().redeem(inviteCode) + try await NetworkProtectionCodeRedemptionCoordinator(accountManager: + AppDependencyProvider.shared.subscriptionManager.accountManager).redeem(inviteCode) VPNWaitlist.shared.sendInviteCodeAvailableNotification() } catch {} } diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index b126ee4458..9f1848dc99 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -42,20 +42,10 @@ import WebKit // swiftlint:disable file_length // swiftlint:disable type_body_length - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { -// swiftlint:enable type_body_length - - static func appDelegate() -> AppDelegate { - guard let delegate = UIApplication.shared.delegate as? AppDelegate else { - fatalError("could not get app delegate ") - } - return delegate - } - - private static let ShowKeyboardOnLaunchThreshold = TimeInterval(20) +@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { + // swiftlint:enable type_body_length + private static let ShowKeyboardOnLaunchThreshold = TimeInterval(20) private struct ShortcutKey { static let clipboard = "com.duckduckgo.mobile.ios.clipboard" static let passwords = "com.duckduckgo.mobile.ios.passwords" @@ -76,7 +66,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults - var vpnFeatureVisibility: DefaultNetworkProtectionVisibility! #endif private var autoClear: AutoClear? @@ -96,48 +85,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? - public let subscriptionManager: SubscriptionManaging - private var accountManager: AccountManaging? { - subscriptionManager.accountManager - } - - override 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(iOS 15.0, *) { - subscriptionManager = SubscriptionManager(storePurchaseManager: StorePurchaseManager(), - accountManager: accountManager, - subscriptionService: subscriptionService, - authService: authService, - subscriptionEnvironment: subscriptionEnvironment) - } else { - // This is used just for iOS <15, it's a sort of mocked environment that will not be used. - subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) - } - let isProduction = (subscriptionEnvironment.serviceEnvironment == .production) - VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = isProduction ? .production : .staging - } // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // SKAD4 support - updateSKAd(conversionValue: 1) - - vpnFeatureVisibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) - #if targetEnvironment(simulator) if ProcessInfo.processInfo.environment["UITESTING"] == "true" { // Disable hardware keyboards. @@ -190,6 +141,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { clearTmp() + // SKAD4 support + updateSKAd(conversionValue: 1) + _ = DefaultUserAgentManager.shared testing = ProcessInfo().arguments.contains("testing") if testing { @@ -359,10 +313,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION widgetRefreshModel.beginObservingVPNStatus() - NetworkProtectionAccessController().refreshNetworkProtectionAccess() + AppDependencyProvider.shared.networkProtectionAccessController.refreshNetworkProtectionAccess() #endif - if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { + if AppDependencyProvider.shared.vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { clearDebugWaitlistState() } @@ -465,20 +419,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } -// private func setupSubscriptionsEnvironment() { -// Task { -// #if ALPHA || DEBUG -// let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.staging -// #else -// let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.production -// #endif -// let environment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: privacyProEnvironment) ?? defaultEnvironment -// SubscriptionPurchaseEnvironment.currentServiceEnvironment = environment -// VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = (environment == .production) ? .production : .staging -// SubscriptionPurchaseEnvironment.current = .appStore -// } -// } - private func reportAdAttribution() { guard AdAttributionPixelReporter.isAdAttributionReportingEnabled else { return } @@ -556,18 +496,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func stopTunnelAndShowThankYouMessagingIfNeeded() { - if let accountManager, - accountManager.isUserAuthenticated { + if AppDependencyProvider.shared.accountManager.isUserAuthenticated { tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true return } - if vpnFeatureVisibility.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { + if AppDependencyProvider.shared.vpnFeatureVisibility.shouldShowThankYouMessaging() + && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { Task { await self.stopAndRemoveVPN(with: "thank-you-dialog") } - } else if let accountManager, - vpnFeatureVisibility.isPrivacyProLaunched() && !accountManager.isUserAuthenticated { + } else if AppDependencyProvider.shared.vpnFeatureVisibility.isPrivacyProLaunched() + && !AppDependencyProvider.shared.accountManager.isUserAuthenticated { Task { await self.stopAndRemoveVPN(with: "subscription-check") } @@ -575,33 +515,34 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func stopAndRemoveVPN(with reason: String) async { - let controller = NetworkProtectionTunnelController() - guard await controller.isInstalled else { + guard await AppDependencyProvider.shared.networkProtectionTunnelController.isInstalled else { return } - let isConnected = await controller.isConnected + let isConnected = await AppDependencyProvider.shared.networkProtectionTunnelController.isConnected DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ "reason": reason, "vpn-connected": String(isConnected) ]) - await controller.stop() - await controller.removeVPN() + await AppDependencyProvider.shared.networkProtectionTunnelController.stop() + await AppDependencyProvider.shared.networkProtectionTunnelController.removeVPN() } func updateSubscriptionStatus() { Task { - guard let token = accountManager?.accessToken else { return } - var subscriptionService: SubscriptionService { subscriptionManager.subscriptionService } + guard let token = AppDependencyProvider.shared.accountManager.accessToken else { return } + var subscriptionService: SubscriptionService { + AppDependencyProvider.shared.subscriptionManager.subscriptionService + } if case .success(let subscription) = await subscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { DailyPixel.fire(pixel: .privacyProSubscriptionActive) } } - await accountManager?.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) + await AppDependencyProvider.shared.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } } @@ -899,8 +840,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION if shortcutItem.type == ShortcutKey.openVPNSettings { - let visibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) - if visibility.shouldShowVPNShortcut() { + if AppDependencyProvider.shared.vpnFeatureVisibility.shouldShowVPNShortcut() { presentNetworkProtectionStatusSettingsModal() } } @@ -937,7 +877,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func refreshShortcuts() { #if NETWORK_PROTECTION - guard vpnFeatureVisibility.shouldShowVPNShortcut() else { + guard AppDependencyProvider.shared.vpnFeatureVisibility.shouldShowVPNShortcut() else { UIApplication.shared.shortcutItems = nil return } @@ -1012,14 +952,14 @@ extension AppDelegate: UNUserNotificationCenterDelegate { #if NETWORK_PROTECTION if NetworkProtectionNotificationIdentifier(rawValue: identifier) != nil { Task { - if case .success(let hasEntitlements) = await accountManager?.hasEntitlement(for: .networkProtection), + if case .success(let hasEntitlements) = await AppDependencyProvider.shared.accountManager.hasEntitlement(for: .networkProtection), hasEntitlements { presentNetworkProtectionStatusSettingsModal() } } } - if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist(), identifier == VPNWaitlist.notificationIdentifier { + if AppDependencyProvider.shared.vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist(), identifier == VPNWaitlist.notificationIdentifier { presentNetworkProtectionWaitlistModal() } #endif diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index da72ff4bb7..618cfaeae4 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -23,6 +23,8 @@ import BrowserServicesKit import DDGSync import Bookmarks import Subscription +import Common +import NetworkProtection protocol DependencyProvider { @@ -41,7 +43,12 @@ protocol DependencyProvider { var toggleProtectionsCounter: ToggleProtectionsCounter { get } var userBehaviorMonitor: UserBehaviorMonitor { get } var subscriptionFeatureAvailability: SubscriptionFeatureAvailability { get } - + var subscriptionManager: SubscriptionManaging { get } + var accountManager: AccountManaging { get } + var vpnFeatureVisibility: DefaultNetworkProtectionVisibility { get } + var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore { get } + var networkProtectionAccessController: NetworkProtectionAccessController { get } + var networkProtectionTunnelController: NetworkProtectionTunnelController { get } } /// Provides dependencies for objects that are not directly instantiated @@ -54,10 +61,7 @@ class AppDependencyProvider: DependencyProvider { let variantManager: VariantManager = DefaultVariantManager() let internalUserDecider: InternalUserDecider = ContentBlocking.shared.privacyConfigurationManager.internalUserDecider - lazy var featureFlagger: FeatureFlagger = DefaultFeatureFlagger( - internalUserDecider: internalUserDecider, - privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager - ) + let featureFlagger: FeatureFlagger let remoteMessagingStore: RemoteMessagingStore = RemoteMessagingStore() lazy var homePageConfiguration: HomePageConfiguration = HomePageConfiguration(variantManager: variantManager, @@ -77,4 +81,84 @@ class AppDependencyProvider: DependencyProvider { privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, subscriptionPlatform: .appStore) + // Subscription + let subscriptionManager: SubscriptionManaging + var accountManager: AccountManaging { + subscriptionManager.accountManager + } + let vpnFeatureVisibility: DefaultNetworkProtectionVisibility + let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore + let networkProtectionAccessController: NetworkProtectionAccessController + let networkProtectionTunnelController: NetworkProtectionTunnelController + + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + + init() { + featureFlagger = DefaultFeatureFlagger(internalUserDecider: internalUserDecider, + privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager) + + // MARK: - Configure Subscription + 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(iOS 15.0, *) { + subscriptionManager = SubscriptionManager(storePurchaseManager: StorePurchaseManager(), + accountManager: accountManager, + subscriptionService: subscriptionService, + authService: authService, + subscriptionEnvironment: subscriptionEnvironment) + } else { + // This is used just for iOS <15, it's a sort of mocked environment that will not be used. + subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) + } + let isProduction = (subscriptionEnvironment.serviceEnvironment == .production) + VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = isProduction ? .production : .staging + + + let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( + privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + subscriptionPlatform: .appStore) + let accessTokenProvider: () -> String? = { + func isSubscriptionEnabled() -> Bool { + if let subscriptionOverrideEnabled = UserDefaults.networkProtectionGroupDefaults.subscriptionOverrideEnabled { +#if ALPHA || DEBUG + return subscriptionOverrideEnabled +#else + return false +#endif + } + return subscriptionFeatureAvailability.isFeatureAvailable + } + + if isSubscriptionEnabled() { + return { accountManager.accessToken } + } + return { nil } + }() + networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), + serviceName: "\(Bundle.main.bundleIdentifier!).authToken", + errorEvents: .networkProtectionAppDebugEvents, + isSubscriptionEnabled: accountManager.isUserAuthenticated, + accessTokenProvider: accessTokenProvider) + networkProtectionTunnelController = NetworkProtectionTunnelController(accountManager: accountManager, + tokenStore: networkProtectionKeychainTokenStore) + networkProtectionAccessController = NetworkProtectionAccessController(featureFlagger: featureFlagger, + internalUserDecider: internalUserDecider, + accountManager: subscriptionManager.accountManager, + tokenStore: networkProtectionKeychainTokenStore, + networkProtectionTunnelController: networkProtectionTunnelController) + self.vpnFeatureVisibility = DefaultNetworkProtectionVisibility(networkProtectionAccessManager: networkProtectionAccessController, + featureFlagger: featureFlagger, + accountManager: accountManager) + } + } diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index 99303c5688..f7bca2ea6b 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -27,34 +27,30 @@ import Core import Subscription struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { - private let privacyConfigurationManager: PrivacyConfigurationManaging - private let networkProtectionTokenStore: NetworkProtectionTokenStore? - private let networkProtectionAccessManager: NetworkProtectionAccess? + public let privacyConfigurationManager: PrivacyConfigurationManaging + private let networkProtectionAccessManager: NetworkProtectionAccess private let featureFlagger: FeatureFlagger private let userDefaults: UserDefaults - private let accountManager: AccountManaging? + private let accountManager: AccountManaging init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, - networkProtectionTokenStore: NetworkProtectionTokenStore? = NetworkProtectionKeychainTokenStore(), - networkProtectionAccessManager: NetworkProtectionAccess? = NetworkProtectionAccessController(), - featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger, + networkProtectionAccessManager: NetworkProtectionAccess, + featureFlagger: FeatureFlagger, userDefaults: UserDefaults = .networkProtectionGroupDefaults, - accountManager: AccountManaging?) { + accountManager: AccountManaging) { self.privacyConfigurationManager = privacyConfigurationManager - self.networkProtectionTokenStore = networkProtectionTokenStore self.networkProtectionAccessManager = networkProtectionAccessManager self.featureFlagger = featureFlagger self.userDefaults = userDefaults self.accountManager = accountManager } - /// A lite version with fewer dependencies - /// We need this to run shouldMonitorEntitlement() check inside the token store - static func forTokenStore() -> DefaultNetworkProtectionVisibility { - DefaultNetworkProtectionVisibility(networkProtectionTokenStore: nil, - networkProtectionAccessManager: nil, - accountManager: AppDelegate.appDelegate().subscriptionManager.accountManager) + var token: String? { + if shouldMonitorEntitlement() { + return accountManager.accessToken + } + return nil } func isWaitlistBetaActive() -> Bool { @@ -62,12 +58,8 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { } func isWaitlistUser() -> Bool { - guard let networkProtectionTokenStore, let networkProtectionAccessManager else { - preconditionFailure("networkProtectionTokenStore and networkProtectionAccessManager must be non-nil") - } - let hasLegacyAuthToken = { - guard let authToken = try? networkProtectionTokenStore.fetchToken(), + guard let authToken = token, !authToken.hasPrefix(NetworkProtectionKeychainTokenStore.authTokenPrefix) else { return false } @@ -107,7 +99,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { func shouldShowVPNShortcut() -> Bool { if isPrivacyProLaunched() { - return accountManager?.isUserAuthenticated ?? false + return accountManager.isUserAuthenticated } else { return shouldKeepVPNAccessViaWaitlist() } diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift index 2a37f41d22..b6627c31f4 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift @@ -25,6 +25,8 @@ import NetworkProtection @available(iOS 15.0, *) struct VPNFeedbackFormCategoryView: View { @Environment(\.dismiss) private var dismiss + let collector = DefaultVPNMetadataCollector(networkProtectionAccessManager: AppDependencyProvider.shared.networkProtectionAccessController, + tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) var body: some View { VStack { @@ -32,7 +34,7 @@ struct VPNFeedbackFormCategoryView: View { Section { ForEach(VPNFeedbackCategory.allCases, id: \.self) { category in NavigationLink { - VPNFeedbackFormView(viewModel: VPNFeedbackFormViewModel(category: category)) { + VPNFeedbackFormView(viewModel: VPNFeedbackFormViewModel(metadataCollector: collector, category: category)) { dismiss() DispatchQueue.main.async { ActionMessageView.present(message: UserText.vpnFeedbackFormSubmittedMessage, diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormViewModel.swift b/DuckDuckGo/Feedback/VPNFeedbackFormViewModel.swift index 9dc1efc175..7f79dd674e 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormViewModel.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormViewModel.swift @@ -59,7 +59,9 @@ final class VPNFeedbackFormViewModel: ObservableObject { private let feedbackSender: VPNFeedbackSender private let category: VPNFeedbackCategory - init(metadataCollector: VPNMetadataCollector = DefaultVPNMetadataCollector(), feedbackSender: VPNFeedbackSender = DefaultVPNFeedbackSender(), category: VPNFeedbackCategory) { + init(metadataCollector: VPNMetadataCollector, + feedbackSender: VPNFeedbackSender = DefaultVPNFeedbackSender(), + category: VPNFeedbackCategory) { self.metadataCollector = metadataCollector self.feedbackSender = feedbackSender self.category = category diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index 2e24afea68..92b281f7c4 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -130,8 +130,8 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { init(statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), - networkProtectionAccessManager: NetworkProtectionAccessController = NetworkProtectionAccessController(), - tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), + networkProtectionAccessManager: NetworkProtectionAccessController, + tokenStore: NetworkProtectionTokenStore, settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { self.statusObserver = statusObserver @@ -278,7 +278,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { enableSource: .init(from: accessManager.networkProtectionAccessType()), betaParticipant: accessType == .waitlistInvited, hasToken: hasToken, - subscriptionActive: AppDelegate.appDelegate().subscriptionManager.accountManager.isUserAuthenticated + subscriptionActive: AppDependencyProvider.shared.subscriptionManager.accountManager.isUserAuthenticated ) } } diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 5fb3540b35..6f93c69e78 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -249,7 +249,7 @@ extension MainViewController { syncPausedStateManager: syncPausedStateManager) let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, - subscriptionManager: AppDelegate.appDelegate().subscriptionManager, + subscriptionManager: AppDependencyProvider.shared.subscriptionManager, deepLink: deepLinkTarget, syncPausedStateManager: syncPausedStateManager) Pixel.fire(pixel: .settingsPresented, diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index f29a4d2bba..df28d45e50 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1379,42 +1379,42 @@ class MainViewController: UIViewController { os_log("[NetP Subscription] Reset expired entitlement messaging", log: .networkProtection, type: .info) } + var networkProtectionTunnelController: NetworkProtectionTunnelController { + AppDependencyProvider.shared.networkProtectionTunnelController + } + @objc private func onEntitlementsChange(_ notification: Notification) { Task { - let accountManager = AppDelegate.appDelegate().subscriptionManager.accountManager + let accountManager = AppDependencyProvider.shared.subscriptionManager.accountManager guard case .success(false) = await accountManager.hasEntitlement(for: .networkProtection) else { return } - let controller = NetworkProtectionTunnelController() - - if await controller.isInstalled { + if await networkProtectionTunnelController.isInstalled { tunnelDefaults.enableEntitlementMessaging() } - if await controller.isConnected { + if await networkProtectionTunnelController.isConnected { DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ "reason": "entitlement-change" ]) } - await controller.stop() - await controller.removeVPN() + await networkProtectionTunnelController.stop() + await networkProtectionTunnelController.removeVPN() } } @objc private func onNetworkProtectionAccountSignOut(_ notification: Notification) { Task { - let controller = NetworkProtectionTunnelController() - - if await controller.isConnected { + if await networkProtectionTunnelController.isConnected { DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ "reason": "account-signed-out" ]) } - await controller.stop() - await controller.removeVPN() + await networkProtectionTunnelController.stop() + await networkProtectionTunnelController.removeVPN() } } #endif diff --git a/DuckDuckGo/NetworkProtectionAccessController.swift b/DuckDuckGo/NetworkProtectionAccessController.swift index aa683bf228..6efbd4c4a7 100644 --- a/DuckDuckGo/NetworkProtectionAccessController.swift +++ b/DuckDuckGo/NetworkProtectionAccessController.swift @@ -25,6 +25,7 @@ import ContentBlocking import Core import NetworkProtection import Waitlist +import Subscription enum NetworkProtectionAccessType { /// Used if the user does not have waitlist feature flag access @@ -57,6 +58,9 @@ struct NetworkProtectionAccessController: NetworkProtectionAccess { private let networkProtectionTermsAndConditionsStore: NetworkProtectionTermsAndConditionsStore private let featureFlagger: FeatureFlagger private let internalUserDecider: InternalUserDecider + private let networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore + private let accountManager: AccountManaging + private let networkProtectionTunnelController: NetworkProtectionTunnelController private var isUserLocaleAllowed: Bool { var regionCode: String? @@ -70,18 +74,22 @@ struct NetworkProtectionAccessController: NetworkProtectionAccess { return (regionCode ?? "US") == "US" } - init( - networkProtectionActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), - networkProtectionWaitlistStorage: WaitlistStorage = WaitlistKeychainStore(waitlistIdentifier: VPNWaitlist.identifier), - networkProtectionTermsAndConditionsStore: NetworkProtectionTermsAndConditionsStore = NetworkProtectionTermsAndConditionsUserDefaultsStore(), - featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger, - internalUserDecider: InternalUserDecider = AppDependencyProvider.shared.internalUserDecider + init(networkProtectionWaitlistStorage: WaitlistStorage = WaitlistKeychainStore(waitlistIdentifier: VPNWaitlist.identifier), + networkProtectionTermsAndConditionsStore: NetworkProtectionTermsAndConditionsStore = NetworkProtectionTermsAndConditionsUserDefaultsStore(), + featureFlagger: FeatureFlagger, + internalUserDecider: InternalUserDecider, + accountManager: AccountManaging, + tokenStore: NetworkProtectionKeychainTokenStore, + networkProtectionTunnelController: NetworkProtectionTunnelController ) { - self.networkProtectionActivation = networkProtectionActivation + self.accountManager = accountManager + self.networkProtectionActivation = tokenStore + self.networkProtectionKeychainTokenStore = tokenStore self.networkProtectionWaitlistStorage = networkProtectionWaitlistStorage self.networkProtectionTermsAndConditionsStore = networkProtectionTermsAndConditionsStore self.featureFlagger = featureFlagger self.internalUserDecider = internalUserDecider + self.networkProtectionTunnelController = networkProtectionTunnelController } func networkProtectionAccessType() -> NetworkProtectionAccessType { @@ -134,15 +142,13 @@ struct NetworkProtectionAccessController: NetworkProtectionAccess { } func revokeNetworkProtectionAccess() { - try? NetworkProtectionKeychainTokenStore().deleteToken() + try? networkProtectionKeychainTokenStore.deleteToken() Task { - let controller = NetworkProtectionTunnelController() - await controller.stop() - await controller.removeVPN() + await networkProtectionTunnelController.stop() + await networkProtectionTunnelController.removeVPN() } } - } #endif diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index 2cbad059d8..8a49eb1920 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -56,34 +56,34 @@ extension ConnectionServerInfoObserverThroughSession { } } -extension NetworkProtectionKeychainTokenStore { - - convenience init() { - let featureVisibility = DefaultNetworkProtectionVisibility.forTokenStore() - let isSubscriptionEnabled = featureVisibility.isPrivacyProLaunched() - let accessTokenProvider: () -> String? = { - if featureVisibility.shouldMonitorEntitlement() { - return { AppDelegate.appDelegate().subscriptionManager.accountManager.accessToken } - } - return { nil } - }() - - self.init(keychainType: .dataProtection(.unspecified), - serviceName: "\(Bundle.main.bundleIdentifier!).authToken", - errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: isSubscriptionEnabled, - accessTokenProvider: accessTokenProvider) - } -} +// extension NetworkProtectionKeychainTokenStore { +// +// convenience init(accountManager: AccountManaging) { +// let featureVisibility = AppDependencyProvider.shared.vpnFeatureVisibility +// let isSubscriptionEnabled = featureVisibility.isPrivacyProLaunched() +// let accessTokenProvider: () -> String? = { +// if featureVisibility.shouldMonitorEntitlement() { +// return { accountManager.accessToken } +// } +// return { nil } +// }() +// +// self.init(keychainType: .dataProtection(.unspecified), +// serviceName: "\(Bundle.main.bundleIdentifier!).authToken", +// errorEvents: .networkProtectionAppDebugEvents, +// isSubscriptionEnabled: isSubscriptionEnabled, +// accessTokenProvider: accessTokenProvider) +// } +// } extension NetworkProtectionCodeRedemptionCoordinator { - convenience init(isManualCodeRedemptionFlow: Bool = false) { + + convenience init(isManualCodeRedemptionFlow: Bool = false, accountManager: AccountManaging) { let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) - var subscriptionManager: SubscriptionManaging { AppDelegate.appDelegate().subscriptionManager } - let networkProtectionVisibility = DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager) + let networkProtectionVisibility = AppDependencyProvider.shared.vpnFeatureVisibility self.init( environment: settings.selectedEnvironment, - tokenStore: NetworkProtectionKeychainTokenStore(), + tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore, isManualCodeRedemptionFlow: isManualCodeRedemptionFlow, errorEvents: .networkProtectionAppDebugEvents, isSubscriptionEnabled: networkProtectionVisibility.isPrivacyProLaunched() @@ -101,21 +101,22 @@ extension NetworkProtectionVPNSettingsViewModel { } extension NetworkProtectionLocationListCompositeRepository { - convenience init() { + + convenience init(accountManager: AccountManaging) { let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) - var subscriptionManager: SubscriptionManaging { AppDelegate.appDelegate().subscriptionManager } self.init( environment: settings.selectedEnvironment, - tokenStore: NetworkProtectionKeychainTokenStore(), + tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore, errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).isPrivacyProLaunched() + isSubscriptionEnabled: AppDependencyProvider.shared.vpnFeatureVisibility.isPrivacyProLaunched() ) } } extension NetworkProtectionVPNLocationViewModel { - convenience init() { - let locationListRepository = NetworkProtectionLocationListCompositeRepository() + + convenience init(accountManager: AccountManaging) { + let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) self.init( locationListRepository: locationListRepository, settings: VPNSettings(defaults: .networkProtectionGroupDefaults) diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 0821f55bb7..fe0555c706 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -154,8 +154,8 @@ final class NetworkProtectionDebugViewController: UITableViewController { } required convenience init?(coder: NSCoder) { - self.init(coder: coder, tokenStore: NetworkProtectionKeychainTokenStore(), - accountManager: AppDelegate.appDelegate().subscriptionManager.accountManager) + self.init(coder: coder, tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore, + accountManager: AppDependencyProvider.shared.subscriptionManager.accountManager) } override func viewWillAppear(_ animated: Bool) { @@ -645,7 +645,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { cell.textLabel?.text = "Subscription Override: N/A" } case .debugInfo: - let vpnVisibility = DefaultNetworkProtectionVisibility(accountManager: accountManager) + let vpnVisibility = AppDependencyProvider.shared.vpnFeatureVisibility cell.textLabel?.font = .monospacedSystemFont(ofSize: 13.0, weight: .regular) cell.textLabel?.text = """ @@ -667,7 +667,8 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") @MainActor private func refreshMetadata() async { - let collector = DefaultVPNMetadataCollector() + let collector = DefaultVPNMetadataCollector(networkProtectionAccessManager: AppDependencyProvider.shared.networkProtectionAccessController, + tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) self.vpnMetadata = await collector.collectMetadata() self.tableView.reloadData() } @@ -715,8 +716,7 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") } private func clearAllVPNData() { - let accessController = NetworkProtectionAccessController() - accessController.revokeNetworkProtectionAccess() + AppDependencyProvider.shared.networkProtectionAccessController.revokeNetworkProtectionAccess() } } diff --git a/DuckDuckGo/NetworkProtectionInviteView.swift b/DuckDuckGo/NetworkProtectionInviteView.swift index a505118dc8..2b7c171e5b 100644 --- a/DuckDuckGo/NetworkProtectionInviteView.swift +++ b/DuckDuckGo/NetworkProtectionInviteView.swift @@ -148,7 +148,8 @@ struct NetworkProtectionInviteView_Previews: PreviewProvider { static var previews: some View { NetworkProtectionInviteView( model: NetworkProtectionInviteViewModel( - redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator() + redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator(accountManager: + AppDependencyProvider.shared.subscriptionManager.accountManager) ) { } ) } diff --git a/DuckDuckGo/NetworkProtectionInviteViewModel.swift b/DuckDuckGo/NetworkProtectionInviteViewModel.swift index 0ee06a582e..02693b9c75 100644 --- a/DuckDuckGo/NetworkProtectionInviteViewModel.swift +++ b/DuckDuckGo/NetworkProtectionInviteViewModel.swift @@ -87,7 +87,7 @@ final class NetworkProtectionInviteViewModel: ObservableObject { @Published var redeemedText: String? private func updateAuthenticatedText() { - redeemedText = NetworkProtectionKeychainTokenStore().isFeatureActivated ? "Already redeemed" : nil + redeemedText = AppDependencyProvider.shared.networkProtectionKeychainTokenStore.isFeatureActivated ? "Already redeemed" : nil } } diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 3f7f3832bc..d6c52e8324 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -21,29 +21,34 @@ import SwiftUI import NetworkProtection +import Subscription @available(iOS 15, *) struct NetworkProtectionRootView: View { - let model = NetworkProtectionRootViewModel() + let model = NetworkProtectionRootViewModel(featureActivation: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) let inviteCompletion: () -> Void + var accountManager: AccountManaging { + AppDependencyProvider.shared.subscriptionManager.accountManager + } var body: some View { let inviteViewModel = NetworkProtectionInviteViewModel( - redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator(isManualCodeRedemptionFlow: true), + redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator(isManualCodeRedemptionFlow: true, + accountManager: accountManager), completion: inviteCompletion ) - if DefaultNetworkProtectionVisibility(accountManager: AppDelegate.appDelegate().subscriptionManager.accountManager).isPrivacyProLaunched() { - NetworkProtectionStatusView( - statusModel: NetworkProtectionStatusViewModel() - ) + + let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) + let statusViewModel = NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, + locationListRepository: locationListRepository) + if AppDependencyProvider.shared.vpnFeatureVisibility.isPrivacyProLaunched() { + NetworkProtectionStatusView(statusModel: statusViewModel) } else { switch model.initialViewKind { case .invite: NetworkProtectionInviteView(model: inviteViewModel) case .status: - NetworkProtectionStatusView( - statusModel: NetworkProtectionStatusViewModel() - ) + NetworkProtectionStatusView(statusModel: statusViewModel ) } } } diff --git a/DuckDuckGo/NetworkProtectionRootViewModel.swift b/DuckDuckGo/NetworkProtectionRootViewModel.swift index e47b5d03e9..b72910a5fd 100644 --- a/DuckDuckGo/NetworkProtectionRootViewModel.swift +++ b/DuckDuckGo/NetworkProtectionRootViewModel.swift @@ -30,7 +30,7 @@ enum NetworkProtectionInitialViewKind { final class NetworkProtectionRootViewModel: ObservableObject { var initialViewKind: NetworkProtectionInitialViewKind - init(featureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore()) { + init(featureActivation: NetworkProtectionFeatureActivation) { initialViewKind = featureActivation.isFeatureActivated ? .status : .invite } } diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 0d317bbdcb..a4a231e83f 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -142,12 +142,12 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @Published public var animationsOn: Bool = false - public init(tunnelController: TunnelController = NetworkProtectionTunnelController(), + public init(tunnelController: TunnelController, settings: VPNSettings = VPNSettings(defaults: .networkProtectionGroupDefaults), statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), errorObserver: ConnectionErrorObserver = ConnectionErrorObserverThroughSession(), - locationListRepository: NetworkProtectionLocationListRepository = NetworkProtectionLocationListCompositeRepository()) { + locationListRepository: NetworkProtectionLocationListRepository) { self.tunnelController = tunnelController self.settings = settings self.statusObserver = statusObserver diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index 3aaedb8115..f3aef0a073 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -24,12 +24,13 @@ import Combine import Core import NetworkExtension import NetworkProtection +import Subscription final class NetworkProtectionTunnelController: TunnelController { static var shouldSimulateFailure: Bool = false private let debugFeatures = NetworkProtectionDebugFeatures() - private let tokenStore = NetworkProtectionKeychainTokenStore() + private let tokenStore: NetworkProtectionKeychainTokenStore private let errorStore = NetworkProtectionTunnelErrorStore() private let notificationCenter: NotificationCenter = .default private var previousStatus: NEVPNStatus = .invalid @@ -72,7 +73,8 @@ final class NetworkProtectionTunnelController: TunnelController { } } - init() { + init(accountManager: AccountManaging, tokenStore: NetworkProtectionKeychainTokenStore) { + self.tokenStore = tokenStore subscribeToStatusChanges() } diff --git a/DuckDuckGo/NetworkProtectionVPNLocationView.swift b/DuckDuckGo/NetworkProtectionVPNLocationView.swift index 2708c23d49..420a952e58 100644 --- a/DuckDuckGo/NetworkProtectionVPNLocationView.swift +++ b/DuckDuckGo/NetworkProtectionVPNLocationView.swift @@ -24,7 +24,7 @@ import SwiftUI @available(iOS 15, *) struct NetworkProtectionVPNLocationView: View { - @StateObject var model = NetworkProtectionVPNLocationViewModel() + @StateObject var model = NetworkProtectionVPNLocationViewModel(accountManager: AppDependencyProvider.shared.subscriptionManager.accountManager) var body: some View { List { diff --git a/DuckDuckGo/RemoteMessaging.swift b/DuckDuckGo/RemoteMessaging.swift index 942181c690..2f0439f356 100644 --- a/DuckDuckGo/RemoteMessaging.swift +++ b/DuckDuckGo/RemoteMessaging.swift @@ -157,9 +157,9 @@ struct RemoteMessaging { let daysSinceNetworkProtectionEnabled: Int #if NETWORK_PROTECTION - let vpnAccess = NetworkProtectionAccessController() + let vpnAccess = AppDependencyProvider.shared.networkProtectionAccessController let accessType = vpnAccess.networkProtectionAccessType() - let isVPNActivated = NetworkProtectionKeychainTokenStore().isFeatureActivated + let isVPNActivated = AppDependencyProvider.shared.networkProtectionKeychainTokenStore.isFeatureActivated let activationDateStore = DefaultVPNWaitlistActivationDateStore() isNetworkProtectionWaitlistUser = (accessType == .waitlistInvited) && isVPNActivated diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 071ffd3339..b079835663 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -454,7 +454,7 @@ extension SettingsViewModel { var enabled = false #if NETWORK_PROTECTION if #available(iOS 15, *) { - enabled = DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).shouldKeepVPNAccessViaWaitlist() + enabled = AppDependencyProvider.shared.vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() } #endif return SettingsState.NetworkProtection(enabled: enabled, status: "") @@ -494,7 +494,7 @@ extension SettingsViewModel { #if NETWORK_PROTECTION private func updateNetPStatus(connectionStatus: ConnectionStatus) { - if DefaultNetworkProtectionVisibility(accountManager: subscriptionManager.accountManager).isPrivacyProLaunched() { + if AppDependencyProvider.shared.vpnFeatureVisibility.isPrivacyProLaunched() { switch connectionStatus { case .connected: self.state.networkProtection.status = UserText.netPCellConnected @@ -502,7 +502,7 @@ extension SettingsViewModel { self.state.networkProtection.status = UserText.netPCellDisconnected } } else { - switch NetworkProtectionAccessController().networkProtectionAccessType() { + switch AppDependencyProvider.shared.networkProtectionAccessController.networkProtectionAccessType() { case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: self.state.networkProtection.status = VPNWaitlist.shared.settingsSubtitle case .waitlistInvited, .inviteCodeInvited: diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 9cc5a933bb..59e4b454da 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -64,7 +64,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { @Published private(set) var state: State - init(subscriptionManager: SubscriptionManaging = AppDelegate.appDelegate().subscriptionManager) { + init(subscriptionManager: SubscriptionManaging = AppDependencyProvider.shared.subscriptionManager) { self.subscriptionManager = subscriptionManager let subscriptionFAQURL = SubscriptionURL.FAQ.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) self.state = State(faqURL: subscriptionFAQURL) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift index 4ee315c419..0cf8a24448 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift @@ -37,7 +37,7 @@ struct SubscriptionContainerView: View { init(currentView: CurrentView) { _currentViewState = State(initialValue: currentView) - self.viewModel = SubscriptionContainerViewModel(subscriptionManager: AppDelegate.appDelegate().subscriptionManager) + self.viewModel = SubscriptionContainerViewModel(subscriptionManager: AppDependencyProvider.shared.subscriptionManager) // let userScript = viewModel.userScript // let subFeature = viewModel.subFeature flowViewModel = viewModel.flow diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index bd1882ff7c..76ed85d96d 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -36,7 +36,7 @@ struct SubscriptionActivityViewController: UIViewControllerRepresentable { struct SubscriptionITPView: View { @Environment(\.dismiss) var dismiss - @StateObject var viewModel = SubscriptionITPViewModel(subscriptionManager: AppDelegate.appDelegate().subscriptionManager) + @StateObject var viewModel = SubscriptionITPViewModel(subscriptionManager: AppDependencyProvider.shared.subscriptionManager) @State private var shouldShowNavigationBar = false @State private var isShowingActivityView = false diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 17bfc48a83..40ecf0bd83 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -30,7 +30,7 @@ import NetworkProtection final class SubscriptionDebugViewController: UITableViewController { private var subscriptionManager: SubscriptionManaging { - AppDelegate.appDelegate().subscriptionManager + AppDependencyProvider.shared.subscriptionManager } private let titles = [ diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index ca4ec1f7cd..b56f035be6 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -127,7 +127,7 @@ class TabViewController: UIViewController { private var trackersInfoWorkItem: DispatchWorkItem? - private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault(subscriptionManager: AppDelegate.appDelegate().subscriptionManager) + private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault(subscriptionManager: AppDependencyProvider.shared.subscriptionManager) private var currentlyLoadedURL: URL? #if NETWORK_PROTECTION diff --git a/DuckDuckGo/VPNWaitlist.swift b/DuckDuckGo/VPNWaitlist.swift index 522a31a414..dfe6788d78 100644 --- a/DuckDuckGo/VPNWaitlist.swift +++ b/DuckDuckGo/VPNWaitlist.swift @@ -87,7 +87,7 @@ final class VPNWaitlist: Waitlist { store: store, request: request, featureFlagger: AppDependencyProvider.shared.featureFlagger, - networkProtectionAccess: NetworkProtectionAccessController() + networkProtectionAccess: AppDependencyProvider.shared.networkProtectionAccessController ) } diff --git a/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift b/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift index 2f4f358689..172af711ec 100644 --- a/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift +++ b/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift @@ -25,6 +25,7 @@ import NetworkProtection import NetworkExtension import NetworkProtectionTestUtils import WaitlistMocks +import SubscriptionTestingUtilities @testable import DuckDuckGo final class NetworkProtectionAccessControllerTests: XCTestCase { @@ -148,13 +149,11 @@ final class NetworkProtectionAccessControllerTests: XCTestCase { let mockFeatureFlagger = createFeatureFlagger(withSubfeatureEnabled: featureFlagsEnabled) let internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore) - return NetworkProtectionAccessController( - networkProtectionActivation: mockActivation, - networkProtectionWaitlistStorage: mockWaitlistStorage, - networkProtectionTermsAndConditionsStore: mockTermsAndConditionsStore, - featureFlagger: mockFeatureFlagger, - internalUserDecider: internalUserDecider - ) + return NetworkProtectionAccessController(networkProtectionWaitlistStorage: mockWaitlistStorage, + networkProtectionTermsAndConditionsStore: mockTermsAndConditionsStore, + featureFlagger: mockFeatureFlagger, + internalUserDecider: internalUserDecider, + accountManager: AccountManagerMock(isUserAuthenticated: true)) } private func createFeatureFlagger(withSubfeatureEnabled enabled: Bool) -> DefaultFeatureFlagger { diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index dc5735b22c..1930cf4058 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -276,12 +276,10 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { return { accountManager.accessToken } } return { nil } }() - let tokenStore = NetworkProtectionKeychainTokenStore( - keychainType: .dataProtection(.unspecified), - errorEvents: nil, - isSubscriptionEnabled: isSubscriptionEnabled, - accessTokenProvider: accessTokenProvider - ) + let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), + errorEvents: nil, + isSubscriptionEnabled: isSubscriptionEnabled, + accessTokenProvider: accessTokenProvider) let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() From b6dc66484a030047e517ca2b4c1a449a137ec52f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 13 May 2024 09:53:43 +0100 Subject: [PATCH 10/39] local BSK removed --- DuckDuckGo.xcodeproj/project.pbxproj | 10 ---------- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9a65fe1e55..c7c1bbaadb 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -880,7 +880,6 @@ F1564F032B7B915F00D454A6 /* AppDelegate+SKAD4.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */; }; F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */; }; F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */; }; - F15E9F3C2BECFCFD00DEFDDE /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15E9F3B2BECFCFD00DEFDDE /* SubscriptionTestingUtilities */; }; F15E9F3E2BEE128200DEFDDE /* SubscriptionManageriOS14.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */; }; F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C141E57336D00DEDCAF /* TabManager.swift */; }; @@ -2525,7 +2524,6 @@ F1134ECF1F40EBE200B73467 /* JsonTestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonTestDataLoader.swift; sourceTree = ""; }; F1134ED41F40F15800B73467 /* StatisticsUserDefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticsUserDefaultsTests.swift; sourceTree = ""; }; F114C55A1E66EB020018F95F /* NibLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoading.swift; sourceTree = ""; }; - F119CCC62BE397850002B30E /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F130D7391E5776C500C45811 /* OmniBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBarDelegate.swift; sourceTree = ""; }; F1386BA31E6846C40062FC3C /* TabDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabDelegate.swift; sourceTree = ""; }; F13B4BBF1F180D8A00814661 /* TabsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsModel.swift; sourceTree = ""; }; @@ -2686,7 +2684,6 @@ F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, EEFAB4672A73C230008A38E4 /* NetworkProtectionTestUtils in Frameworks */, 4BE67B072B96B9B0007335F7 /* Common in Frameworks */, - F15E9F3C2BECFCFD00DEFDDE /* SubscriptionTestingUtilities in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3508,7 +3505,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - F119CCC62BE397850002B30E /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -5577,7 +5573,6 @@ F115ED9B2B4EFC8E001A0453 /* TestUtils */, 4BE67B042B96B9AB007335F7 /* ContentBlocking */, 4BE67B062B96B9B0007335F7 /* Common */, - F15E9F3B2BECFCFD00DEFDDE /* SubscriptionTestingUtilities */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -7063,7 +7058,6 @@ 854858E32937BC550063610B /* CollectionExtension.swift in Sources */, 1E6A4D692984208800A371D3 /* LocaleExtension.swift in Sources */, 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */, - D6FF22482BC95F0B008E7BCC /* AccountManager+AppGroup.swift in Sources */, 566B73732BECE4F200FF1959 /* SyncErrorHandling.swift in Sources */, F1134EB01F40AC6300B73467 /* AtbParser.swift in Sources */, EE50052E29C369D300AE0773 /* FeatureFlag.swift in Sources */, @@ -10049,10 +10043,6 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = TestUtils; }; - F15E9F3B2BECFCFD00DEFDDE /* SubscriptionTestingUtilities */ = { - isa = XCSwiftPackageProductDependency; - productName = SubscriptionTestingUtilities; - }; F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */ = { isa = XCSwiftPackageProductDependency; package = F1D43AF82B99C1D300BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2332370d4d..c29c9c5242 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" : "5936635dc4bd4978e74939e8bc39e6b054e18199" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 7dea7efa1e3eb305c5ccfe7560852874ad809cc4 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 13 May 2024 15:24:35 +0100 Subject: [PATCH 11/39] lint + some unit tests fix --- DuckDuckGo.xcodeproj/project.pbxproj | 42 +++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 9 ---- DuckDuckGo/AppDelegate+Waitlists.swift | 3 +- DuckDuckGo/AppDelegate.swift | 2 +- DuckDuckGo/AppDependencyProvider.swift | 1 + DuckDuckGo/TabURLInterceptor.swift | 9 ++-- DuckDuckGo/TabViewController.swift | 4 +- DuckDuckGoTests/MockDependencyProvider.swift | 47 ++++++++++++++++++- ...tworkProtectionAccessControllerTests.swift | 4 ++ ...workProtectionFeatureVisibilityTests.swift | 3 +- ...etworkProtectionStatusViewModelTests.swift | 15 +++--- DuckDuckGoTests/TabURLInterceptorTests.swift | 9 ++-- 12 files changed, 120 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c7c1bbaadb..b747bc8b95 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -877,6 +877,10 @@ F143C3281E4A9A0E00CFDE3A /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F143C3241E4A9A0E00CFDE3A /* StringExtension.swift */; }; F143C3291E4A9A0E00CFDE3A /* URLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F143C3251E4A9A0E00CFDE3A /* URLExtension.swift */; }; F14E491F1E391CE900DC037C /* URLExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14E491E1E391CE900DC037C /* URLExtensionTests.swift */; }; + F15531902BF215ED0029ED04 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F155318F2BF215ED0029ED04 /* Subscription */; }; + F15531922BF215ED0029ED04 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15531912BF215ED0029ED04 /* SubscriptionTestingUtilities */; }; + F15531942BF215F60029ED04 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F15531932BF215F60029ED04 /* Subscription */; }; + F15531962BF215F60029ED04 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15531952BF215F60029ED04 /* SubscriptionTestingUtilities */; }; F1564F032B7B915F00D454A6 /* AppDelegate+SKAD4.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */; }; F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */; }; F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */; }; @@ -2545,6 +2549,7 @@ F143C32C1E4A9A4800CFDE3A /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIViewControllerExtension.swift; path = ../Core/UIViewControllerExtension.swift; sourceTree = ""; }; F143C3451E4AA32D00CFDE3A /* SearchBarExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchBarExtension.swift; path = ../Core/SearchBarExtension.swift; sourceTree = ""; }; F14E491E1E391CE900DC037C /* URLExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtensionTests.swift; sourceTree = ""; }; + F15531972BF216CF0029ED04 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate+SKAD4.swift"; sourceTree = ""; }; F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = ""; }; F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteViewController.swift; sourceTree = ""; }; @@ -2681,6 +2686,8 @@ F486D3362506A037002D07D7 /* OHHTTPStubs in Frameworks */, F486D3382506A225002D07D7 /* OHHTTPStubsSwift in Frameworks */, 4BE67B052B96B9AB007335F7 /* ContentBlocking in Frameworks */, + F15531922BF215ED0029ED04 /* SubscriptionTestingUtilities in Frameworks */, + F15531902BF215ED0029ED04 /* Subscription in Frameworks */, F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, EEFAB4672A73C230008A38E4 /* NetworkProtectionTestUtils in Frameworks */, 4BE67B072B96B9B0007335F7 /* Common in Frameworks */, @@ -2713,6 +2720,8 @@ 1E1D8B632995143200C96994 /* OHHTTPStubs in Frameworks */, 1E1D8B652995143200C96994 /* OHHTTPStubsSwift in Frameworks */, 4BE67B012B96B741007335F7 /* Common in Frameworks */, + F15531962BF215F60029ED04 /* SubscriptionTestingUtilities in Frameworks */, + F15531942BF215F60029ED04 /* Subscription in Frameworks */, 4BE67B032B96B864007335F7 /* ContentBlocking in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3505,6 +3514,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F15531972BF216CF0029ED04 /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -5573,6 +5583,8 @@ F115ED9B2B4EFC8E001A0453 /* TestUtils */, 4BE67B042B96B9AB007335F7 /* ContentBlocking */, 4BE67B062B96B9B0007335F7 /* Common */, + F155318F2BF215ED0029ED04 /* Subscription */, + F15531912BF215ED0029ED04 /* SubscriptionTestingUtilities */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -5635,6 +5647,8 @@ 1E1D8B642995143200C96994 /* OHHTTPStubsSwift */, 4BE67B002B96B741007335F7 /* Common */, 4BE67B022B96B864007335F7 /* ContentBlocking */, + F15531932BF215F60029ED04 /* Subscription */, + F15531952BF215F60029ED04 /* SubscriptionTestingUtilities */, ); productName = IntegrationTests; productReference = 85D33FCB25C97B6E002B91A6 /* IntegrationTests.xctest */; @@ -8355,6 +8369,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -8363,6 +8378,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ""; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; }; @@ -8373,6 +8389,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -8381,6 +8398,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ""; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; }; @@ -9044,6 +9062,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -9052,6 +9071,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ""; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; }; @@ -9452,6 +9472,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGoTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -9460,6 +9481,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ""; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; }; @@ -10043,6 +10065,26 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = TestUtils; }; + F155318F2BF215ED0029ED04 /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; + F15531912BF215ED0029ED04 /* SubscriptionTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = SubscriptionTestingUtilities; + }; + F15531932BF215F60029ED04 /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; + F15531952BF215F60029ED04 /* SubscriptionTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = SubscriptionTestingUtilities; + }; F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */ = { isa = XCSwiftPackageProductDependency; package = F1D43AF82B99C1D300BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 451255ae58..a94f848fd6 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_2", - "revision" : "ee49b912e40f9330a58f835c9ecd529543fd5530" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index c96c77f5c2..b1573f5d0b 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -64,7 +64,8 @@ extension AppDelegate { Task { do { try await NetworkProtectionCodeRedemptionCoordinator(accountManager: - AppDependencyProvider.shared.subscriptionManager.accountManager).redeem(inviteCode) + AppDependencyProvider.shared.subscriptionManager.accountManager + ).redeem(inviteCode) VPNWaitlist.shared.sendInviteCodeAvailableNotification() } catch {} } diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 9f1848dc99..44427c1f6c 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -213,7 +213,7 @@ import WebKit PixelExperiment.install() // MARK: Sync initialisation - + // TODO: Why are not all these vars in the init? #if DEBUG let defaultEnvironment = ServerEnvironment.development #else diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 618cfaeae4..8bbdf5b349 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -93,6 +93,7 @@ class AppDependencyProvider: DependencyProvider { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + // swiftlint:disable:next function_body_length init() { featureFlagger = DefaultFeatureFlagger(internalUserDecider: internalUserDecider, privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager) diff --git a/DuckDuckGo/TabURLInterceptor.swift b/DuckDuckGo/TabURLInterceptor.swift index 03b65eaf17..66287cf353 100644 --- a/DuckDuckGo/TabURLInterceptor.swift +++ b/DuckDuckGo/TabURLInterceptor.swift @@ -37,10 +37,11 @@ protocol TabURLInterceptor { final class TabURLInterceptorDefault: TabURLInterceptor { - private let subscriptionManager: SubscriptionManaging + typealias CanPurchaseUpdater = () -> Bool + private let canPurchase: CanPurchaseUpdater - init(subscriptionManager: SubscriptionManaging) { - self.subscriptionManager = subscriptionManager + init(canPurchase: @escaping CanPurchaseUpdater) { + self.canPurchase = canPurchase } static let interceptedURLs: [InterceptedURLInfo] = [ @@ -87,7 +88,7 @@ extension TabURLInterceptorDefault { switch url { // Opens the Privacy Pro Subscription Purchase page (if user can purchase) case .privacyPro: - if subscriptionManager.canPurchase { + if canPurchase() { NotificationCenter.default.post(name: .urlInterceptPrivacyPro, object: nil) return false } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index b56f035be6..5d4604b1d5 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -127,7 +127,9 @@ class TabViewController: UIViewController { private var trackersInfoWorkItem: DispatchWorkItem? - private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault(subscriptionManager: AppDependencyProvider.shared.subscriptionManager) + private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault { + return AppDependencyProvider.shared.subscriptionManager.canPurchase + } private var currentlyLoadedURL: URL? #if NETWORK_PROTECTION diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index b49e71fd48..d35593d773 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -22,10 +22,11 @@ import Core import BrowserServicesKit import DDGSync import Subscription +import SubscriptionTestingUtilities +import NetworkProtection @testable import DuckDuckGo class MockDependencyProvider: DependencyProvider { - var appSettings: AppSettings var variantManager: VariantManager var featureFlagger: FeatureFlagger @@ -41,6 +42,12 @@ class MockDependencyProvider: DependencyProvider { var userBehaviorMonitor: UserBehaviorMonitor var toggleProtectionsCounter: ToggleProtectionsCounter var subscriptionFeatureAvailability: SubscriptionFeatureAvailability + var subscriptionManager: SubscriptionManaging + var accountManager: AccountManaging + var vpnFeatureVisibility: DefaultNetworkProtectionVisibility + var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore + var networkProtectionAccessController: NetworkProtectionAccessController + var networkProtectionTunnelController: NetworkProtectionTunnelController init() { let defaultProvider = AppDependencyProvider() @@ -59,5 +66,43 @@ class MockDependencyProvider: DependencyProvider { userBehaviorMonitor = defaultProvider.userBehaviorMonitor toggleProtectionsCounter = defaultProvider.toggleProtectionsCounter subscriptionFeatureAvailability = defaultProvider.subscriptionFeatureAvailability + + // TODO: Create Mocks for everything + accountManager = AccountManagerMock(isUserAuthenticated: true) + if #available(iOS 15.0, *) { + let subscriptionService = SubscriptionService(currentServiceEnvironment: .production) + let authService = AuthService(currentServiceEnvironment: .production) + let storePurchaseManaging = StorePurchaseManager() + subscriptionManager = SubscriptionManagerMock(accountManager: accountManager, + subscriptionService: subscriptionService, + authService: authService, + storePurchaseManaging: storePurchaseManaging, + currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, + platform: .appStore), + canPurchase: true) + } else { + // This is used just for iOS <15, it's a sort of mocked environment that will not be used. + subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) + } + + let accessTokenProvider: () -> String? = { { "sometoken" } }() + let featureFlagger = DefaultFeatureFlagger(internalUserDecider: internalUserDecider, + privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager) + networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), + serviceName: "\(Bundle.main.bundleIdentifier!).authToken", + errorEvents: .networkProtectionAppDebugEvents, + isSubscriptionEnabled: accountManager.isUserAuthenticated, + accessTokenProvider: accessTokenProvider) + networkProtectionTunnelController = NetworkProtectionTunnelController(accountManager: accountManager, + tokenStore: networkProtectionKeychainTokenStore) + networkProtectionAccessController = NetworkProtectionAccessController(featureFlagger: featureFlagger, + internalUserDecider: internalUserDecider, + accountManager: subscriptionManager.accountManager, + tokenStore: networkProtectionKeychainTokenStore, + networkProtectionTunnelController: networkProtectionTunnelController) + vpnFeatureVisibility = DefaultNetworkProtectionVisibility(networkProtectionAccessManager: networkProtectionAccessController, + featureFlagger: featureFlagger, + accountManager: accountManager) + } } diff --git a/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift b/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift index 172af711ec..5b390fe75d 100644 --- a/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift +++ b/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift @@ -17,6 +17,9 @@ // limitations under the License. // +// TODO: Re-implement this in a way that makes sense + +/* #if NETWORK_PROTECTION import XCTest @@ -182,3 +185,4 @@ private class MockNetworkProtectionTermsAndConditionsStore: NetworkProtectionTer } #endif +*/ diff --git a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift b/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift index f48eadcddb..25922303e5 100644 --- a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift +++ b/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift @@ -20,6 +20,7 @@ import XCTest @testable import DuckDuckGo import Subscription +import SubscriptionTestingUtilities /// Test all permutations according to https://app.asana.com/0/0/1206812323779606/f final class NetworkProtectionFeatureVisibilityTests: XCTestCase { @@ -88,7 +89,7 @@ final class NetworkProtectionFeatureVisibilityTests: XCTestCase { struct NetworkProtectionFeatureVisibilityMocks: NetworkProtectionFeatureVisibility { - let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + let accountManager = AccountManagerMock(isUserAuthenticated: true) // TODO: this makes no sense func shouldShowThankYouMessaging() -> Bool { isPrivacyProLaunched() && isWaitlistUser() diff --git a/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift b/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift index 38ae56b519..5ce0d97858 100644 --- a/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift +++ b/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift @@ -21,6 +21,7 @@ import XCTest import NetworkProtection import NetworkExtension import NetworkProtectionTestUtils +import SubscriptionTestingUtilities @testable import DuckDuckGo final class NetworkProtectionStatusViewModelTests: XCTestCase { @@ -39,11 +40,10 @@ final class NetworkProtectionStatusViewModelTests: XCTestCase { tunnelController = MockTunnelController() statusObserver = MockConnectionStatusObserver() serverInfoObserver = MockConnectionServerInfoObserver() - viewModel = NetworkProtectionStatusViewModel( - tunnelController: tunnelController, - statusObserver: statusObserver, - serverInfoObserver: serverInfoObserver - ) + viewModel = NetworkProtectionStatusViewModel(tunnelController: tunnelController, + statusObserver: statusObserver, + serverInfoObserver: serverInfoObserver, + locationListRepository: MockNetworkProtectionLocationListRepository()) } override func tearDown() { @@ -56,7 +56,10 @@ final class NetworkProtectionStatusViewModelTests: XCTestCase { func testInit_prefetchesLocationList() throws { let locationListRepo = MockNetworkProtectionLocationListRepository() - viewModel = NetworkProtectionStatusViewModel(locationListRepository: locationListRepo) + viewModel = NetworkProtectionStatusViewModel(tunnelController: tunnelController, + statusObserver: statusObserver, + serverInfoObserver: serverInfoObserver, + locationListRepository: MockNetworkProtectionLocationListRepository()) waitFor(condition: locationListRepo.didCallFetchLocationList) } diff --git a/DuckDuckGoTests/TabURLInterceptorTests.swift b/DuckDuckGoTests/TabURLInterceptorTests.swift index b96e78ed9e..8958816daa 100644 --- a/DuckDuckGoTests/TabURLInterceptorTests.swift +++ b/DuckDuckGoTests/TabURLInterceptorTests.swift @@ -19,6 +19,7 @@ import XCTest import Subscription +import SubscriptionTestingUtilities @testable import DuckDuckGo class TabURLInterceptorDefaultTests: XCTestCase { @@ -27,9 +28,9 @@ class TabURLInterceptorDefaultTests: XCTestCase { override func setUp() { super.setUp() - // Simulate purchase allowance - SubscriptionPurchaseEnvironment.canPurchase = true - urlInterceptor = TabURLInterceptorDefault() + urlInterceptor = TabURLInterceptorDefault(canPurchase: { + true + }) } override func tearDown() { @@ -48,7 +49,7 @@ class TabURLInterceptorDefaultTests: XCTestCase { } func testNotificationForInterceptedPrivacyProPath() { - let expectation = self.expectation(forNotification: .urlInterceptPrivacyPro, object: nil, handler: nil) +// let expectation = self.expectation(forNotification: .urlInterceptPrivacyPro, object: nil, handler: nil) let url = URL(string: "https://duckduckgo.com/pro")! let canNavigate = urlInterceptor.allowsNavigatingTo(url: url) From da6eb79be02c63743a623484048602d52bdc857f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 13 May 2024 16:38:03 +0100 Subject: [PATCH 12/39] unit tests fixes and task moved from model init to view --- DuckDuckGo/AppDelegate.swift | 6 ++-- .../NetworkProtectionFeatureVisibility.swift | 2 +- DuckDuckGo/NetworkProtectionRootView.swift | 29 ++++++++++++------- .../NetworkProtectionStatusViewModel.swift | 5 ---- ...workProtectionFeatureVisibilityTests.swift | 18 +++++++++++- ...etworkProtectionStatusViewModelTests.swift | 9 ------ DuckDuckGoTests/TabURLInterceptorTests.swift | 2 +- 7 files changed, 40 insertions(+), 31 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 44427c1f6c..86de29e6d2 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -89,6 +89,9 @@ import WebKit // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // SKAD4 support + updateSKAd(conversionValue: 1) + #if targetEnvironment(simulator) if ProcessInfo.processInfo.environment["UITESTING"] == "true" { // Disable hardware keyboards. @@ -141,9 +144,6 @@ import WebKit clearTmp() - // SKAD4 support - updateSKAd(conversionValue: 1) - _ = DefaultUserAgentManager.shared testing = ProcessInfo().arguments.contains("testing") if testing { diff --git a/DuckDuckGo/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/NetworkProtectionFeatureVisibility.swift index 8ee0e79ac4..5b171ac0d8 100644 --- a/DuckDuckGo/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/NetworkProtectionFeatureVisibility.swift @@ -38,6 +38,6 @@ public protocol NetworkProtectionFeatureVisibility { /// N.B. Backend will independently check for valid entitlement regardless of this value func shouldMonitorEntitlement() -> Bool - /// Whether to show VPN shortcut on the homescreen + /// Whether to show VPN shortcut on the home screen func shouldShowVPNShortcut() -> Bool } diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index d6c52e8324..98ebee029f 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -25,22 +25,29 @@ import Subscription @available(iOS 15, *) struct NetworkProtectionRootView: View { + let model = NetworkProtectionRootViewModel(featureActivation: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) + let inviteViewModel: NetworkProtectionInviteViewModel + let statusViewModel: NetworkProtectionStatusViewModel let inviteCompletion: () -> Void - var accountManager: AccountManaging { - AppDependencyProvider.shared.subscriptionManager.accountManager - } - var body: some View { - let inviteViewModel = NetworkProtectionInviteViewModel( - redemptionCoordinator: NetworkProtectionCodeRedemptionCoordinator(isManualCodeRedemptionFlow: true, - accountManager: accountManager), - completion: inviteCompletion - ) - + init(inviteCompletion: @escaping () -> Void) { + self.inviteCompletion = inviteCompletion + let accountManager = AppDependencyProvider.shared.subscriptionManager.accountManager + let redemptionCoordinator = NetworkProtectionCodeRedemptionCoordinator(isManualCodeRedemptionFlow: true, + accountManager: accountManager) + inviteViewModel = NetworkProtectionInviteViewModel(redemptionCoordinator: redemptionCoordinator, completion: inviteCompletion) let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) - let statusViewModel = NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, + statusViewModel = NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, locationListRepository: locationListRepository) + // Prefetching this now for snappy load times on the locations screens + Task { + try? await locationListRepository.fetchLocationList() + } + } + + var body: some View { + if AppDependencyProvider.shared.vpnFeatureVisibility.isPrivacyProLaunched() { NetworkProtectionStatusView(statusModel: statusViewModel) } else { diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index a4a231e83f..153ada66f8 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -167,11 +167,6 @@ final class NetworkProtectionStatusViewModel: ObservableObject { setUpLocationPublishers() setUpThroughputRefreshTimer() setUpErrorPublishers() - - // Prefetching this now for snappy load times on the locations screens - Task { - _ = try? await locationListRepository.fetchLocationList() - } } private func setUpIsConnectedStatePublishers() { diff --git a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift b/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift index 25922303e5..3d00279aee 100644 --- a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift +++ b/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift @@ -21,9 +21,11 @@ import XCTest @testable import DuckDuckGo import Subscription import SubscriptionTestingUtilities +import Common /// Test all permutations according to https://app.asana.com/0/0/1206812323779606/f final class NetworkProtectionFeatureVisibilityTests: XCTestCase { + func testPrivacyProNotYetLaunched() { // Current waitlist user -> VPN works as usual, no thank-you, no entitlement check let mockWithVPNAccess = NetworkProtectionFeatureVisibilityMocks(with: [.isWaitlistBetaActive, .isWaitlistUser]) @@ -89,7 +91,7 @@ final class NetworkProtectionFeatureVisibilityTests: XCTestCase { struct NetworkProtectionFeatureVisibilityMocks: NetworkProtectionFeatureVisibility { - let accountManager = AccountManagerMock(isUserAuthenticated: true) // TODO: this makes no sense + let accountManager: AccountManager func shouldShowThankYouMessaging() -> Bool { isPrivacyProLaunched() && isWaitlistUser() @@ -119,6 +121,20 @@ struct NetworkProtectionFeatureVisibilityMocks: NetworkProtectionFeatureVisibili init(with options: Options) { self.options = options + + 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) + accountManager = AccountManager(accessTokenStorage: accessTokenStorage, + entitlementsCache: entitlementsCache, + subscriptionService: subscriptionService, + authService: authService) } func adding(_ additionalOptions: Options) -> NetworkProtectionFeatureVisibilityMocks { diff --git a/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift b/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift index 5ce0d97858..47a44dce6c 100644 --- a/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift +++ b/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift @@ -54,15 +54,6 @@ final class NetworkProtectionStatusViewModelTests: XCTestCase { super.tearDown() } - func testInit_prefetchesLocationList() throws { - let locationListRepo = MockNetworkProtectionLocationListRepository() - viewModel = NetworkProtectionStatusViewModel(tunnelController: tunnelController, - statusObserver: statusObserver, - serverInfoObserver: serverInfoObserver, - locationListRepository: MockNetworkProtectionLocationListRepository()) - waitFor(condition: locationListRepo.didCallFetchLocationList) - } - func testStatusUpdate_connected_setsIsNetPEnabledToTrue() throws { whenStatusUpdate_connected() } diff --git a/DuckDuckGoTests/TabURLInterceptorTests.swift b/DuckDuckGoTests/TabURLInterceptorTests.swift index 8958816daa..db50d1f6a4 100644 --- a/DuckDuckGoTests/TabURLInterceptorTests.swift +++ b/DuckDuckGoTests/TabURLInterceptorTests.swift @@ -49,7 +49,7 @@ class TabURLInterceptorDefaultTests: XCTestCase { } func testNotificationForInterceptedPrivacyProPath() { -// let expectation = self.expectation(forNotification: .urlInterceptPrivacyPro, object: nil, handler: nil) + let expectation = self.expectation(forNotification: .urlInterceptPrivacyPro, object: nil, handler: nil) let url = URL(string: "https://duckduckgo.com/pro")! let canNavigate = urlInterceptor.allowsNavigatingTo(url: url) From 25e0838b47d56a8f3aea04b2b0796a8715940221 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 13 May 2024 16:38:35 +0100 Subject: [PATCH 13/39] local BSK removed --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b747bc8b95..1e45b49754 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2549,7 +2549,6 @@ F143C32C1E4A9A4800CFDE3A /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIViewControllerExtension.swift; path = ../Core/UIViewControllerExtension.swift; sourceTree = ""; }; F143C3451E4AA32D00CFDE3A /* SearchBarExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchBarExtension.swift; path = ../Core/SearchBarExtension.swift; sourceTree = ""; }; F14E491E1E391CE900DC037C /* URLExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtensionTests.swift; sourceTree = ""; }; - F15531972BF216CF0029ED04 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate+SKAD4.swift"; sourceTree = ""; }; F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = ""; }; F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteViewController.swift; sourceTree = ""; }; @@ -3514,7 +3513,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - F15531972BF216CF0029ED04 /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a94f848fd6..4f0874ddab 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" : "c8e1e9880c3523696fef89d57bbcb834057dc994" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 86532214dd7e55397532cb8500a34d6b4e1be898 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 May 2024 10:03:27 +0100 Subject: [PATCH 14/39] subscription debug menu #1 --- .../SubscriptionDebugViewController.swift | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 40ecf0bd83..27f7f18604 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -29,6 +29,7 @@ import NetworkProtection @available(iOS 15.0, *) final class SubscriptionDebugViewController: UITableViewController { + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) private var subscriptionManager: SubscriptionManaging { AppDependencyProvider.shared.subscriptionManager } @@ -281,40 +282,24 @@ final class SubscriptionDebugViewController: UITableViewController { private func setEnvironment(_ environment: SubscriptionEnvironment.ServiceEnvironment) { - // TODO: reimplement debug menu -// let fullEnv = SubscriptionEnvironment() -// SubscriptionManager.save(subscriptionEnvironment: currentEnvironment, userDefaults: subscriptionUserDefaults) -// -// -// 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) -// } -// -// -// -// -// -// -// if environment.description != privacyProEnvironment { -// accountManager.signOut() -// -// // Update Subscription environment -// privacyProEnvironment = environment.rawValue -// SubscriptionPurchaseEnvironment.currentServiceEnvironment = environment -// -// // Update VPN Environment -// VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = environment == .production -// ? .production -// : .staging -// NetworkProtectionLocationListCompositeRepository.clearCache() -// -// tableView.reloadData() -// } + let subscriptionUserDefaults = UserDefaults(suiteName: subscriptionAppGroup)! + let currentSubscriptionEnvironment = SubscriptionManager.getSavedOrDefaultEnvironment(userDefaults: subscriptionUserDefaults) + var newSubscriptionEnvironment = SubscriptionEnvironment.default + newSubscriptionEnvironment.serviceEnvironment = environment + + if newSubscriptionEnvironment.serviceEnvironment != currentSubscriptionEnvironment.serviceEnvironment { + subscriptionManager.accountManager.signOut() + + // Save Subscription environment + SubscriptionManager.save(subscriptionEnvironment: newSubscriptionEnvironment, userDefaults: subscriptionUserDefaults) + + // Update VPN Environment + VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = newSubscriptionEnvironment.serviceEnvironment == .production + ? .production + : .staging + NetworkProtectionLocationListCompositeRepository.clearCache() + } + + tableView.reloadData() } } From 84be66fc4bc0deaed55346ed73ac3baf41c29e1b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 May 2024 12:32:35 +0100 Subject: [PATCH 15/39] alpha build fixed, settings menu enabled --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/DuckDuckGoAlpha.entitlements | 2 -- DuckDuckGo/SettingsRootView.swift | 1 - DuckDuckGo/SubscriptionDebugViewController.swift | 5 +++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4f0874ddab..9f01f500d6 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" : "c8e1e9880c3523696fef89d57bbcb834057dc994" + "revision" : "2fb70be3b1e98f535192101b44b8f2ffb8437d1d" } }, { diff --git a/DuckDuckGo/DuckDuckGoAlpha.entitlements b/DuckDuckGo/DuckDuckGoAlpha.entitlements index 1fc546febb..790168f55f 100644 --- a/DuckDuckGo/DuckDuckGoAlpha.entitlements +++ b/DuckDuckGo/DuckDuckGoAlpha.entitlements @@ -8,8 +8,6 @@ com.apple.developer.web-browser - com.apple.developer.browser.app-installation - com.apple.security.application-groups group.com.duckduckgo.alpha.bookmarks diff --git a/DuckDuckGo/SettingsRootView.swift b/DuckDuckGo/SettingsRootView.swift index 1ff3260f70..ed98f68998 100644 --- a/DuckDuckGo/SettingsRootView.swift +++ b/DuckDuckGo/SettingsRootView.swift @@ -61,7 +61,6 @@ struct SettingsRootView: View { .accentColor(Color(designSystemColor: .textPrimary)) .environmentObject(viewModel) .conditionalInsetGroupedListStyle() - .onAppear { viewModel.onAppear() } diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 27f7f18604..13d7c0e4b8 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -125,13 +125,14 @@ final class SubscriptionDebugViewController: UITableViewController { case .environment: let staging = SubscriptionEnvironment.ServiceEnvironment.staging let prod = SubscriptionEnvironment.ServiceEnvironment.production + let currentEnv = subscriptionManager.currentEnvironment.serviceEnvironment switch EnvironmentRows(rawValue: indexPath.row) { case .staging: cell.textLabel?.text = "Staging" - cell.accessoryType = subscriptionManager.currentEnvironment.serviceEnvironment == staging ? .checkmark : .none + cell.accessoryType = currentEnv == staging ? .checkmark : .none case .production: cell.textLabel?.text = "Production" - cell.accessoryType = subscriptionManager.currentEnvironment.serviceEnvironment == prod ? .checkmark : .none + cell.accessoryType = currentEnv == prod ? .checkmark : .none case .none: break } From cb61c127a0d3c73109bb8fa806c6b26a472d60e0 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 May 2024 15:02:05 +0100 Subject: [PATCH 16/39] subscription debug menu reimplemented --- DuckDuckGo/AppDependencyProvider.swift | 3 -- .../SubscriptionDebugViewController.swift | 49 +++++++++++++++---- ...etworkProtectionPacketTunnelProvider.swift | 6 +-- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 8bbdf5b349..ef9f08ad34 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -121,9 +121,6 @@ class AppDependencyProvider: DependencyProvider { // This is used just for iOS <15, it's a sort of mocked environment that will not be used. subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) } - let isProduction = (subscriptionEnvironment.serviceEnvironment == .production) - VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = isProduction ? .production : .staging - let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 13d7c0e4b8..cdd8417681 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -175,10 +175,35 @@ final class SubscriptionDebugViewController: UITableViewController { default: break } case .environment: - switch EnvironmentRows(rawValue: indexPath.row) { - case .staging: setEnvironment(.staging) - case .production: setEnvironment(.production) - default: break + guard let subEnv: EnvironmentRows = EnvironmentRows(rawValue: indexPath.row) else { return } + var subEnvDesc: String + switch subEnv { + case .staging: + subEnvDesc = "STAGING" + case .production: + subEnvDesc = "PRODUCTION" + } + let message = """ + Are you sure you want to change the purchase platform to \(subEnvDesc)? + This setting IS persisted between app runs. This action will close the app, do you want to proceed? + """ + let alertController = UIAlertController(title: "⚠️ App restart required! The changes are persistent", + message: message, + preferredStyle: .actionSheet) + alertController.addAction(UIAlertAction(title: "Yes", style: .destructive) { [weak self] _ in + switch subEnv { + case .staging: + self?.setEnvironment(.staging) + case .production: + self?.setEnvironment(.production) + } + // Close the app + exit(0) + }) + let okAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(okAction) + DispatchQueue.main.async { + self.present(alertController, animated: true, completion: nil) } case .none: break @@ -197,6 +222,8 @@ final class SubscriptionDebugViewController: UITableViewController { } } +// func showAlert(title: String, message: String, alternativeAction) + // MARK: Account Status Actions private func clearAuthData() { subscriptionManager.accountManager.signOut() @@ -294,13 +321,15 @@ final class SubscriptionDebugViewController: UITableViewController { // Save Subscription environment SubscriptionManager.save(subscriptionEnvironment: newSubscriptionEnvironment, userDefaults: subscriptionUserDefaults) - // Update VPN Environment - VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment = newSubscriptionEnvironment.serviceEnvironment == .production - ? .production - : .staging + // The VPN environment is forced to match the subscription environment + let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + switch newSubscriptionEnvironment.serviceEnvironment { + case .production: + settings.selectedEnvironment = .production + case .staging: + settings.selectedEnvironment = .staging + } NetworkProtectionLocationListCompositeRepository.clearCache() } - - tableView.reloadData() } } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 1930cf4058..1cb61c51f8 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -348,15 +348,11 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } private static func entitlementCheck(accountManager: AccountManaging) async -> Result { + guard NetworkProtectionVisibilityForTunnelProvider(accountManager: accountManager).shouldMonitorEntitlement() else { return .success(true) } - // TODO: the subscription environment should be matching to the VPNSettings environment, what should we do? -// if VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment == .staging { -// SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging -// } - let result = await accountManager.hasEntitlement(for: .networkProtection) switch result { case .success(let hasEntitlement): From d88ab0b22e9aa085c0e9f19e46e2b3d44c7c0074 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 May 2024 15:07:51 +0100 Subject: [PATCH 17/39] lint --- DuckDuckGo/AppDelegate.swift | 1 - .../SubscriptionDebugViewController.swift | 75 ++++++++++--------- DuckDuckGoTests/MockDependencyProvider.swift | 1 - ...tworkProtectionAccessControllerTests.swift | 2 +- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 86de29e6d2..6323842ddd 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -213,7 +213,6 @@ import WebKit PixelExperiment.install() // MARK: Sync initialisation - // TODO: Why are not all these vars in the init? #if DEBUG let defaultEnvironment = ServerEnvironment.development #else diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index cdd8417681..4ff4fb9009 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -26,9 +26,9 @@ import Core import NetworkProtection #endif -@available(iOS 15.0, *) -final class SubscriptionDebugViewController: UITableViewController { - +// swiftlint:disable:next type_body_length +@available(iOS 15.0, *) final class SubscriptionDebugViewController: UITableViewController { + let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) private var subscriptionManager: SubscriptionManaging { AppDependencyProvider.shared.subscriptionManager @@ -78,7 +78,7 @@ final class SubscriptionDebugViewController: UITableViewController { return titles[section] } - // swiftlint:disable cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) @@ -139,7 +139,6 @@ final class SubscriptionDebugViewController: UITableViewController { } return cell } - // swiftlint:enable cyclomatic_complexity override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Sections(rawValue: section) { @@ -152,7 +151,7 @@ final class SubscriptionDebugViewController: UITableViewController { } } - // swiftlint:disable cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch Sections(rawValue: indexPath.section) { case .authorization: @@ -176,43 +175,45 @@ final class SubscriptionDebugViewController: UITableViewController { } case .environment: guard let subEnv: EnvironmentRows = EnvironmentRows(rawValue: indexPath.row) else { return } - var subEnvDesc: String - switch subEnv { - case .staging: - subEnvDesc = "STAGING" - case .production: - subEnvDesc = "PRODUCTION" - } - let message = """ - Are you sure you want to change the purchase platform to \(subEnvDesc)? - This setting IS persisted between app runs. This action will close the app, do you want to proceed? - """ - let alertController = UIAlertController(title: "⚠️ App restart required! The changes are persistent", - message: message, - preferredStyle: .actionSheet) - alertController.addAction(UIAlertAction(title: "Yes", style: .destructive) { [weak self] _ in - switch subEnv { - case .staging: - self?.setEnvironment(.staging) - case .production: - self?.setEnvironment(.production) - } - // Close the app - exit(0) - }) - let okAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(okAction) - DispatchQueue.main.async { - self.present(alertController, animated: true, completion: nil) - } + changeSubscriptionEnvironment(envRows: subEnv) case .none: break } - tableView.deselectRow(at: indexPath, animated: true) } - // swiftlint:enable cyclomatic_complexity + private func changeSubscriptionEnvironment(envRows: EnvironmentRows) { + var subEnvDesc: String + switch envRows { + case .staging: + subEnvDesc = "STAGING" + case .production: + subEnvDesc = "PRODUCTION" + } + let message = """ + Are you sure you want to change the purchase platform to \(subEnvDesc)? + This setting IS persisted between app runs. This action will close the app, do you want to proceed? + """ + let alertController = UIAlertController(title: "⚠️ App restart required! The changes are persistent", + message: message, + preferredStyle: .actionSheet) + alertController.addAction(UIAlertAction(title: "Yes", style: .destructive) { [weak self] _ in + switch envRows { + case .staging: + self?.setEnvironment(.staging) + case .production: + self?.setEnvironment(.production) + } + // Close the app + exit(0) + }) + let okAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(okAction) + DispatchQueue.main.async { + self.present(alertController, animated: true, completion: nil) + } + } + private func showAlert(title: String, message: String? = nil) { DispatchQueue.main.async { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index d35593d773..195727ac55 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -67,7 +67,6 @@ class MockDependencyProvider: DependencyProvider { toggleProtectionsCounter = defaultProvider.toggleProtectionsCounter subscriptionFeatureAvailability = defaultProvider.subscriptionFeatureAvailability - // TODO: Create Mocks for everything accountManager = AccountManagerMock(isUserAuthenticated: true) if #available(iOS 15.0, *) { let subscriptionService = SubscriptionService(currentServiceEnvironment: .production) diff --git a/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift b/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift index 5b390fe75d..e1c8e3ed8b 100644 --- a/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift +++ b/DuckDuckGoTests/NetworkProtectionAccessControllerTests.swift @@ -17,7 +17,7 @@ // limitations under the License. // -// TODO: Re-implement this in a way that makes sense +// Re-implement this in a way that makes sense /* #if NETWORK_PROTECTION From 735217fb6519a715a650cdd98c3289aee504d292 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 12:52:50 +0100 Subject: [PATCH 18/39] 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 62a668b52a..a5cd2413e3 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" : "2fb70be3b1e98f535192101b44b8f2ffb8437d1d" + "revision" : "c6ec025644e9ebf9c06a5477dc2859c6f0cc7ced" } }, { From 468d886d984b0b88422f9c471cc19596c51002d1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 13:25:49 +0100 Subject: [PATCH 19/39] SubscriptionEnvironment Default moved to main app --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +++ .../xcshareddata/swiftpm/Package.resolved | 2 +- .../SubscriptionEnvironment+Default.swift | 42 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3f7f6694ad..e600928f27 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -933,6 +933,8 @@ F1ED309D1EDC2EA400651986 /* TabSwitcher.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F1ED309B1EDC2EA400651986 /* TabSwitcher.storyboard */; }; F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F5337B1F26A9EF00D80D4F /* UserText.swift */; }; F1F533841F26ABAC00D80D4F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F1F533861F26ABAC00D80D4F /* Localizable.strings */; }; + F1FDC9302BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC9312BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */; }; F40F843728C939760081AE75 /* AutofillLoginListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40F843528C938370081AE75 /* AutofillLoginListViewModelTests.swift */; }; F4147354283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4147353283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift */; }; F41610BC29E5DF66001F709D /* DeprecatedColors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F41610BB29E5DF65001F709D /* DeprecatedColors.xcassets */; }; @@ -2608,6 +2610,7 @@ F1E90C1F1E678E7C005E7E21 /* HomeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeControllerDelegate.swift; sourceTree = ""; }; F1ED309C1EDC2EA400651986 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TabSwitcher.storyboard; sourceTree = ""; }; F1F5337B1F26A9EF00D80D4F /* UserText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; + F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; F40F843528C938370081AE75 /* AutofillLoginListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListViewModelTests.swift; sourceTree = ""; }; F4147353283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillContentScopeFeatureToggles.swift; sourceTree = ""; }; F41610BB29E5DF65001F709D /* DeprecatedColors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DeprecatedColors.xcassets; sourceTree = ""; }; @@ -4426,6 +4429,7 @@ D664C7922B289AA000CBFA76 /* Subscription */ = { isa = PBXGroup; children = ( + F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */, D60170BB2BA32DD6001911B5 /* Subscription.swift */, F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */, D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */, @@ -6303,6 +6307,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F1FDC9312BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, BDFF03232BA3D8E300F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */, EEEB80A32A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift in Sources */, EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, @@ -6734,6 +6739,7 @@ EE4BE0092A740BED00CD6AA8 /* ClearTextField.swift in Sources */, F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */, F44D279C27F331BB0037F371 /* AutofillLoginPromptView.swift in Sources */, + F1FDC9302BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift in Sources */, CBD4F13E279EBFAB00B20FD7 /* HomeMessageView.swift in Sources */, 851DFD87212C39D300D95F20 /* TabSwitcherButton.swift in Sources */, 8505836A219F424500ED4EDB /* UIAlertControllerExtension.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a5cd2413e3..3f647f5372 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/Subscription/SubscriptionEnvironment+Default.swift b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift new file mode 100644 index 0000000000..692484d7e4 --- /dev/null +++ b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift @@ -0,0 +1,42 @@ +// +// 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 ALPHA || DEBUG + let environment: SubscriptionEnvironment.ServiceEnvironment = .staging +#else + let environment: SubscriptionEnvironment.ServiceEnvironment = .production +#endif + return SubscriptionEnvironment(serviceEnvironment: environment, platform: .appStore) + } +} + +extension SubscriptionManager { + + static public func getSavedOrDefaultEnvironment(userDefaults: UserDefaults) -> SubscriptionEnvironment { + if let savedEnvironment = loadEnvironmentFrom(userDefaults: userDefaults) { + return savedEnvironment + } + return SubscriptionEnvironment.default + } +} From ece04c4aeccc65c47a590ab9e127a562c7463894 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 16:27:43 +0100 Subject: [PATCH 20/39] infinite init loop fixed and Platform renamed PurchasePlatform --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/AppDependencyProvider.swift | 3 +++ DuckDuckGo/Feedback/VPNFeedbackFormView.swift | 3 ++- DuckDuckGo/Feedback/VPNMetadataCollector.swift | 2 +- DuckDuckGo/NetworkProtectionDebugViewController.swift | 3 ++- DuckDuckGo/NetworkProtectionRootView.swift | 3 ++- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 7 +------ .../Subscription/SubscriptionEnvironment+Default.swift | 2 +- DuckDuckGo/TabViewController.swift | 2 +- DuckDuckGoTests/MockDependencyProvider.swift | 2 +- 11 files changed, 16 insertions(+), 15 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3f647f5372..55745a57e4 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/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index ef9f08ad34..107cbacfd2 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -49,6 +49,7 @@ protocol DependencyProvider { var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore { get } var networkProtectionAccessController: NetworkProtectionAccessController { get } var networkProtectionTunnelController: NetworkProtectionTunnelController { get } + var connectionObserver: ConnectionStatusObserver { get } } /// Provides dependencies for objects that are not directly instantiated @@ -92,6 +93,8 @@ class AppDependencyProvider: DependencyProvider { let networkProtectionTunnelController: NetworkProtectionTunnelController let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) + + let connectionObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession() // swiftlint:disable:next function_body_length init() { diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift index b6627c31f4..a9deda27db 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift @@ -25,7 +25,8 @@ import NetworkProtection @available(iOS 15.0, *) struct VPNFeedbackFormCategoryView: View { @Environment(\.dismiss) private var dismiss - let collector = DefaultVPNMetadataCollector(networkProtectionAccessManager: AppDependencyProvider.shared.networkProtectionAccessController, + let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver, + networkProtectionAccessManager: AppDependencyProvider.shared.networkProtectionAccessController, tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) var body: some View { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index 92b281f7c4..85014e8da9 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -128,7 +128,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let settings: VPNSettings private let defaults: UserDefaults - init(statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), + init(statusObserver: ConnectionStatusObserver, serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), networkProtectionAccessManager: NetworkProtectionAccessController, tokenStore: NetworkProtectionTokenStore, diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index fe0555c706..16be143b16 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -667,7 +667,8 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") @MainActor private func refreshMetadata() async { - let collector = DefaultVPNMetadataCollector(networkProtectionAccessManager: AppDependencyProvider.shared.networkProtectionAccessController, + let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver, + networkProtectionAccessManager: AppDependencyProvider.shared.networkProtectionAccessController, tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) self.vpnMetadata = await collector.collectMetadata() self.tableView.reloadData() diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 98ebee029f..7a50dfe963 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -39,7 +39,8 @@ struct NetworkProtectionRootView: View { inviteViewModel = NetworkProtectionInviteViewModel(redemptionCoordinator: redemptionCoordinator, completion: inviteCompletion) let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) statusViewModel = NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, - locationListRepository: locationListRepository) + statusObserver: AppDependencyProvider.shared.connectionObserver, + locationListRepository: locationListRepository) // Prefetching this now for snappy load times on the locations screens Task { try? await locationListRepository.fetchLocationList() diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 153ada66f8..78c9d3883c 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -144,7 +144,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { public init(tunnelController: TunnelController, settings: VPNSettings = VPNSettings(defaults: .networkProtectionGroupDefaults), - statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), + statusObserver: ConnectionStatusObserver, serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), errorObserver: ConnectionErrorObserver = ConnectionErrorObserverThroughSession(), locationListRepository: NetworkProtectionLocationListRepository) { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index b079835663..06e242ce5c 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -55,10 +55,6 @@ final class SettingsViewModel: ObservableObject { // Used to cache the lasts subscription state for up to a week private let subscriptionStateCache = UserDefaultsCache(key: UserDefaultsCacheKey.subscriptionState, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .days(7))) -#if NETWORK_PROTECTION - private let connectionObserver = ConnectionStatusObserverThroughSession() -#endif - // Properties private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad private var cancellables = Set() @@ -523,10 +519,9 @@ extension SettingsViewModel { extension SettingsViewModel { private func setupSubscribers() { - #if NETWORK_PROTECTION - connectionObserver.publisher + AppDependencyProvider.shared.connectionObserver.publisher .receive(on: DispatchQueue.main) .sink { [weak self] hasActiveSubscription in self?.updateNetPStatus(connectionStatus: hasActiveSubscription) diff --git a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift index 692484d7e4..3e466e015f 100644 --- a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift +++ b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift @@ -27,7 +27,7 @@ extension SubscriptionEnvironment { #else let environment: SubscriptionEnvironment.ServiceEnvironment = .production #endif - return SubscriptionEnvironment(serviceEnvironment: environment, platform: .appStore) + return SubscriptionEnvironment(serviceEnvironment: environment, purchasePlatform: .appStore) } } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 9cd4a91ab6..f34924774a 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -133,7 +133,7 @@ class TabViewController: UIViewController { private var currentlyLoadedURL: URL? #if NETWORK_PROTECTION - private let netPConnectionObserver = ConnectionStatusObserverThroughSession() + private let netPConnectionObserver: ConnectionStatusObserver = AppDependencyProvider.shared.connectionObserver private var netPConnectionObserverCancellable: AnyCancellable? private var netPConnectionStatus: ConnectionStatus = .default private var netPConnected: Bool { diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index 195727ac55..06bed16603 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -77,7 +77,7 @@ class MockDependencyProvider: DependencyProvider { authService: authService, storePurchaseManaging: storePurchaseManaging, currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, - platform: .appStore), + purchasePlatform: .appStore), canPurchase: true) } else { // This is used just for iOS <15, it's a sort of mocked environment that will not be used. From 07137834fa67d8ba1375d627de79622b182477bd Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 18:06:03 +0100 Subject: [PATCH 21/39] VPNSettings environment is now aligned with subscription environment --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +++++ .../xcshareddata/swiftpm/Package.resolved | 13 ++----- DuckDuckGo/AppDelegate.swift | 2 +- DuckDuckGo/AppDependencyProvider.swift | 11 +++--- DuckDuckGo/MainViewController.swift | 2 +- ...orkProtectionConvenienceInitialisers.swift | 8 ++--- ...NetworkProtectionDebugViewController.swift | 4 +-- DuckDuckGo/NetworkProtectionRootView.swift | 1 + .../NetworkProtectionStatusViewModel.swift | 2 +- .../NetworkProtectionTunnelController.swift | 2 +- .../Extensions/VPNSettings+Environment.swift | 34 +++++++++++++++++++ .../SubscriptionDebugViewController.swift | 2 +- ...etworkProtectionPacketTunnelProvider.swift | 2 ++ 13 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 DuckDuckGo/Subscription/Extensions/VPNSettings+Environment.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e600928f27..25c23c6b88 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -935,6 +935,8 @@ F1F533841F26ABAC00D80D4F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F1F533861F26ABAC00D80D4F /* Localizable.strings */; }; F1FDC9302BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */; }; F1FDC9312BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */; }; + F1FDC9352BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9342BF51E41006B1435 /* VPNSettings+Environment.swift */; }; + F1FDC9362BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FDC9342BF51E41006B1435 /* VPNSettings+Environment.swift */; }; F40F843728C939760081AE75 /* AutofillLoginListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40F843528C938370081AE75 /* AutofillLoginListViewModelTests.swift */; }; F4147354283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4147353283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift */; }; F41610BC29E5DF66001F709D /* DeprecatedColors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F41610BB29E5DF65001F709D /* DeprecatedColors.xcassets */; }; @@ -2611,6 +2613,8 @@ F1ED309C1EDC2EA400651986 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TabSwitcher.storyboard; sourceTree = ""; }; F1F5337B1F26A9EF00D80D4F /* UserText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; + F1FDC9332BF513FC006B1435 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; + F1FDC9342BF51E41006B1435 /* VPNSettings+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VPNSettings+Environment.swift"; sourceTree = ""; }; F40F843528C938370081AE75 /* AutofillLoginListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListViewModelTests.swift; sourceTree = ""; }; F4147353283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillContentScopeFeatureToggles.swift; sourceTree = ""; }; F41610BB29E5DF65001F709D /* DeprecatedColors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DeprecatedColors.xcassets; sourceTree = ""; }; @@ -3522,6 +3526,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + F1FDC9332BF513FC006B1435 /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -4461,6 +4466,7 @@ D664C7962B289AA000CBFA76 /* Extensions */ = { isa = PBXGroup; children = ( + F1FDC9342BF51E41006B1435 /* VPNSettings+Environment.swift */, D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */, ); @@ -6312,6 +6318,7 @@ EEEB80A32A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift in Sources */, EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, 4BB697A52B1D99C5003699B5 /* VPNWaitlistActivationDateStore.swift in Sources */, + F1FDC9362BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */, BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */, EEFC6A602AC0F2F80065027D /* UserText.swift in Sources */, ); @@ -6664,6 +6671,7 @@ 1DEAADFF2BA7832F00E25A97 /* EmailProtectionView.swift in Sources */, 988F3DD3237DE8D900AEE34C /* ForgetDataAlert.swift in Sources */, D6FEB8B12B7498A300C3615F /* HeadlessWebView.swift in Sources */, + F1FDC9352BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */, 850ABD012AC3961100A733DF /* MainViewController+Segues.swift in Sources */, 9817C9C321EF594700884F65 /* AutoClear.swift in Sources */, 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 55745a57e4..be8497cbff 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,22 +27,13 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", - "state" : { - "branch" : "fcappelli/subscription_refactoring_2", - "revision" : "a3a0614439f3e2ea0ca7cec60ebbc835b4bac920" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "1bb3bc5eb565735051f342a87b5405d4374876c7", - "version" : "5.12.0" + "revision" : "bb8e7e62104ed6506c7bfd3ef7aa4aca3686ed4f", + "version" : "5.15.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 574f7d574d..eebff8a263 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -394,7 +394,7 @@ import WebKit private func presentExpiredEntitlementNotificationIfNeeded() { let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator( - settings: VPNSettings(defaults: .networkProtectionGroupDefaults), + settings: AppDependencyProvider.shared.vpnSettings, defaults: .networkProtectionGroupDefaults, wrappee: NetworkProtectionUNNotificationPresenter() ) diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 107cbacfd2..3245b8a25c 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -50,6 +50,7 @@ protocol DependencyProvider { var networkProtectionAccessController: NetworkProtectionAccessController { get } var networkProtectionTunnelController: NetworkProtectionTunnelController { get } var connectionObserver: ConnectionStatusObserver { get } + var vpnSettings: VPNSettings { get } } /// Provides dependencies for objects that are not directly instantiated @@ -95,6 +96,7 @@ class AppDependencyProvider: DependencyProvider { let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs) let connectionObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession() + let vpnSettings = VPNSettings(defaults: .networkProtectionGroupDefaults) // swiftlint:disable:next function_body_length init() { @@ -104,6 +106,8 @@ class AppDependencyProvider: DependencyProvider { // MARK: - Configure Subscription 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))) @@ -157,9 +161,8 @@ class AppDependencyProvider: DependencyProvider { accountManager: subscriptionManager.accountManager, tokenStore: networkProtectionKeychainTokenStore, networkProtectionTunnelController: networkProtectionTunnelController) - self.vpnFeatureVisibility = DefaultNetworkProtectionVisibility(networkProtectionAccessManager: networkProtectionAccessController, - featureFlagger: featureFlagger, - accountManager: accountManager) + vpnFeatureVisibility = DefaultNetworkProtectionVisibility(networkProtectionAccessManager: networkProtectionAccessController, + featureFlagger: featureFlagger, + accountManager: accountManager) } - } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 2ed3d73654..fd88c6d309 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1442,7 +1442,7 @@ class MainViewController: UIViewController { private func presentExpiredEntitlementNotification() { let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator( - settings: VPNSettings(defaults: .networkProtectionGroupDefaults), + settings: AppDependencyProvider.shared.vpnSettings, defaults: .networkProtectionGroupDefaults, wrappee: NetworkProtectionUNNotificationPresenter() ) diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index 8a49eb1920..d4659f8860 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -79,7 +79,7 @@ extension ConnectionServerInfoObserverThroughSession { extension NetworkProtectionCodeRedemptionCoordinator { convenience init(isManualCodeRedemptionFlow: Bool = false, accountManager: AccountManaging) { - let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + let settings = AppDependencyProvider.shared.vpnSettings let networkProtectionVisibility = AppDependencyProvider.shared.vpnFeatureVisibility self.init( environment: settings.selectedEnvironment, @@ -95,7 +95,7 @@ extension NetworkProtectionVPNSettingsViewModel { convenience init() { self.init( notificationsAuthorization: NotificationsAuthorizationController(), - settings: VPNSettings(defaults: .networkProtectionGroupDefaults) + settings: AppDependencyProvider.shared.vpnSettings ) } } @@ -103,7 +103,7 @@ extension NetworkProtectionVPNSettingsViewModel { extension NetworkProtectionLocationListCompositeRepository { convenience init(accountManager: AccountManaging) { - let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + let settings = AppDependencyProvider.shared.vpnSettings self.init( environment: settings.selectedEnvironment, tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore, @@ -119,7 +119,7 @@ extension NetworkProtectionVPNLocationViewModel { let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) self.init( locationListRepository: locationListRepository, - settings: VPNSettings(defaults: .networkProtectionGroupDefaults) + settings: AppDependencyProvider.shared.vpnSettings ) } } diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 16be143b16..01da3194bb 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -631,7 +631,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { private func configure(_ cell: UITableViewCell, forVisibilityRow row: Int) { switch FeatureVisibilityRows(rawValue: row) { case .toggleSelectedEnvironment: - let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + let settings = AppDependencyProvider.shared.vpnSettings if settings.selectedEnvironment == .production { cell.textLabel?.text = "Selected Environment: PRODUCTION" } else { @@ -649,7 +649,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { cell.textLabel?.font = .monospacedSystemFont(ofSize: 13.0, weight: .regular) cell.textLabel?.text = """ -Endpoint: \(VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment.endpointURL.absoluteString) +Endpoint: \(AppDependencyProvider.shared.vpnSettings.selectedEnvironment.endpointURL.absoluteString) isPrivacyProLaunched: \(vpnVisibility.isPrivacyProLaunched() ? "YES" : "NO") isWaitlistBetaActive: \(vpnVisibility.isWaitlistBetaActive() ? "YES" : "NO") diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 7a50dfe963..a5a5209784 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -39,6 +39,7 @@ struct NetworkProtectionRootView: View { inviteViewModel = NetworkProtectionInviteViewModel(redemptionCoordinator: redemptionCoordinator, completion: inviteCompletion) let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) statusViewModel = NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, + settings: AppDependencyProvider.shared.vpnSettings, statusObserver: AppDependencyProvider.shared.connectionObserver, locationListRepository: locationListRepository) // Prefetching this now for snappy load times on the locations screens diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 78c9d3883c..b2058ed8b4 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -143,7 +143,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @Published public var animationsOn: Bool = false public init(tunnelController: TunnelController, - settings: VPNSettings = VPNSettings(defaults: .networkProtectionGroupDefaults), + settings: VPNSettings, statusObserver: ConnectionStatusObserver, serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), errorObserver: ConnectionErrorObserver = ConnectionErrorObserverThroughSession(), diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index f3aef0a073..4952559934 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -187,7 +187,7 @@ final class NetworkProtectionTunnelController: TunnelController { } catch { throw StartError.fetchAuthTokenFailed(error) } - options[NetworkProtectionOptionKey.selectedEnvironment] = VPNSettings(defaults: .networkProtectionGroupDefaults) + options[NetworkProtectionOptionKey.selectedEnvironment] = AppDependencyProvider.shared.vpnSettings .selectedEnvironment.rawValue as NSString do { diff --git a/DuckDuckGo/Subscription/Extensions/VPNSettings+Environment.swift b/DuckDuckGo/Subscription/Extensions/VPNSettings+Environment.swift new file mode 100644 index 0000000000..4e046637ed --- /dev/null +++ b/DuckDuckGo/Subscription/Extensions/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/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 4ff4fb9009..6d595d9923 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -323,7 +323,7 @@ import NetworkProtection SubscriptionManager.save(subscriptionEnvironment: newSubscriptionEnvironment, userDefaults: subscriptionUserDefaults) // The VPN environment is forced to match the subscription environment - let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + let settings = AppDependencyProvider.shared.vpnSettings switch newSubscriptionEnvironment.serviceEnvironment { case .production: settings.selectedEnvironment = .production diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 1cb61c51f8..90575ba756 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -284,6 +284,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) + let notificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator( settings: settings, defaults: .networkProtectionGroupDefaults, From b3562ba668f2320e61cab052032c2ece9fa7140b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 15 May 2024 18:19:47 +0100 Subject: [PATCH 22/39] unit tests fix --- DuckDuckGoTests/MockDependencyProvider.swift | 4 ++++ DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift | 1 + 2 files changed, 5 insertions(+) diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index 06bed16603..682bb631f1 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -48,6 +48,8 @@ class MockDependencyProvider: DependencyProvider { var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore var networkProtectionAccessController: NetworkProtectionAccessController var networkProtectionTunnelController: NetworkProtectionTunnelController + var connectionObserver: NetworkProtection.ConnectionStatusObserver + var vpnSettings: NetworkProtection.VPNSettings init() { let defaultProvider = AppDependencyProvider() @@ -103,5 +105,7 @@ class MockDependencyProvider: DependencyProvider { featureFlagger: featureFlagger, accountManager: accountManager) + connectionObserver = ConnectionStatusObserverThroughSession() + vpnSettings = VPNSettings(defaults: .networkProtectionGroupDefaults) } } diff --git a/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift b/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift index 47a44dce6c..c182b6cae2 100644 --- a/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift +++ b/DuckDuckGoTests/NetworkProtectionStatusViewModelTests.swift @@ -41,6 +41,7 @@ final class NetworkProtectionStatusViewModelTests: XCTestCase { statusObserver = MockConnectionStatusObserver() serverInfoObserver = MockConnectionServerInfoObserver() viewModel = NetworkProtectionStatusViewModel(tunnelController: tunnelController, + settings: VPNSettings(defaults: .networkProtectionGroupDefaults), statusObserver: statusObserver, serverInfoObserver: serverInfoObserver, locationListRepository: MockNetworkProtectionLocationListRepository()) From 4873bf74aa2216cea7d272726b4770542ebf9ccd Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 11:01:00 +0100 Subject: [PATCH 23/39] lint and unit tests fix --- .../Extensions/VPNSettings+Environment.swift | 1 + .../SubscriptionEnvironment+Default.swift | 1 + DuckDuckGoTests/MockDependencyProvider.swift | 1 + .../SubscriptionContainerViewModelTests.swift | 21 ++++++++++++++----- .../SubscriptionFlowViewModelTests.swift | 16 ++++++++++---- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/Subscription/Extensions/VPNSettings+Environment.swift b/DuckDuckGo/Subscription/Extensions/VPNSettings+Environment.swift index 4e046637ed..65329d0d12 100644 --- a/DuckDuckGo/Subscription/Extensions/VPNSettings+Environment.swift +++ b/DuckDuckGo/Subscription/Extensions/VPNSettings+Environment.swift @@ -1,5 +1,6 @@ // // VPNSettings+Environment.swift +// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift index 3e466e015f..821cd16378 100644 --- a/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift +++ b/DuckDuckGo/Subscription/SubscriptionEnvironment+Default.swift @@ -1,5 +1,6 @@ // // SubscriptionEnvironment+Default.swift +// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index 682bb631f1..785eab1a70 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -51,6 +51,7 @@ class MockDependencyProvider: DependencyProvider { var connectionObserver: NetworkProtection.ConnectionStatusObserver var vpnSettings: NetworkProtection.VPNSettings + // swiftlint:disable:next function_body_length init() { let defaultProvider = AppDependencyProvider() appSettings = defaultProvider.appSettings diff --git a/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift b/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift index 00039d336b..991da6ec7c 100644 --- a/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift +++ b/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift @@ -19,19 +19,26 @@ import XCTest @testable import DuckDuckGo +import Subscription +import SubscriptionTestingUtilities @available(iOS 15.0, *) final class SubscriptionContainerViewModelTests: XCTestCase { - private var sut: SubscriptionContainerViewModel! + var sut: SubscriptionContainerViewModel! + let mockDependencyProvider = MockDependencyProvider() func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { // GIVEN let origin = "test_origin" let queryParameter = URLQueryItem(name: "origin", value: "test_origin") - let expectedURL = URL.subscriptionPurchase.appending(percentEncodedQueryItem: queryParameter) + let expectedURL = SubscriptionURL.purchase.subscriptionURL(environment: .production).appending(percentEncodedQueryItem: queryParameter) // WHEN - sut = .init(origin: origin, userScript: .init(), subFeature: .init(subscriptionAttributionOrigin: nil)) + sut = .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + origin: origin, + userScript: .init(), + subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + subscriptionAttributionOrigin: nil)) // THEN XCTAssertEqual(sut.flow.purchaseURL, expectedURL) @@ -39,10 +46,14 @@ final class SubscriptionContainerViewModelTests: XCTestCase { func testWhenInitWithoutOriginThenSubscriptionFlowPurchaseURLDoesNotHaveOriginSet() { // WHEN - sut = .init(origin: nil, userScript: .init(), subFeature: .init(subscriptionAttributionOrigin: nil)) + sut = .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + origin: nil, + userScript: .init(), + subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + subscriptionAttributionOrigin: nil)) // THEN - XCTAssertEqual(sut.flow.purchaseURL, URL.subscriptionPurchase) + XCTAssertEqual(sut.flow.purchaseURL, SubscriptionURL.purchase.subscriptionURL(environment: .production)) } } diff --git a/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift b/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift index 3552d74b95..bef993d72a 100644 --- a/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift +++ b/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift @@ -19,19 +19,25 @@ import XCTest @testable import DuckDuckGo +import Subscription +import SubscriptionTestingUtilities @available(iOS 15.0, *) final class SubscriptionFlowViewModelTests: XCTestCase { private var sut: SubscriptionFlowViewModel! + let mockDependencyProvider = MockDependencyProvider() + func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { // GIVEN let origin = "test_origin" let queryParameter = URLQueryItem(name: "origin", value: "test_origin") - let expectedURL = URL.subscriptionPurchase.appending(percentEncodedQueryItem: queryParameter) + let expectedURL = SubscriptionURL.purchase.subscriptionURL(environment: .production).appending(percentEncodedQueryItem: queryParameter) // WHEN - sut = .init(origin: origin, userScript: .init(), subFeature: .init(subscriptionAttributionOrigin: nil)) + sut = .init(origin: origin, userScript: .init(), subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + subscriptionAttributionOrigin: nil), + subscriptionManager: mockDependencyProvider.subscriptionManager) // THEN XCTAssertEqual(sut.purchaseURL, expectedURL) @@ -39,10 +45,12 @@ final class SubscriptionFlowViewModelTests: XCTestCase { func testWhenInitWithoutOriginThenSubscriptionFlowPurchaseURLDoesNotHaveOriginSet() { // WHEN - sut = .init(origin: nil, userScript: .init(), subFeature: .init(subscriptionAttributionOrigin: nil)) + sut = .init(origin: nil, userScript: .init(), subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + subscriptionAttributionOrigin: nil), + subscriptionManager: mockDependencyProvider.subscriptionManager) // THEN - XCTAssertEqual(sut.purchaseURL, URL.subscriptionPurchase) + XCTAssertEqual(sut.purchaseURL, SubscriptionURL.purchase.subscriptionURL(environment: .production)) } } From 38d1725d83f5d04762b8fc4b6622c0ff686e8484 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 13:10:14 +0100 Subject: [PATCH 24/39] 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 f6945b132c..743cae830f 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" : "7967d93ea23c3062d6ae2479b12f0ecad5c73ffc" + "revision" : "46ef62f6dffcc7911913aa0ff5bae470c5b512c6" } }, { From 6e937b3ded8f6bd0a7118f0dc8ef3158157deef2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 14:33:16 +0100 Subject: [PATCH 25/39] BSK updated, renaming and access control improvements --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../Subscription/ViewModel/SubscriptionSettingsViewModel.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 743cae830f..607862771d 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/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 59e4b454da..2833ab3412 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -66,7 +66,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { init(subscriptionManager: SubscriptionManaging = AppDependencyProvider.shared.subscriptionManager) { self.subscriptionManager = subscriptionManager - let subscriptionFAQURL = SubscriptionURL.FAQ.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let subscriptionFAQURL = SubscriptionURL.faq.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) self.state = State(faqURL: subscriptionFAQURL) setupSubscriptionUpdater() From 6af618dbde56bd38e676118766068ec6bf0b15b7 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 15:43:55 +0100 Subject: [PATCH 26/39] testing with speeding up unit/ui tests --- DuckDuckGo/AppDelegate.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 983e731b36..377d0867a2 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -149,6 +149,8 @@ import WebKit _ = DefaultUserAgentManager.shared testing = ProcessInfo().arguments.contains("testing") if testing { + UIApplication.shared.keyWindow?.layer.speed = Float.leastNonzeroMagnitude + Pixel.isDryRun = true _ = DefaultUserAgentManager.shared Database.shared.loadStore { _, _ in } From 17234dd49821019fd6b33139b2912f7b40d602ff Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 16:28:51 +0100 Subject: [PATCH 27/39] flaky test disabled --- .../xcshareddata/xcschemes/DuckDuckGo.xcscheme | 3 +++ DuckDuckGo/AppDelegate.swift | 2 -- DuckDuckGoTests/AutoconsentMessageProtocolTests.swift | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme index f875782b6b..d2c6604405 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme @@ -61,6 +61,9 @@ + + Date: Thu, 16 May 2024 16:29:13 +0100 Subject: [PATCH 28/39] unit test fixed --- DuckDuckGoTests/AutoconsentMessageProtocolTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift b/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift index 0d3f642dad..f762bbe73a 100644 --- a/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift +++ b/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift @@ -117,7 +117,7 @@ final class AutoconsentMessageProtocolTests: XCTestCase { } // Flaky test that fails often, to re-evaluate. See 15s timeout, something wrong here - + @MainActor func testEval() { let message = MockWKScriptMessage(name: "eval", body: [ "type": "eval", From b485a5a9df1428b8ca507bcd77f10c4af6ebc4b8 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 16:54:11 +0100 Subject: [PATCH 29/39] getStorePurchaseManager() renamed storePurchaseManager() --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Subscription/SubscriptionManageriOS14.swift | 2 +- .../UserScripts/SubscriptionPagesUseSubscriptionFeature.swift | 4 ++-- DuckDuckGo/SubscriptionDebugViewController.swift | 2 +- DuckDuckGoTests/MockDependencyProvider.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 607862771d..e676cd6cf3 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" : "845353bfe722f133abf7999d9911c9125231b353" } }, { diff --git a/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift index ce5ec6c4f7..376c1cb94c 100644 --- a/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift +++ b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift @@ -27,7 +27,7 @@ class SubscriptionManageriOS14: SubscriptionManaging { var authService: AuthService = AuthService(currentServiceEnvironment: .production) @available(iOS 15, *) - func getStorePurchaseManager() -> StorePurchaseManaging { + func storePurchaseManager() -> StorePurchaseManaging { StorePurchaseManager() } var currentEnvironment: SubscriptionEnvironment = SubscriptionEnvironment.default diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index d61434c44a..fa9c017526 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -190,7 +190,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { resetSubscriptionFlow() - if let subscriptionOptions = await subscriptionManager.getStorePurchaseManager().subscriptionOptions() { + if let subscriptionOptions = await subscriptionManager.storePurchaseManager().subscriptionOptions() { if AppDependencyProvider.shared.subscriptionFeatureAvailability.isSubscriptionPurchaseAllowed { return subscriptionOptions } else { @@ -223,7 +223,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // Check for active subscriptions - if await subscriptionManager.getStorePurchaseManager().hasActiveSubscription() { + if await subscriptionManager.storePurchaseManager().hasActiveSubscription() { setTransactionError(.hasActiveSubscription) Pixel.fire(pixel: .privacyProRestoreAfterPurchaseAttempt) setTransactionStatus(.idle) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 6d595d9923..11335510c8 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -250,7 +250,7 @@ import NetworkProtection private func syncAppleIDAccount() { Task { - switch await subscriptionManager.getStorePurchaseManager().syncAppleIDAccount() { + switch await subscriptionManager.storePurchaseManager().syncAppleIDAccount() { case .success: showAlert(title: "Account synced!", message: "") case .failure(let error): diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index 785eab1a70..bb27c1c2ae 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -78,7 +78,7 @@ class MockDependencyProvider: DependencyProvider { subscriptionManager = SubscriptionManagerMock(accountManager: accountManager, subscriptionService: subscriptionService, authService: authService, - storePurchaseManaging: storePurchaseManaging, + storePurchaseManager: storePurchaseManaging, currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, purchasePlatform: .appStore), canPurchase: true) From 1742a63623f9d30dfd1cba195ca0ad64d1eb2a5c Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 17:26:58 +0100 Subject: [PATCH 30/39] various improvements suggested in the PR --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../SubscriptionManageriOS14.swift | 6 +++++- .../ViewModel/SubscriptionEmailViewModel.swift | 12 ++++++------ .../ViewModel/SubscriptionFlowViewModel.swift | 18 ++++++++---------- .../ViewModel/SubscriptionITPViewModel.swift | 2 +- .../SubscriptionSettingsViewModel.swift | 5 ++--- .../SubscriptionContainerViewModelTests.swift | 2 +- .../SubscriptionFlowViewModelTests.swift | 2 +- 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e676cd6cf3..f379d2f70d 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" : "845353bfe722f133abf7999d9911c9125231b353" + "revision" : "0335dcb7559e12f3ab7b77951d04400b4771b66d" } }, { diff --git a/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift index 376c1cb94c..35d962f035 100644 --- a/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift +++ b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift @@ -21,7 +21,7 @@ import Foundation import Subscription class SubscriptionManageriOS14: SubscriptionManaging { - + var accountManager: AccountManaging var subscriptionService: SubscriptionService = SubscriptionService(currentServiceEnvironment: .production) var authService: AuthService = AuthService(currentServiceEnvironment: .production) @@ -35,6 +35,10 @@ class SubscriptionManageriOS14: SubscriptionManaging { func loadInitialData() {} func updateSubscriptionStatus(completion: @escaping (Bool) -> Void) {} + func url(for type: SubscriptionURL) -> URL { + URL(string: "https://duckduckgo.com")! + } + init(accountManager: AccountManaging) { self.accountManager = accountManager } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 45af64f6ee..fdde1b81c5 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -77,8 +77,8 @@ final class SubscriptionEmailViewModel: ObservableObject { var accountManager: AccountManaging { subscriptionManager.accountManager } private var isWelcomePageOrSuccessPage: Bool { - let subscriptionActivateSuccessURL = SubscriptionURL.activateSuccess.subscriptionURL(environment: subscriptionServiceEnvironment) - let subscriptionPurchaseURL = SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment) + let subscriptionActivateSuccessURL = subscriptionManager.url(for: .activateSuccess) + let subscriptionPurchaseURL = subscriptionManager.url(for: .purchase) return webViewModel.url?.forComparison() == subscriptionActivateSuccessURL.forComparison() || webViewModel.url?.forComparison() == subscriptionPurchaseURL.forComparison() } @@ -94,7 +94,7 @@ final class SubscriptionEmailViewModel: ObservableObject { settings: AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: Self.allowedDomains, contentBlocking: false)) - self.emailURL = SubscriptionURL.activateViaEmail.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + self.emailURL = subscriptionManager.url(for: .activateViaEmail) } @MainActor @@ -104,7 +104,7 @@ final class SubscriptionEmailViewModel: ObservableObject { } else { // If not in the Welcome page, dismiss the view, otherwise, assume we // came from Activation, so dismiss the entire stack - let subscriptionPurchaseURL = SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment) + let subscriptionPurchaseURL = subscriptionManager.url(for: .purchase) if webViewModel.url?.forComparison() != subscriptionPurchaseURL.forComparison() { state.shouldDismissView = true } else { @@ -133,8 +133,8 @@ final class SubscriptionEmailViewModel: ObservableObject { // If the user is Authenticated & not in the Welcome page if accountManager.isUserAuthenticated && !isWelcomePageOrSuccessPage { // If user is authenticated, we want to "Add or manage email" instead of activating - let addEmailToSubscriptionURL = SubscriptionURL.addEmail.subscriptionURL(environment: subscriptionServiceEnvironment) - let manageSubscriptionEmailURL = SubscriptionURL.manageEmail.subscriptionURL(environment: subscriptionServiceEnvironment) + let addEmailToSubscriptionURL = subscriptionManager.url(for: .addEmail) + let manageSubscriptionEmailURL = subscriptionManager.url(for: .manageEmail) emailURL = accountManager.email == nil ? addEmailToSubscriptionURL : manageSubscriptionEmailURL state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index a5f56ed808..572aac269f 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -76,7 +76,7 @@ final class SubscriptionFlowViewModel: ObservableObject { subFeature: SubscriptionPagesUseSubscriptionFeature, subscriptionManager: SubscriptionManaging, selectedFeature: SettingsViewModel.SettingsDeepLinkSection? = nil) { - let url = SubscriptionURL.purchase.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let url = subscriptionManager.url(for: .purchase) if let origin { purchaseURL = url.appendingParameter(name: AttributionParameter.origin, value: origin) } else { @@ -243,9 +243,8 @@ final class SubscriptionFlowViewModel: ObservableObject { guard let currentURL = self?.webViewModel.url else { return } Task { await strongSelf.setTransactionStatus(.idle) } - let addEmailURL = SubscriptionURL.addEmail.subscriptionURL(environment: strongSelf.subscriptionServiceEnvironment) - let addEmailSuccessURL = SubscriptionURL.addEmailToSubscriptionSuccess.subscriptionURL(environment: - strongSelf.subscriptionServiceEnvironment) + let addEmailURL = strongSelf.subscriptionManager.url(for: .addEmail) + let addEmailSuccessURL = strongSelf.subscriptionManager.url(for: .addEmailToSubscriptionSuccess) if currentURL.forComparison() == addEmailURL.forComparison() || currentURL.forComparison() == addEmailSuccessURL.forComparison() { strongSelf.state.viewTitle = UserText.subscriptionRestoreAddEmailTitle @@ -257,9 +256,9 @@ final class SubscriptionFlowViewModel: ObservableObject { } private func backButtonForURL(currentURL: URL) -> Bool { - return currentURL.forComparison() != SubscriptionURL.baseURL.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() && - currentURL.forComparison() != SubscriptionURL.activateSuccess.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() && - currentURL.forComparison() != SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() + return currentURL.forComparison() != subscriptionManager.url(for: .baseURL).forComparison() && + currentURL.forComparison() != subscriptionManager.url(for: .activateSuccess).forComparison() && + currentURL.forComparison() != subscriptionManager.url(for: .purchase).forComparison() } private func cleanUp() { @@ -322,9 +321,8 @@ final class SubscriptionFlowViewModel: ObservableObject { DispatchQueue.main.async { self.resetState() } - if webViewModel.url != SubscriptionURL.purchase.subscriptionURL(environment: subscriptionServiceEnvironment).forComparison() { - self.webViewModel.navigationCoordinator.navigateTo(url: SubscriptionURL.purchase.subscriptionURL(environment: - self.subscriptionServiceEnvironment)) + if webViewModel.url != subscriptionManager.url(for: .purchase).forComparison() { + self.webViewModel.navigationCoordinator.navigateTo(url: subscriptionManager.url(for: .purchase)) } await self.setupTransactionObserver() await self.setupWebViewObservers() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index c0768394db..fc9e3ef33e 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -62,7 +62,7 @@ final class SubscriptionITPViewModel: ObservableObject { private var canGoBackCancellable: AnyCancellable? init(subscriptionManager: SubscriptionManaging) { - self.itpURL = SubscriptionURL.identityTheftRestoration.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + self.itpURL = subscriptionManager.url(for: .identityTheftRestoration) self.manageITPURL = self.itpURL self.userScript = IdentityTheftRestorationPagesUserScript() self.subFeature = IdentityTheftRestorationPagesFeature(accountManager: subscriptionManager.accountManager) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 2833ab3412..710bd92ce7 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -66,7 +66,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { init(subscriptionManager: SubscriptionManaging = AppDependencyProvider.shared.subscriptionManager) { self.subscriptionManager = subscriptionManager - let subscriptionFAQURL = SubscriptionURL.faq.subscriptionURL(environment: subscriptionManager.currentEnvironment.serviceEnvironment) + let subscriptionFAQURL = subscriptionManager.url(for: .faq) self.state = State(faqURL: subscriptionFAQURL) setupSubscriptionUpdater() @@ -204,8 +204,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { @MainActor private func manageAppleSubscription() async { if state.subscriptionInfo?.isActive ?? false { - let url = SubscriptionURL.manageSubscriptionsInAppStore.subscriptionURL(environment: - subscriptionManager.currentEnvironment.serviceEnvironment) + let url = subscriptionManager.url(for: .manageSubscriptionsInAppStore) if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { do { try await AppStore.showManageSubscriptions(in: windowScene) diff --git a/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift b/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift index 991da6ec7c..74fab67672 100644 --- a/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift +++ b/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift @@ -19,7 +19,7 @@ import XCTest @testable import DuckDuckGo -import Subscription +@testable import Subscription import SubscriptionTestingUtilities @available(iOS 15.0, *) diff --git a/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift b/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift index bef993d72a..eaf6cbdaa3 100644 --- a/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift +++ b/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift @@ -19,7 +19,7 @@ import XCTest @testable import DuckDuckGo -import Subscription +@testable import Subscription import SubscriptionTestingUtilities @available(iOS 15.0, *) From 53c55287893ef809bb38e42b6871b16a3ceff71b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 16 May 2024 17:48:12 +0100 Subject: [PATCH 31/39] 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 f379d2f70d..e0f1f0a0c2 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 3b4b2c03ae8f4f7f924123ebf46fb35d9c872cf6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 17 May 2024 09:51:26 +0100 Subject: [PATCH 32/39] Update DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift bad merge fix Co-authored-by: Alessandro Boron --- .../Subscription/ViewModel/SubscriptionFlowViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 572aac269f..6ab51b6c8f 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -322,7 +322,7 @@ final class SubscriptionFlowViewModel: ObservableObject { self.resetState() } if webViewModel.url != subscriptionManager.url(for: .purchase).forComparison() { - self.webViewModel.navigationCoordinator.navigateTo(url: subscriptionManager.url(for: .purchase)) + self.webViewModel.navigationCoordinator.navigateTo(url: purchaseURL) } await self.setupTransactionObserver() await self.setupWebViewObservers() From 6f7b0333f59f2fc652372695e6043998a532192d Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 17 May 2024 15:25:03 +0100 Subject: [PATCH 33/39] accountmanager fixed --- DuckDuckGo/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index b7aff1930e..89a3ed89b5 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -977,7 +977,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { func presentNetworkProtectionStatusSettingsModal() { Task { - let accountManager = AccountManager() + let accountManager = AppDependencyProvider.shared.accountManager if case .success(let hasEntitlements) = await accountManager.hasEntitlement(for: .networkProtection), hasEntitlements { if #available(iOS 15, *) { From 6606f4d0cf0890513353460c0b9ed3cc972d9be8 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 21 May 2024 12:51:58 +0200 Subject: [PATCH 34/39] BSK update --- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- DuckDuckGo/AppDependencyProvider.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 425dc1b09a..31a1df66ce 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" : "dc996ee2c8a8d3a9476ed4776dc570ee7b9ce845" + "revision" : "874ae4269db821797742655e134e72199c2813c8" } }, { @@ -185,8 +185,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { - "revision" : "6c84fd19139414fc0edbf9673ade06e532a564f0", - "version" : "2.0.0" + "revision" : "c01e6a59d000356b58ec77053e0a99d538be56a5", + "version" : "2.1.1" } }, { diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 3245b8a25c..63649d774e 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -81,7 +81,7 @@ class AppDependencyProvider: DependencyProvider { let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, - subscriptionPlatform: .appStore) + purchasePlatform: .appStore) // Subscription let subscriptionManager: SubscriptionManaging @@ -131,7 +131,7 @@ class AppDependencyProvider: DependencyProvider { let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, - subscriptionPlatform: .appStore) + purchasePlatform: .appStore) let accessTokenProvider: () -> String? = { func isSubscriptionEnabled() -> Bool { if let subscriptionOverrideEnabled = UserDefaults.networkProtectionGroupDefaults.subscriptionOverrideEnabled { From 87ff5248c742829e1441947ee31fd9ef20ecbac2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 22 May 2024 12:14:45 +0200 Subject: [PATCH 35/39] PR improvements --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ---- DuckDuckGo/AppDependencyProvider.swift | 8 ++++--- .../DefaultNetworkProtectionVisibility.swift | 7 ++++-- .../SubscriptionSettingsViewModel.swift | 4 ++-- .../Views/SubscriptionSettingsView.swift | 2 +- .../SubscriptionDebugViewController.swift | 9 +++---- DuckDuckGo/UserDefaultsCacheKey.swift | 24 ------------------- ...etworkProtectionPacketTunnelProvider.swift | 22 +++++++++++------ 8 files changed, 31 insertions(+), 49 deletions(-) delete mode 100644 DuckDuckGo/UserDefaultsCacheKey.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d4745172e1..1f101adc05 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -785,7 +785,6 @@ D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; - D6A4645C2BD6D6DA00F80DC2 /* UserDefaultsCacheKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4645B2BD6D6DA00F80DC2 /* UserDefaultsCacheKey.swift */; }; D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */; }; D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; @@ -2441,7 +2440,6 @@ D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; - D6A4645B2BD6D6DA00F80DC2 /* UserDefaultsCacheKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsCacheKey.swift; sourceTree = ""; }; D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptor.swift; sourceTree = ""; }; D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; @@ -5258,7 +5256,6 @@ 85C8E61C2B0E47380029A6BD /* BookmarksDatabaseSetup.swift */, 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */, 9821234F2B6D233E00F08C57 /* UserSession.swift */, - D6A4645B2BD6D6DA00F80DC2 /* UserDefaultsCacheKey.swift */, ); name = Application; sourceTree = ""; @@ -6742,7 +6739,6 @@ D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, - D6A4645C2BD6D6DA00F80DC2 /* UserDefaultsCacheKey.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */, D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */, diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 63649d774e..b7cd432e47 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -161,8 +161,10 @@ class AppDependencyProvider: DependencyProvider { accountManager: subscriptionManager.accountManager, tokenStore: networkProtectionKeychainTokenStore, networkProtectionTunnelController: networkProtectionTunnelController) - vpnFeatureVisibility = DefaultNetworkProtectionVisibility(networkProtectionAccessManager: networkProtectionAccessController, - featureFlagger: featureFlagger, - accountManager: accountManager) + vpnFeatureVisibility = DefaultNetworkProtectionVisibility( + networkProtectionTokenStore: networkProtectionKeychainTokenStore, + networkProtectionAccessManager: networkProtectionAccessController, + featureFlagger: featureFlagger, + accountManager: accountManager) } } diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index f7bca2ea6b..8adb019716 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -27,19 +27,22 @@ import Core import Subscription struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { - public let privacyConfigurationManager: PrivacyConfigurationManaging + private let privacyConfigurationManager: PrivacyConfigurationManaging + private let networkProtectionTokenStore: NetworkProtectionTokenStore private let networkProtectionAccessManager: NetworkProtectionAccess private let featureFlagger: FeatureFlagger private let userDefaults: UserDefaults private let accountManager: AccountManaging init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, + networkProtectionTokenStore: NetworkProtectionTokenStore, networkProtectionAccessManager: NetworkProtectionAccess, featureFlagger: FeatureFlagger, userDefaults: UserDefaults = .networkProtectionGroupDefaults, accountManager: AccountManaging) { self.privacyConfigurationManager = privacyConfigurationManager + self.networkProtectionTokenStore = networkProtectionTokenStore self.networkProtectionAccessManager = networkProtectionAccessManager self.featureFlagger = featureFlagger self.userDefaults = userDefaults @@ -59,7 +62,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { func isWaitlistUser() -> Bool { let hasLegacyAuthToken = { - guard let authToken = token, + guard let authToken = try? networkProtectionTokenStore.fetchToken(), !authToken.hasPrefix(NetworkProtectionKeychainTokenStore.authTokenPrefix) else { return false } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 710bd92ce7..d5fbc1a00f 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -50,10 +50,10 @@ final class SubscriptionSettingsViewModel: ObservableObject { var isShowingConnectionError: Bool = false // Used to display the FAQ WebUI - var FAQViewModel: SubscriptionExternalLinkViewModel + var faqViewModel: SubscriptionExternalLinkViewModel init(faqURL: URL) { - self.FAQViewModel = SubscriptionExternalLinkViewModel(url: faqURL) + self.faqViewModel = SubscriptionExternalLinkViewModel(url: faqURL) } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index dc477fafbb..679bae8284 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -252,7 +252,7 @@ struct SubscriptionSettingsView: View { } .sheet(isPresented: $isShowingFAQView, content: { - SubscriptionExternalLinkView(viewModel: viewModel.state.FAQViewModel, title: UserText.subscriptionFAQ) + SubscriptionExternalLinkView(viewModel: viewModel.state.faqViewModel, title: UserText.subscriptionFAQ) }) .onFirstAppear { diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 11335510c8..72c5eafd6f 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -78,7 +78,6 @@ import NetworkProtection return titles[section] } - // swiftlint:disable:next cyclomatic_complexity function_body_length override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) @@ -123,16 +122,14 @@ import NetworkProtection } case .environment: - let staging = SubscriptionEnvironment.ServiceEnvironment.staging - let prod = SubscriptionEnvironment.ServiceEnvironment.production let currentEnv = subscriptionManager.currentEnvironment.serviceEnvironment switch EnvironmentRows(rawValue: indexPath.row) { case .staging: cell.textLabel?.text = "Staging" - cell.accessoryType = currentEnv == staging ? .checkmark : .none + cell.accessoryType = currentEnv == .staging ? .checkmark : .none case .production: cell.textLabel?.text = "Production" - cell.accessoryType = currentEnv == prod ? .checkmark : .none + cell.accessoryType = currentEnv == .production ? .checkmark : .none case .none: break } @@ -191,7 +188,7 @@ import NetworkProtection subEnvDesc = "PRODUCTION" } let message = """ - Are you sure you want to change the purchase platform to \(subEnvDesc)? + Are you sure you want to change the environment to \(subEnvDesc)? This setting IS persisted between app runs. This action will close the app, do you want to proceed? """ let alertController = UIAlertController(title: "⚠️ App restart required! The changes are persistent", diff --git a/DuckDuckGo/UserDefaultsCacheKey.swift b/DuckDuckGo/UserDefaultsCacheKey.swift deleted file mode 100644 index f3af33833f..0000000000 --- a/DuckDuckGo/UserDefaultsCacheKey.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// UserDefaultsCacheKey.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Common - -// public enum UserDefaultsCacheKey: String, UserDefaultsCacheKeyStore { -// case subscriptionState = "com.duckduckgo.ios.subscription.state" -// } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index ea1eb6c416..f5076fd9bb 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -254,15 +254,25 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { super.stopTunnel(with: reason, completionHandler: completionHandler) } + // swiftlint:disable:next function_body_length @objc init() { + + let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) + + // Align Subscription environment to the VPN environment + var subscriptionEnvironment = SubscriptionEnvironment.default + switch settings.selectedEnvironment { + case .production: + subscriptionEnvironment.serviceEnvironment = .production + case .staging: + subscriptionEnvironment.serviceEnvironment = .staging + } + // 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, + let entitlementsCache = UserDefaultsCache<[Entitlement]>(userDefaults: UserDefaults.standard, key: UserDefaultsCacheKey.subscriptionEntitlements, settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) - let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) + let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.unspecified)) let subscriptionService = SubscriptionService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let authService = AuthService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let accountManager = AccountManager(accessTokenStorage: accessTokenStorage, @@ -284,8 +294,6 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() - let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) - settings.alignTo(subscriptionEnvironment: subscriptionEnvironment) let notificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator( settings: settings, From 77632a83c1ff12ac2e7792508827b220052d3f59 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 22 May 2024 12:21:15 +0200 Subject: [PATCH 36/39] mock fixed --- DuckDuckGoTests/MockDependencyProvider.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index bb27c1c2ae..8cac957fa0 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -102,7 +102,9 @@ class MockDependencyProvider: DependencyProvider { accountManager: subscriptionManager.accountManager, tokenStore: networkProtectionKeychainTokenStore, networkProtectionTunnelController: networkProtectionTunnelController) - vpnFeatureVisibility = DefaultNetworkProtectionVisibility(networkProtectionAccessManager: networkProtectionAccessController, + vpnFeatureVisibility = DefaultNetworkProtectionVisibility( + networkProtectionTokenStore: networkProtectionKeychainTokenStore, + networkProtectionAccessManager: networkProtectionAccessController, featureFlagger: featureFlagger, accountManager: accountManager) From a3ec6530853f1e019a0325534bd1baf6509b762b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 22 May 2024 12:29:34 +0200 Subject: [PATCH 37/39] commented code removed --- ...orkProtectionConvenienceInitialisers.swift | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index d4659f8860..5873925cfc 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -56,26 +56,6 @@ extension ConnectionServerInfoObserverThroughSession { } } -// extension NetworkProtectionKeychainTokenStore { -// -// convenience init(accountManager: AccountManaging) { -// let featureVisibility = AppDependencyProvider.shared.vpnFeatureVisibility -// let isSubscriptionEnabled = featureVisibility.isPrivacyProLaunched() -// let accessTokenProvider: () -> String? = { -// if featureVisibility.shouldMonitorEntitlement() { -// return { accountManager.accessToken } -// } -// return { nil } -// }() -// -// self.init(keychainType: .dataProtection(.unspecified), -// serviceName: "\(Bundle.main.bundleIdentifier!).authToken", -// errorEvents: .networkProtectionAppDebugEvents, -// isSubscriptionEnabled: isSubscriptionEnabled, -// accessTokenProvider: accessTokenProvider) -// } -// } - extension NetworkProtectionCodeRedemptionCoordinator { convenience init(isManualCodeRedemptionFlow: Bool = false, accountManager: AccountManaging) { From 67696824466ac5941442c67eb3a45dd7fd1b15b0 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 22 May 2024 12:35:11 +0200 Subject: [PATCH 38/39] unused code removed --- .../Subscription/ViewModel/SubscriptionEmailViewModel.swift | 4 ---- .../Subscription/ViewModel/SubscriptionFlowViewModel.swift | 3 --- 2 files changed, 7 deletions(-) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index fdde1b81c5..3c209c3f1c 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -70,10 +70,6 @@ final class SubscriptionEmailViewModel: ObservableObject { } private var cancellables = Set() - - var subscriptionServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment { - subscriptionManager.currentEnvironment.serviceEnvironment - } var accountManager: AccountManaging { subscriptionManager.accountManager } private var isWelcomePageOrSuccessPage: Bool { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 6af2769921..b3469edc2a 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -31,9 +31,6 @@ final class SubscriptionFlowViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature var webViewModel: AsyncHeadlessWebViewViewModel let subscriptionManager: SubscriptionManaging - var subscriptionServiceEnvironment: SubscriptionEnvironment.ServiceEnvironment { - subscriptionManager.currentEnvironment.serviceEnvironment - } let purchaseURL: URL private var cancellables = Set() From e4630809a4a431fd0454a79562cb0120de9b00d5 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 22 May 2024 23:31:19 +0100 Subject: [PATCH 39/39] BSK v146.0.0 --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/AppDelegate.swift | 15 +++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 30ba8172d3..6d498896b3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9873,8 +9873,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - branch = fcappelli/subscription_refactoring_2; - kind = branch; + kind = exactVersion; + version = 146.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 31a1df66ce..3a2023b938 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" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 178a9ebda7..bfcea59ff4 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -88,6 +88,10 @@ import WebKit @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? + var accountManager: AccountManaging { + AppDependencyProvider.shared.accountManager + } + // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { @@ -502,7 +506,7 @@ import WebKit private func stopTunnelAndShowThankYouMessagingIfNeeded() { - if AppDependencyProvider.shared.accountManager.isUserAuthenticated { + if accountManager.isUserAuthenticated { tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true return } @@ -513,7 +517,7 @@ import WebKit await self.stopAndRemoveVPN(with: "thank-you-dialog") } } else if AppDependencyProvider.shared.vpnFeatureVisibility.isPrivacyProLaunched() - && !AppDependencyProvider.shared.accountManager.isUserAuthenticated { + && !accountManager.isUserAuthenticated { Task { await self.stopAndRemoveVPN(with: "subscription-check") } @@ -538,7 +542,7 @@ import WebKit func updateSubscriptionStatus() { Task { - guard let token = AppDependencyProvider.shared.accountManager.accessToken else { return } + guard let token = accountManager.accessToken else { return } var subscriptionService: SubscriptionService { AppDependencyProvider.shared.subscriptionManager.subscriptionService } @@ -548,7 +552,7 @@ import WebKit DailyPixel.fire(pixel: .privacyProSubscriptionActive) } } - await AppDependencyProvider.shared.accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) + await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } } @@ -893,7 +897,7 @@ import WebKit return } - if case .success(true) = await AccountManager().hasEntitlement(for: .networkProtection, cachePolicy: .returnCacheDataDontLoad) { + if case .success(true) = await accountManager.hasEntitlement(for: .networkProtection, cachePolicy: .returnCacheDataDontLoad) { let items = [ UIApplicationShortcutItem(type: ShortcutKey.openVPNSettings, localizedTitle: UserText.netPOpenVPNQuickAction, @@ -988,7 +992,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate { func presentNetworkProtectionStatusSettingsModal() { Task { - let accountManager = AppDependencyProvider.shared.accountManager if case .success(let hasEntitlements) = await accountManager.hasEntitlement(for: .networkProtection), hasEntitlements { if #available(iOS 15, *) {