From d67271ab17dcc997ca986d381c09a23a29faccbf Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 21 Mar 2024 10:14:38 -0500 Subject: [PATCH 01/10] Add additional parameters to breakage reports (#2592) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 10 +-- DuckDuckGo/AppDelegate.swift | 2 +- DuckDuckGo/MainViewController.swift | 14 ++-- .../PrivacyDashboardViewController.swift | 69 +++++++++++++------ DuckDuckGo/TabViewController.swift | 59 +++++++++++++++- DuckDuckGo/UserScripts.swift | 7 +- .../BrokenSiteReportingTests.swift | 5 ++ 8 files changed, 133 insertions(+), 35 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c81dc1cd68..f4d8fb9bd1 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 128.0.0; + version = 129.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f09cda753e..7af82db6e5 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" : "128.0.0", - "revision" : "6c5ed334b3ea7b4394bec697067654182f75c877" + "revision" : "3a6d8f3f3fe278fb316fe927c4ef005a19d03d71", + "version" : "129.0.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "f6241631fc14cc2d0f47950bfdc4d6c30bf90130", - "version" : "5.4.0" + "revision" : "edd96481d49b094c260f9a79e078abdbdd3a83fb", + "version" : "5.6.0" } }, { @@ -183,7 +183,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 7348724343..774472e543 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -693,7 +693,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { showKeyboardIfSettingOn = false if !handleAppDeepLink(app, mainViewController, url) { - mainViewController?.loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil) + mainViewController?.loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil, fromExternalLink: true) } } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index e8f98c203b..3f6e04dcb8 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -874,7 +874,7 @@ class MainViewController: UIViewController { loadUrlInNewTab(url, reuseExisting: reuseExisting, inheritedAttribution: nil) } - func loadUrlInNewTab(_ url: URL, reuseExisting: Bool = false, inheritedAttribution: AdClickAttributionLogic.State?) { + func loadUrlInNewTab(_ url: URL, reuseExisting: Bool = false, inheritedAttribution: AdClickAttributionLogic.State?, fromExternalLink: Bool = false) { func worker() { allowContentUnderflow = false viewCoordinator.navigationBarContainer.alpha = 1 @@ -884,9 +884,9 @@ class MainViewController: UIViewController { return } else if reuseExisting, let existing = tabManager.firstHomeTab() { tabManager.selectTab(existing) - loadUrl(url) + loadUrl(url, fromExternalLink: fromExternalLink) } else { - addTab(url: url, inheritedAttribution: inheritedAttribution) + addTab(url: url, inheritedAttribution: inheritedAttribution, fromExternalLink: fromExternalLink) } refreshOmniBar() refreshTabIcon() @@ -921,9 +921,12 @@ class MainViewController: UIViewController { loadUrl(url) } - func loadUrl(_ url: URL) { + func loadUrl(_ url: URL, fromExternalLink: Bool = false) { prepareTabForRequest { self.currentTab?.load(url: url) + if fromExternalLink { + self.currentTab?.inferredOpenerContext = .external + } } } @@ -956,8 +959,9 @@ class MainViewController: UIViewController { select(tab: tab) } - private func addTab(url: URL?, inheritedAttribution: AdClickAttributionLogic.State?) { + private func addTab(url: URL?, inheritedAttribution: AdClickAttributionLogic.State?, fromExternalLink: Bool = false) { let tab = tabManager.add(url: url, inheritedAttribution: inheritedAttribution) + tab.inferredOpenerContext = .external dismissOmniBar() attachTab(tab: tab) } diff --git a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift index ec3eca1efa..6c6926aae1 100644 --- a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift @@ -223,16 +223,18 @@ extension PrivacyDashboardViewController: PrivacyDashboardReportBrokenSiteDelega func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) { - let source: BrokenSiteReport.Source = privacyDashboardController.initDashboardMode == .report ? .appMenu : .dashboard - do { - let report = try makeBrokenSiteReport(category: category, description: description, source: source) - try brokenSiteReporter.report(report, reportMode: .regular) - } catch { - os_log("Failed to generate or send the broken site report: %@", type: .error, error.localizedDescription) + Task { @MainActor in + let source: BrokenSiteReport.Source = privacyDashboardController.initDashboardMode == .report ? .appMenu : .dashboard + do { + let report = try await makeBrokenSiteReport(category: category, description: description, source: source) + try brokenSiteReporter.report(report, reportMode: .regular) + } catch { + os_log("Failed to generate or send the broken site report: %@", type: .error, error.localizedDescription) + } + + ActionMessageView.present(message: UserText.feedbackSumbittedConfirmation) + privacyDashboardCloseHandler() } - - ActionMessageView.present(message: UserText.feedbackSumbittedConfirmation) - privacyDashboardCloseHandler() } } @@ -245,16 +247,18 @@ extension PrivacyDashboardViewController: PrivacyDashboardToggleReportDelegate { didRequestSubmitToggleReportWithSource source: BrokenSiteReport.Source, didOpenReportInfo: Bool, toggleReportCounter: Int?) { - do { - let report = try makeBrokenSiteReport(source: source, - didOpenReportInfo: didOpenReportInfo, - toggleReportCounter: toggleReportCounter) - try toggleProtectionsOffReporter.report(report, reportMode: .toggle) - } catch { - os_log("Failed to generate or send the broken site report: %@", type: .error, error.localizedDescription) - } + Task { @MainActor in + do { + let report = try await makeBrokenSiteReport(source: source, + didOpenReportInfo: didOpenReportInfo, + toggleReportCounter: toggleReportCounter) + try toggleProtectionsOffReporter.report(report, reportMode: .toggle) + } catch { + os_log("Failed to generate or send the broken site report: %@", type: .error, error.localizedDescription) + } - privacyDashboardCloseHandler() + privacyDashboardCloseHandler() + } } } @@ -271,23 +275,44 @@ extension PrivacyDashboardViewController { let isDesktop: Bool let error: Error? let httpStatusCode: Int? + let openerContext: BrokenSiteReport.OpenerContext? + let vpnOn: Bool + let userRefreshCount: Int + let performanceMetrics: PerformanceMetricsSubfeature? } enum BrokenSiteReportError: Error { case failedToFetchTheCurrentWebsiteInfo } + private func calculateWebVitals(breakageAdditionalInfo: BreakageAdditionalInfo, privacyConfig: PrivacyConfiguration) async -> [Double]? { + var webVitalsResult: [Double]? + if privacyConfig.isEnabled(featureKey: .performanceMetrics) { + webVitalsResult = await withCheckedContinuation({ continuation in + guard let performanceMetrics = breakageAdditionalInfo.performanceMetrics else { continuation.resume(returning: nil); return } + performanceMetrics.notifyHandler { result in + continuation.resume(returning: result) + } + }) + } + + return webVitalsResult + } + private func makeBrokenSiteReport(category: String = "", description: String = "", source: BrokenSiteReport.Source, didOpenReportInfo: Bool = false, - toggleReportCounter: Int? = nil) throws -> BrokenSiteReport { + toggleReportCounter: Int? = nil) async throws -> BrokenSiteReport { guard let privacyInfo = privacyDashboardController.privacyInfo, let breakageAdditionalInfo = breakageAdditionalInfo else { throw BrokenSiteReportError.failedToFetchTheCurrentWebsiteInfo } - + + let webVitalsResult = await calculateWebVitals(breakageAdditionalInfo: breakageAdditionalInfo, + privacyConfig: privacyConfigurationManager.privacyConfig) + let blockedTrackerDomains = privacyInfo.trackerInfo.trackersBlocked.compactMap { $0.domain } let protectionsState = privacyConfigurationManager.privacyConfig.isFeature(.contentBlocking, enabledForDomain: breakageAdditionalInfo.currentURL.host) @@ -320,6 +345,10 @@ extension PrivacyDashboardViewController { model: UIDevice.current.model, errors: errors, httpStatusCodes: statusCodes, + openerContext: breakageAdditionalInfo.openerContext, + vpnOn: breakageAdditionalInfo.vpnOn, + jsPerformance: webVitalsResult, + userRefreshCount: breakageAdditionalInfo.userRefreshCount, didOpenReportInfo: didOpenReportInfo, toggleReportCounter: toggleReportCounter) } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 1299336bbf..5429c52948 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -118,6 +118,10 @@ class TabViewController: UIViewController { private var urlProvidedBasicAuthCredential: (credential: URLCredential, url: URL)? private var emailProtectionSignOutCancellable: AnyCancellable? + public var inferredOpenerContext: BrokenSiteReport.OpenerContext? + private var refreshCountSinceLoad: Int = 0 + private var performanceMetrics: PerformanceMetricsSubfeature? + private var detectedLoginURL: URL? private var preserveLoginsWorker: PreserveLoginsWorker? @@ -127,6 +131,16 @@ class TabViewController: UIViewController { private let netPConnectionObserver = ConnectionStatusObserverThroughSession() private var netPConnectionObserverCancellable: AnyCancellable? private var netPConnectionStatus: ConnectionStatus = .default + private var netPConnected: Bool { + switch netPConnectionStatus { + case .connected: + return true + default: + break + } + + return false + } #endif // Required to know when to disable autofill, see SaveLoginViewModel for details @@ -577,6 +591,8 @@ class TabViewController: UIViewController { assert(urlRequest.attribution == .user, "WebView requests should be user attributed") } + refreshCountSinceLoad = 0 + webView.stopLoading() dismissJSAlertIfNeeded() webView.load(urlRequest) @@ -927,13 +943,18 @@ class TabViewController: UIViewController { guard let currentURL = url else { return nil } + return PrivacyDashboardViewController.BreakageAdditionalInfo(currentURL: currentURL, httpsForced: httpsForced, ampURLString: linkProtection.lastAMPURLString ?? "", urlParametersRemoved: linkProtection.urlParametersRemoved, isDesktop: tabModel.isDesktop, error: lastError, - httpStatusCode: lastHttpStatusCode) + httpStatusCode: lastHttpStatusCode, + openerContext: inferredOpenerContext, + vpnOn: netPConnected, + userRefreshCount: refreshCountSinceLoad, + performanceMetrics: performanceMetrics) } public func print() { @@ -1197,11 +1218,17 @@ extension TabViewController: WKNavigationDelegate { private func onWebpageDidFinishLoading() { os_log("webpageLoading finished", log: .generalLog, type: .debug) - + tabModel.link = link delegate?.tabLoadingStateDidChange(tab: self) showDaxDialogOrStartTrackerNetworksAnimationIfNeeded() + + Task { @MainActor in + if await webView.isCurrentSiteReferredFromDuckDuckGo { + inferredOpenerContext = .serp + } + } } func showDaxDialogOrStartTrackerNetworksAnimationIfNeeded() { @@ -1383,6 +1410,12 @@ extension TabViewController: WKNavigationDelegate { didGoBackForward = (navigationAction.navigationType == .backForward) + if navigationAction.navigationType != .reload && navigationAction.navigationType != .other { + // Ignore .other actions because refresh can cause a redirect + // This is also handled in loadRequest(_:) + refreshCountSinceLoad = 0 + } + // This check needs to happen before GPC checks. Otherwise the navigation type may be rewritten to `.other` // which would skip link rewrites. if navigationAction.navigationType != .backForward && navigationAction.isTargetingMainFrame() { @@ -1529,6 +1562,22 @@ extension TabViewController: WKNavigationDelegate { } } + private func inferLoadContext(for navigationAction: WKNavigationAction) -> BrokenSiteReport.OpenerContext? { + guard navigationAction.navigationType != .reload else { return nil } + guard let currentUrl = webView.url, let newUrl = navigationAction.request.url else { return nil } + + if currentUrl.isDuckDuckGoSearch && !newUrl.isDuckDuckGoSearch { + return .serp + } else { + switch navigationAction.navigationType { + case .linkActivated, .other, .formSubmitted: + return .navigation + default: + return nil + } + } + } + private func performNavigationFor(url: URL, navigationAction: WKNavigationAction, allowPolicy: WKNavigationActionPolicy, @@ -1550,6 +1599,8 @@ extension TabViewController: WKNavigationDelegate { self.urlProvidedBasicAuthCredential = nil } + inferredOpenerContext = inferLoadContext(for: navigationAction) + if shouldReissueSearch(for: url) { reissueSearchWithRequiredParams(for: url) completion(.cancel) @@ -2108,6 +2159,7 @@ extension TabViewController: UIGestureRecognizerDelegate { reload() } + refreshCountSinceLoad += 1 AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.refresh) } @@ -2146,6 +2198,9 @@ extension TabViewController: UserContentControllerDelegate { userScripts.loginFormDetectionScript?.delegate = self userScripts.autoconsentUserScript.delegate = self + performanceMetrics = PerformanceMetricsSubfeature(targetWebview: webView) + userScripts.contentScopeUserScriptIsolated.registerSubfeature(delegate: performanceMetrics!) + adClickAttributionLogic.onRulesChanged(latestRules: ContentBlocking.shared.contentBlockingManager.currentRules) let tdsKey = DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName diff --git a/DuckDuckGo/UserScripts.swift b/DuckDuckGo/UserScripts.swift index 06cd05c46e..9b0595ddba 100644 --- a/DuckDuckGo/UserScripts.swift +++ b/DuckDuckGo/UserScripts.swift @@ -31,6 +31,7 @@ final class UserScripts: UserScriptsProvider { let autofillUserScript: AutofillUserScript let loginFormDetectionScript: LoginFormDetectionUserScript? let contentScopeUserScript: ContentScopeUserScript + let contentScopeUserScriptIsolated: ContentScopeUserScript let autoconsentUserScript: AutoconsentUserScript private(set) var faviconScript = FaviconUserScript() @@ -50,6 +51,9 @@ final class UserScripts: UserScriptsProvider { loginFormDetectionScript = sourceProvider.loginDetectionEnabled ? LoginFormDetectionUserScript() : nil contentScopeUserScript = ContentScopeUserScript(sourceProvider.privacyConfigurationManager, properties: sourceProvider.contentScopeProperties) + contentScopeUserScriptIsolated = ContentScopeUserScript(sourceProvider.privacyConfigurationManager, + properties: sourceProvider.contentScopeProperties, + isIsolated: true) autoconsentUserScript = AutoconsentUserScript(config: sourceProvider.privacyConfigurationManager.privacyConfig) } @@ -66,7 +70,8 @@ final class UserScripts: UserScriptsProvider { autofillUserScript, printingUserScript, loginFormDetectionScript, - contentScopeUserScript + contentScopeUserScript, + contentScopeUserScriptIsolated ].compactMap({ $0 }) @MainActor diff --git a/DuckDuckGoTests/BrokenSiteReportingTests.swift b/DuckDuckGoTests/BrokenSiteReportingTests.swift index 7045dd5b80..d701766ad5 100644 --- a/DuckDuckGoTests/BrokenSiteReportingTests.swift +++ b/DuckDuckGoTests/BrokenSiteReportingTests.swift @@ -82,6 +82,7 @@ final class BrokenSiteReportingTests: XCTestCase { waitForExpectations(timeout: 10, handler: nil) } + // swiftlint:disable:next function_body_length private func runReferenceTests(onTestExecuted: XCTestExpectation) throws { guard let test = referenceTests.popLast() else { @@ -114,6 +115,10 @@ final class BrokenSiteReportingTests: XCTestCase { model: test.model ?? "", errors: errors, httpStatusCodes: test.httpErrorCodes ?? [], + openerContext: nil, + vpnOn: false, + jsPerformance: nil, + userRefreshCount: 0, didOpenReportInfo: false, toggleReportCounter: nil) From 066e677f1c273639e3d9f3b663e9304579ed6e4d Mon Sep 17 00:00:00 2001 From: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:01:23 +0100 Subject: [PATCH 02/10] Update BSK with autofill 10.2.0 (#2590) Task/Issue URL: https://app.asana.com/0/1206839421155398/1206839421155398 Autofill Release: https://github.com/duckduckgo/duckduckgo-autofill/releases/tag/10.2.0 BSK PR: duckduckgo/BrowserServicesKit#723 Description Updates Autofill to version 10.2.0. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++-- DuckDuckGo/AutofillDebugViewController.swift | 5 +++ DuckDuckGo/Debug.storyboard | 39 ++++++++++++------- DuckDuckGo/EmailSignupViewController.swift | 11 +++++- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f4d8fb9bd1..0451ab8d4a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.0.0; + version = 129.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7af82db6e5..d57c1ee698 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "3a6d8f3f3fe278fb316fe927c4ef005a19d03d71", - "version" : "129.0.0" + "revision" : "2f64ae6ce5aee5d648258eef5a5b39e61b9bd8e3", + "version" : "129.1.0" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "03d3e3a959dd75afbe8c59b5a203ea676d37555d", - "version" : "10.1.0" + "revision" : "6493e296934bf09277c03df45f11f4619711cb24", + "version" : "10.2.0" } }, { diff --git a/DuckDuckGo/AutofillDebugViewController.swift b/DuckDuckGo/AutofillDebugViewController.swift index c57819bac2..42fd467562 100644 --- a/DuckDuckGo/AutofillDebugViewController.swift +++ b/DuckDuckGo/AutofillDebugViewController.swift @@ -19,12 +19,14 @@ import UIKit import BrowserServicesKit +import Core class AutofillDebugViewController: UITableViewController { enum Row: Int { case toggleAutofillDebugScript = 201 case resetEmailProtectionInContextSignUp = 202 + case resetDaysSinceInstalledTo0 = 203 } let defaults = AppUserDefaults() @@ -46,6 +48,9 @@ class AutofillDebugViewController: UITableViewController { } else if cell.tag == Row.resetEmailProtectionInContextSignUp.rawValue { EmailManager().resetEmailProtectionInContextPrompt() tableView.deselectRow(at: indexPath, animated: true) + } else if cell.tag == Row.resetDaysSinceInstalledTo0.rawValue { + StatisticsUserDefaults().installDate = Date() + tableView.deselectRow(at: indexPath, animated: true) } } diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 974a09e5a3..95327eadf4 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -252,7 +252,7 @@ - + @@ -261,7 +261,7 @@ - + @@ -270,7 +270,7 @@ - + @@ -279,7 +279,7 @@ - + @@ -288,7 +288,7 @@ - + @@ -352,6 +352,15 @@ + + + + + + + + + @@ -864,34 +873,34 @@ - + - + - + - + @@ -959,13 +968,13 @@ - + - + diff --git a/DuckDuckGo/EmailSignupViewController.swift b/DuckDuckGo/EmailSignupViewController.swift index cf8eca1863..38890335c7 100644 --- a/DuckDuckGo/EmailSignupViewController.swift +++ b/DuckDuckGo/EmailSignupViewController.swift @@ -425,7 +425,16 @@ extension EmailSignupViewController: SecureVaultManagerDelegate { } func secureVaultManager(_: SecureVaultManager, didRequestRuntimeConfigurationForDomain domain: String, completionHandler: @escaping (String?) -> Void) { - completionHandler(nil) + let contentScopeProperties = ContentScopeProperties(gpcEnabled: AppDependencyProvider.shared.appSettings.sendDoNotSell, + sessionKey: "", + featureToggles: ContentScopeFeatureToggles.supportedFeaturesOniOS) + + let runtimeConfig = DefaultAutofillSourceProvider.Builder(privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + properties: contentScopeProperties) + .build() + .buildRuntimeConfigResponse() + + completionHandler(runtimeConfig) } func secureVaultManager(_: SecureVaultManager, didReceivePixel pixel: AutofillUserScript.JSPixel) { From 9f2f4b823b844bf8a60ffed7a6ae55e07c9b8b1e Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 22 Mar 2024 07:52:32 -0700 Subject: [PATCH 03/10] Update VPN configuration name (#2623) Task/Issue URL: https://app.asana.com/0/414235014887631/1206904075625849/f Tech Design URL: CC: Description: This PR updates the VPN configuration name. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/NetworkProtectionTunnelController.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0451ab8d4a..a193190ebd 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.0; + version = 129.1.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d57c1ee698..646823f365 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "2f64ae6ce5aee5d648258eef5a5b39e61b9bd8e3", - "version" : "129.1.0" + "revision" : "40093418106389c945bbe6df585fdb02833716df", + "version" : "129.1.1" } }, { diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index e8aea07fce..1d130bf0cc 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -222,7 +222,7 @@ final class NetworkProtectionTunnelController: TunnelController { /// Setups the tunnel manager if it's not set up already. /// private func setup(_ tunnelManager: NETunnelProviderManager) { - tunnelManager.localizedDescription = "DuckDuckGo Network Protection" + tunnelManager.localizedDescription = "DuckDuckGo VPN" tunnelManager.isEnabled = true tunnelManager.protocolConfiguration = { From f2e2c3175a0531b435028895f927a45adbf83561 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Fri, 22 Mar 2024 10:46:40 -0500 Subject: [PATCH 04/10] Fix webview leak in performancemetrics (#2624) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a193190ebd..0bca2809f6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.1; + version = 129.1.2; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 646823f365..40fa1c998b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "40093418106389c945bbe6df585fdb02833716df", - "version" : "129.1.1" + "revision" : "781b5f10b6030273e745ba3c0b71ff9317d8ade0", + "version" : "129.1.2" } }, { From 40fbb495ad212954a3f6da3db8201443541ed5a1 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Fri, 22 Mar 2024 21:05:17 +0100 Subject: [PATCH 05/10] Rework the caching logic for subscription and entitlements (#2627) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- DuckDuckGo/AppDelegate.swift | 19 ++++++++++--------- .../SubscriptionDebugViewController.swift | 4 ++-- ...etworkProtectionPacketTunnelProvider.swift | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0bca2809f6..bbf3a43ffc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.2; + version = 129.1.3; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 40fa1c998b..2078795732 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "781b5f10b6030273e745ba3c0b71ff9317d8ade0", - "version" : "129.1.2" + "revision" : "91d9eb23c33ae8c1a6e19314c0349e42eab70326", + "version" : "129.1.3" } }, { @@ -183,7 +183,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 774472e543..1c141e9ae1 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -435,7 +435,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } #endif SubscriptionPurchaseEnvironment.current = .appStore - await AccountManager().checkSubscriptionState() } } #endif @@ -508,18 +507,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func updateSubscriptionStatus() { #if SUBSCRIPTION Task { - guard let token = AccountManager().accessToken else { - return - } - let result = await SubscriptionService.getSubscription(accessToken: token) + let accountManager = AccountManager() - switch result { - case .success(let success): - if success.isActive { + guard let token = accountManager.accessToken else { return } + + if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, + cachePolicy: .reloadIgnoringLocalCacheData) { + if subscription.isActive { DailyPixel.fire(pixel: .privacyProSubscriptionActive) + } else { + accountManager.signOut() } - case .failure: break } + + _ = await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } #endif } diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 429420d8d0..8368fb92e2 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -222,7 +222,7 @@ final class SubscriptionDebugViewController: UITableViewController { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") return } - switch await SubscriptionService.getSubscription(accessToken: token) { + switch await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { case .success(let response): showAlert(title: "Subscription info", message: "\(response)") case .failure(let error): @@ -240,7 +240,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) { + 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) diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index d2205689f2..a2d7eb5989 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -331,7 +331,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } let result = await AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - .hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + .hasEntitlement(for: .networkProtection) switch result { case .success(let hasEntitlement): return .success(hasEntitlement) From 07e7aa7c95287cc120abfdaf82342ec2fcc43f64 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:05:24 -0400 Subject: [PATCH 06/10] Address Ship Review issues with entitlement expiration (#2628) Task/Issue URL: https://app.asana.com/0/1203137811378537/1206896148261286/f Tech Design URL: CC: Description: This PR addresses some Ship Review issues with entitlement expiration not being picked up by adding entitlementsDidChange and accountDidSignOut notification handling Steps to test this PR: Subscribe to PP Remove sub from device VPN should stop and be removed from Settings.app Revoke the entitlements Open another app and go back to the app The app should detect the entitlement change and show messaging & stop the VPN --- DuckDuckGo/MainViewController.swift | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 3f6e04dcb8..3544f62240 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1355,6 +1355,18 @@ class MainViewController: UIViewController { self?.onNetworkProtectionAccountSignIn(notification) } .store(in: &vpnCancellables) + NotificationCenter.default.publisher(for: .entitlementsDidChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + self?.onEntitlementsChange(notification) + } + .store(in: &vpnCancellables) + NotificationCenter.default.publisher(for: .accountDidSignOut) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + self?.onNetworkProtectionAccountSignOut(notification) + } + .store(in: &vpnCancellables) NotificationCenter.default.publisher(for: .vpnEntitlementMessagingDidChange) .receive(on: DispatchQueue.main) @@ -1409,6 +1421,27 @@ class MainViewController: UIViewController { tunnelDefaults.resetEntitlementMessaging() os_log("[NetP Subscription] Reset expired entitlement messaging", log: .networkProtection, type: .info) } + + @objc + private func onEntitlementsChange(_ notification: Notification) { + Task { + guard case .success(false) = await AccountManager().hasEntitlement(for: .networkProtection) else { return } + + tunnelDefaults.enableEntitlementMessaging() + + let controller = NetworkProtectionTunnelController() + await controller.stop() + } + } + + @objc + private func onNetworkProtectionAccountSignOut(_ notification: Notification) { + Task { + let controller = NetworkProtectionTunnelController() + await controller.stop() + await controller.removeVPN() + } + } #endif @objc From 195ab82d424bfb987677323a73da8f183ae478bb Mon Sep 17 00:00:00 2001 From: Juan Manuel Pereira Date: Fri, 22 Mar 2024 18:33:08 -0300 Subject: [PATCH 07/10] Bump BSK to 129.1.4 (#2629) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bbf3a43ffc..21bcdd74e8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.3; + version = 129.1.4; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2078795732..0d68ef32b2 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "91d9eb23c33ae8c1a6e19314c0349e42eab70326", - "version" : "129.1.3" + "revision" : "91b012d9450af211f7c47b9fde811e3a8292b16b", + "version" : "129.1.4" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "edd96481d49b094c260f9a79e078abdbdd3a83fb", - "version" : "5.6.0" + "revision" : "2f44185cca2edefbae7557393a61a23c282abbf8", + "version" : "5.7.0" } }, { From 3d8666d029bd923e0501c2d7d8231989dd1c122e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 23:58:59 +0100 Subject: [PATCH 08/10] Bump submodules/privacy-reference-tests from `6b7ad1e` to `a603ff9` (#2589) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- submodules/privacy-reference-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 6b7ad1e7f1..a603ff9af2 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 +Subproject commit a603ff9af22ca3ff7ce2e7ffbfe18c447d9f23e8 From e8479dd2184963fc9bdcb1ea6b7b3c960545d724 Mon Sep 17 00:00:00 2001 From: Kate Ferrandino <94081185+KateFerrandino@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:05:06 -0700 Subject: [PATCH 09/10] Clarified readme "instruments" section (#2607) Task/Issue URL: https://app.asana.com/0/414235014887631/1206915607562737/f documentation change only normalized capitalization and grammar --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3f88970641..4f81944322 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ We use [SwifLint](https://github.com/realm/SwiftLint) for enforcing Swift style ### Instruments -We have Custom Instruments tool to help visualize and track events that happen during runtime. +We have a Custom Instruments tool to help visualize and track events that happen during runtime. In order to run it: -1. Build a Debug version and install it on Simulator/Device. -2. Select Instruments target and run it on a Mac. A New instance of Instruments app will be run that has a grayed out icon indicating that it works in debug mode with custom instruments attached. -3. Select 'DDG Trace' template or set up a custom one by importing 'DDG Timeline' instrument from Library. +1. Build a debug version and install it on a simulator or device. +2. Select the Instruments target and run it on a Mac. A new instance of the Instruments app will run. It will have a grayed out icon indicating that it works in debug mode with custom instruments attached. +3. Select the 'DDG Trace' template or set up a custom one by importing the 'DDG Timeline' instrument from Library. 4. Start recording. See [Instruments Developer Help](https://help.apple.com/instruments/developer/mac/current/) for reference how to create custom instruments. From 96ceae1aa1c7df120d161afb990f4003fc12f122 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Mon, 25 Mar 2024 00:57:37 -0400 Subject: [PATCH 10/10] Collect extra metadata (#2622) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Feedback/VPNMetadataCollector.swift | 71 ++++++++++++++----- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 21bcdd74e8..b76bb7a78c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.4; + version = 129.1.6; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0d68ef32b2..e0a30aaaa8 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "91b012d9450af211f7c47b9fde811e3a8292b16b", - "version" : "129.1.4" + "branch" : "anh/pp/add-metadata", + "revision" : "0cba47cf651175806bc151366605f8b19802d3ee" } }, { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index a4103cdd00..b858cf4672 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -24,6 +24,7 @@ import Common import NetworkProtection import NetworkExtension import Network +import Subscription struct VPNMetadata: Encodable { @@ -61,11 +62,27 @@ struct VPNMetadata: Encodable { let selectedServer: String } + struct PrivacyProInfo: Encodable { + // swiftlint:disable nesting + enum Source: String, Encodable { + case `internal` + case waitlist + case other + } + // swiftlint:enable nesting + + let enableSource: Source + let betaParticipant: Bool + let hasToken: Bool + let subscriptionActive: Bool + } + let appInfo: AppInfo let deviceInfo: DeviceInfo let networkInfo: NetworkInfo let vpnState: VPNState let vpnSettingsState: VPNSettingsState + let privacyProInfo: PrivacyProInfo func toPrettyPrintedJSON() -> String? { let encoder = JSONEncoder() @@ -99,15 +116,21 @@ protocol VPNMetadataCollector { final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver + private let accessManager: NetworkProtectionAccessController + private let tokenStore: NetworkProtectionTokenStore private let settings: VPNSettings private let defaults: UserDefaults init(statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), + networkProtectionAccessManager: NetworkProtectionAccessController = NetworkProtectionAccessController(), + tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { self.statusObserver = statusObserver self.serverInfoObserver = serverInfoObserver + self.accessManager = networkProtectionAccessManager + self.tokenStore = tokenStore self.settings = settings self.defaults = defaults } @@ -118,13 +141,15 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { let networkInfoMetadata = await collectNetworkInformation() let vpnState = await collectVPNState() let vpnSettingsState = collectVPNSettingsState() + let privacyProInfo = collectPrivacyProInfo() return VPNMetadata( appInfo: appInfoMetadata, deviceInfo: deviceInfoMetadata, networkInfo: networkInfoMetadata, vpnState: vpnState, - vpnSettingsState: vpnSettingsState + vpnSettingsState: vpnSettingsState, + privacyProInfo: privacyProInfo ) } @@ -242,7 +267,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectVPNSettingsState() -> VPNMetadata.VPNSettingsState { - return .init( + .init( connectOnLoginEnabled: settings.connectOnLogin, includeAllNetworksEnabled: settings.includeAllNetworks, enforceRoutesEnabled: settings.enforceRoutes, @@ -251,25 +276,35 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { selectedServer: settings.selectedServer.stringValue ?? "automatic" ) } -} - -extension Network.NWPath { - /// A description that's safe from a privacy standpoint. - /// - /// Ref: https://app.asana.com/0/0/1206712493935053/1206712516729780/f - /// - var anonymousDescription: String { - var description = "NWPath(" - description += "status: \(status), " - - if #available(iOS 14.2, *), case .unsatisfied = status { - description += "unsatisfiedReason: \(unsatisfiedReason), " + func collectPrivacyProInfo() -> VPNMetadata.PrivacyProInfo { + let accessType = accessManager.networkProtectionAccessType() + var hasToken: Bool { + guard let token = try? tokenStore.fetchToken(), + !token.hasPrefix(NetworkProtectionKeychainTokenStore.authTokenPrefix) else { + return false + } + return true } - description += "availableInterfaces: \(availableInterfaces)" - description += ")" + return .init( + enableSource: .init(from: accessManager.networkProtectionAccessType()), + betaParticipant: accessType == .waitlistJoined, + hasToken: hasToken, + subscriptionActive: AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).accessToken != nil + ) + } +} - return description +extension VPNMetadata.PrivacyProInfo.Source { + init(from accessType: NetworkProtectionAccessType) { + switch accessType { + case .inviteCodeInvited: + self = .internal + case .waitlistInvited: + self = .waitlist + default: + self = .other + } } }