Skip to content

Commit

Permalink
Merge branch 'main' into sam/vpn-ui-improvements
Browse files Browse the repository at this point in the history
* main:
  Adds Privacy Pro Feature Flags (#717)
  macOS: Display error messaging for cancelled subscriptions  (#714)
  Handle subscription-related iOS use cases (#728)
  Remove hardcoded NetP endpoint (#734)
  Use History in Suggestions on iOS (#705)
  • Loading branch information
samsymons committed Mar 20, 2024
2 parents cce9119 + 0c73586 commit c43b58f
Show file tree
Hide file tree
Showing 28 changed files with 488 additions and 25 deletions.
26 changes: 26 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ let package = Package(
.library(name: "SecureStorage", targets: ["SecureStorage"]),
.library(name: "Subscription", targets: ["Subscription"]),
.library(name: "History", targets: ["History"]),
.library(name: "Suggestions", targets: ["Suggestions"]),
],
dependencies: [
.package(url: "https://github.com/duckduckgo/duckduckgo-autofill.git", exact: "10.1.0"),
Expand Down Expand Up @@ -112,6 +113,16 @@ let package = Package(
],
plugins: [swiftlintPlugin]
),
.target(
name: "Suggestions",
dependencies: [
"Common"
],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug))
],
plugins: [swiftlintPlugin]
),
.executableTarget(
name: "BookmarksTestDBBuilder",
dependencies: [
Expand Down Expand Up @@ -325,6 +336,7 @@ let package = Package(
.target(
name: "Subscription",
dependencies: [
"BrowserServicesKit",
"Common",
],
swiftSettings: [
Expand All @@ -341,6 +353,13 @@ let package = Package(
],
plugins: [swiftlintPlugin]
),
.testTarget(
name: "SuggestionsTests",
dependencies: [
"Suggestions",
],
plugins: [swiftlintPlugin]
),
.testTarget(
name: "BookmarksTests",
dependencies: [
Expand Down Expand Up @@ -490,6 +509,13 @@ let package = Package(
],
plugins: [swiftlintPlugin]
),
.testTarget(
name: "SubscriptionTests",
dependencies: [
"Subscription",
],
plugins: [swiftlintPlugin]
),
],
cxxLanguageStandard: .cxx11
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public enum PrivacyFeature: String {
case sync
case privacyDashboard
case history
case privacyPro
}

/// An abstraction to be implemented by any "subfeature" of a given `PrivacyConfiguration` feature.
Expand Down Expand Up @@ -112,3 +113,14 @@ public enum AutoconsentSubfeature: String, PrivacySubfeature {

case onByDefault
}

public enum PrivacyProSubfeature: String, Equatable, PrivacySubfeature {
public var parent: PrivacyFeature { .privacyPro }

case isLaunched
case isLaunchedStripe
case allowPurchase
case allowPurchaseStripe
case isLaunchedOverride
case isLaunchedOverrideStripe
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public actor NetworkProtectionEntitlementMonitor {

// MARK: - Init & deinit

init() {
public init() {
os_log("[+] %{public}@", log: .networkProtectionMemoryLog, type: .debug, String(describing: self))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,16 @@ public final class NetworkProtectionKeychainTokenStore: NetworkProtectionTokenSt
private let isSubscriptionEnabled: Bool
private let accessTokenProvider: () -> String?

private static var authTokenPrefix: String { "ddg:" }
public static var authTokenPrefix: String { "ddg:" }

public struct Defaults {
static let tokenStoreEntryLabel = "DuckDuckGo Network Protection Auth Token"
public static let tokenStoreService = "com.duckduckgo.networkprotection.authToken"
static let tokenStoreName = "com.duckduckgo.networkprotection.token"
}

/// - isSubscriptionEnabled: Controls whether the subscription access token is used to authenticate with the NetP backend
/// - accessTokenProvider: Defines how to actually retrieve the subscription access token
public init(keychainType: KeychainType,
serviceName: String = Defaults.tokenStoreService,
errorEvents: EventMapping<NetworkProtectionError>?,
Expand All @@ -74,13 +76,13 @@ public final class NetworkProtectionKeychainTokenStore: NetworkProtectionTokenSt
}
}

public func makeToken(from subscriptionAccessToken: String) -> String {
private func makeToken(from subscriptionAccessToken: String) -> String {
Self.authTokenPrefix + subscriptionAccessToken
}

public func fetchToken() throws -> String? {
if isSubscriptionEnabled, let authToken = accessTokenProvider() {
return makeToken(from: authToken)
if isSubscriptionEnabled {
return accessTokenProvider().map { makeToken(from: $0) }
}

do {
Expand Down Expand Up @@ -114,6 +116,3 @@ public final class NetworkProtectionKeychainTokenStore: NetworkProtectionTokenSt
errorEvents?.fire(error.networkProtectionError)
}
}

extension NetworkProtectionTokenStore {
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ final class NetworkProtectionBackendClient: NetworkProtectionClient {

enum Constants {
static let productionEndpoint = URL(string: "https://controller.netp.duckduckgo.com")!
static let stagingEndpoint = URL(string: "https://staging.netp.duckduckgo.com")!
static let stagingEndpoint = URL(string: "https://staging1.netp.duckduckgo.com")!
}

private enum DecoderError: Error {
Expand Down Expand Up @@ -170,13 +170,7 @@ final class NetworkProtectionBackendClient: NetworkProtectionClient {

init(environment: VPNSettings.SelectedEnvironment = .default, isSubscriptionEnabled: Bool) {
self.isSubscriptionEnabled = isSubscriptionEnabled

// todo - https://app.asana.com/0/0/1206470585910129/f
if isSubscriptionEnabled {
self.endpointURL = URL(string: "https://staging1.netp.duckduckgo.com")!
} else {
self.endpointURL = environment.endpointURL
}
self.endpointURL = environment.endpointURL
}

func getLocations(authToken: String) async -> Result<[NetworkProtectionLocation], NetworkProtectionClientError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public enum NetworkProtectionNotification: String {
case showConnectedNotification
case showIssuesNotResolvedNotification
case showVPNSupersededNotification
case showExpiredEntitlementNotification
case showTestNotification

// Server Selection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// UserDefaults+showEntitlementMessaging.swift
// UserDefaults+showMessaging.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
Expand Down Expand Up @@ -85,3 +85,24 @@ extension UserDefaults {
public extension Notification.Name {
static let vpnEntitlementMessagingDidChange = Notification.Name("com.duckduckgo.network-protection.entitlement-messaging-changed")
}

extension UserDefaults {
private var vpnEarlyAccessOverAlertAlreadyShownKey: String {
"vpnEarlyAccessOverAlertAlreadyShown"
}

@objc
public dynamic var vpnEarlyAccessOverAlertAlreadyShown: Bool {
get {
value(forKey: vpnEarlyAccessOverAlertAlreadyShownKey) as? Bool ?? false
}

set {
set(newValue, forKey: vpnEarlyAccessOverAlertAlreadyShownKey)
}
}

public func resetThankYouMessaging() {
removeObject(forKey: vpnEarlyAccessOverAlertAlreadyShownKey)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// UserDefaults+subscriptionOverrideEnabled.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 Combine
import Foundation

extension UserDefaults {
private var subscriptionOverrideEnabledKey: String {
"subscriptionOverrideEnabled"
}

public var subscriptionOverrideEnabled: Bool? {
get {
value(forKey: subscriptionOverrideEnabledKey) as? Bool
}

set {
set(newValue, forKey: subscriptionOverrideEnabledKey)
}
}

public func resetsubscriptionOverrideEnabled() {
removeObject(forKey: subscriptionOverrideEnabledKey)
}
}
4 changes: 4 additions & 0 deletions Sources/Subscription/Flows/PurchaseFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public struct SubscriptionOptions: Encodable {
let platform: String
let options: [SubscriptionOption]
let features: [SubscriptionFeature]
public static var empty: SubscriptionOptions {
let features = SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }
return SubscriptionOptions(platform: "macos", options: [], features: features)
}
}

public struct SubscriptionOption: Encodable {
Expand Down
76 changes: 76 additions & 0 deletions Sources/Subscription/SubscriptionFeatureAvailability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// SubscriptionFeatureAvailability.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 BrowserServicesKit

public protocol SubscriptionFeatureAvailability {
var isFeatureAvailable: Bool { get }
var isSubscriptionPurchaseAllowed: Bool { get }
}

public final class DefaultSubscriptionFeatureAvailability: SubscriptionFeatureAvailability {

private let privacyConfigurationManager: PrivacyConfigurationManaging
private let purchasePlatform: SubscriptionPurchaseEnvironment.Environment

public init(privacyConfigurationManager: PrivacyConfigurationManaging, purchasePlatform: SubscriptionPurchaseEnvironment.Environment) {
self.privacyConfigurationManager = privacyConfigurationManager
self.purchasePlatform = purchasePlatform
}

public var isFeatureAvailable: Bool {
isInternalUser || isSubscriptionLaunched || isSubscriptionLaunchedOverride
}

public var isSubscriptionPurchaseAllowed: Bool {
let isPurchaseAllowed: Bool

switch purchasePlatform {
case .appStore:
isPurchaseAllowed = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.allowPurchase)
case .stripe:
isPurchaseAllowed = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.allowPurchaseStripe)
}

return isPurchaseAllowed || isInternalUser
}

// MARK: - Conditions

private var isInternalUser: Bool {
privacyConfigurationManager.internalUserDecider.isInternalUser
}

private var isSubscriptionLaunched: Bool {
switch purchasePlatform {
case .appStore:
privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.isLaunched)
case .stripe:
privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.isLaunchedStripe)
}
}

private var isSubscriptionLaunchedOverride: Bool {
switch purchasePlatform {
case .appStore:
privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.isLaunchedOverride)
case .stripe:
privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.isLaunchedOverrideStripe)
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit c43b58f

Please sign in to comment.