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: Prerequisite Checks and Scheduled Scans #3178

Merged
6 changes: 6 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2571,6 +2571,8 @@
BDADBDCD2BD2BC5700421B9B /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC72BBC821100EF3735 /* vpn-light-mode.json */; };
BDE981D92BBD10D600645880 /* vpn-dark-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC82BBC821100EF3735 /* vpn-dark-mode.json */; };
BDE981DA2BBD10D600645880 /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC72BBC821100EF3735 /* vpn-light-mode.json */; };
C126B35A2C820924005DC2A3 /* FreemiumDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C126B3592C820924005DC2A3 /* FreemiumDebugMenu.swift */; };
C126B35B2C820924005DC2A3 /* FreemiumDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C126B3592C820924005DC2A3 /* FreemiumDebugMenu.swift */; };
C1372EF42BBC5BAD003F8793 /* SecureTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1372EF32BBC5BAD003F8793 /* SecureTextField.swift */; };
C1372EF52BBC5BAD003F8793 /* SecureTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1372EF32BBC5BAD003F8793 /* SecureTextField.swift */; };
C13909EF2B85FD4E001626ED /* AutofillActionExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13909EE2B85FD4E001626ED /* AutofillActionExecutor.swift */; };
Expand Down Expand Up @@ -4261,6 +4263,7 @@
BDA7647B2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultVPNLocationFormatter.swift; sourceTree = "<group>"; };
BDA7648C2BC4E4EF00D0400C /* DefaultVPNLocationFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultVPNLocationFormatterTests.swift; sourceTree = "<group>"; };
BDA764902BC4E57200D0400C /* MockVPNLocationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockVPNLocationFormatter.swift; sourceTree = "<group>"; };
C126B3592C820924005DC2A3 /* FreemiumDebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreemiumDebugMenu.swift; sourceTree = "<group>"; };
C1372EF32BBC5BAD003F8793 /* SecureTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureTextField.swift; sourceTree = "<group>"; };
C13909EE2B85FD4E001626ED /* AutofillActionExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillActionExecutor.swift; sourceTree = "<group>"; };
C13909F32B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillDeleteAllPasswordsExecutorTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8533,6 +8536,7 @@
isa = PBXGroup;
children = (
C1858CD02C7C95E500C9BEAB /* PIR */,
C126B3592C820924005DC2A3 /* FreemiumDebugMenu.swift */,
);
path = Freemium;
sourceTree = "<group>";
Expand Down Expand Up @@ -10207,6 +10211,7 @@
3706FB2C293F65D500E42796 /* UserDefaultsWrapper.swift in Sources */,
3706FB2D293F65D500E42796 /* PasswordManagementPopover.swift in Sources */,
3706FB2F293F65D500E42796 /* HomePageRecentlyVisitedModel.swift in Sources */,
C126B35B2C820924005DC2A3 /* FreemiumDebugMenu.swift in Sources */,
3707C718294B5D0F00682A9F /* AdClickAttributionTabExtension.swift in Sources */,
31EF1E812B63FFB800E6DB17 /* DataBrokerProtectionManager.swift in Sources */,
3706FEBA293F6EFF00E42796 /* BWStatus.swift in Sources */,
Expand Down Expand Up @@ -12150,6 +12155,7 @@
B6C00ECD292F89D9009C73A6 /* FindInPageTabExtension.swift in Sources */,
85589E8327BBB8630038AD11 /* HomePageViewController.swift in Sources */,
5614B3A12BBD639D009B5031 /* ZoomPopover.swift in Sources */,
C126B35A2C820924005DC2A3 /* FreemiumDebugMenu.swift in Sources */,
B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */,
4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */,
1D36F4242A3B85C50052B527 /* TabCleanupPreparer.swift in Sources */,
Expand Down
18 changes: 14 additions & 4 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import Subscription
import NetworkProtectionIPC
import DataBrokerProtection
import RemoteMessaging
import Freemium

final class AppDelegate: NSObject, NSApplicationDelegate {

Expand Down Expand Up @@ -375,7 +376,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
UNUserNotificationCenter.current().delegate = self

dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents()
DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: subscriptionManager.accountManager)).applicationDidFinishLaunching()

let freemiumPIRUserState = DefaultFreemiumPIRUserState(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)
let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(accountManager:
subscriptionManager.accountManager,
freemiumPIRUserState: freemiumPIRUserState)

