diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fd1f901ac4..c6ccc166f1 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14399,7 +14399,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 82.2.3; + version = 82.3.0; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 658d8304f1..1d69658009 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "f2936a65ef7685fe9c39d6a996c8391cdb3d95ff", - "version" : "82.2.3" + "revision" : "c4d5f6df0340f0a5c109dcded9801ab676de7db5", + "version" : "82.3.0" } }, { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index b73b218a34..16cf31c323 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -25,9 +25,11 @@ import Common extension NetworkProtectionDeviceManager { static func create() -> NetworkProtectionDeviceManager { + let settings = TunnelSettings(defaults: .shared) + let networkClient = NetworkProtectionBackendClient(environment: settings.selectedEnvironment) let keyStore = NetworkProtectionKeychainKeyStore() let tokenStore = NetworkProtectionKeychainTokenStore() - return NetworkProtectionDeviceManager(tokenStore: tokenStore, keyStore: keyStore, errorEvents: .networkProtectionAppDebugEvents) + return NetworkProtectionDeviceManager(networkClient: networkClient, tokenStore: tokenStore, keyStore: keyStore, errorEvents: .networkProtectionAppDebugEvents) } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index a9d61924a2..6ff1f39b81 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -29,6 +29,8 @@ import SwiftUI @MainActor final class NetworkProtectionDebugMenu: NSMenu { + private let environmentMenu = NSMenu() + private let preferredServerMenu: NSMenu private let preferredServerAutomaticItem = NSMenuItem(title: "Automatic", action: #selector(NetworkProtectionDebugMenu.setSelectedServer)) @@ -87,6 +89,9 @@ final class NetworkProtectionDebugMenu: NSMenu { NSMenuItem(title: "Onboarding") .submenu(NetworkProtectionOnboardingMenu()) + NSMenuItem(title: "Environment") + .submenu(environmentMenu) + NSMenuItem(title: "Preferred Server").submenu(preferredServerMenu) NSMenuItem(title: "Registration Key") { @@ -148,6 +153,7 @@ final class NetworkProtectionDebugMenu: NSMenu { } preferredServerMenu.autoenablesItems = false + populateNetworkProtectionEnvironmentListMenuItems() populateNetworkProtectionServerListMenuItems() populateNetworkProtectionRegistrationKeyValidityMenuItems() @@ -293,6 +299,13 @@ final class NetworkProtectionDebugMenu: NSMenu { // MARK: Populating Menu Items + private func populateNetworkProtectionEnvironmentListMenuItems() { + environmentMenu.items = [ + NSMenuItem(title: "Production", action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), + NSMenuItem(title: "Staging", action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), + ] + } + private func populateNetworkProtectionServerListMenuItems() { let networkProtectionServerStore = NetworkProtectionServerListFileSystemStore(errorEvents: nil) let servers = (try? networkProtectionServerStore.storedNetworkProtectionServerList()) ?? [] @@ -381,12 +394,26 @@ final class NetworkProtectionDebugMenu: NSMenu { // MARK: - Menu State Update override func update() { + updateEnvironmentMenu() updatePreferredServerMenu() updateRekeyValidityMenu() updateNetworkProtectionMenuItemsState() updateNetworkProtectionItems() } + private func updateEnvironmentMenu() { + let selectedEnvironment = settings.selectedEnvironment + + switch selectedEnvironment { + case .production: + environmentMenu.items.first?.state = .on + environmentMenu.items.last?.state = .off + case .staging: + environmentMenu.items.first?.state = .off + environmentMenu.items.last?.state = .on + } + } + private func updatePreferredServerMenu() { let selectedServer = settings.selectedServer @@ -533,6 +560,28 @@ final class NetworkProtectionDebugMenu: NSMenu { return "" } } + + // MARK: Environment + @objc func setSelectedEnvironment(_ menuItem: NSMenuItem) { + let title = menuItem.title + let selectedEnvironment: TunnelSettings.SelectedEnvironment + + if title == "Staging" { + selectedEnvironment = .staging + } else { + selectedEnvironment = .production + } + + settings.selectedEnvironment = selectedEnvironment + + Task { + _ = try await NetworkProtectionDeviceManager.create().refreshServerList() + await MainActor.run { + populateNetworkProtectionServerListMenuItems() + } + settings.selectedServer = .automatic + } + } } extension NetworkProtectionDebugMenu: NSMenuDelegate { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift index 4c2babed28..5dca24962f 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift @@ -63,7 +63,7 @@ final class NetworkProtectionDebugUtilities { } func removeSystemExtensionAndAgents() async throws { - await networkProtectionFeatureDisabler.resetAllStateForVPNApp(uninstallSystemExtension: true) + await networkProtectionFeatureDisabler.removeSystemExtension() networkProtectionFeatureDisabler.disableLoginItems() } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index bc5ac23bc7..916fa99d05 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -163,7 +163,8 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle case .setExcludeLocalNetworks(let excludeLocalNetworks): try await handleSetExcludeLocalNetworks(excludeLocalNetworks) case .setRegistrationKeyValidity, - .setSelectedServer: + .setSelectedServer, + .setSelectedEnvironment: // Intentional no-op as this is handled by the extension break } @@ -385,6 +386,7 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString options[NetworkProtectionOptionKey.authToken] = try tokenStore.fetchToken() as NSString? + options[NetworkProtectionOptionKey.selectedEnvironment] = settings.selectedEnvironment.rawValue as? NSString options[NetworkProtectionOptionKey.selectedServer] = settings.selectedServer.stringValue as? NSString if case .custom(let keyValidity) = settings.registrationKeyValidity { diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift index 6f21a07abc..6458ed80e4 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift @@ -61,21 +61,26 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling /// func disable(keepAuthToken: Bool, uninstallSystemExtension: Bool) { Task { + // To disable NetP we need the login item to be running + // This should be fine though as we'll disable them further down below + enableLoginItems() + + // Allow some time for the login items to fully launch + try? await Task.sleep(interval: 0.5) + unpinNetworkProtection() if uninstallSystemExtension { - await resetAllStateForVPNApp(uninstallSystemExtension: uninstallSystemExtension) + await removeSystemExtension() } - disableLoginItems() - - await resetNetworkExtensionState() + await removeVPNConfiguration() - // ☝️ Take care of resetting all state within the extension first, and wait half a second + // We want to give some time for the login item to reset state before disabling it try? await Task.sleep(interval: 0.5) - // 👇 And only afterwards turn off the tunnel and remove it from preferences - await stopTunnel() + disableLoginItems() + resetUserDefaults() if !keepAuthToken { @@ -84,12 +89,16 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling } } + private func enableLoginItems() { + loginItemsManager.enableLoginItems(LoginItemsManager.networkProtectionLoginItems, log: log) + } + func disableLoginItems() { loginItemsManager.disableLoginItems(LoginItemsManager.networkProtectionLoginItems) } - func resetAllStateForVPNApp(uninstallSystemExtension: Bool) async { - await ipcClient.resetAll(uninstallSystemExtension: uninstallSystemExtension) + func removeSystemExtension() async { + await ipcClient.debugCommand(.removeSystemExtension) #if NETP_SYSTEM_EXTENSION userDefaults.networkProtectionOnboardingStatusRawValue = OnboardingStatus.default.rawValue @@ -104,15 +113,11 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling try NetworkProtectionKeychainTokenStore().deleteToken() } - private func resetNetworkExtensionState() async { - if let activeSession = try? await ConnectionSessionUtilities.activeSession() { - try? activeSession.sendProviderMessage(.resetAllState) { - os_log("Status was reset in the extension", log: self.log) - } - } - } + private func removeVPNConfiguration() async { + // Remove the agent VPN configuration + await ipcClient.debugCommand(.removeVPNConfiguration) - private func stopTunnel() async { + // Remove the legacy (local) configuration let tunnels = try? await NETunnelProviderManager.loadAllFromPreferences() if let tunnels = tunnels { diff --git a/DuckDuckGoVPN/TunnelControllerIPCService.swift b/DuckDuckGoVPN/TunnelControllerIPCService.swift index 7d6f8263dc..3210cf2be2 100644 --- a/DuckDuckGoVPN/TunnelControllerIPCService.swift +++ b/DuckDuckGoVPN/TunnelControllerIPCService.swift @@ -108,10 +108,25 @@ extension TunnelControllerIPCService: IPCServerInterface { } func debugCommand(_ command: DebugCommand) async { - guard let activeSession = try? await ConnectionSessionUtilities.activeSession(networkExtensionBundleID: Bundle.main.networkExtensionBundleID) else { - return + if let activeSession = try? await ConnectionSessionUtilities.activeSession(networkExtensionBundleID: Bundle.main.networkExtensionBundleID) { + + // First give a chance to the extension to process the command, since some commands + // may remove the VPN configuration or deactivate the extension. + try? await activeSession.sendProviderRequest(.debugCommand(command)) } - try? await activeSession.sendProviderRequest(.debugCommand(command)) + switch command { + case .removeSystemExtension: + await VPNConfigurationManager().removeVPNConfiguration() + try? await networkExtensionController.deactivateSystemExtension() + case .expireRegistrationKey: + // Intentional no-op: handled by the extension + break + case .sendTestNotification: + // Intentional no-op: handled by the extension + break + case .removeVPNConfiguration: + await VPNConfigurationManager().removeVPNConfiguration() + } } } diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index de83abd13b..c8509fc12a 100644 --- a/LocalPackages/Account/Package.swift +++ b/LocalPackages/Account/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["Account"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.2.3"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.3.0"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 5ef07e682f..807e3ddc3d 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.2.3"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.3.0"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index eca1d86fe0..c87596f894 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,7 +31,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.2.3"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.3.0"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift index 7479b0e8cf..dd34206618 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift @@ -95,30 +95,22 @@ extension TunnelControllerIPCClient: IPCServerInterface { }) } - public func resetAll(uninstallSystemExtension: Bool) async { - xpc.execute(call: { server in - Task { - await server.resetAll(uninstallSystemExtension: uninstallSystemExtension) - } - }, xpcReplyErrorHandler: { _ in - // Intentional no-op as there's no completion block - // If you add a completion block, please remember to call it here too! - }) - } - public func debugCommand(_ command: DebugCommand) async { guard let payload = try? JSONEncoder().encode(command) else { return } - xpc.execute(call: { server in - Task { - await server.debugCommand(payload) - } - }, xpcReplyErrorHandler: { _ in - // Intentional no-op as there's no completion block - // If you add a completion block, please remember to call it here too! - }) + await withCheckedContinuation { continuation in + xpc.execute(call: { server in + server.debugCommand(payload) { + continuation.resume() + } + }, xpcReplyErrorHandler: { _ in + // Intentional no-op as there's no completion block + // If you add a completion block, please remember to call it here too! + continuation.resume() + }) + } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift index ccabc5f144..8ef52836e8 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift @@ -37,10 +37,6 @@ public protocol IPCServerInterface: AnyObject { /// func stop() - /// Resets all of Network Protection's state that's handled by the server - /// - func resetAll(uninstallSystemExtension: Bool) async - /// Debug commands /// func debugCommand(_ command: DebugCommand) async @@ -67,13 +63,9 @@ protocol XPCServerInterface { /// func stop() - /// Resets all of Network Protection's state that's handled by the server - /// - func resetAll(uninstallSystemExtension: Bool) async - /// Debug commands /// - func debugCommand(_ payload: Data) async + func debugCommand(_ payload: Data, completion: @escaping () -> Void) } public final class TunnelControllerIPCServer { @@ -156,15 +148,15 @@ extension TunnelControllerIPCServer: XPCServerInterface { serverDelegate?.stop() } - func resetAll(uninstallSystemExtension: Bool) async { - await serverDelegate?.resetAll(uninstallSystemExtension: uninstallSystemExtension) - } - - func debugCommand(_ payload: Data) async { + func debugCommand(_ payload: Data, completion: @escaping () -> Void) { guard let command = try? JSONDecoder().decode(DebugCommand.self, from: payload) else { + completion() return } - await serverDelegate?.debugCommand(command) + Task { + await serverDelegate?.debugCommand(command) + completion() + } } }