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

Check entitlement periodically and while rekeying NetP #2461

Merged
merged 56 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
5e8d6b0
Exchange access token for auth token
quanganhdo Jan 19, 2024
9254447
Fix typos
quanganhdo Jan 19, 2024
8e047ab
Merge branch 'main' into anh/netp-subscription
quanganhdo Jan 19, 2024
8263aed
Merge branch 'main' into anh/netp-check-entitlement
quanganhdo Jan 23, 2024
647f6ef
Just enough for AccountManager check
quanganhdo Jan 23, 2024
9dc98f1
Fix Alpha build not installing NetP
quanganhdo Jan 24, 2024
ea8d32c
Merge branch 'main' into anh/netp-check-entitlement
quanganhdo Jan 24, 2024
f837fae
Show last disconnect error in Debug view
quanganhdo Jan 25, 2024
770c0b8
Minor refactoring
quanganhdo Jan 26, 2024
eb64fe3
To be dropped
quanganhdo Jan 26, 2024
51de3a7
Merge branch 'main' into anh/netp-check-entitlement
quanganhdo Jan 26, 2024
461b700
Merge branch 'main' into anh/netp-check-entitlement
quanganhdo Jan 29, 2024
f91093d
Show notification
quanganhdo Jan 30, 2024
140992e
Merge branch 'main' into anh/netp-check-entitlement
quanganhdo Jan 31, 2024
d1ec958
Add ALPHA to Alpha Debug build
quanganhdo Feb 1, 2024
a0ee6c2
Populate isSubscriptionEnabled
quanganhdo Feb 1, 2024
7ece8fa
Use SubscriptionConfig instead
quanganhdo Feb 1, 2024
56e11bf
Update entitlement for Alpha Debug
quanganhdo Feb 1, 2024
6da1586
Use ddg:accessToken for NetP authentication
quanganhdo Feb 1, 2024
9fe8986
Clean up
quanganhdo Feb 1, 2024
4a9d0d0
Merge branch 'main' into anh/netp-check-entitlement
quanganhdo Feb 2, 2024
11ba78f
Update BSK
quanganhdo Feb 2, 2024
9c261ab
Update BSK
quanganhdo Feb 2, 2024
844e655
Update BSK
quanganhdo Feb 2, 2024
b655829
Merge branch 'main' into anh/netp-check-entitlement-while-rekeying
quanganhdo Feb 5, 2024
695f857
Add missing file
quanganhdo Feb 5, 2024
e53ce3d
Clean up
quanganhdo Feb 5, 2024
c67390c
Update BSK
quanganhdo Feb 6, 2024
e762552
Update BSK
quanganhdo Feb 7, 2024
f0357e4
Release 7.108.0-1 (#2440)
dus7 Feb 5, 2024
15df96e
Subscription is always ON for ALPHA
quanganhdo Feb 6, 2024
e6e7b07
Show alert for expired entitlement
quanganhdo Feb 7, 2024
9bd3a69
Update BSK
quanganhdo Feb 9, 2024
3048c93
Merge branch 'main' into anh/netp-check-entitlement-while-rekeying
quanganhdo Feb 20, 2024
bba1c79
Update BSK
quanganhdo Feb 20, 2024
e5577d7
Address PR comments
quanganhdo Feb 20, 2024
67bebd0
Simplify subscription config
quanganhdo Feb 20, 2024
dbb8f44
Merge branch 'anh/netp-check-entitlement-while-rekeying' into anh/net…
quanganhdo Feb 20, 2024
b08b513
Update BSK
quanganhdo Feb 20, 2024
c000215
Update BSK
quanganhdo Feb 21, 2024
a383c15
Update Package.resolved
quanganhdo Feb 21, 2024
98a348a
Update BSK
quanganhdo Feb 21, 2024
029a9dc
Merge branch 'main' into anh/netp-check-entitlement-periodically
quanganhdo Feb 21, 2024
9ce17c6
Update BSK
quanganhdo Feb 21, 2024
1d6a497
Update BSK
quanganhdo Feb 22, 2024
24e9be5
Update BSK
quanganhdo Feb 22, 2024
c2012df
Update BSK
quanganhdo Feb 26, 2024
20068ae
Merge branch 'main' into anh/netp-check-entitlement-periodically
quanganhdo Feb 26, 2024
32fa675
Simplify messaging
quanganhdo Feb 27, 2024
624b09c
Update BSK
quanganhdo Feb 27, 2024
ac9c465
Merge branch 'main' into anh/netp-check-entitlement-periodically
quanganhdo Feb 28, 2024
11bf095
Update BSK
quanganhdo Feb 28, 2024
b4058f2
Update environment
quanganhdo Feb 28, 2024
cef0702
Merge branch 'main' into anh/netp-check-entitlement-periodically
quanganhdo Mar 1, 2024
1e6f908
Update BSK
quanganhdo Mar 1, 2024
da81fe3
Update BSK
quanganhdo Mar 1, 2024
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
1 change: 1 addition & 0 deletions Core/NetworkProtectionNotificationIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ public enum NetworkProtectionNotificationIdentifier: String {
case connection = "network-protection.notification.connection"
case superseded = "network-protection.notification.superseded"
case test = "network-protection.notification.test"
case entitlement = "network-protection.notification.entitlement"
}
10 changes: 5 additions & 5 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8903,7 +8903,7 @@
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION ALPHA";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)";
Expand All @@ -8915,7 +8915,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha";
CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements;
CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 1;
Expand Down Expand Up @@ -9040,7 +9040,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements;
CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand Down Expand Up @@ -9907,8 +9907,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 109.0.1;
branch = "anh/netp-check-entitlement-periodically";
kind = branch;
};
};
C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
{
"identity" : "browserserviceskit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "da6a822844922401d80e26963b8b11dcd6ef221a",
"version" : "109.0.1"
"branch" : "anh/netp-check-entitlement-periodically",
"revision" : "3b85d36a1b15041f742f529939997bd3e9c43321"
}
},
{
Expand Down
17 changes: 17 additions & 0 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
VPNWaitlist.shared.registerBackgroundRefreshTaskHandler()
#endif

#if NETWORK_PROTECTION && SUBSCRIPTION
if let messaging = VPNSettings(defaults: .networkProtectionGroupDefaults).shouldShowExpiredEntitlementMessaging,
messaging.showsAlert {
presentExpiredEntitlementAlert()
}
#endif

RemoteMessaging.registerBackgroundRefreshTaskHandler(
bookmarksDatabase: bookmarksDatabase,
favoritesDisplayMode: AppDependencyProvider.shared.appSettings.favoritesDisplayMode
Expand Down Expand Up @@ -341,6 +348,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window?.rootViewController?.present(alertController, animated: true, completion: nil)
}

private func presentExpiredEntitlementAlert() {
let alertController = CriticalAlerts.makeExpiredEntitlementAlert()
window?.rootViewController?.present(alertController, animated: true) {
if let messaging = VPNSettings(defaults: .networkProtectionGroupDefaults).shouldShowExpiredEntitlementMessaging {
let newMessaging = UserDefaults.ExpiredEntitlementMessaging(showsAlert: false, showsNotification: messaging.showsNotification)
VPNSettings(defaults: .networkProtectionGroupDefaults).apply(change: .setShouldShowExpiredEntitlementMessaging(newMessaging))
}
}
}

private func cleanUpMacPromoExperiment2() {
UserDefaults.standard.removeObject(forKey: "com.duckduckgo.ios.macPromoMay23.exp2.cohort")
}
Expand Down
16 changes: 16 additions & 0 deletions DuckDuckGo/CriticalAlerts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,20 @@ struct CriticalAlerts {
return alertController
}

static func makeExpiredEntitlementAlert() -> UIAlertController {
let alertController = UIAlertController(title: UserText.vpnAccessRevokedAlertTitle,
message: UserText.vpnAccessRevokedAlertMessage,
preferredStyle: .alert)
alertController.overrideUserInterfaceStyle()

let closeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionCancel, style: .cancel)
let signInButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in
UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil)
}

