Skip to content

Commit

Permalink
Use cookie to share subscription access token on DDG domains (#3488)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1108686900785972/1208264562025859/f

**Description**:
Store and keep in sync the subscription access token for the
duckduckgo.com domain cookie. For the implementation guidelines please
see description of the linked task.
  • Loading branch information
miasma13 authored Oct 28, 2024
1 parent dc67e00 commit 41cf8e9
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 19 deletions.
10 changes: 10 additions & 0 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,11 @@ extension Pixel {
case privacyProTransactionProgressNotHiddenAfter60s
case privacyProSuccessfulSubscriptionAttribution
case privacyProKeychainAccessError
case privacyProSubscriptionCookieMissingTokenOnSignIn
case privacyProSubscriptionCookieMissingCookieOnSignOut
case privacyProSubscriptionCookieRefreshedWithUpdate
case privacyProSubscriptionCookieRefreshedWithDelete
case privacyProSubscriptionCookieFailedToSetSubscriptionCookie

// MARK: Pixel Experiment
case pixelExperimentEnrollment
Expand Down Expand Up @@ -1520,6 +1525,11 @@ extension Pixel.Event {
case .privacyProTransactionProgressNotHiddenAfter60s: return "m_privacy-pro_progress_not_hidden_after_60s"
case .privacyProSuccessfulSubscriptionAttribution: return "m_subscribe"
case .privacyProKeychainAccessError: return "m_privacy-pro_keychain_access_error"
case .privacyProSubscriptionCookieMissingTokenOnSignIn: return "m_privacy-pro_subscription-cookie-missing_token_on_sign_in"
case .privacyProSubscriptionCookieMissingCookieOnSignOut: return "m_privacy-pro_subscription-cookie-missing_cookie_on_sign_out"
case .privacyProSubscriptionCookieRefreshedWithUpdate: return "m_privacy-pro_subscription-cookie-refreshed_with_update"
case .privacyProSubscriptionCookieRefreshedWithDelete: return "m_privacy-pro_subscription-cookie-refreshed_with_delete"
case .privacyProSubscriptionCookieFailedToSetSubscriptionCookie: return "m_privacy-pro_subscription-cookie-failed_to_set_subscription_cookie"

// MARK: Pixel Experiment
case .pixelExperimentEnrollment: return "pixel_experiment_enrollment"
Expand Down
6 changes: 5 additions & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
1E1D8B6C29953CE300C96994 /* autoconsent-test-page-banner.html in Resources */ = {isa = PBXBuildFile; fileRef = 1E1D8B6929953CE300C96994 /* autoconsent-test-page-banner.html */; };
1E24295E293F57FA00584836 /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E24295D293F57FA00584836 /* LottieView.swift */; };
1E242960293F585300584836 /* cookie-icon-animated-40-light.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E24295F293F585300584836 /* cookie-icon-animated-40-light.json */; };
1E39BEB02CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */; };
1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4527B6A33600961E25 /* DownloadsListViewModel.swift */; };
1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4727B6A35400961E25 /* DownloadsListModel.swift */; };
1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4927B6A38000961E25 /* DownloadListRepresentable.swift */; };
Expand Down Expand Up @@ -1378,6 +1379,7 @@
1E24295D293F57FA00584836 /* LottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieView.swift; sourceTree = "<group>"; };
1E24295F293F585300584836 /* cookie-icon-animated-40-light.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-icon-animated-40-light.json"; sourceTree = "<group>"; };
1E25D5312C92126B004400F0 /* SubscriptionFeatureAvailabilityMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFeatureAvailabilityMock.swift; sourceTree = "<group>"; };
1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionCookieManageEventPixelMapping.swift; sourceTree = "<group>"; };
1E4DCF4527B6A33600961E25 /* DownloadsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListViewModel.swift; sourceTree = "<group>"; };
1E4DCF4727B6A35400961E25 /* DownloadsListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListModel.swift; sourceTree = "<group>"; };
1E4DCF4927B6A38000961E25 /* DownloadListRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadListRepresentable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5369,6 +5371,7 @@
D664C7962B289AA000CBFA76 /* Extensions */,
D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */,
BDE219E52C406D19005D5884 /* PrivacyProDataReporting.swift */,
1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */,
);
path = Subscription;
sourceTree = "<group>";
Expand Down Expand Up @@ -7576,6 +7579,7 @@
C1641EAF2BC2F5140012607A /* ImportPasswordsViewController.swift in Sources */,
D63FF8982C1B6A45006DE24D /* DuckPlayer.swift in Sources */,
85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */,
1E39BEB02CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift in Sources */,
C1935A102C88D131001AD72D /* AutofillSurveyManager.swift in Sources */,
7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */,
98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */,
Expand Down Expand Up @@ -11006,7 +11010,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 200.3.0;
version = 201.0.0;
};
};
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "fdf6f75d570a5ef6058efa881e11f9467627fbf4",
"version" : "200.2.1"
"revision" : "e5946eee6af859690cc1cc5e51daef3c8368981b",
"version" : "201.0.0"
}
},
{
"identity" : "content-scope-scripts",
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/content-scope-scripts",
"state" : {
"revision" : "1ed569676555d493c9c5575eaed22aa02569aac9",
"version" : "6.19.0"
"revision" : "b74549bd869fdecc16fad851f2f608b1724764df",
"version" : "6.25.0"
}
},
{
Expand Down
21 changes: 19 additions & 2 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import os.log
private var autofillUsageMonitor = AutofillUsageMonitor()

private(set) var subscriptionFeatureAvailability: SubscriptionFeatureAvailability!
private var subscriptionCookieManager: SubscriptionCookieManaging!
var privacyProDataReporter: PrivacyProDataReporting!

// MARK: - Feature specific app event handlers
Expand Down Expand Up @@ -122,7 +123,7 @@ import os.log
}
}

