Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freemium PIR: Disable and Delete PIR If the Freemium Feature Is Disabled #3200

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions DuckDuckGo/Freemium/PIR/FreemiumPIRFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
private let featureFlagger: FeatureFlagger
private let subscriptionManager: SubscriptionManager
private let accountManager: AccountManager
private var freemiumPIRUserStateManager: FreemiumPIRUserStateManager
private let featureDisabler: DataBrokerProtectionFeatureDisabling

var isAvailable: Bool {
/* Freemium PIR availability criteria:
Expand All @@ -41,18 +43,59 @@
4. (Temp) In experiment cohort
*/
featureFlagger.isFeatureOn(.freemiumPIR) // #1
&& subscriptionManager.isPrivacyProPurchaseAvailable // #2
&& !accountManager.isUserAuthenticated // #3
&& isPotentialPrivacyProSubscriber // #2 & #3
// TODO: - Also check experiment cohort here

Check failure on line 47 in DuckDuckGo/Freemium/PIR/FreemiumPIRFeature.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

TODOs should be resolved (- Also check experiment cohort...) (todo)
}

init(featureFlagger: FeatureFlagger = NSApp.delegateTyped.featureFlagger,
subscriptionManager: SubscriptionManager,
accountManager: AccountManager) {
accountManager: AccountManager,
freemiumPIRUserStateManager: FreemiumPIRUserStateManager,
featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) {

self.featureFlagger = featureFlagger
self.subscriptionManager = subscriptionManager
self.accountManager = accountManager
self.freemiumPIRUserStateManager = freemiumPIRUserStateManager
self.featureDisabler = featureDisabler

offBoardIfNecessary()
}
}