alertController.addAction(closeButton)
alertController.addAction(signInButton)
return alertController
}

}
2 changes: 2 additions & 0 deletions DuckDuckGo/EventMapping+NetworkProtectionError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ extension EventMapping where Event == NetworkProtectionError {
params[PixelParameters.keychainErrorCode] = String(status)
case .noAuthTokenFound:
pixelEvent = .networkProtectionNoAuthTokenFoundError
case .vpnAccessRevoked:
return
case
.noServerRegistrationInfo,
.couldNotSelectClosestServer,
Expand Down
26 changes: 7 additions & 19 deletions DuckDuckGo/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1334,12 +1334,6 @@ class MainViewController: UIViewController {
self?.onNetworkProtectionAccountSignIn(notification)
}
.store(in: &netpCancellables)
NotificationCenter.default.publisher(for: .accountDidSignOut)
.receive(on: DispatchQueue.main)
.sink { [weak self] notification in
self?.onNetworkProtectionAccountSignOut(notification)
}
.store(in: &netpCancellables)
}

@objc
Expand All @@ -1349,25 +1343,19 @@ class MainViewController: UIViewController {
return
}

VPNSettings(defaults: .networkProtectionGroupDefaults).apply(change: .setShouldShowExpiredEntitlementMessaging(nil))
print("[NetP Subscription] Reset expired entitlement messaging")