DataBrokerProtectionAppEvents(featureGatekeeper: pirGatekeeper).applicationDidFinishLaunching()

setUpAutoClearHandler()

Expand Down Expand Up @@ -422,9 +429,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

NetworkProtectionAppEvents(featureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager)).applicationDidBecomeActive()

DataBrokerProtectionAppEvents(featureGatekeeper:
DefaultDataBrokerProtectionFeatureGatekeeper(accountManager:
subscriptionManager.accountManager)).applicationDidBecomeActive()
let freemiumPIRUserState = DefaultFreemiumPIRUserState(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)
let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(accountManager:
subscriptionManager.accountManager,
freemiumPIRUserState: freemiumPIRUserState)

DataBrokerProtectionAppEvents(featureGatekeeper: pirGatekeeper).applicationDidBecomeActive()

subscriptionManager.refreshCachedSubscriptionAndEntitlements { isSubscriptionActive in
if isSubscriptionActive {
Expand Down
31 changes: 14 additions & 17 deletions DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ import BrowserServicesKit
import Common
import DataBrokerProtection
import Subscription
import Freemium

protocol DataBrokerProtectionFeatureGatekeeper {
func isFeatureVisible() -> Bool
func disableAndDeleteForAllUsers()
func isPrivacyProEnabled() -> Bool
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Methods unused, removing.

func arePrerequisitesSatisfied() async -> Bool
}

Expand All @@ -36,19 +35,22 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature
private let userDefaults: UserDefaults
private let subscriptionAvailability: SubscriptionFeatureAvailability
private let accountManager: AccountManager
private let freemiumPIRUserState: FreemiumPIRUserState

init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager,
featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler(),
pixelHandler: EventMapping<DataBrokerProtectionPixels> = DataBrokerProtectionPixelsHandler(),
userDefaults: UserDefaults = .standard,
subscriptionAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability(),
accountManager: AccountManager) {
accountManager: AccountManager,
freemiumPIRUserState: FreemiumPIRUserState) {
self.privacyConfigurationManager = privacyConfigurationManager
self.featureDisabler = featureDisabler
self.pixelHandler = pixelHandler
self.userDefaults = userDefaults
self.subscriptionAvailability = subscriptionAvailability
self.accountManager = accountManager
self.freemiumPIRUserState = freemiumPIRUserState
}

var isUserLocaleAllowed: Bool {
Expand All @@ -69,28 +71,23 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature
return (regionCode ?? "US") == "US"
}

func isPrivacyProEnabled() -> Bool {
return subscriptionAvailability.isFeatureAvailable
}

func disableAndDeleteForAllUsers() {
featureDisabler.disableAndDelete()

os_log("Disabling and removing DBP for all users", log: .dataBrokerProtection)
}

/// If we want to prevent new users from joining the waitlist while still allowing waitlist users to continue using it,
/// we should set isWaitlistEnabled to false and isWaitlistBetaActive to true.
/// To remove it from everyone, isWaitlistBetaActive should be set to false
func isFeatureVisible() -> Bool {
// only US locale should be available
guard isUserLocaleAllowed else { return false }
/// Checks PIR prerequisites
///
/// Prerequisites are satisified if either:
/// 1. The user is an active freemium user
/// 2. The user has a subscription with valid entitlements
///
/// - Returns: Bool indicating prerequisites are satisfied
func arePrerequisitesSatisfied() async -> Bool {

// US internal users should have it available by default
return isInternalUser
}
if freemiumPIRUserState.isActiveUser { return true }

func arePrerequisitesSatisfied() async -> Bool {
let entitlements = await accountManager.hasEntitlement(forProductName: .dataBrokerProtection,
cachePolicy: .reloadIgnoringLocalCacheData)
var hasEntitlements: Bool
Expand Down
52 changes: 52 additions & 0 deletions DuckDuckGo/Freemium/FreemiumDebugMenu.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// FreemiumDebugMenu.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import Freemium

final class FreemiumDebugMenu: NSMenuItem {

required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

public init() {
super.init(title: "Freemium", action: nil, keyEquivalent: "")
self.submenu = makeSubmenu()
}

private func makeSubmenu() -> NSMenu {
let menu = NSMenu(title: "")

menu.addItem(NSMenuItem(title: "Set Freemium PIR Onboarded State TRUE", action: #selector(setFreemiumPIROnboardStateEnabled), target: self))
menu.addItem(NSMenuItem(title: "Set Freemium PIR Onboarded State FALSE", action: #selector(setFreemiumPIROnboardStateDisabled), target: self))
menu.addItem(.separator())

return menu
}

@objc
func setFreemiumPIROnboardStateEnabled() {
DefaultFreemiumPIRUserState(userDefaults: .dbp, accountManager: Application.appDelegate.subscriptionManager.accountManager).didOnboard = true
}

@objc
func setFreemiumPIROnboardStateDisabled() {
DefaultFreemiumPIRUserState(userDefaults: .dbp, accountManager: Application.appDelegate.subscriptionManager.accountManager).didOnboard = false
}
}
2 changes: 2 additions & 0 deletions DuckDuckGo/Menus/MainMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,8 @@ final class MainMenu: NSMenu {
NSMenuItem(title: "Personal Information Removal")
.submenu(DataBrokerProtectionDebugMenu())

FreemiumDebugMenu()

if case .normal = NSApp.runType {
NSMenuItem(title: "VPN")
.submenu(NetworkProtectionDebugMenu())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele
authenticationRepository: KeychainAuthenticationData())
let authenticationManager = DataBrokerAuthenticationManagerBuilder.buildAuthenticationManager(redeemUseCase: redeemUseCase,
subscriptionManager: subscriptionManager)
manager = DataBrokerProtectionAgentManagerProvider.agentManager(authenticationManager: authenticationManager)
manager = DataBrokerProtectionAgentManagerProvider.agentManager(authenticationManager: authenticationManager,
accountManager: subscriptionManager.accountManager)
manager?.agentFinishedLaunching()

setupStatusBarMenu()
Expand Down
3 changes: 3 additions & 0 deletions LocalPackages/DataBrokerProtection/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ let package = Package(
.package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "186.0.0"),
.package(path: "../SwiftUIExtensions"),
.package(path: "../XPCHelper"),
.package(path: "../Freemium"),
],
targets: [
.target(
Expand All @@ -41,6 +42,7 @@ let package = Package(
.product(name: "SwiftUIExtensions", package: "SwiftUIExtensions"),
.byName(name: "XPCHelper"),
.product(name: "PixelKit", package: "BrowserServicesKit"),
.product(name: "Freemium", package: "Freemium"),
],
resources: [.process("Resources")],
swiftSettings: [
Expand All @@ -52,6 +54,7 @@ let package = Package(
dependencies: [
"DataBrokerProtection",
"BrowserServicesKit",
"Freemium",
],
resources: [
.process("Resources")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface {
os_log("Error \(error.localizedDescription)")
// Intentional no-op as there's no completion block
// If you add a completion block, please remember to call it here too!
}) }
})
}

public func runAllOptOuts(showWebView: Bool) {
xpc.execute(call: { server in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ struct DefaultDataBrokerOperationDependencies: DataBrokerOperationDependencies {
}

enum OperationType {
case scan
case manualScan
case scheduledScan
case optOut
case all
}
Expand Down Expand Up @@ -119,7 +120,7 @@ class DataBrokerOperation: Operation, @unchecked Sendable {
switch operationType {
case .optOut:
operationsData = brokerProfileQueriesData.flatMap { $0.optOutJobData }
case .scan:
case .manualScan, .scheduledScan:
operationsData = brokerProfileQueriesData.filter { $0.profileQuery.deprecated == false }.compactMap { $0.scanJobData }
case .all:
operationsData = brokerProfileQueriesData.flatMap { $0.operationsData }
Expand Down Expand Up @@ -179,7 +180,7 @@ class DataBrokerOperation: Operation, @unchecked Sendable {
runner: operationDependencies.runnerProvider.getJobRunner(),
pixelHandler: operationDependencies.pixelHandler,
showWebView: showWebView,
isImmediateOperation: operationType == .scan,
isImmediateOperation: operationType == .manualScan,
userNotificationService: operationDependencies.userNotificationService,
shouldRunNextStep: { [weak self] in
guard let self = self else { return false }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public struct DataBrokerExecutionConfig {
private let concurrentOperationsOnManualScans: Int = 6
func concurrentOperationsFor(_ operation: OperationType) -> Int {
switch operation {
case .all, .optOut:
case .all, .optOut, .scheduledScan:
return concurrentOperationsDifferentBrokers
case .scan:
case .manualScan:
aataraxiaa marked this conversation as resolved.
Show resolved Hide resolved
return concurrentOperationsOnManualScans
}
}
Expand Down
Loading
Loading