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 freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)
let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(accountManager:
subscriptionManager.accountManager,
freemiumPIRUserStateManager: freemiumPIRUserStateManager)

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 freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)
let pirGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper(accountManager:
subscriptionManager.accountManager,
freemiumPIRUserStateManager: freemiumPIRUserStateManager)

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 freemiumPIRUserStateManager: FreemiumPIRUserStateManager

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

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 freemiumPIRUserStateManager.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() {
DefaultFreemiumPIRUserStateManager(userDefaults: .dbp, accountManager: Application.appDelegate.subscriptionManager.accountManager).didOnboard = true
}

@objc
func setFreemiumPIROnboardStateDisabled() {
DefaultFreemiumPIRUserStateManager(userDefaults: .dbp, accountManager: Application.appDelegate.subscriptionManager.accountManager).didOnboard = false
}
}
10 changes: 5 additions & 5 deletions DuckDuckGo/HomePage/View/HomePageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
private let fireViewModel: FireViewModel
private let onboardingViewModel: OnboardingViewModel
private let freemiumPIRFeature: FreemiumPIRFeature
private var freemiumPIRUserState: FreemiumPIRUserState
private var freemiumPIRUserStateManager: FreemiumPIRUserStateManager
private let freemiumPIRPresenter: FreemiumPIRPresenter

