Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit fe9d1bb

Browse files
committed
Freemium PIR: Disable and Delete PIR If the Freemium Feature Is Disabled (#3200)
Task/Issue URL: https://app.asana.com/0/0/1208081037247881/f **Description**: This PR implements disabling of PIR and deletion of related data when: 1. The user had onboarded to Freemium AND 2. The feature flag is disabled
1 parent 9338127 commit fe9d1bb

File tree

10 files changed

+257
-119
lines changed

10 files changed

+257
-119
lines changed

DuckDuckGo/Freemium/PIR/FreemiumPIRFeature.swift

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ final class DefaultFreemiumPIRFeature: FreemiumPIRFeature {
3232
private let featureFlagger: FeatureFlagger
3333
private let subscriptionManager: SubscriptionManager
3434
private let accountManager: AccountManager
35+
private var freemiumPIRUserStateManager: FreemiumPIRUserStateManager
36+
private let featureDisabler: DataBrokerProtectionFeatureDisabling
3537

3638
var isAvailable: Bool {
3739
/* Freemium PIR availability criteria:
@@ -41,18 +43,59 @@ final class DefaultFreemiumPIRFeature: FreemiumPIRFeature {
4143
4. (Temp) In experiment cohort
4244
*/
4345
featureFlagger.isFeatureOn(.freemiumPIR) // #1
44-
&& subscriptionManager.isPrivacyProPurchaseAvailable // #2
45-
&& !accountManager.isUserAuthenticated // #3
46+
&& isPotentialPrivacyProSubscriber // #2 & #3
4647
// TODO: - Also check experiment cohort here
4748
}
4849

4950
init(featureFlagger: FeatureFlagger = NSApp.delegateTyped.featureFlagger,
5051
subscriptionManager: SubscriptionManager,
51-
accountManager: AccountManager) {
52+
accountManager: AccountManager,
53+
freemiumPIRUserStateManager: FreemiumPIRUserStateManager,
54+
featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) {
5255

5356
self.featureFlagger = featureFlagger
5457
self.subscriptionManager = subscriptionManager
5558
self.accountManager = accountManager
59+
self.freemiumPIRUserStateManager = freemiumPIRUserStateManager
60+
self.featureDisabler = featureDisabler
61+
62+
offBoardIfNecessary()
63+
}
64+
}
65+
66+
private extension DefaultFreemiumPIRFeature {
67+
68+
/// Returns true if a user is a "potential" Privacy Pro subscriber. This means:
69+
///
70+
/// 1. Is eligible to purchase
71+
/// 2. Is not a current subscriber
72+
var isPotentialPrivacyProSubscriber: Bool {
73+
subscriptionManager.isPrivacyProPurchaseAvailable
74+
&& !accountManager.isUserAuthenticated
75+
}
76+
77+
/// Returns true IFF:
78+
///
79+
/// 1. The user did onboard to Freemium PIR
80+
/// 2. The feature flag is disabled
81+
/// 3. The user `isPotentialPrivacyProSubscriber` (see definition)
82+
var shouldDisableAndDelete: Bool {
83+
guard freemiumPIRUserStateManager.didOnboard else { return false }
84+
85+
return !featureFlagger.isFeatureOn(.freemiumPIR)
86+
&& isPotentialPrivacyProSubscriber
87+
}
88+
89+
/// This method offboards a Freemium user if the feature flag was disabled
90+
///
91+
/// Offboarding involves:
92+
/// - Resettting `FreemiumPIRUserStateManager`state
93+
/// - Disabling and deleting PIR data
94+
func offBoardIfNecessary() {
95+
if shouldDisableAndDelete {
96+
freemiumPIRUserStateManager.didOnboard = false
97+
featureDisabler.disableAndDelete()
98+
}
5699
}
57100
}
58101

DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ final class NavigationBarViewController: NSViewController {
287287
@IBAction func optionsButtonAction(_ sender: NSButton) {
288288
let internalUserDecider = NSApp.delegateTyped.internalUserDecider
289289
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
290-
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
290+
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
291291
let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel,
292292
passwordManagerCoordinator: PasswordManagerCoordinator.shared,
293293
vpnFeatureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager),

DuckDuckGo/Tab/View/BrowserTabViewController.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,8 +790,8 @@ final class BrowserTabViewController: NSViewController {
790790
private func homePageViewControllerCreatingIfNeeded() -> HomePageViewController {
791791
return homePageViewController ?? {
792792
let subscriptionManager = Application.appDelegate.subscriptionManager
793-
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
794793
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
794+
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
795795
let homePageViewController = HomePageViewController(tabCollectionViewModel: tabCollectionViewModel, bookmarkManager: bookmarkManager,
796796
freemiumPIRFeature: freemiumPIRFeature,
797797
freemiumPIRUserStateManager: freemiumPIRUserStateManager)
@@ -806,7 +806,8 @@ final class BrowserTabViewController: NSViewController {
806806
private func dataBrokerProtectionHomeViewControllerCreatingIfNeeded() -> DBPHomeViewController {
807807
return dataBrokerProtectionHomeViewController ?? {
808808
let subscriptionManager = Application.appDelegate.subscriptionManager
809-
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
809+
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
810+
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
810811
let dataBrokerProtectionHomeViewController = DBPHomeViewController(dataBrokerProtectionManager: DataBrokerProtectionManager.shared, freemiumPIRFeature: freemiumPIRFeature)
811812
self.dataBrokerProtectionHomeViewController = dataBrokerProtectionHomeViewController
812813
return dataBrokerProtectionHomeViewController

LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionAgentManagerTests.swift

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
3737
private var mockConfigurationManager: MockConfigurationManager!
3838
private var mockPrivacyConfigurationManager: DBPPrivacyConfigurationManager!
3939
private var mockAuthenticationManager: MockAuthenticationManager!
40-
private var mockFreemiumPIRUserState: MockFreemiumPIRUserState!
40+
private var mockFreemiumPIRUserStateManager: MockFreemiumPIRUserStateManager!
4141

4242
override func setUpWithError() throws {
4343

@@ -76,7 +76,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
7676
phones: [],
7777
birthYear: 1992)
7878

79-
mockFreemiumPIRUserState = MockFreemiumPIRUserState()
79+
mockFreemiumPIRUserStateManager = MockFreemiumPIRUserStateManager()
8080
}
8181

8282
func testWhenAgentStart_andProfileExists_andUserIsNotFreemium_thenActivityIsScheduled_andScheduledAllOperationsRun() async throws {
@@ -93,11 +93,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
9393
configurationManager: mockConfigurationManager,
9494
privacyConfigurationManager: mockPrivacyConfigurationManager,
9595
authenticationManager: mockAuthenticationManager,
96-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
96+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
9797

9898
mockDataManager.profileToReturn = mockProfile
9999
mockAuthenticationManager.isUserAuthenticatedValue = true
100-
mockFreemiumPIRUserState.didOnboard = false
100+
mockFreemiumPIRUserStateManager.didOnboard = false
101101

102102
let schedulerStartedExpectation = XCTestExpectation(description: "Scheduler started")
103103
var schedulerStarted = false
@@ -136,10 +136,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
136136
configurationManager: mockConfigurationManager,
137137
privacyConfigurationManager: mockPrivacyConfigurationManager,
138138
authenticationManager: mockAuthenticationManager,
139-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
139+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
140140

141141
mockDataManager.profileToReturn = mockProfile
142-
mockFreemiumPIRUserState.didOnboard = true
142+
mockFreemiumPIRUserStateManager.didOnboard = true
143143

144144
let schedulerStartedExpectation = XCTestExpectation(description: "Scheduler started")
145145
var schedulerStarted = false
@@ -171,7 +171,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
171171
entitlementMonitor: DataBrokerProtectionEntitlementMonitor(),
172172
authenticationManager: MockAuthenticationManager(),
173173
pixelHandler: mockPixelHandler,
174-
stopAction: mockStopAction, freemiumPIRUserStateManager: MockFreemiumPIRUserState())
174+
stopAction: mockStopAction, freemiumPIRUserStateManager: MockFreemiumPIRUserStateManager())
175175
sut = DataBrokerProtectionAgentManager(
176176
userNotificationService: mockNotificationService,
177177
activityScheduler: mockActivityScheduler,
@@ -184,10 +184,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
184184
configurationManager: mockConfigurationManager,
185185
privacyConfigurationManager: mockPrivacyConfigurationManager,
186186
authenticationManager: mockAuthenticationManager,
187-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
187+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
188188

189189
mockDataManager.profileToReturn = nil
190-
mockFreemiumPIRUserState.didOnboard = true
190+
mockFreemiumPIRUserStateManager.didOnboard = true
191191

192192
let stopAgentExpectation = XCTestExpectation(description: "Stop agent expectation")
193193

@@ -221,7 +221,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
221221
configurationManager: mockConfigurationManager,
222222
privacyConfigurationManager: mockPrivacyConfigurationManager,
223223
authenticationManager: mockAuthenticationManager,
224-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
224+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
225225

226226
mockDataManager.profileToReturn = nil
227227

@@ -262,11 +262,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
262262
configurationManager: mockConfigurationManager,
263263
privacyConfigurationManager: mockPrivacyConfigurationManager,
264264
authenticationManager: mockAuthenticationManager,
265-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
265+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
266266

267267
mockDataManager.profileToReturn = mockProfile
268268
mockAuthenticationManager.isUserAuthenticatedValue = true
269-
mockFreemiumPIRUserState.didOnboard = false
269+
mockFreemiumPIRUserStateManager.didOnboard = false
270270

271271
var startScheduledScansCalled = false
272272
mockQueueManager.startScheduledAllOperationsIfPermittedCalledCompletion = { _ in
@@ -294,10 +294,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
294294
configurationManager: mockConfigurationManager,
295295
privacyConfigurationManager: mockPrivacyConfigurationManager,
296296
authenticationManager: mockAuthenticationManager,
297-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
297+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
298298

299299
mockDataManager.profileToReturn = mockProfile
300-
mockFreemiumPIRUserState.didOnboard = true
300+
mockFreemiumPIRUserStateManager.didOnboard = true
301301

302302
var startScheduledScansCalled = false
303303
mockQueueManager.startScheduledScanOperationsIfPermittedCalledCompletion = { _ in
@@ -325,10 +325,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
325325
configurationManager: mockConfigurationManager,
326326
privacyConfigurationManager: mockPrivacyConfigurationManager,
327327
authenticationManager: mockAuthenticationManager,
328-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
328+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
329329

330330
mockDataManager.profileToReturn = mockProfile
331-
mockFreemiumPIRUserState.didOnboard = false
331+
mockFreemiumPIRUserStateManager.didOnboard = false
332332

333333
var startImmediateScansCalled = false
334334
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
@@ -356,10 +356,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
356356
configurationManager: mockConfigurationManager,
357357
privacyConfigurationManager: mockPrivacyConfigurationManager,
358358
authenticationManager: mockAuthenticationManager,
359-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
359+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
360360

361361
mockDataManager.profileToReturn = mockProfile
362-
mockFreemiumPIRUserState.didOnboard = true
362+
mockFreemiumPIRUserStateManager.didOnboard = true
363363

364364
var startImmediateScansCalled = false
365365
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
@@ -387,7 +387,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
387387
configurationManager: mockConfigurationManager,
388388
privacyConfigurationManager: mockPrivacyConfigurationManager,
389389
authenticationManager: mockAuthenticationManager,
390-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
390+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
391391

392392
mockNotificationService.reset()
393393

@@ -412,7 +412,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
412412
configurationManager: mockConfigurationManager,
413413
privacyConfigurationManager: mockPrivacyConfigurationManager,
414414
authenticationManager: mockAuthenticationManager,
415-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
415+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
416416

417417
mockNotificationService.reset()
418418

@@ -437,7 +437,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
437437
configurationManager: mockConfigurationManager,
438438
privacyConfigurationManager: mockPrivacyConfigurationManager,
439439
authenticationManager: mockAuthenticationManager,
440-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
440+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
441441

442442
mockNotificationService.reset()
443443
mockQueueManager.startImmediateScanOperationsIfPermittedCompletionError = DataBrokerProtectionAgentErrorCollection(oneTimeError: NSError(domain: "test", code: 10))
@@ -463,7 +463,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
463463
configurationManager: mockConfigurationManager,
464464
privacyConfigurationManager: mockPrivacyConfigurationManager,
465465
authenticationManager: mockAuthenticationManager,
466-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
466+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
467467

468468
mockNotificationService.reset()
469469
mockDataManager.shouldReturnHasMatches = true
@@ -489,7 +489,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
489489
configurationManager: mockConfigurationManager,
490490
privacyConfigurationManager: mockPrivacyConfigurationManager,
491491
authenticationManager: mockAuthenticationManager,
492-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
492+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
493493

494494
mockNotificationService.reset()
495495
mockDataManager.shouldReturnHasMatches = false
@@ -515,10 +515,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
515515
configurationManager: mockConfigurationManager,
516516
privacyConfigurationManager: mockPrivacyConfigurationManager,
517517
authenticationManager: mockAuthenticationManager,
518-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
518+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
519519

520520
mockAuthenticationManager.isUserAuthenticatedValue = true
521-
mockFreemiumPIRUserState.didOnboard = false
521+
mockFreemiumPIRUserStateManager.didOnboard = false
522522

523523
var startScheduledScansCalled = false
524524
mockQueueManager.startScheduledAllOperationsIfPermittedCalledCompletion = { _ in
@@ -546,9 +546,9 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
546546
configurationManager: mockConfigurationManager,
547547
privacyConfigurationManager: mockPrivacyConfigurationManager,
548548
authenticationManager: mockAuthenticationManager,
549-
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
549+
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)
550550

551-
mockFreemiumPIRUserState.didOnboard = true
551+
mockFreemiumPIRUserStateManager.didOnboard = true
552552

553553
var startScheduledScansCalled = false
554554
mockQueueManager.startScheduledScanOperationsIfPermittedCalledCompletion = { _ in

0 commit comments

Comments
 (0)