Skip to content

Commit

Permalink
Improves expiration handling
Browse files Browse the repository at this point in the history
  • Loading branch information
diegoreymendez committed Mar 22, 2024
1 parent 9c8a825 commit c117a41
Show file tree
Hide file tree
Showing 18 changed files with 218 additions and 210 deletions.
57 changes: 49 additions & 8 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

9 changes: 0 additions & 9 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel
let subscriptionFeatureAvailability: SubscriptionFeatureAvailability
#endif

#if NETWORK_PROTECTION && SUBSCRIPTION
// Needs to be lazy as indirectly depends on AppDelegate
private lazy var networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler()
#endif

#if DBP && SUBSCRIPTION
private let dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler()
#endif
Expand Down Expand Up @@ -292,10 +287,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel

UserDefaultsWrapper<Any>.clearRemovedKeys()

#if NETWORK_PROTECTION && SUBSCRIPTION
networkProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents()
#endif

#if NETWORK_PROTECTION
NetworkProtectionAppEvents().applicationDidFinishLaunching()
UNUserNotificationCenter.current().delegate = self
Expand Down
1 change: 0 additions & 1 deletion DuckDuckGo/Menus/MainMenuActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,6 @@ extension MainViewController {
///
private func clearPrivacyProState() {
resetThankYouModalChecks(nil)
UserDefaults.netP.networkProtectionEntitlementsExpired = false

// Clear pixel data
DailyPixel.clearLastFireDate(pixel: .privacyProFeatureEnabled)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import Foundation
import NetworkProtection
import NetworkProtectionSubscription
import Common

#if SUBSCRIPTION
Expand Down Expand Up @@ -88,4 +89,10 @@ extension NetworkProtectionLocationListCompositeRepository {
}
}

extension VPNSubscriptionStatusObserver {
convenience init() {
self.init(accountManager: AccountManager())
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ final class NetworkProtectionDebugUtilities {

UserDefaults().removeObject(forKey: UserDefaultsWrapper<Bool>.Key.networkProtectionTermsAndConditionsAccepted.rawValue)
NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil)
UserDefaults.netP.networkProtectionEntitlementsExpired = false
}

func removeSystemExtensionAndAgents() async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import LoginItems
import NetworkProtection
import NetworkProtectionIPC
import NetworkProtectionUI
import NetworkProtectionSubscription

#if SUBSCRIPTION
import Subscription
Expand Down Expand Up @@ -82,7 +83,10 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager {
_ = VPNSettings(defaults: .netP)
let appLauncher = AppLauncher(appBundleURL: Bundle.main.bundleURL)

let subscriptionStatusObserver = VPNSubscriptionStatusObserver()

let popover = NetworkProtectionPopover(controller: controller,
showSubscriptionExpired: subscriptionStatusObserver.$showSubscriptionExpired,
onboardingStatusPublisher: onboardingStatusPublisher,
statusReporter: statusReporter,
appLauncher: appLauncher,
Expand Down

This file was deleted.

24 changes: 11 additions & 13 deletions DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import BrowserServicesKit
import Combine
import DDGSync
import LoginItems
import NetworkProtectionSubscription
import SwiftUI

#if SUBSCRIPTION
Expand Down Expand Up @@ -83,10 +85,8 @@ final class PreferencesSidebarModel: ObservableObject {
userDefaults: UserDefaults = .netP
) {
let loadSections = {
#if SUBSCRIPTION
let includingVPN = !userDefaults.networkProtectionEntitlementsExpired && DefaultNetworkProtectionVisibility().isOnboarded
#elseif NETWORK_PROTECTION
let includingVPN = DefaultNetworkProtectionVisibility().isOnboarded
#if NETWORK_PROTECTION
let includingVPN = DefaultNetworkProtectionVisibility().isInstalled
#else
let includingVPN = false
#endif
Expand Down Expand Up @@ -121,15 +121,13 @@ final class PreferencesSidebarModel: ObservableObject {
}
.store(in: &cancellables)

UserDefaults.netP.publisher(for: \.networkProtectionEntitlementsExpired)
.receive(on: DispatchQueue.main)
.sink { [weak self] entitlementsExpired in
guard let self else { return }
if !entitlementsExpired && self.selectedPane == .vpn {
self.selectedPane = .general
}
self.refreshSections()
}.store(in: &cancellables)
VPNSubscriptionStatusObserver().$showSubscriptionExpired.sink { expired in
if expired && self.selectedPane == .vpn {
self.selectedPane = .general
}
self.refreshSections()
}
.store(in: &cancellables)
}
#endif

Expand Down
21 changes: 17 additions & 4 deletions DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ protocol NetworkProtectionFeatureVisibility {
var isEligibleForThankYouMessage: Bool { get }

func isNetworkProtectionVisible() -> Bool
func shouldUninstallAutomatically() -> Bool
func shouldUninstallAutomatically() async -> Bool
func disableForAllUsers() async
func disableForWaitlistUsers()
@discardableResult
Expand Down Expand Up @@ -80,6 +80,10 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility {
return isEasterEggUser || waitlistIsOngoing
}

var isInstalled: Bool {
LoginItem.vpnMenu.status.isInstalled && isOnboarded
}

/// We've had to add this method because accessing the singleton in app delegate is crashing the integration tests.
///
var subscriptionFeatureAvailability: DefaultSubscriptionFeatureAvailability {
Expand All @@ -88,9 +92,18 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility {

/// Returns whether the VPN should be uninstalled automatically.
/// This is only true when the user is not an Easter Egg user, the waitlist test has ended, and the user is onboarded.
func shouldUninstallAutomatically() -> Bool {
func shouldUninstallAutomatically() async -> Bool {
#if SUBSCRIPTION
return subscriptionFeatureAvailability.isFeatureAvailable && defaults.networkProtectionEntitlementsExpired && LoginItem.vpnMenu.status.isInstalled
guard subscriptionFeatureAvailability.isFeatureAvailable,
LoginItem.vpnMenu.status.isInstalled else {
return false
}

if case .success(let hasEntitlement) = await AccountManager().hasEntitlement(for: .networkProtection) {
return hasEntitlement
}

return false
#else
let waitlistAccessEnded = isWaitlistUser && !waitlistIsOngoing
let isNotEasterEggUser = !isEasterEggUser
Expand Down Expand Up @@ -194,7 +207,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility {
///
@discardableResult
func disableIfUserHasNoAccess() async -> Bool {
if shouldUninstallAutomatically() {
if await shouldUninstallAutomatically() {
await disableForAllUsers()
return true
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// ConvenienceInitializers.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 NetworkProtectionSubscription
import Subscription

extension VPNSubscriptionStatusObserver {
convenience init() {
self.init(accountManager: AccountManager())
}
}
40 changes: 15 additions & 25 deletions DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import NetworkExtension
import NetworkProtection
import NetworkProtectionProxy
import NetworkProtectionUI
import NetworkProtectionSubscription
import ServiceManagement
import PixelKit

Expand Down Expand Up @@ -95,6 +96,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {
private lazy var tunnelSettings = VPNSettings(defaults: .netP)
private lazy var userDefaults = UserDefaults.netP
private lazy var proxySettings = TransparentProxySettings(defaults: .netP)
private let subscriptionStatusObserver = VPNSubscriptionStatusObserver()

@MainActor
private lazy var vpnProxyLauncher = VPNProxyLauncher(
Expand Down Expand Up @@ -232,6 +234,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {

return StatusBarMenu(
model: model,
showSubscriptionExpired: subscriptionStatusObserver.$showSubscriptionExpired,
onboardingStatusPublisher: onboardingStatusPublisher,
statusReporter: statusReporter,
controller: tunnelController,
Expand Down Expand Up @@ -355,37 +358,24 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {
}.store(in: &cancellables)
}

private lazy var entitlementMonitor = NetworkProtectionEntitlementMonitor()

private func setUpSubscriptionMonitoring() {
#if SUBSCRIPTION
guard AccountManager().isUserAuthenticated else { return }
let entitlementsCheck = {
await AccountManager().hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData)
}
subscriptionStatusObserver.$showSubscriptionExpired.sink { expired in
guard expired else {
return
}

Task {
await entitlementMonitor.start(entitlementCheck: entitlementsCheck) { [weak self] result in
switch result {
case .validEntitlement:
UserDefaults.netP.networkProtectionEntitlementsExpired = false
case .invalidEntitlement:
UserDefaults.netP.networkProtectionEntitlementsExpired = true
PixelKit.fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, frequency: .dailyAndContinuous)

guard let self else { return }
Task {
let isConnected = await self.tunnelController.isConnected
if isConnected {
await self.tunnelController.stop()
DistributedNotificationCenter.default().post(.showExpiredEntitlementNotification)
}
}
case .error:
break
PixelKit.fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, frequency: .dailyAndContinuous)

Task {
let isConnected = await self.tunnelController.isConnected
if isConnected {
await self.tunnelController.stop()
DistributedNotificationCenter.default().post(.showExpiredEntitlementNotification)
}
}
}
.store(in: &cancellables)
#endif
}
}
Expand Down
17 changes: 17 additions & 0 deletions LocalPackages/NetworkProtectionMac/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ let package = Package(
.library(name: "NetworkProtectionIPC", targets: ["NetworkProtectionIPC"]),
.library(name: "NetworkProtectionProxy", targets: ["NetworkProtectionProxy"]),
.library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]),
.library(name: "NetworkProtectionSubscription", targets: ["NetworkProtectionSubscription"]),
],
dependencies: [
.package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "129.1.0"),
Expand Down Expand Up @@ -67,6 +68,22 @@ let package = Package(
plugins: [.plugin(name: "SwiftLintPlugin", package: "apple-toolbox")]
),

// MARK: - NetworkProtectionSubscription

.target(
name: "NetworkProtectionSubscription",
dependencies: [
.product(name: "Subscription", package: "BrowserServicesKit"),
],
resources: [
.copy("Resources/Assets.xcassets")
],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug))
],
plugins: [.plugin(name: "SwiftLintPlugin", package: "apple-toolbox")]
),

// MARK: - NetworkProtectionUI

.target(
Expand Down
Loading

0 comments on commit c117a41

Please sign in to comment.