private(set) lazy var faviconsFetcherOnboarding: FaviconsFetcherOnboarding? = {
Expand Down Expand Up @@ -70,7 +70,7 @@
appearancePreferences: AppearancePreferences = AppearancePreferences.shared,
defaultBrowserPreferences: DefaultBrowserPreferences = DefaultBrowserPreferences.shared,
freemiumPIRFeature: FreemiumPIRFeature,
freemiumPIRUserState: FreemiumPIRUserState,
freemiumPIRUserStateManager: FreemiumPIRUserStateManager,
freemiumPIRPresenter: FreemiumPIRPresenter = DefaultFreemiumPIRPresenter()) {

self.tabCollectionViewModel = tabCollectionViewModel
Expand All @@ -82,7 +82,7 @@
self.appearancePreferences = appearancePreferences
self.defaultBrowserPreferences = defaultBrowserPreferences
self.freemiumPIRFeature = freemiumPIRFeature
self.freemiumPIRUserState = freemiumPIRUserState
self.freemiumPIRUserStateManager = freemiumPIRUserStateManager
self.freemiumPIRPresenter = freemiumPIRPresenter

super.init(nibName: nil, bundle: nil)
Expand Down Expand Up @@ -217,10 +217,10 @@

private func createPromotionModel() -> PromotionViewModel {
return PromotionViewModel.freemiumPIRPromotion { [weak self] in
// TODO: Remove this

Check failure on line 220 in DuckDuckGo/HomePage/View/HomePageViewController.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

TODOs should be resolved (Remove this) (todo)
self?.freemiumPIRUserState.didOnboard = true
self?.freemiumPIRUserStateManager.didOnboard = true
// ------
self?.freemiumPIRPresenter.showFreemiumPIR(didOnboard: self?.freemiumPIRUserState.didOnboard ?? false,
self?.freemiumPIRPresenter.showFreemiumPIR(didOnboard: self?.freemiumPIRUserStateManager.didOnboard ?? false,
windowControllerManager: WindowControllersManager.shared)
self?.appearancePreferences.didDismissHomePagePromotion = true
} closeAction: { [weak self] in
Expand Down
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
10 changes: 5 additions & 5 deletions DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
private lazy var sharingMenu: NSMenu = SharingMenu(title: UserText.shareMenuItem)
private var accountManager: AccountManager { subscriptionManager.accountManager }
private let subscriptionManager: SubscriptionManager
private var freemiumPIRUserState: FreemiumPIRUserState
private var freemiumPIRUserStateManager: FreemiumPIRUserStateManager
private let freemiumPIRFeature: FreemiumPIRFeature
private let freemiumPIRPresenter: FreemiumPIRPresenter
private let appearancePreferences: AppearancePreferences
Expand All @@ -79,7 +79,7 @@
sharingMenu: NSMenu? = nil,
internalUserDecider: InternalUserDecider,
subscriptionManager: SubscriptionManager,
freemiumPIRUserState: FreemiumPIRUserState,
freemiumPIRUserStateManager: FreemiumPIRUserStateManager,
freemiumPIRFeature: FreemiumPIRFeature,
freemiumPIRPresenter: FreemiumPIRPresenter = DefaultFreemiumPIRPresenter(),
appearancePreferences: AppearancePreferences = .shared) {
Expand All @@ -91,7 +91,7 @@
self.subscriptionFeatureAvailability = subscriptionFeatureAvailability
self.internalUserDecider = internalUserDecider
self.subscriptionManager = subscriptionManager
self.freemiumPIRUserState = freemiumPIRUserState
self.freemiumPIRUserStateManager = freemiumPIRUserStateManager
self.freemiumPIRFeature = freemiumPIRFeature
self.freemiumPIRPresenter = freemiumPIRPresenter
self.appearancePreferences = appearancePreferences
Expand Down Expand Up @@ -260,10 +260,10 @@

@objc func openFreemiumPIR(_ sender: NSMenuItem) {

// TODO: Remove this

Check failure on line 263 in DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

TODOs should be resolved (Remove this) (todo)
freemiumPIRUserState.didOnboard = true
freemiumPIRUserStateManager.didOnboard = true
// ------
freemiumPIRPresenter.showFreemiumPIR(didOnboard: freemiumPIRUserState.didOnboard, windowControllerManager: WindowControllersManager.shared)
freemiumPIRPresenter.showFreemiumPIR(didOnboard: freemiumPIRUserStateManager.didOnboard, windowControllerManager: WindowControllersManager.shared)
appearancePreferences.isHomePagePromotionVisible = false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,14 +272,14 @@ final class NavigationBarViewController: NSViewController {

@IBAction func optionsButtonAction(_ sender: NSButton) {
let internalUserDecider = NSApp.delegateTyped.internalUserDecider
let freemiumPIRUserState = DefaultFreemiumPIRUserState(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel,
passwordManagerCoordinator: PasswordManagerCoordinator.shared,
vpnFeatureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager),
internalUserDecider: internalUserDecider,
subscriptionManager: subscriptionManager,
freemiumPIRUserState: freemiumPIRUserState,
freemiumPIRUserStateManager: freemiumPIRUserStateManager,
freemiumPIRFeature: freemiumPIRFeature)

menu.actionDelegate = self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr

let deprecatedRemoteMessageStorage = DefaultSurveyRemoteMessagingStorage.surveys()

let freemiumPIRUserState = DefaultFreemiumPIRUserState(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp, accountManager: subscriptionManager.accountManager)

return RemoteMessagingConfigMatcher(
appAttributeMatcher: AppAttributeMatcher(statisticsStore: statisticsStore,
Expand All @@ -164,7 +164,7 @@ final class RemoteMessagingConfigMatcherProvider: RemoteMessagingConfigMatcherPr
hasCustomHomePage: startupPreferencesPersistor().launchToCustomHomePage,
isDuckPlayerOnboarded: duckPlayerPreferencesPersistor.youtubeOverlayAnyButtonPressed,
isDuckPlayerEnabled: duckPlayerPreferencesPersistor.duckPlayerModeBool != false,
isCurrentFreemiumPIRUser: freemiumPIRUserState.isActiveUser,
isCurrentFreemiumPIRUser: freemiumPIRUserStateManager.isActiveUser,
dismissedDeprecatedMacRemoteMessageIds: deprecatedRemoteMessageStorage.dismissedMessageIDs()
),
percentileStore: RemoteMessagingPercentileUserDefaultsStore(keyValueStore: UserDefaults.standard),
Expand Down
Loading
Loading