private extension DefaultFreemiumPIRFeature {

/// Returns true if a user is a "potential" Privacy Pro subscriber. This means:
///
/// 1. Is eligible to purchase
/// 2. Is not a current subscriber
var isPotentialPrivacyProSubscriber: Bool {
subscriptionManager.isPrivacyProPurchaseAvailable
&& !accountManager.isUserAuthenticated
}

Check failure on line 76 in DuckDuckGo/Freemium/PIR/FreemiumPIRFeature.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Lines should not have trailing whitespace (trailing_whitespace)
/// Returns true IFF:
///
/// 1. The user did onboard to Freemium PIR
/// 2. The feature flag is disabled
/// 3. The user `isPotentialPrivacyProSubscriber` (see definition)
var shouldDisableAndDelete: Bool {
guard freemiumPIRUserStateManager.didOnboard else { return false }

return !featureFlagger.isFeatureOn(.freemiumPIR)
&& isPotentialPrivacyProSubscriber
}

/// This method offboards a Freemium user if the feature flag was disabled
///
/// Offboarding involves:
/// - Resettting `FreemiumPIRUserStateManager`state
/// - Disabling and deleting PIR data
func offBoardIfNecessary() {
if shouldDisableAndDelete {
freemiumPIRUserStateManager.didOnboard = false
featureDisabler.disableAndDelete()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ final class NavigationBarViewController: NSViewController {
@IBAction func optionsButtonAction(_ sender: NSButton) {
let internalUserDecider = NSApp.delegateTyped.internalUserDecider
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel,
passwordManagerCoordinator: PasswordManagerCoordinator.shared,
vpnFeatureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager),
Expand Down
5 changes: 3 additions & 2 deletions DuckDuckGo/Tab/View/BrowserTabViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,8 @@ final class BrowserTabViewController: NSViewController {
private func homePageViewControllerCreatingIfNeeded() -> HomePageViewController {
return homePageViewController ?? {
let subscriptionManager = Application.appDelegate.subscriptionManager
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
let homePageViewController = HomePageViewController(tabCollectionViewModel: tabCollectionViewModel, bookmarkManager: bookmarkManager,
freemiumPIRFeature: freemiumPIRFeature,
freemiumPIRUserStateManager: freemiumPIRUserStateManager)
Expand All @@ -691,7 +691,8 @@ final class BrowserTabViewController: NSViewController {
private func dataBrokerProtectionHomeViewControllerCreatingIfNeeded() -> DBPHomeViewController {
return dataBrokerProtectionHomeViewController ?? {
let subscriptionManager = Application.appDelegate.subscriptionManager
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
let dataBrokerProtectionHomeViewController = DBPHomeViewController(dataBrokerProtectionManager: DataBrokerProtectionManager.shared, freemiumPIRFeature: freemiumPIRFeature)
self.dataBrokerProtectionHomeViewController = dataBrokerProtectionHomeViewController
return dataBrokerProtectionHomeViewController
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
private var mockProfile: DataBrokerProtectionProfile!
private var mockAgentStopper: MockAgentStopper!
private var mockAuthenticationManager: MockAuthenticationManager!
private var mockFreemiumPIRUserState: MockFreemiumPIRUserState!
private var mockFreemiumPIRUserStateManager: MockFreemiumPIRUserStateManager!

override func setUpWithError() throws {

Expand Down Expand Up @@ -70,7 +70,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
phones: [],
birthYear: 1992)

mockFreemiumPIRUserState = MockFreemiumPIRUserState()
mockFreemiumPIRUserStateManager = MockFreemiumPIRUserStateManager()
}

func testWhenAgentStart_andProfileExists_andUserIsNotFreemium_thenActivityIsScheduled_andScheduledAllOperationsRun() async throws {
Expand All @@ -85,11 +85,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockAuthenticationManager.isUserAuthenticatedValue = true
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

let schedulerStartedExpectation = XCTestExpectation(description: "Scheduler started")
var schedulerStarted = false
Expand Down Expand Up @@ -126,10 +126,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

let schedulerStartedExpectation = XCTestExpectation(description: "Scheduler started")
var schedulerStarted = false
Expand Down Expand Up @@ -161,7 +161,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
entitlementMonitor: DataBrokerProtectionEntitlementMonitor(),
authenticationManager: MockAuthenticationManager(),
pixelHandler: mockPixelHandler,
stopAction: mockStopAction, freemiumPIRUserStateManager: MockFreemiumPIRUserState())
stopAction: mockStopAction, freemiumPIRUserStateManager: MockFreemiumPIRUserStateManager())
sut = DataBrokerProtectionAgentManager(
userNotificationService: mockNotificationService,
activityScheduler: mockActivityScheduler,
Expand All @@ -172,10 +172,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: agentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = nil
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

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

Expand Down Expand Up @@ -207,7 +207,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = nil

Expand Down Expand Up @@ -246,11 +246,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockAuthenticationManager.isUserAuthenticatedValue = true
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

var startScheduledScansCalled = false
mockQueueManager.startScheduledAllOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -276,10 +276,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

var startScheduledScansCalled = false
mockQueueManager.startScheduledScanOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -305,10 +305,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

var startImmediateScansCalled = false
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -334,10 +334,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

var startImmediateScansCalled = false
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -363,7 +363,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()

Expand All @@ -386,7 +386,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()

Expand All @@ -409,7 +409,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockQueueManager.startImmediateScanOperationsIfPermittedCompletionError = DataBrokerProtectionAgentErrorCollection(oneTimeError: NSError(domain: "test", code: 10))
Expand All @@ -433,7 +433,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockDataManager.shouldReturnHasMatches = true
Expand All @@ -457,7 +457,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockDataManager.shouldReturnHasMatches = false
Expand All @@ -481,10 +481,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockAuthenticationManager.isUserAuthenticatedValue = true
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

var startScheduledScansCalled = false
mockQueueManager.startScheduledAllOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -510,9 +510,9 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

var startScheduledScansCalled = false
mockQueueManager.startScheduledScanOperationsIfPermittedCalledCompletion = { _ in
Expand Down
Loading
Loading