Task {
do {
try await NetworkProtectionCodeRedemptionCoordinator().exchange(accessToken: token)
print("[NetP Subscription] Exchanged access token for auth token successfully")
// todo - https://app.asana.com/0/0/1206541966681608/f
try NetworkProtectionKeychainTokenStore().store(NetworkProtectionKeychainTokenStore.makeToken(from: token))
print("[NetP Subscription] Stored derived NetP auth token")
} catch {
print("[NetP Subscription] Failed to exchange access token for auth token: \(error)")
print("[NetP Subscription] Failed to store derived NetP auth token: \(error)")
}
}
}

@objc
private func onNetworkProtectionAccountSignOut(_ notification: Notification) {
do {
try NetworkProtectionKeychainTokenStore().deleteToken()
print("[NetP Subscription] Deleted NetP auth token after signing out from Privacy Pro")
} catch {
print("[NetP Subscription] Failed to delete NetP auth token after signing out from Privacy Pro: \(error)")
}
}
#endif

@objc
Expand Down
9 changes: 6 additions & 3 deletions DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ extension NetworkProtectionKeychainTokenStore {
convenience init() {
self.init(keychainType: .dataProtection(.unspecified),
serviceName: "\(Bundle.main.bundleIdentifier!).authToken",
errorEvents: .networkProtectionAppDebugEvents)
errorEvents: .networkProtectionAppDebugEvents,
isSubscriptionEnabled: AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription))
}
}

Expand All @@ -69,7 +70,8 @@ extension NetworkProtectionCodeRedemptionCoordinator {
environment: settings.selectedEnvironment,
tokenStore: NetworkProtectionKeychainTokenStore(),
isManualCodeRedemptionFlow: isManualCodeRedemptionFlow,
errorEvents: .networkProtectionAppDebugEvents
errorEvents: .networkProtectionAppDebugEvents,
isSubscriptionEnabled: AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription)
)
}
}
Expand All @@ -95,7 +97,8 @@ extension NetworkProtectionLocationListCompositeRepository {
self.init(
environment: settings.selectedEnvironment,
tokenStore: NetworkProtectionKeychainTokenStore(),
errorEvents: .networkProtectionAppDebugEvents
errorEvents: .networkProtectionAppDebugEvents,
isSubscriptionEnabled: AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription)
)
}
}
Expand Down
9 changes: 7 additions & 2 deletions DuckDuckGo/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,11 @@ In addition to the details entered into this form, your app issue report will co
static let vpnFeedbackFormErrorMessage = NSLocalizedString("vpn.feedback-form.error.message", value: "Failed to share your feedback. Please try again.", comment: "Message for the alert when the VPN feedback form can't be submitted")
static let vpnFeedbackFormErrorAction = NSLocalizedString("vpn.feedback-form.error.action", value: "OK", comment: "Action title for the alert when the VPN feedback form can't be submitted")

