Skip to content

Commit

Permalink
Add feature-flagged VPN Settings Pane. (#1858)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/0/1205958731729755/f

BSK PR: duckduckgo/BrowserServicesKit#565
iOS PR: duckduckgo/iOS#2165

Description

Adds a feature-flagged VPN Settings Pane.
  • Loading branch information
diegoreymendez authored Nov 28, 2023
1 parent 27a22c1 commit a6141af
Show file tree
Hide file tree
Showing 55 changed files with 948 additions and 250 deletions.
49 changes: 30 additions & 19 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"state" : {
"revision" : "543e1d7ed9b5743d4e6b0ebe18a5fbf8f1441f02",
"version" : "85.1.1"
"revision" : "1331652ad0dc21c23b495b4a9a42e2a0eb44859d",
"version" : "86.0.0"
}
},
{
Expand Down
2 changes: 2 additions & 0 deletions DuckDuckGo/Application/URLEventHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ final class URLEventHandler {
Task {
await WindowControllersManager.shared.showNetworkProtectionStatus()
}
case AppLaunchCommand.showSettings.launchURL:
WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn)
default:
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Info-Subtle-16.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
12 changes: 12 additions & 0 deletions DuckDuckGo/Assets.xcassets/Images/VPN.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "VPN-Multicolor-16 1.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ extension UserText {
// MARK: - Navigation Bar Status View

static let networkProtectionNavBarStatusViewShareFeedback = NSLocalizedString("network.protection.navbar.status.view.share.feedback", value: "Share Feedback...", comment: "Menu item for 'Share Feedback' in the Network Protection status view that's shown in the navigation bar")
static let networkProtectionNavBarStatusMenuVPNSettings = NSLocalizedString("network.protection.status.menu.vpn.settings", value: "VPN Settings...", comment: "The status menu 'VPN Settings' menu item")

// MARK: - System Extension Installation Messages

Expand Down
49 changes: 49 additions & 0 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct UserText {
static let deleteBookmark = NSLocalizedString("delete-bookmark", value: "Delete Bookmark", comment: "Delete Bookmark button")
static let removeFavorite = NSLocalizedString("remove-favorite", value: "Remove Favorite", comment: "Remove Favorite button")
static let quit = NSLocalizedString("quit", value: "Quit", comment: "Quit button")
static let uninstall = NSLocalizedString("uninstall", value: "Uninstall", comment: "Uninstall button")
static let dontQuit = NSLocalizedString("dont.quit", value: "Don’t Quit", comment: "Don’t Quit button")
static let next = NSLocalizedString("next", value: "Next", comment: "Next button")
static let pasteAndGo = NSLocalizedString("paste.and.go", value: "Paste & Go", comment: "Paste & Go button")
Expand Down Expand Up @@ -299,6 +300,53 @@ struct UserText {
static let autoconsentCheckboxTitle = NSLocalizedString("autoconsent.checkbox.title", value: "Automatically handle cookie pop-ups", comment: "Autoconsent settings checkbox title")
static let autoconsentExplanation = NSLocalizedString("autoconsent.explanation", value: "DuckDuckGo will try to select the most private settings available and hide these pop-ups for you.", comment: "Autoconsent feature explanation in settings")

// VPN Setting Titles

static let vpnGeneralTitle = NSLocalizedString("vpn.general.title", value: "General", comment: "General section title in VPN settings")
static let vpnNotificationsSettingsTitle = NSLocalizedString("vpn.notifications.settings.title", value: "Notifications", comment: "Notifications section title in VPN settings")
static let vpnAdvancedSettingsTitle = NSLocalizedString("vpn.advanced.settings.title", value: "Advanced", comment: "VPN Advanced section title in VPN settings")

// VPN Settings

static let vpnConnectOnLoginSettingTitle = NSLocalizedString(
"vpn.setting.title.connect.on.login",
value: "Connect on login",
comment: "Connect on Login setting title")
static let vpnShowInMenuBarSettingTitle = NSLocalizedString(
"vpn.setting.title.connect.on.login",
value: "Show VPN in menu bar",
comment: "Display VPN status in the menu bar.")
static let vpnAlwaysOnSettingDescription = NSLocalizedString(
"vpn.setting.description.always.on",
value: "Automatically restores the VPN connection after interruption. For your security, this setting cannot be disabled.",
comment: "Always ON setting description")
static let vpnExcludeLocalNetworksSettingTitle = NSLocalizedString(
"vpn.setting.title.exclude.local.networks",
value: "Exclude local networks",
comment: "Exclude Local Networks setting title")
static let vpnExcludeLocalNetworksSettingDescription = NSLocalizedString(
"vpn.setting.description.exclude.local.networks",
value: "Bypass the VPN for local network connections, like to a printer.",
comment: "Exclude Local Networks setting description")
static let vpnSecureDNSSettingDescription = NSLocalizedString(
"vpn.setting.description.secure.dns",
value: "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit.",
comment: "Secure DNS setting description")
static let uninstallVPNButtonTitle = NSLocalizedString(
"vpn.button.title.uninstall.vpn",
value: "Uninstall DuckDuckGo VPN...",
comment: "Uninstall VPN button title")

// VPN Settings Alerts

static let uninstallVPNAlertTitle = NSLocalizedString("vpn.uninstall.alert.title", value: "Are you sure you want to uninstall the VPN?", comment: "Alert title when the user selects to uninstall our VPN")
static let uninstallVPNInformativeText = NSLocalizedString(
"vpn.uninstall.alert.informative.text",
value: "Uninstalling the DuckDuckGo VPN will disconnect the VPN and remove it from your device.",
comment: "Informative text for the alert that comes up when the user decides to uninstall our VPN")

// Misc

static let duckPlayerSettingsTitle = NSLocalizedString("duck-player.title", value: "Duck Player", comment: "Private YouTube Player settings title")
static let duckPlayerAlwaysOpenInPlayer = NSLocalizedString("duck-player.always-open-in-player", value: "Always open YouTube videos in Duck Player", comment: "Private YouTube Player option")
static let duckPlayerShowPlayerButtons = NSLocalizedString("duck-player.show-buttons", value: "Show option to use Duck Player over YouTube previews on hover", comment: "Private YouTube Player option")
Expand Down Expand Up @@ -434,6 +482,7 @@ struct UserText {
static let defaultBrowser = NSLocalizedString("preferences.default-browser", value: "Default Browser", comment: "Show default browser preferences")
static let appearance = NSLocalizedString("preferences.appearance", value: "Appearance", comment: "Show appearance preferences")
static let privacy = NSLocalizedString("preferences.privacy", value: "Privacy", comment: "Show privacy browser preferences")
static let vpn = NSLocalizedString("preferences.vpn", value: "VPN", comment: "Show VPN preferences")
static let duckPlayer = NSLocalizedString("preferences.duck-player", value: "Duck Player", comment: "Show Duck Player browser preferences")
static let about = NSLocalizedString("preferences.about", value: "About", comment: "Show about screen")

Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import LoginItems

extension LoginItem {

static let dbpBackgroundAgent = LoginItem(bundleId: Bundle.main.dbpBackgroundAgentBundleId, log: .dbp)
static let dbpBackgroundAgent = LoginItem(bundleId: Bundle.main.dbpBackgroundAgentBundleId, defaults: .dbp, log: .dbp)

}

Expand Down
13 changes: 12 additions & 1 deletion DuckDuckGo/MessageViews/PopoverMessageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,25 @@ final class PopoverMessageViewController: NSHostingController<PopoverMessageView
scheduleAutoDismissTimer()
}

func show(onParent parent: NSViewController, rect: NSRect, of view: NSView) {
// Set the content size to match the SwiftUI view's intrinsic size
self.preferredContentSize = self.view.fittingSize

parent.present(self,
asPopoverRelativeTo: rect,
of: view,
preferredEdge: .maxY,
behavior: .applicationDefined)
}

func show(onParent parent: NSViewController, relativeTo view: NSView) {
let rect = view.bounds

// Set the content size to match the SwiftUI view's intrinsic size
self.preferredContentSize = self.view.fittingSize

parent.present(self,
asPopoverRelativeTo: rect,
asPopoverRelativeTo: self.view.bounds,
of: view,
preferredEdge: .maxY,
behavior: .applicationDefined)
Expand Down
17 changes: 16 additions & 1 deletion DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,22 @@ final class NavigationBarViewController: NSViewController {
selector: #selector(showAutoconsentFeedback(_:)),
name: AutoconsentUserScript.Constants.newSitePopupHidden,
object: nil)

#if NETWORK_PROTECTION
NotificationCenter.default.addObserver(self,
selector: #selector(showVPNUninstalledFeedback(_:)),
name: NetworkProtectionFeatureDisabler.vpnUninstalledNotificationName,
object: nil)
#endif
}

@objc private func showVPNUninstalledFeedback(_ sender: Notification) {
guard view.window?.isKeyWindow == true else { return }

DispatchQueue.main.async {
let viewController = PopoverMessageViewController(message: "Network Protection was uninstalled")
viewController.show(onParent: self, relativeTo: self.optionsButton)
}
}

@objc private func showPrivateEmailCopiedToClipboard(_ sender: Notification) {
Expand All @@ -427,7 +443,6 @@ final class NavigationBarViewController: NSViewController {
let viewController = PopoverMessageViewController(message: UserText.privateEmailCopiedToClipboard)
viewController.show(onParent: self, relativeTo: self.optionsButton)
}

}

@objc private func showFireproofingFeedback(_ sender: Notification) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ extension AppLaunchCommand {
case .justOpen: return "justOpen"
case .shareFeedback: return "shareFeedback"
case .showStatus: return "showStatus"
case .showSettings: return "showSettings"
case .enableOnDemand: return "enableOnDemand"
}
}
Expand All @@ -48,7 +49,7 @@ public final class AppLauncher: AppLaunching {

public func launchApp(withCommand command: AppLaunchCommand) async {
let configuration = NSWorkspace.OpenConfiguration()
configuration.allowsRunningApplicationSubstitution = false
configuration.allowsRunningApplicationSubstitution = command.allowsRunningApplicationSubstitution
configuration.arguments = [command.rawValue]

if command.hideApp {
Expand Down Expand Up @@ -85,11 +86,22 @@ extension AppLaunchCommand {
return "https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945"
case .showStatus:
return "networkprotection://show-status"
case .showSettings:
return "networkprotection://show-settings"
default:
return nil
}
}

var allowsRunningApplicationSubstitution: Bool {
switch self {
case .showSettings:
return true
default:
return false
}
}

var helperAppPath: String? {
switch self {
case .startVPN:
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,19 @@ extension UserDefaults {
}
}

var networkProtectionOnboardingStatus: OnboardingStatus {
get {
OnboardingStatus(rawValue: networkProtectionOnboardingStatusRawValue) ?? .default
}

set {
networkProtectionOnboardingStatusRawValue = newValue.rawValue
}
}

var networkProtectionOnboardingStatusPublisher: AnyPublisher<OnboardingStatus, Never> {
// It's important to subscribe to the publisher for the raw value, since this
// is the way to get KVO when the UserDefaults are modified by another process.
publisher(for: \.networkProtectionOnboardingStatusRawValue).map { value in
OnboardingStatus(rawValue: value) ?? .default
}.eraseToAnyPublisher()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ extension EventMapping where Event == NetworkProtectionError {
domainEvent = .networkProtectionKeychainDeleteError(status: status)
case .noAuthTokenFound:
domainEvent = .networkProtectionNoAuthTokenFoundError
case
.noServerRegistrationInfo,
case .noServerRegistrationInfo,
.couldNotSelectClosestServer,
.couldNotGetPeerPublicKey,
.couldNotGetPeerHostName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import LoginItems

extension LoginItem {
static let vpnMenu = LoginItem(bundleId: Bundle.main.vpnMenuAgentBundleId, log: .networkProtection)
static let vpnMenu = LoginItem(bundleId: Bundle.main.vpnMenuAgentBundleId, defaults: .netP, log: .networkProtection)
#if NETP_SYSTEM_EXTENSION
static let notificationsAgent = LoginItem(bundleId: Bundle.main.notificationsAgentBundleId, log: .networkProtection)
static let notificationsAgent = LoginItem(bundleId: Bundle.main.notificationsAgentBundleId, defaults: .netP, log: .networkProtection)
#endif

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import Common
extension NetworkProtectionDeviceManager {

static func create() -> NetworkProtectionDeviceManager {
let settings = TunnelSettings(defaults: .netP)
let settings = VPNSettings(defaults: .netP)
let keyStore = NetworkProtectionKeychainKeyStore()
let tokenStore = NetworkProtectionKeychainTokenStore()
return NetworkProtectionDeviceManager(environment: settings.selectedEnvironment, tokenStore: tokenStore, keyStore: keyStore, errorEvents: .networkProtectionAppDebugEvents)
Expand All @@ -34,7 +34,7 @@ extension NetworkProtectionDeviceManager {

extension NetworkProtectionCodeRedemptionCoordinator {
convenience init() {
let settings = TunnelSettings(defaults: .netP)
let settings = VPNSettings(defaults: .netP)
self.init(environment: settings.selectedEnvironment,
tokenStore: NetworkProtectionKeychainTokenStore(),
errorEvents: .networkProtectionAppDebugEvents)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ final class NetworkProtectionAppEvents {
// MARK: - Legacy Login Item and Extension

private func removeLegacyLoginItemAndVPNConfiguration() async {
LoginItem(bundleId: legacyAgentBundleID).forceStop()
LoginItem(bundleId: legacyAgentBundleID, defaults: .netP).forceStop()

let tunnels = try? await NETunnelProviderManager.loadAllFromPreferences()
let tunnel = tunnels?.first {
Expand Down
Loading

0 comments on commit a6141af

Please sign in to comment.