// swiftlint:disable:next function_body_length
// swiftlint:disable:next function_body_length cyclomatic_complexity
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

#if targetEnvironment(simulator)
Expand Down Expand Up @@ -312,6 +313,17 @@ import os.log
subscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability(
privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager,
purchasePlatform: .appStore)

subscriptionCookieManager = SubscriptionCookieManager(subscriptionManager: AppDependencyProvider.shared.subscriptionManager,
currentCookieStore: { [weak self] in
guard self?.mainViewController?.tabManager.model.hasActiveTabs ?? false else {
// We shouldn't interact with WebKit's cookie store unless we have a WebView,
// eventually the subscription cookie will be refreshed on opening the first tab
return nil
}

return WKWebsiteDataStore.current().httpCookieStore
}, eventMapping: SubscriptionCookieManageEventPixelMapping())

homePageConfiguration = HomePageConfiguration(variantManager: AppDependencyProvider.shared.variantManager,
remoteMessagingClient: remoteMessagingClient,
Expand Down Expand Up @@ -350,7 +362,8 @@ import os.log
contextualOnboardingLogic: daxDialogs,
contextualOnboardingPixelReporter: onboardingPixelReporter,
subscriptionFeatureAvailability: subscriptionFeatureAvailability,
voiceSearchHelper: voiceSearchHelper)
voiceSearchHelper: voiceSearchHelper,
subscriptionCookieManager: subscriptionCookieManager)

main.loadViewIfNeeded()
syncErrorHandler.alertPresenter = main
Expand Down Expand Up @@ -574,6 +587,10 @@ import os.log
}
}

Task { @MainActor in
await subscriptionCookieManager.refreshSubscriptionCookie()
}

let importPasswordsStatusHandler = ImportPasswordsStatusHandler(syncService: syncService)
importPasswordsStatusHandler.checkSyncSuccessStatus()