static let vpnAccessRevokedAlertTitle = NSLocalizedString("vpn.access-revoked.alert.title", value: "VPN disconnected due to expired subscription", comment: "Alert title for the alert when the Privacy Pro subscription expires")
static let vpnAccessRevokedAlertMessage = NSLocalizedString("vpn.access-revoked.alert.message", value: "Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.", comment: "Alert message for the alert when the Privacy Pro subscription expiress")
static let vpnAccessRevokedAlertActionSubscribe = NSLocalizedString("vpn.access-revoked.alert.action.subscribe", value: "Subscribe", comment: "Primary action for the alert when the subscription expires")
static let vpnAccessRevokedAlertActionCancel = NSLocalizedString("vpn.access-revoked.alert.action.cancel", value: "Dismiss", comment: "Cancel action for the alert when the subscription expires")

// MARK: Notifications

public static let macWaitlistAvailableNotificationTitle = NSLocalizedString("mac-waitlist.available.notification.title", value: "DuckDuckGo for Mac is ready!", comment: "Title for the macOS waitlist notification")
Expand Down Expand Up @@ -922,10 +927,10 @@ But if you *do* want a peek under the hood, you can find more information about

static let networkProtectionNotificationPromptTitle = NSLocalizedString("network-protection.waitlist.notification-prompt-title", value: "Know the instant you're invited", comment: "Title for the alert to confirm enabling notifications")
static let networkProtectionNotificationPromptDescription = NSLocalizedString("network-protection.waitlist.notification-prompt-description", value: "Get a notification when your copy of Network Protection early access is ready.", comment: "Subtitle for the alert to confirm enabling notifications")

// MARK: Settings Screeen
public static let settingsTitle = NSLocalizedString("settings.title", value: "Settings", comment: "Title for the Settings View")

// General Section
public static let settingsSetDefault = NSLocalizedString("settings.default.browser", value: "Set as Default Browser", comment: "Settings screen cell text for setting the app as default browser")
public static let settingsAddToDock = NSLocalizedString("settings.add.to.dock", value: "Add App to Your Dock", comment: "Settings screen cell text for adding the app to the dock")
Expand Down
12 changes: 12 additions & 0 deletions DuckDuckGo/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,18 @@ But if you *do* want a peek under the hood, you can find more information about
/* Voice-search footer note with on-device privacy warning */
"voiceSearch.footer.note" = "Audio is processed on-device. It's not stored or shared with anyone, including DuckDuckGo.";

/* Cancel action for the alert when the subscription expires */
"vpn.access-revoked.alert.action.cancel" = "Dismiss";

/* Primary action for the alert when the subscription expires */
"vpn.access-revoked.alert.action.subscribe" = "Subscribe";

/* Alert message for the alert when the Privacy Pro subscription expiress */
"vpn.access-revoked.alert.message" = "Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.";

/* Alert title for the alert when the Privacy Pro subscription expires */
"vpn.access-revoked.alert.title" = "VPN disconnected due to expired subscription";

/* Title for the Cancel button of the VPN feedback form */
"vpn.feedback-form.button.cancel" = "Cancel";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Core
import Networking
import NetworkExtension
import NetworkProtection
import Subscription

