Skip to content

Commit

Permalink
Handle subscription-related iOS use cases (#2597)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/414235014887631/1206844393131400/f

This PR does the necessary work for the PP launch.

Steps to test this PR:

Check different scenarios according to https://app.asana.com/0/0/1206812323779606/f
Make sure the followings are handled correctly: waitlist users, entitlement check, entry points, etc.
  • Loading branch information
quanganhdo authored Mar 19, 2024
1 parent 2deb813 commit 9c0553e
Show file tree
Hide file tree
Showing 22 changed files with 572 additions and 82 deletions.
28 changes: 27 additions & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,11 @@
BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; };
BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC234F62B27F51100D3C798 /* UniquePixel.swift */; };
BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; };
BDFF031D2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */; };
BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */; };
BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; };
BDFF03232BA3D8E300F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; };
BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */; };
C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; };
C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; };
C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */; };
Expand Down Expand Up @@ -2363,6 +2368,10 @@
BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormView.swift; sourceTree = "<group>"; };
BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerExtension.swift; sourceTree = "<group>"; };
BDC234F62B27F51100D3C798 /* UniquePixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniquePixel.swift; sourceTree = "<group>"; };
BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = "<group>"; };
BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNetworkProtectionVisibility.swift; sourceTree = "<group>"; };
BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVisibilityForTunnelProvider.swift; sourceTree = "<group>"; };
BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibilityTests.swift; sourceTree = "<group>"; };
C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillViews.swift; sourceTree = "<group>"; };
C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOrFolderTests.swift; sourceTree = "<group>"; };
C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3577,6 +3586,7 @@
4BCD146A2B05C4B5000B1E4C /* VPNWaitlistTermsAndConditionsViewController.swift */,
4B78074B2B1823C5009DB2CF /* VPNWaitlistActivationDateStore.swift */,
4B78074D2B183A1F009DB2CF /* SurveyURLBuilder.swift */,
BDFF031F2BA3D3AD00F324C9 /* Feature Visibility */,
);
name = VPN;
sourceTree = "<group>";
Expand Down Expand Up @@ -4419,6 +4429,16 @@
name = Subscription;
sourceTree = "<group>";
};
BDFF031F2BA3D3AD00F324C9 /* Feature Visibility */ = {
isa = PBXGroup;
children = (
BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */,
BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */,
BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */,
);
name = "Feature Visibility";
sourceTree = "<group>";
};
C14882D627F2010700D59F0C /* ImportExport */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4807,6 +4827,7 @@
EE41BD182A729E9C00546C57 /* NetworkProtectionInviteViewModelTests.swift */,
4BCD146C2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift */,
EEC02C152B065BE00045CE11 /* NetworkProtectionVPNLocationViewModelTests.swift */,
BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */,
);
name = NetworkProtection;
sourceTree = "<group>";
Expand Down Expand Up @@ -6437,6 +6458,7 @@
files = (
02025B0D29884D2C00E694E7 /* AppTrackerData.swift in Sources */,
4BEF656C2989C2FC00B650CB /* TunnelEvent.swift in Sources */,
BDFF03232BA3D8E300F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */,
02025A9A2988229800E694E7 /* TUNInterface.swift in Sources */,
02025A9B2988229800E694E7 /* IPStackProtocol.swift in Sources */,
02025AA32988229800E694E7 /* PacketProtocolParser.swift in Sources */,
Expand Down Expand Up @@ -6494,6 +6516,7 @@
02025AEC2988229800E694E7 /* AppTrackingProtectionPacketTunnelProvider.swift in Sources */,
02025B1029884DC500E694E7 /* AppTrackerDataParser.swift in Sources */,
4BB697A52B1D99C5003699B5 /* VPNWaitlistActivationDateStore.swift in Sources */,
BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */,
EEFC6A602AC0F2F80065027D /* UserText.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -6557,6 +6580,7 @@
F4E1936625AF722F001D2666 /* HighlightCutOutView.swift in Sources */,
1E162605296840D80004127F /* Triangle.swift in Sources */,
B609D5522862EAFF0088CAC2 /* InlineWKDownloadDelegate.swift in Sources */,
BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */,
B652DEFD287BE67400C12A9C /* UserScripts.swift in Sources */,
31DD208427395A5A008FB313 /* VoiceSearchHelper.swift in Sources */,
9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */,
Expand Down Expand Up @@ -6599,6 +6623,7 @@
853C5F6121C277C7001F7A05 /* global.swift in Sources */,
EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */,
F13B4BD31F1822C700814661 /* Tab.swift in Sources */,
BDFF031D2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift in Sources */,
F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */,
1EE52ABB28FB1D6300B750C1 /* UIImageExtension.swift in Sources */,
858650D12469BCDE00C36F8A /* DaxDialogs.swift in Sources */,
Expand Down Expand Up @@ -7046,6 +7071,7 @@
850559D223CF710C0055C0D5 /* WebCacheManagerTests.swift in Sources */,
EEC02C162B065BE00045CE11 /* NetworkProtectionVPNLocationViewModelTests.swift in Sources */,
987130C5294AAB9F00AB05E0 /* BookmarkEditorViewModelTests.swift in Sources */,
BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */,
8341D807212D5E8D000514C2 /* HashExtensionTest.swift in Sources */,
C1D21E2F293A599C006E5A05 /* AutofillLoginSessionTests.swift in Sources */,
85D2187924BF6B8B004373D2 /* FaviconSourcesProviderTests.swift in Sources */,
Expand Down Expand Up @@ -10003,7 +10029,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 126.0.1;
version = 126.1.0;
};
};
B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "d01c760dadbc2e987e7577e2476f95983dc6d38c",
"version" : "126.0.1"
"revision" : "f4894b9c00dd7514c66d6b929c12315e0cd9c151",
"version" : "126.1.0"
}
},
{
Expand Down
6 changes: 5 additions & 1 deletion DuckDuckGo/AppDelegate+Waitlists.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ extension AppDelegate {
func checkWaitlists() {

#if NETWORK_PROTECTION
checkNetworkProtectionWaitlist()
if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() {
checkNetworkProtectionWaitlist()
}
#endif
checkWaitlistBackgroundTasks()

Expand Down Expand Up @@ -93,6 +95,8 @@ extension AppDelegate {
#endif

private func checkWaitlistBackgroundTasks() {
guard vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() else { return }

BGTaskScheduler.shared.getPendingTaskRequests { tasks in

#if NETWORK_PROTECTION
Expand Down
35 changes: 29 additions & 6 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
#if NETWORK_PROTECTION
private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel()
private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults
lazy var vpnFeatureVisibilty = DefaultNetworkProtectionVisibility()
#endif

private var autoClear: AutoClear?
Expand Down Expand Up @@ -303,7 +304,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
AppConfigurationFetch.registerBackgroundRefreshTaskHandler()

#if NETWORK_PROTECTION
VPNWaitlist.shared.registerBackgroundRefreshTaskHandler()
if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() {
VPNWaitlist.shared.registerBackgroundRefreshTaskHandler()
}
#endif

RemoteMessaging.registerBackgroundRefreshTaskHandler(
Expand Down Expand Up @@ -332,7 +335,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
setupSubscriptionsEnvironment()
#endif

clearDebugWaitlistState()
if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() {
clearDebugWaitlistState()
}

AppDependencyProvider.shared.toggleProtectionsCounter.sendEventsIfNeeded()
AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp)
Expand Down Expand Up @@ -385,14 +390,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}

private func presentExpiredEntitlementNotification() {
private func presentExpiredEntitlementNotificationIfNeeded() {
let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator(
settings: VPNSettings(defaults: .networkProtectionGroupDefaults),
defaults: .networkProtectionGroupDefaults,
wrappee: NetworkProtectionUNNotificationPresenter()
)
presenter.showEntitlementNotification()
}

private func presentVPNEarlyAccessOverAlert() {
let alertController = CriticalAlerts.makeVPNEarlyAccessOverAlert()
window?.rootViewController?.present(alertController, animated: true) { [weak self] in
self?.tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true
}
}
#endif

private func cleanUpMacPromoExperiment2() {
Expand All @@ -417,6 +429,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private func setupSubscriptionsEnvironment() {
Task {
SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging
#if NETWORK_PROTECTION
if VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment == .staging {
SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging
}
#endif
SubscriptionPurchaseEnvironment.current = .appStore
await AccountManager().checkSubscriptionState()
}
Expand Down Expand Up @@ -474,11 +491,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
#if NETWORK_PROTECTION
widgetRefreshModel.refreshVPNWidget()

if vpnFeatureVisibilty.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown {
presentVPNEarlyAccessOverAlert()
}

if tunnelDefaults.showEntitlementAlert {
presentExpiredEntitlementAlert()
}

presentExpiredEntitlementNotification()
presentExpiredEntitlementNotificationIfNeeded()
#endif

updateSubscriptionStatus()
Expand Down Expand Up @@ -830,7 +851,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func refreshShortcuts() {
#if NETWORK_PROTECTION
guard NetworkProtectionKeychainTokenStore().isFeatureActivated else {
guard vpnFeatureVisibilty.shouldShowVPNShortcut() else {
UIApplication.shared.shortcutItems = nil
return
}

Expand Down Expand Up @@ -906,10 +928,11 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
presentNetworkProtectionStatusSettingsModal()
}

if identifier == VPNWaitlist.notificationIdentifier {
if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist(), identifier == VPNWaitlist.notificationIdentifier {
presentNetworkProtectionWaitlistModal()
DailyPixel.fire(pixel: .networkProtectionWaitlistNotificationLaunched)
}

#endif
}

Expand Down
12 changes: 12 additions & 0 deletions DuckDuckGo/CriticalAlerts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,16 @@ struct CriticalAlerts {
return alertController
}

static func makeVPNEarlyAccessOverAlert() -> UIAlertController {
let alertController = UIAlertController(title: UserText.vpnEarlyAccessOverAlertTitle,
message: UserText.vpnEarlyAccessOverAlertMessage,
preferredStyle: .alert)
alertController.overrideUserInterfaceStyle()

let closeButton = UIAlertAction(title: UserText.vpnEarlyAccessOverAlertAction, style: .cancel)

alertController.addAction(closeButton)
return alertController
}

}
96 changes: 96 additions & 0 deletions DuckDuckGo/DefaultNetworkProtectionVisibility.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// DefaultNetworkProtectionVisibility.swift
// DuckDuckGo
//
// 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.
//

#if NETWORK_PROTECTION

import Foundation
import BrowserServicesKit
import Waitlist
import NetworkProtection
import Core

struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility {
private let privacyConfigurationManager: PrivacyConfigurationManaging
private let networkProtectionTokenStore: NetworkProtectionTokenStore?
private let networkProtectionAccessManager: NetworkProtectionAccess?
private let featureFlagger: FeatureFlagger
private let userDefaults: UserDefaults

init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager,
networkProtectionTokenStore: NetworkProtectionTokenStore? = NetworkProtectionKeychainTokenStore(),
networkProtectionAccessManager: NetworkProtectionAccess? = NetworkProtectionAccessController(),
featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger,
userDefaults: UserDefaults = .networkProtectionGroupDefaults) {
self.privacyConfigurationManager = privacyConfigurationManager
self.networkProtectionTokenStore = networkProtectionTokenStore
self.networkProtectionAccessManager = networkProtectionAccessManager
self.featureFlagger = featureFlagger
self.userDefaults = userDefaults
}

/// A lite version with fewer dependencies
/// We need this to run shouldMonitorEntitlement() check inside the token store
static func forTokenStore() -> DefaultNetworkProtectionVisibility {
DefaultNetworkProtectionVisibility(networkProtectionTokenStore: nil, networkProtectionAccessManager: nil)
}

func isWaitlistBetaActive() -> Bool {
privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(NetworkProtectionSubfeature.waitlistBetaActive)
}

func isWaitlistUser() -> Bool {
guard let networkProtectionTokenStore, let networkProtectionAccessManager else {
preconditionFailure("networkProtectionTokenStore and networkProtectionAccessManager must be non-nil")
}

let hasLegacyAuthToken = {
guard let authToken = try? networkProtectionTokenStore.fetchToken(),
!authToken.hasPrefix(NetworkProtectionKeychainTokenStore.authTokenPrefix) else {
return false
}
return true
}()
let hasBeenInvited = {
let vpnAccessType = networkProtectionAccessManager.networkProtectionAccessType()
return vpnAccessType == .waitlistInvited || vpnAccessType == .inviteCodeInvited
}()

return hasLegacyAuthToken || hasBeenInvited
}

// todo - https://app.asana.com/0/0/1206844038943626/f
func isPrivacyProLaunched() -> Bool {
if let subscriptionOverrideEnabled = userDefaults.subscriptionOverrideEnabled {
#if ALPHA || DEBUG
return subscriptionOverrideEnabled
#else
return false
#endif
}

return featureFlagger.isFeatureOn(.subscription)
}

// todo - https://app.asana.com/0/0/1206844038943626/f
func shouldMonitorEntitlement() -> Bool {
isPrivacyProLaunched()
}
}

#endif
2 changes: 1 addition & 1 deletion DuckDuckGo/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,7 @@ class MainViewController: UIViewController {
@objc
private func onNetworkProtectionAccountSignIn(_ notification: Notification) {
tunnelDefaults.resetEntitlementMessaging()
print("[NetP Subscription] Reset expired entitlement messaging")
os_log("[NetP Subscription] Reset expired entitlement messaging", log: .networkProtection, type: .info)
}
#endif

Expand Down
Loading

0 comments on commit 9c0553e

Please sign in to comment.