diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e295b7b2a8..23658cf31a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1786,6 +1786,10 @@ 7B8594192B5B26230007EB3E /* UDSHelper in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8594182B5B26230007EB3E /* UDSHelper */; }; 7B874BD62CBD7E8D009C6C63 /* VPNProxyExtension.appex in Embed Network Extensions */ = {isa = PBXBuildFile; fileRef = 7BDA36E52B7E037100AD5388 /* VPNProxyExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7B8DB31A2B504D7500EC16DA /* VPNAppEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */; }; + 7B8FDD202CDD88C500720907 /* FeatureFlags in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8FDD1F2CDD88C500720907 /* FeatureFlags */; }; + 7B8FDD222CDD88CB00720907 /* FeatureFlags in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8FDD212CDD88CB00720907 /* FeatureFlags */; }; + 7B8FDD242CDD88D300720907 /* FeatureFlags in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8FDD232CDD88D300720907 /* FeatureFlags */; }; + 7B8FDD262CDD88D900720907 /* FeatureFlags in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8FDD252CDD88D900720907 /* FeatureFlags */; }; 7B934C412A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7B97CD592B7E0B57004FEF43 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B97CD582B7E0B57004FEF43 /* NetworkProtectionProxy */; }; 7B97CD5B2B7E0B85004FEF43 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 7B97CD5A2B7E0B85004FEF43 /* Common */; }; @@ -3041,8 +3045,6 @@ EEC8EB3E2982CA3B0065AA39 /* JSAlertViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC111E5294D06290086524F /* JSAlertViewModel.swift */; }; EEC8EB3F2982CA440065AA39 /* JSAlert.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EEC111E3294D06020086524F /* JSAlert.storyboard */; }; EEC8EB402982CD550065AA39 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; }; - EECE10E529DD77E60044D027 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECE10E429DD77E60044D027 /* FeatureFlag.swift */; }; - EECE10E629DD77E60044D027 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECE10E429DD77E60044D027 /* FeatureFlag.swift */; }; EED4D3D82C874AE200C79EEA /* PopoverInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED4D3D72C874AE200C79EEA /* PopoverInfoViewController.swift */; }; EED4D3D92C874AE200C79EEA /* PopoverInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED4D3D72C874AE200C79EEA /* PopoverInfoViewController.swift */; }; EED4D3DF2C8A298D00C79EEA /* AutofillPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED4D3DE2C8A298D00C79EEA /* AutofillPixelEvent.swift */; }; @@ -4046,6 +4048,7 @@ 7B7F5D232C52725A00826256 /* AddExcludedDomainButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExcludedDomainButtonsView.swift; sourceTree = ""; }; 7B8594172B5B25FB0007EB3E /* UDSHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = UDSHelper; sourceTree = ""; }; 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAppEventsHandler.swift; sourceTree = ""; }; + 7B8FDD1E2CDD877000720907 /* FeatureFlags */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FeatureFlags; sourceTree = ""; }; 7B9167A82C09E88800322310 /* AppLauncher */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = AppLauncher; sourceTree = ""; }; 7B934C3D2A866CFF00FC8F9C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; }; 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionShared.swift"; sourceTree = ""; }; @@ -4860,7 +4863,6 @@ EEC4A66C2B2C894F00F7C0AA /* VPNLocationPreferenceItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationPreferenceItemModel.swift; sourceTree = ""; }; EEC4A6702B2C90AB00F7C0AA /* VPNLocationPreferenceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationPreferenceItem.swift; sourceTree = ""; }; EEC7BE2D2BC6C09400F86835 /* AddressBarKeyboardShortcutsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBarKeyboardShortcutsTests.swift; sourceTree = ""; }; - EECE10E429DD77E60044D027 /* FeatureFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlag.swift; sourceTree = ""; }; EED4D3D72C874AE200C79EEA /* PopoverInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverInfoViewController.swift; sourceTree = ""; }; EED4D3DE2C8A298D00C79EEA /* AutofillPixelEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPixelEvent.swift; sourceTree = ""; }; EED735352BB46B6000F173D6 /* AutocompleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteTests.swift; sourceTree = ""; }; @@ -4918,6 +4920,7 @@ 4BF97AD32B43C43F00EB4240 /* NetworkProtectionUI in Frameworks */, 7B1459572B7D43E500047F2C /* NetworkProtectionProxy in Frameworks */, B6F7128229F6820A00594A45 /* QuickLookUI.framework in Frameworks */, + 7B8FDD262CDD88D900720907 /* FeatureFlags in Frameworks */, CBECDB902CDBD631005B8B87 /* BrokenSitePrompt in Frameworks */, 9FF521482BAA909C00B9819B /* Lottie in Frameworks */, CBECDB8E2CDBD62C005B8B87 /* PageRefreshMonitor in Frameworks */, @@ -5029,6 +5032,7 @@ 4B41EDAB2B1544B2001EEDF4 /* LoginItems in Frameworks */, 7B00997D2B6508B700FE7C31 /* NetworkProtectionProxy in Frameworks */, 7BEEA5122AD1235B00A9E72B /* NetworkProtectionIPC in Frameworks */, + 7B8FDD202CDD88C500720907 /* FeatureFlags in Frameworks */, 7BA7CC5F2AD1210C0042E5CE /* Networking in Frameworks */, 02A15D942C88D78F001A4237 /* Persistence in Frameworks */, 7BEEA5162AD1236E00A9E72B /* NetworkProtectionUI in Frameworks */, @@ -5059,6 +5063,7 @@ EE7295EF2A545C12008C0991 /* NetworkProtection in Frameworks */, 4B2D067F2A1334D700DE1F49 /* NetworkProtectionUI in Frameworks */, 4BA7C4DD2B3F64E500AFE511 /* LoginItems in Frameworks */, + 7B8FDD222CDD88CB00720907 /* FeatureFlags in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5182,6 +5187,7 @@ D6BC8AC62C5A95AA0025375B /* DuckPlayer in Frameworks */, 987799ED299998B1005D8EB6 /* Bookmarks in Frameworks */, 1E950E3F2912A10D0051A99B /* ContentBlocking in Frameworks */, + 7B8FDD242CDD88D300720907 /* FeatureFlags in Frameworks */, 31A3A4E32B0C115F0021063C /* DataBrokerProtection in Frameworks */, 378F44E429B4BDE900899924 /* SwiftUIExtensions in Frameworks */, F1D43AF32B98E47800BAB743 /* BareBonesBrowserKit in Frameworks */, @@ -5276,29 +5282,12 @@ path = FeatureFlagging; sourceTree = ""; }; - 1D36E651298A84F600AA485D /* FeatureFlagging */ = { - isa = PBXGroup; - children = ( - 1D36E655298AA33400AA485D /* Model */, - 1D36E656298AA33C00AA485D /* Services */, - ); - path = FeatureFlagging; - sourceTree = ""; - }; - 1D36E655298AA33400AA485D /* Model */ = { - isa = PBXGroup; - children = ( - EECE10E429DD77E60044D027 /* FeatureFlag.swift */, - ); - path = Model; - sourceTree = ""; - }; - 1D36E656298AA33C00AA485D /* Services */ = { + 1D36E651298A84F600AA485D /* InternalUserDecider */ = { isa = PBXGroup; children = ( 1D36E657298AA3BA00AA485D /* InternalUserDeciderStore.swift */, ); - path = Services; + path = InternalUserDecider; sourceTree = ""; }; 1D3B1AB7293405F5006F4388 /* PasswordManagers */ = { @@ -5820,6 +5809,7 @@ 7B9167A82C09E88800322310 /* AppLauncher */, 378E279D2970217400FCADA2 /* BuildToolPlugins */, 3192A2702A4C4E330084EA89 /* DataBrokerProtection */, + 7B8FDD1E2CDD877000720907 /* FeatureFlags */, 9DF2DB592C73B52F0025F43C /* Freemium */, 9DB6E7222AA0DA7A00A17F3C /* LoginItems */, 7B25FE322AD12C990012AFAB /* NetworkProtectionMac */, @@ -7768,7 +7758,6 @@ 4B65143C26392483005B46EB /* Email */, B68412192B6A16030092F66A /* ErrorPage */, AA5FA695275F823900DCE9C9 /* Favicons */, - 1D36E651298A84F600AA485D /* FeatureFlagging */, AA3863C227A1E1C000749AB5 /* Feedback */, 8556A60C256C15C60092FA9D /* FileDownload */, 85A0115D25AF1C4700FA6A0C /* FindInPage */, @@ -7781,6 +7770,7 @@ EED4D3D62C87480B00C79EEA /* InfoViews */, 56CEE9092B7A66C500CF10AA /* Info.plist */, 56CEE90D2B7A6DE100CF10AA /* InfoPlist.xcstrings */, + 1D36E651298A84F600AA485D /* InternalUserDecider */, EEAEA3F4294D05CF00D04DF3 /* JSAlert */, B658BAB52B0F845D00D1F2C7 /* Localizable.xcstrings */, 9D03F5A22AA74829001A50E8 /* LoginItems */, @@ -9777,6 +9767,7 @@ C18BF9CD2C73678C00ED6B8A /* Freemium */, 567A23C42C7F75BB0010F66C /* SpecialErrorPages */, CD34F0C32C8869FF006826BE /* PhishingDetection */, + 7B8FDD252CDD88D900720907 /* FeatureFlags */, CBECDB8D2CDBD62C005B8B87 /* PageRefreshMonitor */, CBECDB8F2CDBD631005B8B87 /* BrokenSitePrompt */, ); @@ -9944,6 +9935,7 @@ 9D9DE5802C63BA0B00D20B15 /* AppKitExtensions */, 02A15D912C88D789001A4237 /* Configuration */, 02A15D932C88D78F001A4237 /* Persistence */, + 7B8FDD1F2CDD88C500720907 /* FeatureFlags */, ); productName = DuckDuckGoAgent; productReference = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */; @@ -9980,6 +9972,7 @@ 9D9DE5822C63BE9600D20B15 /* AppKitExtensions */, 02A15D952C88D797001A4237 /* Configuration */, 02A15D972C88D79D001A4237 /* Persistence */, + 7B8FDD212CDD88CB00720907 /* FeatureFlags */, ); productName = DuckDuckGoAgentAppStore; productReference = 4B2D06692A13318400DE1F49 /* DuckDuckGo VPN App Store.app */; @@ -10249,6 +10242,7 @@ C18BF9CB2C73678500ED6B8A /* Freemium */, 567A23C02C7F71570010F66C /* SpecialErrorPages */, CD34F0BB2C885D65006826BE /* PhishingDetection */, + 7B8FDD232CDD88D300720907 /* FeatureFlags */, CBECDB892CDBD616005B8B87 /* PageRefreshMonitor */, CBECDB8B2CDBD61C005B8B87 /* BrokenSitePrompt */, ); @@ -11973,7 +11967,6 @@ 3706FCA4293F65D500E42796 /* RecentlyClosedMenu.swift in Sources */, 8400DC4C2C6E26AE006509D2 /* ItemCachingCollectionView.swift in Sources */, 4B9DB02D2A983B24000927DB /* WaitlistKeychainStorage.swift in Sources */, - EECE10E629DD77E60044D027 /* FeatureFlag.swift in Sources */, C181945D2C7CDCC700381092 /* PromotionView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -12802,7 +12795,6 @@ B6D574B429472253008ED1B6 /* FBProtectionTabExtension.swift in Sources */, AAC30A28268E045400D2D9CD /* CrashReportReader.swift in Sources */, 85AC3B3525DA82A600C7D2AA /* DataTaskProviding.swift in Sources */, - EECE10E529DD77E60044D027 /* FeatureFlag.swift in Sources */, 56406D4B2C636A8900BF8FA2 /* SpecialPagesUserScriptExtension.swift in Sources */, 31521AC02CC013AD00248E6F /* AIChatMenuVisibilityConfigurable.swift in Sources */, AA3D531727A1EEED00074EC1 /* FeedbackViewController.swift in Sources */, @@ -15024,7 +15016,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 208.1.0; + version = 209.0.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { @@ -15527,6 +15519,22 @@ isa = XCSwiftPackageProductDependency; productName = UDSHelper; }; + 7B8FDD1F2CDD88C500720907 /* FeatureFlags */ = { + isa = XCSwiftPackageProductDependency; + productName = FeatureFlags; + }; + 7B8FDD212CDD88CB00720907 /* FeatureFlags */ = { + isa = XCSwiftPackageProductDependency; + productName = FeatureFlags; + }; + 7B8FDD232CDD88D300720907 /* FeatureFlags */ = { + isa = XCSwiftPackageProductDependency; + productName = FeatureFlags; + }; + 7B8FDD252CDD88D900720907 /* FeatureFlags */ = { + isa = XCSwiftPackageProductDependency; + productName = FeatureFlags; + }; 7B97CD582B7E0B57004FEF43 /* NetworkProtectionProxy */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionProxy; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1659b95a86..49f16e700e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "6be781530a2516c703b8e1bcf0c90e6e763d3300", - "version" : "208.1.0" + "revision" : "614ea57db48db644ce7f3a3de9c20c9a7fbb08ff", + "version" : "209.0.0" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index b9212e03e7..1556feef05 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -76,6 +76,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { private(set) var stateRestorationManager: AppStateRestorationManager! private var grammarFeaturesManager = GrammarFeaturesManager() let internalUserDecider: InternalUserDecider + private var isInternalUserSharingCancellable: AnyCancellable? let featureFlagger: FeatureFlagger private var appIconChanger: AppIconChanger! private var autoClearHandler: AutoClearHandler! @@ -433,6 +434,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { subscribeToEmailProtectionStatusNotifications() subscribeToDataImportCompleteNotification() + subscribeToInternalUserChanges() fireFailedCompilationsPixelIfNeeded() @@ -748,6 +750,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { NotificationCenter.default.addObserver(self, selector: #selector(dataImportCompleteNotification(_:)), name: .dataImportComplete, object: nil) } + private func subscribeToInternalUserChanges() { + UserDefaults.appConfiguration.isInternalUser = internalUserDecider.isInternalUser + + isInternalUserSharingCancellable = internalUserDecider.isInternalUserPublisher + .assign(to: \.isInternalUser, onWeaklyHeld: UserDefaults.appConfiguration) + } + private func emailDidSignInNotification(_ notification: Notification) { PixelKit.fire(NonStandardEvent(NonStandardPixel.emailEnabled)) if AppDelegate.isNewUser { diff --git a/DuckDuckGo/FeatureFlagging/Services/InternalUserDeciderStore.swift b/DuckDuckGo/InternalUserDecider/InternalUserDeciderStore.swift similarity index 100% rename from DuckDuckGo/FeatureFlagging/Services/InternalUserDeciderStore.swift rename to DuckDuckGo/InternalUserDecider/InternalUserDeciderStore.swift diff --git a/DuckDuckGo/NavigationBar/View/NetPPopoverManagerMock.swift b/DuckDuckGo/NavigationBar/View/NetPPopoverManagerMock.swift index 3b0d6efd9a..5ed1684c3b 100644 --- a/DuckDuckGo/NavigationBar/View/NetPPopoverManagerMock.swift +++ b/DuckDuckGo/NavigationBar/View/NetPPopoverManagerMock.swift @@ -88,6 +88,10 @@ final class IPCClientMock: NetworkProtectionIPCClient { completion(nil) } + func command(_ command: VPNCommand) async throws { + return + } + } final class ConnectivityIssueObserverMock: ConnectivityIssueObserver { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 69b8de0d0b..9b32eb21ff 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -69,18 +69,20 @@ final class NetworkProtectionDebugMenu: NSMenu { NSMenuItem(title: "Reset All State", action: #selector(NetworkProtectionDebugMenu.resetAllState)) .targetting(self) - NSMenuItem(title: "Reset Site Issue Alert", action: #selector(NetworkProtectionDebugMenu.resetSiteIssuesAlert(_:))) - .targetting(self) + NSMenuItem.separator() // Resetting single components should go below this point - resetToDefaults + NSMenuItem(title: "Remove Network Extension and Login Items", action: #selector(NetworkProtectionDebugMenu.removeSystemExtensionAndAgents)) .targetting(self) - NSMenuItem.separator() + NSMenuItem(title: "Remove VPN configuration", action: #selector(NetworkProtectionDebugMenu.removeVPNConfiguration(_:))) + .targetting(self) - NSMenuItem(title: "Remove Network Extension and Login Items", action: #selector(NetworkProtectionDebugMenu.removeSystemExtensionAndAgents)) + resetToDefaults .targetting(self) - NSMenuItem(title: "Remove VPN configuration", action: #selector(NetworkProtectionDebugMenu.removeVPNConfiguration(_:))) + NSMenuItem.separator() // Resetting VPN subfeatures should go below this point + + NSMenuItem(title: "Reset Site Issue Alert", action: #selector(NetworkProtectionDebugMenu.resetSiteIssuesAlert(_:))) .targetting(self) } @@ -310,14 +312,29 @@ final class NetworkProtectionDebugMenu: NSMenu { @objc func toggleEnforceRoutesAction(_ sender: Any?) { settings.enforceRoutes.toggle() + + Task { + try await Task.sleep(interval: 0.1) + try await debugUtilities.restartAdapter() + } } @objc func toggleIncludeAllNetworks(_ sender: Any?) { settings.includeAllNetworks.toggle() + + Task { + try await Task.sleep(interval: 0.1) + try await debugUtilities.restartAdapter() + } } @objc func toggleShouldExcludeLocalRoutes(_ sender: Any?) { settings.excludeLocalNetworks.toggle() + + Task { + try await Task.sleep(interval: 0.1) + try await debugUtilities.restartAdapter() + } } @objc func openAppContainerInFinder(_ sender: Any?) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index c8656a8ae0..14966d2a3c 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -38,6 +38,7 @@ protocol NetworkProtectionIPCClient { func start(completion: @escaping (Error?) -> Void) func stop(completion: @escaping (Error?) -> Void) + func command(_ command: VPNCommand) async throws } extension VPNControllerXPCClient: NetworkProtectionIPCClient { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 72d6255c4b..8982657cf6 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -20,6 +20,7 @@ import Foundation import Combine import SwiftUI import Common +import FeatureFlags import NetworkExtension import NetworkProtection import NetworkProtectionProxy @@ -34,18 +35,17 @@ import SystemExtensions #endif import Subscription +import BrowserServicesKit typealias NetworkProtectionStatusChangeHandler = (NetworkProtection.ConnectionStatus) -> Void typealias NetworkProtectionConfigChangeHandler = () -> Void final class NetworkProtectionTunnelController: TunnelController, TunnelSessionProvider { - // MARK: - Settings + // MARK: - Configuration + private let featureFlagger: FeatureFlagger let settings: VPNSettings - - // MARK: - Defaults - let defaults: UserDefaults // MARK: - Combine Cancellables @@ -154,11 +154,13 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr /// init(networkExtensionBundleID: String, networkExtensionController: NetworkExtensionController, + featureFlagger: FeatureFlagger, settings: VPNSettings, defaults: UserDefaults, notificationCenter: NotificationCenter = .default, accessTokenStorage: SubscriptionTokenKeychainStorage) { + self.featureFlagger = featureFlagger self.networkExtensionBundleID = networkExtensionBundleID self.networkExtensionController = networkExtensionController self.notificationCenter = notificationCenter @@ -295,8 +297,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } private func handleSetExcludeLocalNetworks(_ excludeLocalNetworks: Bool) async throws { - guard let tunnelManager = await manager, - tunnelManager.protocolConfiguration?.excludeLocalNetworks == !excludeLocalNetworks else { + guard let tunnelManager = await manager else { return } @@ -349,23 +350,19 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr protocolConfiguration.providerBundleIdentifier = Bundle.tunnelExtensionBundleID protocolConfiguration.providerConfiguration = [ NetworkProtectionOptionKey.defaultPixelHeaders: APIRequest.Headers().httpHeaders, - NetworkProtectionOptionKey.includedRoutes: includedRoutes().map(\.stringRepresentation) as NSArray ] // always-on protocolConfiguration.disconnectOnSleep = false // kill switch - protocolConfiguration.enforceRoutes = settings.enforceRoutes + protocolConfiguration.enforceRoutes = enforceRoutes // this setting breaks Connection Tester protocolConfiguration.includeAllNetworks = settings.includeAllNetworks - // This is intentionally not used but left here for documentation purposes. - // The reason for this is that we want to have full control of the routes that - // are excluded, so instead of using this setting we're just configuring the - // excluded routes through our VPNSettings class, which our extension reads directly. - // protocolConfiguration.excludeLocalNetworks = settings.excludeLocalNetworks + // This messes up the routing, so please keep it always disabled + protocolConfiguration.excludeLocalNetworks = false return protocolConfiguration }() @@ -618,6 +615,8 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr options[NetworkProtectionOptionKey.selectedEnvironment] = settings.selectedEnvironment.rawValue as NSString options[NetworkProtectionOptionKey.selectedServer] = settings.selectedServer.stringValue as? NSString + options[NetworkProtectionOptionKey.excludeLocalNetworks] = NSNumber(value: settings.excludeLocalNetworks) + #if NETP_SYSTEM_EXTENSION if let data = try? JSONEncoder().encode(settings.selectedLocation) { options[NetworkProtectionOptionKey.selectedLocation] = NSData(data: data) @@ -685,6 +684,10 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } } + func command(_ command: VPNCommand) async throws { + try await sendProviderMessageToActiveSession(.request(.command(command))) + } + /// Restarts the tunnel. /// @MainActor @@ -732,55 +735,12 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr try await tunnelManager.saveToPreferences() } - /* Temporarily disabled until we fix this menu: https://app.asana.com/0/0/1205766100762904/f - @MainActor - private func excludedRoutes() -> [NetworkProtection.IPAddressRange] { - settings.excludedRoutes.compactMap { [excludedRoutesPreferences] item -> NetworkProtection.IPAddressRange? in - guard case .exclusion(range: let range, description: _, default: let defaultValue) = item, - excludedRoutesPreferences[range.stringRepresentation, default: defaultValue] == true - else { return nil } - // TO BE fixed: - // when 10.11.12.1 DNS is used 10.0.0.0/8 should be included (not excluded) - // but marking 10.11.12.1 as an Included Route breaks tunnel (probably these routes are conflicting) - if settings.enforceRoutes && range == "10.0.0.0/8" { - return nil - } + // MARK: - Routing - return range - } - }*/ - - /// extra Included Routes appended to 0.0.0.0, ::/0 (peers) and interface.addresses - @MainActor - private func includedRoutes() -> [NetworkProtection.IPAddressRange] { - [] + private var enforceRoutes: Bool { + featureFlagger.isFeatureOn(.networkProtectionEnforceRoutes) } - /* Temporarily disabled - https://app.asana.com/0/0/1205766100762904/f - @MainActor - func setExcludedRoute(_ route: String, enabled: Bool) { - excludedRoutesPreferences[route] = enabled - } - - @MainActor - func isExcludedRouteEnabled(_ route: String) -> Bool { - guard let range = IPAddressRange(from: route), - let exclusionListItem = settings.exclusionList.first(where: { - if case .exclusion(range: range, description: _, default: _) = $0 { return true } - return false - }), - case .exclusion(range: _, description: _, default: let defaultValue) = exclusionListItem else { - - assertionFailure("Invalid route \(route)") - return false - } - // TO BE fixed: see excludedRoutes() - if settings.enforceRoutes && route == "10.0.0.0/8" { - return false - } - return excludedRoutesPreferences[route, default: defaultValue] - }*/ - struct TunnelFailureError: LocalizedError { let errorDescription: String? } @@ -815,6 +775,11 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr } } + @MainActor + private func sendProviderRequestToActiveSession(_ request: ExtensionRequest) async throws { + try await sendProviderMessageToActiveSession(.request(request)) + } + @MainActor private func sendProviderMessageToActiveSession(_ message: ExtensionMessage) async throws { guard await isConnected, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift index f9febc6a09..b7f2ee4afd 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -144,6 +144,10 @@ extension NetworkProtectionIPCTunnelController: TunnelController { } } + func command(_ command: VPNCommand) async throws { + try await ipcClient.command(command) + } + /// Queries VPN to know if it's connected. /// /// - Returns: `true` if the VPN is connected, connecting or reasserting, and `false` otherwise. diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index ec7c79bd5b..5978e58af7 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -556,6 +556,25 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - NEPacketTunnelProvider + public override func load(options: StartupOptions) throws { + try super.load(options: options) + +#if NETP_SYSTEM_EXTENSION + loadExcludeLocalNetworks(from: options) +#endif + } + + private func loadExcludeLocalNetworks(from options: StartupOptions) { + switch options.excludeLocalNetworks { + case .set(let exclude): + settings.excludeLocalNetworks = exclude + case .useExisting: + break + case .reset: + settings.excludeLocalNetworks = true + } + } + enum ConfigurationError: Error { case missingProviderConfiguration case missingPixelHeaders diff --git a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift index f5850f642b..fd841fcedf 100644 --- a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift @@ -39,7 +39,11 @@ final class VPNPreferencesModel: ObservableObject { @Published var excludeLocalNetworks: Bool { didSet { settings.excludeLocalNetworks = excludeLocalNetworks + Task { + // We need to allow some time for the setting to propagate + // But ultimately this should actually be a user choice + try await Task.sleep(interval: 0.1) try await vpnXPCClient.command(.restartAdapter) } } diff --git a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift index 36a0d4768c..3f8942d5fc 100644 --- a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift @@ -18,6 +18,7 @@ import AppKit import Combine +import FeatureFlags import PreferencesViews import SwiftUI import SwiftUIExtensions diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 29034247d1..6f8115af19 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -133,7 +133,11 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private let configurationStore = ConfigurationStore() private let configurationManager: ConfigurationManager - private let privacyConfigurationManager = VPNPrivacyConfigurationManager() + private var configurationSubscription: AnyCancellable? + private let privacyConfigurationManager = VPNPrivacyConfigurationManager(internalUserDecider: DefaultInternalUserDecider(store: UserDefaults.appConfiguration)) + private lazy var featureFlagger = DefaultFeatureFlagger( + internalUserDecider: privacyConfigurationManager.internalUserDecider, + privacyConfigManager: privacyConfigurationManager) public init(accountManager: AccountManager, accessTokenStorage: SubscriptionTokenKeychainStorage, @@ -223,6 +227,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { private lazy var tunnelController = NetworkProtectionTunnelController( networkExtensionBundleID: tunnelExtensionBundleID, networkExtensionController: networkExtensionController, + featureFlagger: featureFlagger, settings: tunnelSettings, defaults: userDefaults, accessTokenStorage: accessTokenStorage) diff --git a/DuckDuckGoVPN/VPNPrivacyConfigurationManager.swift b/DuckDuckGoVPN/VPNPrivacyConfigurationManager.swift index e25274db35..d49e705266 100644 --- a/DuckDuckGoVPN/VPNPrivacyConfigurationManager.swift +++ b/DuckDuckGoVPN/VPNPrivacyConfigurationManager.swift @@ -24,8 +24,13 @@ import PixelKit public final class VPNPrivacyConfigurationManager: PrivacyConfigurationManaging { + public let internalUserDecider: InternalUserDecider private let lock = NSLock() + init(internalUserDecider: InternalUserDecider) { + self.internalUserDecider = internalUserDecider + } + var embeddedConfigData: Data { let configString = """ { @@ -81,8 +86,6 @@ public final class VPNPrivacyConfigurationManager: PrivacyConfigurationManaging return privacyConfig } - public var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()) - @discardableResult public func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult { let result: PrivacyConfigurationManager.ReloadResult diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 979cbc8746..25ff9aba77 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: "208.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "209.0.0"), .package(path: "../SwiftUIExtensions"), .package(path: "../AppKitExtensions"), .package(path: "../XPCHelper"), diff --git a/LocalPackages/FeatureFlags/.gitignore b/LocalPackages/FeatureFlags/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/LocalPackages/FeatureFlags/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/FeatureFlags/Package.swift b/LocalPackages/FeatureFlags/Package.swift new file mode 100644 index 0000000000..aff74b354d --- /dev/null +++ b/LocalPackages/FeatureFlags/Package.swift @@ -0,0 +1,50 @@ +// swift-tools-version: 5.7 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Package.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 PackageDescription + +let package = Package( + name: "FeatureFlags", + platforms: [ + .macOS("11.4") + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "FeatureFlags", + targets: ["FeatureFlags"]), + ], + dependencies: [ + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "209.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "FeatureFlags", + dependencies: [ + .product(name: "BrowserServicesKit", package: "BrowserServicesKit") + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ] + ), + ] +) diff --git a/LocalPackages/FeatureFlags/README.md b/LocalPackages/FeatureFlags/README.md new file mode 100644 index 0000000000..2efa32e98b --- /dev/null +++ b/LocalPackages/FeatureFlags/README.md @@ -0,0 +1,6 @@ +# FeatureFlags + +This package contains all feature flags for our macOS app targets. + +The convenience of having feature flags in a module is that we can quickly add this module +to new targets to get the `FeatureFlag` logic with minimum effort. diff --git a/DuckDuckGo/FeatureFlagging/Model/FeatureFlag.swift b/LocalPackages/FeatureFlags/Sources/FeatureFlags/FeatureFlag.swift similarity index 92% rename from DuckDuckGo/FeatureFlagging/Model/FeatureFlag.swift rename to LocalPackages/FeatureFlags/Sources/FeatureFlags/FeatureFlag.swift index 6f6bc2c95e..3811ad546a 100644 --- a/DuckDuckGo/FeatureFlagging/Model/FeatureFlag.swift +++ b/LocalPackages/FeatureFlags/Sources/FeatureFlags/FeatureFlag.swift @@ -41,6 +41,9 @@ public enum FeatureFlag: String { /// https://app.asana.com/0/72649045549333/1208231259093710/f case networkProtectionUserTips + + /// https://app.asana.com/0/72649045549333/1208617860225199/f + case networkProtectionEnforceRoutes } extension FeatureFlag: FeatureFlagSourceProviding { @@ -66,6 +69,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.credentialsImportPromotionForExistingUsers)) case .networkProtectionUserTips: return .remoteDevelopment(.subfeature(NetworkProtectionSubfeature.userTips)) + case .networkProtectionEnforceRoutes: + return .remoteDevelopment(.subfeature(NetworkProtectionSubfeature.enforceRoutes)) } } } diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 4177532a74..d32ae8dc9e 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -32,7 +32,7 @@ let package = Package( .library(name: "VPNAppLauncher", targets: ["VPNAppLauncher"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "208.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "209.0.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.3"), .package(path: "../AppLauncher"), .package(path: "../UDSHelper"), diff --git a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift index 22ea5ffbc6..80ef588859 100644 --- a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift +++ b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift @@ -35,6 +35,10 @@ final class StatusBarMenuTests: XCTestCase { // no-op } + func command(_ command: VPNCommand) async throws { + // no-op + } + var isConnected: Bool { true } diff --git a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift index 4f9407e5cc..dde3d60300 100644 --- a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift +++ b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift @@ -102,6 +102,10 @@ final class TunnelControllerViewModelTests: XCTestCase { func stop() async { stopCallback?() } + + func command(_ command: VPNCommand) async throws { + // no-op + } } // MARK: - Tests diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index fa22cbe544..be83b48bab 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "208.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "209.0.0"), .package(path: "../SwiftUIExtensions") ], targets: [