Expand Down
8 changes: 6 additions & 2 deletions DuckDuckGo/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class MainViewController: UIViewController {
private var feedbackCancellable: AnyCancellable?

let subscriptionFeatureAvailability: SubscriptionFeatureAvailability
private let subscriptionCookieManager: SubscriptionCookieManaging
let privacyProDataReporter: PrivacyProDataReporting

private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger
Expand Down Expand Up @@ -195,7 +196,8 @@ class MainViewController: UIViewController {
tutorialSettings: TutorialSettings = DefaultTutorialSettings(),
statisticsStore: StatisticsStore = StatisticsUserDefaults(),
subscriptionFeatureAvailability: SubscriptionFeatureAvailability,
voiceSearchHelper: VoiceSearchHelperProtocol
voiceSearchHelper: VoiceSearchHelperProtocol,
subscriptionCookieManager: SubscriptionCookieManaging
) {
self.bookmarksDatabase = bookmarksDatabase
self.bookmarksDatabaseCleaner = bookmarksDatabaseCleaner
Expand All @@ -217,7 +219,8 @@ class MainViewController: UIViewController {
privacyProDataReporter: privacyProDataReporter,
contextualOnboardingPresenter: contextualOnboardingPresenter,
contextualOnboardingLogic: contextualOnboardingLogic,
onboardingPixelReporter: contextualOnboardingPixelReporter)
onboardingPixelReporter: contextualOnboardingPixelReporter,
subscriptionCookieManager: subscriptionCookieManager)
self.syncPausedStateManager = syncPausedStateManager
self.privacyProDataReporter = privacyProDataReporter
self.homeTabManager = NewTabPageManager()
Expand All @@ -228,6 +231,7 @@ class MainViewController: UIViewController {
self.statisticsStore = statisticsStore
self.subscriptionFeatureAvailability = subscriptionFeatureAvailability
self.voiceSearchHelper = voiceSearchHelper
self.subscriptionCookieManager = subscriptionCookieManager

super.init(nibName: nil, bundle: nil)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// SubscriptionCookieManageEventPixelMapping.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 Common
import Core
import Subscription

public final class SubscriptionCookieManageEventPixelMapping: EventMapping<SubscriptionCookieManagerEvent> {

public init() {
super.init { event, _, _, _ in
let pixel: Pixel.Event = {
switch event {
case .errorHandlingAccountDidSignInTokenIsMissing:
return .privacyProSubscriptionCookieMissingTokenOnSignIn
case .errorHandlingAccountDidSignOutCookieIsMissing:
return .privacyProSubscriptionCookieMissingCookieOnSignOut
case .subscriptionCookieRefreshedWithUpdate:
return .privacyProSubscriptionCookieRefreshedWithUpdate
case .subscriptionCookieRefreshedWithDelete:
return .privacyProSubscriptionCookieRefreshedWithDelete
case .failedToSetSubscriptionCookie:
return .privacyProSubscriptionCookieFailedToSetSubscriptionCookie
}
}()

Pixel.fire(pixel: pixel)

}
}

override init(mapping: @escaping EventMapping<SubscriptionCookieManagerEvent>.Mapping) {
fatalError("Use init()")
}
}
12 changes: 9 additions & 3 deletions DuckDuckGo/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import WebKit
import BrowserServicesKit
import Persistence
import History
import Subscription
import os.log

class TabManager {
Expand All @@ -41,6 +42,7 @@ class TabManager {
private let contextualOnboardingPresenter: ContextualOnboardingPresenting
private let contextualOnboardingLogic: ContextualOnboardingLogic
private let onboardingPixelReporter: OnboardingPixelReporting
private let subscriptionCookieManager: SubscriptionCookieManaging

weak var delegate: TabDelegate?

Expand All @@ -57,7 +59,8 @@ class TabManager {
privacyProDataReporter: PrivacyProDataReporting,
contextualOnboardingPresenter: ContextualOnboardingPresenting,
contextualOnboardingLogic: ContextualOnboardingLogic,
onboardingPixelReporter: OnboardingPixelReporting) {
onboardingPixelReporter: OnboardingPixelReporting,
subscriptionCookieManager: SubscriptionCookieManaging) {
self.model = model
self.previewsSource = previewsSource
self.bookmarksDatabase = bookmarksDatabase
Expand All @@ -68,6 +71,7 @@ class TabManager {
self.contextualOnboardingPresenter = contextualOnboardingPresenter
self.contextualOnboardingLogic = contextualOnboardingLogic
self.onboardingPixelReporter = onboardingPixelReporter
self.subscriptionCookieManager = subscriptionCookieManager
registerForNotifications()
}

Expand All @@ -89,7 +93,8 @@ class TabManager {
contextualOnboardingPresenter: contextualOnboardingPresenter,
contextualOnboardingLogic: contextualOnboardingLogic,
onboardingPixelReporter: onboardingPixelReporter,
featureFlagger: AppDependencyProvider.shared.featureFlagger)
featureFlagger: AppDependencyProvider.shared.featureFlagger,
subscriptionCookieManager: subscriptionCookieManager)
controller.applyInheritedAttribution(inheritedAttribution)
controller.attachWebView(configuration: configuration,
andLoadRequest: url == nil ? nil : URLRequest.userInitiated(url!),
Expand Down Expand Up @@ -167,7 +172,8 @@ class TabManager {
contextualOnboardingPresenter: contextualOnboardingPresenter,
contextualOnboardingLogic: contextualOnboardingLogic,
onboardingPixelReporter: onboardingPixelReporter,
featureFlagger: AppDependencyProvider.shared.featureFlagger)
featureFlagger: AppDependencyProvider.shared.featureFlagger,
subscriptionCookieManager: subscriptionCookieManager)
controller.attachWebView(configuration: configCopy,
andLoadRequest: request,
consumeCookies: !model.hasActiveTabs,
Expand Down
14 changes: 11 additions & 3 deletions DuckDuckGo/TabViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import NetworkProtection
import Onboarding
import os.log
import Navigation
import Subscription

class TabViewController: UIViewController {

Expand Down Expand Up @@ -92,6 +93,7 @@ class TabViewController: UIViewController {
let appSettings: AppSettings

var featureFlagger: FeatureFlagger
let subscriptionCookieManager: SubscriptionCookieManaging
private lazy var internalUserDecider = AppDependencyProvider.shared.internalUserDecider

private lazy var autofillNeverPromptWebsitesManager = AppDependencyProvider.shared.autofillNeverPromptWebsitesManager
Expand Down Expand Up @@ -321,7 +323,8 @@ class TabViewController: UIViewController {
contextualOnboardingLogic: ContextualOnboardingLogic,
onboardingPixelReporter: OnboardingCustomInteractionPixelReporting,
urlCredentialCreator: URLCredentialCreating = URLCredentialCreator(),
featureFlagger: FeatureFlagger) -> TabViewController {
featureFlagger: FeatureFlagger,
subscriptionCookieManager: SubscriptionCookieManaging) -> TabViewController {
let storyboard = UIStoryboard(name: "Tab", bundle: nil)
let controller = storyboard.instantiateViewController(identifier: "TabViewController", creator: { coder in
TabViewController(coder: coder,
Expand All @@ -336,7 +339,8 @@ class TabViewController: UIViewController {
contextualOnboardingLogic: contextualOnboardingLogic,
onboardingPixelReporter: onboardingPixelReporter,
urlCredentialCreator: urlCredentialCreator,
featureFlagger: featureFlagger
featureFlagger: featureFlagger,
subscriptionCookieManager: subscriptionCookieManager
)
})
return controller
Expand Down Expand Up @@ -369,7 +373,8 @@ class TabViewController: UIViewController {
contextualOnboardingLogic: ContextualOnboardingLogic,
onboardingPixelReporter: OnboardingCustomInteractionPixelReporting,
urlCredentialCreator: URLCredentialCreating = URLCredentialCreator(),
featureFlagger: FeatureFlagger) {
featureFlagger: FeatureFlagger,
subscriptionCookieManager: SubscriptionCookieManaging) {
self.tabModel = tabModel
self.appSettings = appSettings
self.bookmarksDatabase = bookmarksDatabase
Expand All @@ -388,6 +393,7 @@ class TabViewController: UIViewController {
self.onboardingPixelReporter = onboardingPixelReporter
self.urlCredentialCreator = urlCredentialCreator
self.featureFlagger = featureFlagger
self.subscriptionCookieManager = subscriptionCookieManager
super.init(coder: aDecoder)
}

Expand Down Expand Up @@ -636,6 +642,8 @@ class TabViewController: UIViewController {
await webView.configuration.websiteDataStore.dataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes())
let cookieStore = webView.configuration.websiteDataStore.httpCookieStore
await WebCacheManager.shared.consumeCookies(httpCookieStore: cookieStore)
subscriptionCookieManager.resetLastRefreshDate()
await subscriptionCookieManager.refreshSubscriptionCookie()
doLoad()
}
}
Expand Down
Loading

0 comments on commit 41cf8e9

Please sign in to comment.