// Initial implementation for initial Network Protection tests. Will be fleshed out with https://app.asana.com/0/1203137811378537/1204630829332227/f
final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider {
Expand Down Expand Up @@ -162,6 +163,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider {
params[PixelParameters.wireguardErrorCode] = String(code)
case .noAuthTokenFound:
pixelEvent = .networkProtectionNoAuthTokenFoundError
case .vpnAccessRevoked:
return
case .unhandledError(function: let function, line: let line, error: let error):
pixelEvent = .networkProtectionUnhandledError
params[PixelParameters.function] = function
Expand Down Expand Up @@ -201,24 +204,32 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider {
}

@objc init() {
#if ALPHA
let isSubscriptionEnabled = true
#else
let isSubscriptionEnabled = false
#endif
let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified),
errorEvents: nil)
errorEvents: nil,
isSubscriptionEnabled: isSubscriptionEnabled)
let errorStore = NetworkProtectionTunnelErrorStore()
let notificationsPresenter = NetworkProtectionUNNotificationPresenter()
let settings = VPNSettings(defaults: .networkProtectionGroupDefaults)
let nofificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator(
let notificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator(
settings: settings,
wrappee: notificationsPresenter
)
notificationsPresenter.requestAuthorization()
super.init(notificationsPresenter: nofificationsPresenterDecorator,
super.init(notificationsPresenter: notificationsPresenterDecorator,
tunnelHealthStore: NetworkProtectionTunnelHealthStore(),
controllerErrorStore: errorStore,
keychainType: .dataProtection(.unspecified),
tokenStore: tokenStore,
debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore),
providerEvents: Self.packetTunnelProviderEvents,
settings: settings)
settings: settings,
isSubscriptionEnabled: isSubscriptionEnabled,
entitlementCheck: Self.entitlementCheck)
startMonitoringMemoryPressureEvents()
observeServerChanges()
observeStatusChanges()
Expand Down Expand Up @@ -269,6 +280,15 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider {
.store(in: &cancellables)
}

private static func entitlementCheck() async -> Result<Bool, Error> {
let result = await AccountManager().hasEntitlement(for: .networkProtection)
switch result {
case .success(let hasEntitlement):
return .success(hasEntitlement)
case .failure(let error):
return .failure(error)
}
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio
content.title = UserText.networkProtectionNotificationsTitle
content.body = body

if #available(iOSApplicationExtension 15.0, *) {
if #available(iOS 15.0, *) {
content.interruptionLevel = .timeSensitive
content.relevanceScore = 0
}
Expand Down Expand Up @@ -105,6 +105,11 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio
func showSupersededNotification() {
}

func showExpiredEntitlementNotification() {
let content = notificationContent(body: UserText.networkProtectionEntitlementExpiredNotificationBody)
showNotification(.entitlement, content)
}

private func showNotification(_ identifier: NetworkProtectionNotificationIdentifier, _ content: UNNotificationContent) {
let request = UNNotificationRequest(identifier: identifier.rawValue, content: content, trigger: .none)

Expand Down
2 changes: 2 additions & 0 deletions PacketTunnelProvider/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@ final class UserText {
static let networkProtectionConnectionInterruptedNotificationBody = NSLocalizedString("network.protection.interrupted.notification.body", value: "Network Protection was interrupted. Attempting to reconnect now...", comment: "The body of the notification shown when Network Protection's connection is interrupted")

static let networkProtectionConnectionFailureNotificationBody = NSLocalizedString("network.protection.failure.notification.body", value: "Network Protection failed to connect. Please try again later.", comment: "The body of the notification shown when Network Protection fails to reconnect")

static let networkProtectionEntitlementExpiredNotificationBody = NSLocalizedString("network.protection.entitlement.expired.notification.body", value: "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.", comment: "The body of the notification when Privacy Pro subscription expired")
}
// swiftlint:enable line_length
3 changes: 3 additions & 0 deletions PacketTunnelProvider/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/* The body of the notification when Privacy Pro subscription expired */
"network.protection.entitlement.expired.notification.body" = "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.";

/* The body of the notification shown when Network Protection fails to reconnect */
"network.protection.failure.notification.body" = "Network Protection failed to connect. Please try again later.";

Expand Down
Loading