From 19c06f20c02e794918f965d3590068bfa46eacd0 Mon Sep 17 00:00:00 2001 From: Thom Espach Date: Wed, 20 Dec 2023 10:56:11 +0000 Subject: [PATCH 01/10] Fix: external application requests via redirect URLs shows wrong origin. (#1900) Task/Issue URL: https://app.asana.com/0/1176047645786601/1204521390227183/f **Description**: If the external application URL was the result of a cross-origin redirect, then we display the wrong origin in the external application request popup. Instead, I propose we check if redirects occurred, and use the origin of the most recent redirect as the first choice for the domain displayed to the user. This is only a proposed fix, and will likely need a thorough review to ensure we don't cause breakage in legitimate external application requests. **Steps to test this PR**: 1. Visit https://alesandroortiz.com/security/chromium/external-protocol/spoof-links.html 2. Click **Tel** 3. Check the origin in the popup is aogarantiza.com and not alesandroortiz.com 4. Manually enter tel://155555 into address bar 5. Ensure popup appears with origin "155555" 6. Visit this test page: https://crossorigin.site/tel.html 7. Click the button 8. Ensure the origin is displayed as "crossorigin.site" --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- .../Tab/Navigation/ExternalAppSchemeHandler.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift b/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift index ba60396610..75fdbbea30 100644 --- a/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift +++ b/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift @@ -53,8 +53,8 @@ final class ExternalAppSchemeHandler { extension ExternalAppSchemeHandler: NavigationResponder { - @MainActor - func decidePolicy(for navigationAction: NavigationAction, preferences: inout NavigationPreferences) async -> NavigationActionPolicy? { + // swiftlint:disable:next function_body_length + @MainActor func decidePolicy(for navigationAction: NavigationAction, preferences: inout NavigationPreferences) async -> NavigationActionPolicy? { let externalUrl = navigationAction.url // only proceed with non-external-scheme navigations guard externalUrl.isExternalSchemeLink, let scheme = externalUrl.scheme else { @@ -114,19 +114,18 @@ extension ExternalAppSchemeHandler: NavigationResponder { } let permissionType = PermissionType.externalScheme(scheme: scheme) - // use domain from the url for user-entered app schemes, otherwise use current website domain - let domain = navigationAction.isUserEnteredUrl ? navigationAction.url.host ?? "" : navigationAction.sourceFrame.securityOrigin.host - permissionModel.permissions([permissionType], requestedForDomain: domain, url: externalUrl) { [workspace, weak self] isGranted in + // Check for cross-origin redirects first, then use domain from the url for user-entered app schemes, then use current website domain + let redirectDomain = navigationAction.redirectHistory?.reversed().first(where: { $0.url.host != navigationAction.url.host })?.url.host + let domain = redirectDomain ?? (navigationAction.isUserEnteredUrl ? navigationAction.url.host ?? "" : navigationAction.sourceFrame.securityOrigin.host) + permissionModel.permissions([permissionType], requestedForDomain: domain, url: externalUrl) { [workspace] isGranted in if isGranted { workspace.open(externalUrl) - // if "Always allow" is set and this is the only navigation in the tab: close the tab - if self?.shouldCloseTabOnExternalAppOpen == true, let webView = navigationAction.targetFrame?.webView { + if self.shouldCloseTabOnExternalAppOpen == true, let webView = navigationAction.targetFrame?.webView { webView.close() } } } - return .cancel } From be73f44321c560a415c35e45cd1c5f0d5eeef310 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Wed, 20 Dec 2023 13:04:35 +0000 Subject: [PATCH 02/10] Do not reload DBP tab when switching to it (#1942) Task/Issue URL: https://app.asana.com/0/1203581873609357/1206115814306009/f **Description**: Do not reload the DBP tab when switching to it --- .../Tab/View/BrowserTabViewController.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index fc216e21c3..872b4fb27e 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -219,6 +219,10 @@ final class BrowserTabViewController: NSViewController { tabCollectionViewModel.tabCollection.$tabs .sink(receiveValue: setDelegate()) .store(in: &cancellables) + + tabCollectionViewModel.tabCollection.$tabs + .sink(receiveValue: removeDataBrokerViewIfNecessary()) + .store(in: &cancellables) } private func subscribeToPinnedTabs() { @@ -226,6 +230,19 @@ final class BrowserTabViewController: NSViewController { .sink(receiveValue: setDelegate()) } + private func removeDataBrokerViewIfNecessary() -> ([Tab]) -> Void { + { [weak self] (tabs: [Tab]) in + guard let self else { return } +#if DBP + if let dataBrokerProtectionHomeViewController, + !tabs.contains(where: { $0.content == .dataBrokerProtection }) { + dataBrokerProtectionHomeViewController.removeCompletely() + self.dataBrokerProtectionHomeViewController = nil + } +#endif + } + } + private func setDelegate() -> ([Tab]) -> Void { { [weak self] (tabs: [Tab]) in guard let self else { return } @@ -431,7 +448,6 @@ final class BrowserTabViewController: NSViewController { bookmarksViewController?.removeCompletely() #if DBP dataBrokerProtectionHomeViewController?.removeCompletely() - dataBrokerProtectionHomeViewController = nil #endif if includingWebView { self.removeWebViewFromHierarchy() From 00e4e18643f59d3a9a4a01099ea80b1cc6383f2a Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Wed, 20 Dec 2023 16:13:17 +0100 Subject: [PATCH 03/10] Add daily stats pixel (#1993) Task/Issue URL: https://app.asana.com/0/0/1206206145252506/f https://app.asana.com/0/0/1204831721662171/f Description: Add Sync success rate pixel. --- DuckDuckGo.xcodeproj/project.pbxproj | 114 +++++++++++------- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/Application/AppDelegate.swift | 6 +- .../Services/LocalBookmarkStore.swift | 12 -- DuckDuckGo/Statistics/PixelEvent.swift | 2 - LocalPackages/Account/Package.swift | 2 +- .../DataBrokerProtection/Package.swift | 2 +- LocalPackages/LoginItems/Package.swift | 2 +- .../NetworkProtectionMac/Package.swift | 2 +- LocalPackages/PixelKit/Package.swift | 2 +- LocalPackages/Purchase/Package.swift | 2 +- LocalPackages/Subscription/Package.swift | 2 +- LocalPackages/SwiftUIExtensions/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- .../SystemExtensionManager/Package.swift | 2 +- LocalPackages/XPCHelper/Package.swift | 2 +- UnitTests/Sync/SyncPreferencesTests.swift | 4 + 17 files changed, 94 insertions(+), 70 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d0c80dad3c..0965772e2b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -77,7 +77,6 @@ 1DFAB5232A8983E100A0F7F6 /* SetExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFAB51F2A89830D00A0F7F6 /* SetExtensionTests.swift */; }; 1E0C72062ABC63BD00802009 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */; }; 1E0C72072ABC63BD00802009 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */; }; - 1E25269C28F8741A00E44DFA /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 1E25269B28F8741A00E44DFA /* Common */; }; 1E2AE4C72ACB215900684E0A /* NetworkProtectionRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */; }; 1E2AE4C82ACB216B00684E0A /* HoverTrackingArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B140872ABDBCC1004F8E85 /* HoverTrackingArea.swift */; }; 1E2AE4C92ACB217800684E0A /* NetworkProtectionRemoteMessagingStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15DA2ABB8CED0083F6DF /* NetworkProtectionRemoteMessagingStorage.swift */; }; @@ -632,7 +631,6 @@ 3706FCAB293F65D500E42796 /* TrackerRadarKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA6B293F65D500E42796 /* TrackerRadarKit */; }; 3706FCAE293F65D500E42796 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA6D293F65D500E42796 /* Lottie */; }; 3706FCAF293F65D500E42796 /* PrivacyDashboard in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA77293F65D500E42796 /* PrivacyDashboard */; }; - 3706FCB0293F65D500E42796 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA75293F65D500E42796 /* Common */; }; 3706FCB2293F65D500E42796 /* Fireproofing.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B02198425E05FAC00ED7DEA /* Fireproofing.storyboard */; }; 3706FCB3293F65D500E42796 /* Suggestion.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA80EC75256C46A2007083E7 /* Suggestion.storyboard */; }; 3706FCB4293F65D500E42796 /* CrashReports.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA693E5D2696E5B90007BB78 /* CrashReports.storyboard */; }; @@ -927,6 +925,15 @@ 37197EAC294244D600394917 /* FutureExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B634DBE6293C98C500C3C99E /* FutureExtension.swift */; }; 371C0A2927E33EDC0070591F /* FeedbackPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371C0A2827E33EDC0070591F /* FeedbackPresenter.swift */; }; 371D00E129D8509400EC8598 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 371D00E029D8509400EC8598 /* OpenSSL */; }; + 372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 3722177F2B3337FE00B8E9C2 /* TestUtils */; }; + 372217822B33380700B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 372217812B33380700B8E9C2 /* TestUtils */; }; + 372217842B33380E00B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 372217832B33380E00B8E9C2 /* TestUtils */; }; + 37269EFB2B332F9E005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFA2B332F9E005E8E46 /* Common */; }; + 37269EFD2B332FAC005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFC2B332FAC005E8E46 /* Common */; }; + 37269EFF2B332FBB005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFE2B332FBB005E8E46 /* Common */; }; + 37269F012B332FC8005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269F002B332FC8005E8E46 /* Common */; }; + 37269F032B332FD8005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269F022B332FD8005E8E46 /* Common */; }; + 37269F052B3332C2005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269F042B3332C2005E8E46 /* Common */; }; 372A0FEC2B2379310033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEB2B2379310033BF7F /* SyncMetricsEventsHandler.swift */; }; 372A0FED2B2379310033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEB2B2379310033BF7F /* SyncMetricsEventsHandler.swift */; }; 372A0FEE2B2379310033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEB2B2379310033BF7F /* SyncMetricsEventsHandler.swift */; }; @@ -1088,7 +1095,6 @@ 4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; }; 4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D062B2A11C0E100DE1F49 /* Networking */; }; 4B2D062D2A11C12300DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logging.swift */; }; - 4B2D06302A11C15900DE1F49 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D062F2A11C15900DE1F49 /* Common */; }; 4B2D06322A11C1D300DE1F49 /* NSApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C8F622591021700748EB7 /* NSApplicationExtension.swift */; }; 4B2D06332A11C1E300DE1F49 /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B637273C26CCF0C200C8CB02 /* OptionalExtension.swift */; }; 4B2D065B2A11D1FF00DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; }; @@ -1227,7 +1233,6 @@ 4B8AC93D26B49BE600879451 /* FirefoxLoginReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AC93C26B49BE600879451 /* FirefoxLoginReaderTests.swift */; }; 4B8AD0B127A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AD0B027A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift */; }; 4B8D9062276D1D880078DB17 /* LocaleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8D9061276D1D880078DB17 /* LocaleExtension.swift */; }; - 4B8F52352A169D2D00BE7131 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B8F52342A169D2D00BE7131 /* Common */; }; 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928526670D1600AD2C21 /* BookmarksOutlineView.swift */; }; 4B92928C26670D1700AD2C21 /* OutlineSeparatorViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */; }; 4B92928D26670D1700AD2C21 /* BookmarkOutlineViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */; }; @@ -1929,7 +1934,6 @@ 4B957BE72AC7AE700062CA31 /* SyncDataProviders in Frameworks */ = {isa = PBXBuildFile; productRef = 4B95793C2AC7AE700062CA31 /* SyncDataProviders */; }; 4B957BE82AC7AE700062CA31 /* SyncUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4B9579362AC7AE700062CA31 /* SyncUI */; }; 4B957BE92AC7AE700062CA31 /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4B95793D2AC7AE700062CA31 /* NetworkProtectionUI */; }; - 4B957BEA2AC7AE700062CA31 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B95792D2AC7AE700062CA31 /* Common */; }; 4B957BEB2AC7AE700062CA31 /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = 4B9579312AC7AE700062CA31 /* Persistence */; }; 4B957BED2AC7AE700062CA31 /* Fireproofing.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B02198425E05FAC00ED7DEA /* Fireproofing.storyboard */; }; 4B957BEE2AC7AE700062CA31 /* Suggestion.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA80EC75256C46A2007083E7 /* Suggestion.storyboard */; }; @@ -4254,11 +4258,13 @@ 984FD3BF299ACF35007334DD /* Bookmarks in Frameworks */, 37A5E2F0298AA1B20047046B /* Persistence in Frameworks */, 9DC70B1A2AA1FA5B005A844B /* LoginItems in Frameworks */, + 37269EFD2B332FAC005E8E46 /* Common in Frameworks */, 378F44E629B4BDEE00899924 /* SwiftUIExtensions in Frameworks */, 3706FCA7293F65D500E42796 /* BrowserServicesKit in Frameworks */, 3706FCA9293F65D500E42796 /* ContentBlocking in Frameworks */, 37F44A5F298C17830025E7FE /* Navigation in Frameworks */, B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */, + 372217822B33380700B8E9C2 /* TestUtils in Frameworks */, 3706FCAA293F65D500E42796 /* UserScript in Frameworks */, 3706FCAB293F65D500E42796 /* TrackerRadarKit in Frameworks */, 3739326529AE4B39009346AE /* DDGSync in Frameworks */, @@ -4266,7 +4272,6 @@ 3706FCAE293F65D500E42796 /* Lottie in Frameworks */, 37BA812F29B3CD6E0053F1A3 /* SyncUI in Frameworks */, 3706FCAF293F65D500E42796 /* PrivacyDashboard in Frameworks */, - 3706FCB0293F65D500E42796 /* Common in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4307,7 +4312,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4B2D06302A11C15900DE1F49 /* Common in Frameworks */, + 37269F012B332FC8005E8E46 /* Common in Frameworks */, EE7295E92A545BC4008C0991 /* NetworkProtection in Frameworks */, 4B2537772A11BFE100610219 /* PixelKit in Frameworks */, 4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */, @@ -4346,6 +4351,7 @@ buildActionMask = 2147483647; files = ( EE7295EB2A545BFC008C0991 /* NetworkProtection in Frameworks */, + 37269F052B3332C2005E8E46 /* Common in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4353,7 +4359,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4B8F52352A169D2D00BE7131 /* Common in Frameworks */, + 37269EFF2B332FBB005E8E46 /* Common in Frameworks */, EE7295E72A545BBB008C0991 /* NetworkProtection in Frameworks */, 4B4D60982A0B2A5C00BCD287 /* PixelKit in Frameworks */, 4B4D60AF2A0C837F00BCD287 /* Networking in Frameworks */, @@ -4367,6 +4373,7 @@ files = ( 4B957BD52AC7AE700062CA31 /* QuickLookUI.framework in Frameworks */, 3143C8792B0D1F3D00382627 /* DataBrokerProtection in Frameworks */, + 372217842B33380E00B8E9C2 /* TestUtils in Frameworks */, 4B957BD62AC7AE700062CA31 /* LoginItems in Frameworks */, 4B957BD72AC7AE700062CA31 /* NetworkProtection in Frameworks */, 4B957BD82AC7AE700062CA31 /* BrowserServicesKit in Frameworks */, @@ -4388,9 +4395,9 @@ 4B957BE62AC7AE700062CA31 /* PrivacyDashboard in Frameworks */, 7B8C083C2AE1268E00F4C67F /* PixelKit in Frameworks */, 4B957BE72AC7AE700062CA31 /* SyncDataProviders in Frameworks */, + 37269F032B332FD8005E8E46 /* Common in Frameworks */, 4B957BE82AC7AE700062CA31 /* SyncUI in Frameworks */, 4B957BE92AC7AE700062CA31 /* NetworkProtectionUI in Frameworks */, - 4B957BEA2AC7AE700062CA31 /* Common in Frameworks */, 4B957BEB2AC7AE700062CA31 /* Persistence in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4446,6 +4453,7 @@ 1E950E432912A10D0051A99B /* UserScript in Frameworks */, CBC83E3629B63D380008E19C /* Configuration in Frameworks */, 7B31FD8C2AD125620086AA24 /* NetworkProtectionIPC in Frameworks */, + 37269EFB2B332F9E005E8E46 /* Common in Frameworks */, 1E3ED4FD2AC1E0290075F60F /* Purchase in Frameworks */, 4B2AAAF529E70DEA0026AFC0 /* Lottie in Frameworks */, AA06B6B72672AF8100F541C5 /* Sparkle in Frameworks */, @@ -4456,8 +4464,8 @@ 1E950E412912A10D0051A99B /* PrivacyDashboard in Frameworks */, 37DF000529F9C056002B7D3E /* SyncDataProviders in Frameworks */, 37BA812D29B3CD690053F1A3 /* SyncUI in Frameworks */, + 372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */, 4B4D60B12A0C83B900BCD287 /* NetworkProtectionUI in Frameworks */, - 1E25269C28F8741A00E44DFA /* Common in Frameworks */, 98A50964294B691800D10880 /* Persistence in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7967,7 +7975,6 @@ 3706FA6B293F65D500E42796 /* TrackerRadarKit */, 3706FA6D293F65D500E42796 /* Lottie */, 3706FA71293F65D500E42796 /* BrowserServicesKit */, - 3706FA75293F65D500E42796 /* Common */, 3706FA76293F65D500E42796 /* ContentBlocking */, 3706FA77293F65D500E42796 /* PrivacyDashboard */, 3706FA78293F65D500E42796 /* UserScript */, @@ -7981,6 +7988,8 @@ 37DF000629F9C061002B7D3E /* SyncDataProviders */, 9DC70B192AA1FA5B005A844B /* LoginItems */, 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */, + 37269EFC2B332FAC005E8E46 /* Common */, + 372217812B33380700B8E9C2 /* TestUtils */, ); productName = DuckDuckGo; productReference = 3706FD05293F65D500E42796 /* DuckDuckGo App Store.app */; @@ -8089,8 +8098,8 @@ packageProductDependencies = ( 4B2537762A11BFE100610219 /* PixelKit */, 4B2D062B2A11C0E100DE1F49 /* Networking */, - 4B2D062F2A11C15900DE1F49 /* Common */, EE7295E82A545BC4008C0991 /* NetworkProtection */, + 37269F002B332FC8005E8E46 /* Common */, ); productName = NetworkProtectionSystemExtension; productReference = 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */; @@ -8169,6 +8178,7 @@ name = DuckDuckGoNotifications; packageProductDependencies = ( EE7295EA2A545BFC008C0991 /* NetworkProtection */, + 37269F042B3332C2005E8E46 /* Common */, ); productName = DuckDuckGoNotifications; productReference = 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */; @@ -8191,8 +8201,8 @@ packageProductDependencies = ( 4B4D60972A0B2A5C00BCD287 /* PixelKit */, 4B4D60AE2A0C837F00BCD287 /* Networking */, - 4B8F52342A169D2D00BE7131 /* Common */, EE7295E62A545BBB008C0991 /* NetworkProtection */, + 37269EFE2B332FBB005E8E46 /* Common */, ); productName = NetworkProtectionAppExtension; productReference = 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */; @@ -8221,7 +8231,6 @@ packageProductDependencies = ( 4B9579292AC7AE700062CA31 /* Sparkle */, 4B95792B2AC7AE700062CA31 /* BrowserServicesKit */, - 4B95792D2AC7AE700062CA31 /* Common */, 4B95792E2AC7AE700062CA31 /* ContentBlocking */, 4B95792F2AC7AE700062CA31 /* PrivacyDashboard */, 4B9579302AC7AE700062CA31 /* UserScript */, @@ -8244,6 +8253,8 @@ 7B8C083B2AE1268E00F4C67F /* PixelKit */, 7B31FD8F2AD1257B0086AA24 /* NetworkProtectionIPC */, 3143C8782B0D1F3D00382627 /* DataBrokerProtection */, + 37269F022B332FD8005E8E46 /* Common */, + 372217832B33380E00B8E9C2 /* TestUtils */, ); productName = DuckDuckGo; productReference = 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */; @@ -8355,7 +8366,6 @@ packageProductDependencies = ( AA06B6B62672AF8100F541C5 /* Sparkle */, 9807F644278CA16F00E1547B /* BrowserServicesKit */, - 1E25269B28F8741A00E44DFA /* Common */, 1E950E3E2912A10D0051A99B /* ContentBlocking */, 1E950E402912A10D0051A99B /* PrivacyDashboard */, 1E950E422912A10D0051A99B /* UserScript */, @@ -8378,6 +8388,8 @@ 7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */, 7B5DD6992AE51FFA001DE99C /* PixelKit */, 31A3A4E22B0C115F0021063C /* DataBrokerProtection */, + 37269EFA2B332F9E005E8E46 /* Common */, + 3722177F2B3337FE00B8E9C2 /* TestUtils */, ); productName = DuckDuckGo; productReference = AA585D7E248FD31100E9A3E2 /* DuckDuckGo.app */; @@ -12930,7 +12942,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 95.0.0; + version = 96.0.1; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { @@ -12960,11 +12972,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 1E25269B28F8741A00E44DFA /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 1E3ED4FC2AC1E0290075F60F /* Purchase */ = { isa = XCSwiftPackageProductDependency; productName = Purchase; @@ -13011,11 +13018,6 @@ package = 3706FA72293F65D500E42796 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = BrowserServicesKit; }; - 3706FA75293F65D500E42796 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 3706FA72293F65D500E42796 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 3706FA76293F65D500E42796 /* ContentBlocking */ = { isa = XCSwiftPackageProductDependency; package = 3706FA72293F65D500E42796 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -13046,6 +13048,51 @@ package = 371D00DF29D8509400EC8598 /* XCRemoteSwiftPackageReference "OpenSSL-XCFramework" */; productName = OpenSSL; }; + 3722177F2B3337FE00B8E9C2 /* TestUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = TestUtils; + }; + 372217812B33380700B8E9C2 /* TestUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = TestUtils; + }; + 372217832B33380E00B8E9C2 /* TestUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = TestUtils; + }; + 37269EFA2B332F9E005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269EFC2B332FAC005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269EFE2B332FBB005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269F002B332FC8005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269F022B332FD8005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269F042B3332C2005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; 3739326429AE4B39009346AE /* DDGSync */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -13106,11 +13153,6 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Networking; }; - 4B2D062F2A11C15900DE1F49 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 4B2D067E2A1334D700DE1F49 /* NetworkProtectionUI */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionUI; @@ -13156,11 +13198,6 @@ isa = XCSwiftPackageProductDependency; productName = PixelKitTestingUtilities; }; - 4B8F52342A169D2D00BE7131 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 4B9579292AC7AE700062CA31 /* Sparkle */ = { isa = XCSwiftPackageProductDependency; package = 4B95792A2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "Sparkle" */; @@ -13171,11 +13208,6 @@ package = 4B95792C2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = BrowserServicesKit; }; - 4B95792D2AC7AE700062CA31 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 4B95792C2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 4B95792E2AC7AE700062CA31 /* ContentBlocking */ = { isa = XCSwiftPackageProductDependency; package = 4B95792C2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 301109ce38..e9e774e2b4 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" : "ae9e9180f74d92c83fc3cc1d2fc23f4855fb361c", - "version" : "95.0.0" + "revision" : "308abf4ebf170dc73d9f1a8a1730ed3170bed2d5", + "version" : "96.0.1" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 8d21c98b97..92e373af52 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -356,9 +356,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel isSyncInProgressCancellable = syncService.isSyncInProgressPublisher .filter { $0 } .asVoid() - .prefix(1) - .sink { + .sink { [weak syncService] in Pixel.fire(.syncDaily, limitTo: .dailyFirst) + syncService?.syncDailyStats.sendStatsIfNeeded(handler: { params in + Pixel.fire(.syncSuccessRateDaily, withAdditionalParameters: params) + }) } subscribeSyncQueueToScreenLockedNotifications() diff --git a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift index 7ea5d2b43a..b955524cac 100644 --- a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift @@ -279,9 +279,6 @@ final class LocalBookmarkStore: BookmarkStore { // will return the children of the root folder, as the root folder is an implementation detail of the bookmarks store. let rootFolder = self.bookmarksRoot(in: context) let orphanedEntities = BookmarkUtils.fetchOrphanedEntities(context) - if !orphanedEntities.isEmpty { - self.reportOrphanedBookmarksIfNeeded() - } results = (rootFolder?.childrenArray ?? []) + orphanedEntities case .favorites: results = self.favoritesRoot(in: context)?.favoritesArray ?? [] @@ -300,15 +297,6 @@ final class LocalBookmarkStore: BookmarkStore { } } - private func reportOrphanedBookmarksIfNeeded() { - Task { @MainActor in - guard let syncService = NSApp.delegateTyped.syncService, syncService.authState == .inactive else { - return - } - Pixel.fire(.debug(event: .orphanedBookmarksPresent)) - } - } - func save(bookmark: Bookmark, parent: BookmarkFolder?, index: Int?, completion: @escaping (Bool, Error?) -> Void) { applyChangesAndSave(changes: { [weak self] context in guard let self = self else { diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index 4acdf3e1e4..5694bb4432 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -310,7 +310,6 @@ extension Pixel { case missingParent case bookmarksSaveFailed case bookmarksSaveFailedOnImport - case orphanedBookmarksPresent case bookmarksCouldNotLoadDatabase case bookmarksCouldNotPrepareDatabase @@ -753,7 +752,6 @@ extension Pixel.Event.Debug { case .missingParent: return "bookmark_missing_parent" case .bookmarksSaveFailed: return "bookmarks_save_failed" case .bookmarksSaveFailedOnImport: return "bookmarks_save_failed_on_import" - case .orphanedBookmarksPresent: return "bookmarks_orphans_present" case .bookmarksCouldNotLoadDatabase: return "bookmarks_could_not_load_database" case .bookmarksCouldNotPrepareDatabase: return "bookmarks_could_not_prepare_database" diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index 05b9494e5f..9062d1b12f 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: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index ef12d3e7c6..046edbc6a8 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: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index 2920672864..c98d6bb942 100644 --- a/LocalPackages/LoginItems/Package.swift +++ b/LocalPackages/LoginItems/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 148359e65b..337ecff4d0 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index e629606c8a..a9cbf81607 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -20,7 +20,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ .target( diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift index 8f773ac9a6..b2a61f6b19 100644 --- a/LocalPackages/Purchase/Package.swift +++ b/LocalPackages/Purchase/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index 08009324ff..e95d8aba1d 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(path: "../Account"), .package(path: "../Purchase"), .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index 233c7d2eba..1574244e15 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 5d6fa9f888..1f60d74fb1 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index e9cb8b771e..63be2bf115 100644 --- a/LocalPackages/SystemExtensionManager/Package.swift +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift index 5a182803f2..a272ed0be1 100644 --- a/LocalPackages/XPCHelper/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/UnitTests/Sync/SyncPreferencesTests.swift b/UnitTests/Sync/SyncPreferencesTests.swift index cd8d09b957..f3a9a4f8fb 100644 --- a/UnitTests/Sync/SyncPreferencesTests.swift +++ b/UnitTests/Sync/SyncPreferencesTests.swift @@ -21,6 +21,7 @@ import Combine import Persistence import SyncUI import XCTest +import TestUtils @testable import BrowserServicesKit @testable import DDGSync @testable import DuckDuckGo_Privacy_Browser @@ -136,6 +137,7 @@ final class SyncPreferencesTests: XCTestCase { } class MockDDGSyncing: DDGSyncing { + let registeredDevices = [RegisteredDevice(id: "1", name: "Device 1", type: "desktop"), RegisteredDevice(id: "2", name: "Device 2", type: "mobile"), RegisteredDevice(id: "3", name: "Device 1", type: "desktop")] var disconnectCalled = false @@ -151,6 +153,8 @@ class MockDDGSyncing: DDGSyncing { var scheduler: Scheduling + var syncDailyStats = SyncDailyStats(store: MockKeyValueStore()) + @Published var isSyncInProgress: Bool var isSyncInProgressPublisher: AnyPublisher { From 17d5a92de1af4d40af76920e3885d0e4a1811951 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Wed, 20 Dec 2023 16:51:38 +0100 Subject: [PATCH 04/10] Add Sync feature flags (#1992) Task/Issue URL: https://app.asana.com/0/0/1206046777189407/f Description: This change add support for Sync feature in Privacy Configuration, together with 4 subfeatures defining availability of various parts of Sync experience. DDGSyncing gets a read-only feature flag variable as well as a publisher. PrivacyConfigurationManager is now a Sync dependency, and DDGSync takes care internally of listening to Privacy Config changes and updating feature flags as needed. Feature flag responsible for actual data syncing is handled internally in DDGSync by cancelling all pending sync operations and disabling adding new operations. Other feature flags should be handled by client apps. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/Application/AppDelegate.swift | 9 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- DuckDuckGo/ContentBlocker/macos-config.json | 77 +++++++++++---- .../VPNLocation/VPNLocationView.swift | 4 +- .../Model/PreferencesSection.swift | 4 +- .../Model/PreferencesSidebarModel.swift | 24 ++++- .../Preferences/Model/SyncPreferences.swift | 34 ++++++- .../View/PreferencesViewController.swift | 12 ++- .../Sync/FaviconsFetcherOnboarding.swift | 3 +- .../Tab/View/BrowserTabViewController.swift | 5 +- LocalPackages/Account/Package.swift | 2 +- .../DataBrokerProtection/Package.swift | 2 +- LocalPackages/LoginItems/Package.swift | 2 +- .../NetworkProtectionMac/Package.swift | 2 +- LocalPackages/PixelKit/Package.swift | 2 +- LocalPackages/Purchase/Package.swift | 2 +- LocalPackages/Subscription/Package.swift | 2 +- LocalPackages/SwiftUIExtensions/Package.swift | 2 +- .../PreferencePaneSection.swift | 4 +- .../SwiftUIExtensions/TextButton.swift | 5 +- LocalPackages/SyncUI/Package.swift | 2 +- .../ViewModels/ManagementViewModel.swift | 5 + .../ManagementView/SyncEnabledView.swift | 46 +++++---- .../Views/ManagementView/SyncSetupView.swift | 93 ++++++++++++------- .../ManagementView/SyncWarningMessage.swift | 53 +++++++++++ .../ManagementView/SyncedDevicesView.swift | 17 ++-- .../Sources/SyncUI/internal/UserText.swift | 5 + .../SystemExtensionManager/Package.swift | 2 +- LocalPackages/XPCHelper/Package.swift | 2 +- .../PreferencesSidebarModelTests.swift | 7 +- UnitTests/Sync/SyncPreferencesTests.swift | 8 +- 33 files changed, 327 insertions(+), 120 deletions(-) create mode 100644 LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0965772e2b..38fb77252b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12942,7 +12942,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 96.0.1; + version = 97.0.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 e9e774e2b4..24e8e97f71 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" : "308abf4ebf170dc73d9f1a8a1730ed3170bed2d5", - "version" : "96.0.1" + "revision" : "d671accf1bf7097c4e7f5cd55cd1c6dfa005cf92", + "version" : "97.0.0" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 92e373af52..bf83796dcf 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -69,6 +69,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel private(set) var syncDataProviders: SyncDataProviders! private(set) var syncService: DDGSyncing? private var isSyncInProgressCancellable: AnyCancellable? + private var syncFeatureFlagsCancellable: AnyCancellable? private var screenLockedCancellable: AnyCancellable? private var emailCancellables = Set() let bookmarksManager = LocalBookmarkManager.shared @@ -339,7 +340,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel let environment = defaultEnvironment #endif let syncDataProviders = SyncDataProviders(bookmarksDatabase: BookmarkDatabase.shared.db) - let syncService = DDGSync(dataProvidersSource: syncDataProviders, errorEvents: SyncErrorHandler(), log: OSLog.sync, environment: environment) + let syncService = DDGSync( + dataProvidersSource: syncDataProviders, + errorEvents: SyncErrorHandler(), + privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + log: OSLog.sync, + environment: environment + ) syncService.initializeIfNeeded() syncDataProviders.setUpDatabaseCleaners(syncService: syncService) diff --git a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift index fb7b34b26e..f0e8b44787 100644 --- a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"ca66d409eb00e5c19f3a0abae449dd1a\"" - public static let embeddedDataSHA = "42f9d3064372bc85ac8ee37afe883ed4741d6a3cfcb9ce927c2f732c3f694140" + public static let embeddedDataETag = "\"8e38db3f042f4a2a5c9f7ca8d33f17c9\"" + public static let embeddedDataSHA = "a9fc8ee927a37d1b5545377ff29de798f3ec3b8a757430c3af72d13a3844c258" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/macos-config.json b/DuckDuckGo/ContentBlocker/macos-config.json index 94914fa75a..6ec6c5ecf4 100644 --- a/DuckDuckGo/ContentBlocker/macos-config.json +++ b/DuckDuckGo/ContentBlocker/macos-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1702579565498, + "version": 1703003631137, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -245,6 +245,9 @@ { "domain": "metro.co.uk" }, + { + "domain": "youtube.com" + }, { "domain": "earth.google.com" }, @@ -264,7 +267,7 @@ ] }, "state": "enabled", - "hash": "9ab9e1acdb6a8617c77109acc1e3943c" + "hash": "a1060783f1bf56f5cc661b994c9c9e56" }, "autofill": { "exceptions": [ @@ -1030,12 +1033,16 @@ { "domain": "flysas.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1347" + }, + { + "domain": "facebook.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1641" } ] }, "exceptions": [], "state": "enabled", - "hash": "d757f6509e9a9a20140c755ed0f21ea2" + "hash": "6ed03febdd780fd845cc1f7d6ce74ecf" }, "dbp": { "state": "enabled", @@ -2014,6 +2021,23 @@ } ] }, + { + "domain": "dexerto.com", + "rules": [ + { + "selector": "#leaderboard-top-adhesion", + "type": "closest-empty" + }, + { + "selector": "[data-cy='Ad']", + "type": "closest-empty" + }, + { + "selector": "[data-cy='VidazooPlayer']", + "type": "closest-empty" + } + ] + }, { "domain": "dpreview.com", "rules": [ @@ -3678,7 +3702,7 @@ ] }, "state": "enabled", - "hash": "c34713afaf78e090b587a923f132ed56" + "hash": "8551081ffe228a4bef49deba3fe37b19" }, "exceptionHandler": { "exceptions": [ @@ -3933,10 +3957,13 @@ }, { "domain": "airbnb.com.br" + }, + { + "domain": "zoom.us" } ], "state": "enabled", - "hash": "75e673ce365430652fc7cc896ed49a5f" + "hash": "82ebfddf791575ada285a49b7fdc71ca" }, "fingerprintingScreenSize": { "settings": { @@ -4460,6 +4487,11 @@ "state": "disabled", "hash": "5e792dd491428702bc0104240fbce0ce" }, + "sync": { + "exceptions": [], + "state": "disabled", + "hash": "728493ef7a1488e4781656d3f9db84aa" + }, "trackerAllowlist": { "state": "enabled", "settings": { @@ -4750,8 +4782,7 @@ { "rule": "analytics.analytics-egain.com/onetag/", "domains": [ - "landsend.com", - "support.norton.com" + "" ] } ] @@ -5323,6 +5354,7 @@ "asics.com", "brooklinen.com", "carters.com", + "otterbox.com", "seatosummit.com" ] } @@ -5382,6 +5414,16 @@ } ] }, + "egain.cloud": { + "rules": [ + { + "rule": "egain.cloud/", + "domains": [ + "" + ] + } + ] + }, "ensighten.com": { "rules": [ { @@ -5509,8 +5551,7 @@ { "rule": "app.five9.com", "domains": [ - "gmsdnv.com", - "machiassavings.bank" + "" ] } ] @@ -5750,6 +5791,12 @@ "rawstory.com", "usatoday.com" ] + }, + { + "rule": "storage.googleapis.com/code.snapengage.com/", + "domains": [ + "" + ] } ] }, @@ -5977,15 +6024,9 @@ "gorgias.chat": { "rules": [ { - "rule": "config.gorgias.chat", + "rule": "gorgias.chat", "domains": [ - "help.athleticbrewing.com" - ] - }, - { - "rule": "assets.gorgias.chat", - "domains": [ - "help.athleticbrewing.com" + "" ] } ] @@ -7509,7 +7550,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "5cecb6d28193f468b28b9afdaca04da1" + "hash": "b6996e16fe1785f6d75cd6302da1f48e" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift index dec1e1cf46..a7dc7255d6 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift @@ -61,7 +61,7 @@ struct VPNLocationView: View { @ViewBuilder private func nearest(isSelected: Bool) -> some View { - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.vpnLocationRecommendedSectionTitle) .font(.system(size: 15)) .foregroundColor(.primary) @@ -98,7 +98,7 @@ struct VPNLocationView: View { EmptyView() .listRowBackground(Color.clear) case .loaded(let countryItems): - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.vpnLocationCustomSectionTitle) .font(.system(size: 15)) .foregroundColor(.primary) diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 2c71c401a7..ad061c7ed0 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -24,7 +24,7 @@ struct PreferencesSection: Hashable, Identifiable { let panes: [PreferencePaneIdentifier] @MainActor - static func defaultSections(includingDuckPlayer: Bool, includingVPN: Bool) -> [PreferencesSection] { + static func defaultSections(includingDuckPlayer: Bool, includingSync: Bool, includingVPN: Bool) -> [PreferencesSection] { let regularPanes: [PreferencePaneIdentifier] = { #if SUBSCRIPTION var panes: [PreferencePaneIdentifier] = [.privacy, .subscription, .general, .appearance, .autofill, .downloads] @@ -37,7 +37,7 @@ struct PreferencesSection: Hashable, Identifiable { #else var panes: [PreferencePaneIdentifier] = [.general, .appearance, .privacy, .autofill, .downloads] - if NSApp.delegateTyped.internalUserDecider.isInternalUser { + if includingSync { panes.insert(.sync, at: 1) } #endif diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index 363378641a..11e81dd11c 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -16,9 +16,10 @@ // limitations under the License. // -import SwiftUI import BrowserServicesKit import Combine +import DDGSync +import SwiftUI final class PreferencesSidebarModel: ObservableObject { @@ -33,7 +34,8 @@ final class PreferencesSidebarModel: ObservableObject { init( loadSections: @escaping () -> [PreferencesSection], tabSwitcherTabs: [Tab.TabContent], - privacyConfigurationManager: PrivacyConfigurationManaging + privacyConfigurationManager: PrivacyConfigurationManaging, + syncService: DDGSyncing ) { self.loadSections = loadSections self.tabSwitcherTabs = tabSwitcherTabs @@ -41,12 +43,18 @@ final class PreferencesSidebarModel: ObservableObject { resetTabSelectionIfNeeded() refreshSections() - privacyConfigurationManager.updatesPublisher + let duckPlayerFeatureFlagDidChange = privacyConfigurationManager.updatesPublisher .map { [weak privacyConfigurationManager] in privacyConfigurationManager?.privacyConfig.isEnabled(featureKey: .duckPlayer) == true } .removeDuplicates() .asVoid() + + let syncFeatureFlagsDidChange = syncService.featureFlagsPublisher.map { $0.contains(.userInterface) } + .removeDuplicates() + .asVoid() + + Publishers.Merge(duckPlayerFeatureFlagDidChange, syncFeatureFlagsDidChange) .receive(on: DispatchQueue.main) .sink { [weak self] in self?.refreshSections() @@ -62,6 +70,7 @@ final class PreferencesSidebarModel: ObservableObject { convenience init( tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, + syncService: DDGSyncing, includeDuckPlayer: Bool ) { let loadSections = { @@ -71,12 +80,17 @@ final class PreferencesSidebarModel: ObservableObject { let includingVPN = false #endif - return PreferencesSection.defaultSections(includingDuckPlayer: includeDuckPlayer, includingVPN: includingVPN) + return PreferencesSection.defaultSections( + includingDuckPlayer: includeDuckPlayer, + includingSync: syncService.featureFlags.contains(.userInterface), + includingVPN: includingVPN + ) } self.init(loadSections: loadSections, tabSwitcherTabs: tabSwitcherTabs, - privacyConfigurationManager: privacyConfigurationManager) + privacyConfigurationManager: privacyConfigurationManager, + syncService: syncService) } // MARK: - Setup diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index 60933937e5..e1f62308ba 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -89,14 +89,38 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { private var isScreenLocked: Bool = false private var recoveryKey: SyncCode.RecoveryKey? + @Published var syncFeatureFlags: SyncFeatureFlags { + didSet { + updateSyncFeatureFlags(syncFeatureFlags) + } + } + + @Published var isDataSyncingAvailable: Bool = true + @Published var isConnectingDevicesAvailable: Bool = true + @Published var isAccountCreationAvailable: Bool = true + @Published var isAccountRecoveryAvailable: Bool = true + + private func updateSyncFeatureFlags(_ syncFeatureFlags: SyncFeatureFlags) { + isDataSyncingAvailable = syncFeatureFlags.contains(.dataSyncing) + isConnectingDevicesAvailable = syncFeatureFlags.contains(.connectFlows) + isAccountCreationAvailable = syncFeatureFlags.contains(.accountCreation) + isAccountRecoveryAvailable = syncFeatureFlags.contains(.accountRecovery) + } + var recoveryCode: String? { syncService.account?.recoveryCode } - init(syncService: DDGSyncing, syncBookmarksAdapter: SyncBookmarksAdapter, appearancePreferences: AppearancePreferences = .shared, managementDialogModel: ManagementDialogModel = ManagementDialogModel()) { + init( + syncService: DDGSyncing, + syncBookmarksAdapter: SyncBookmarksAdapter, + appearancePreferences: AppearancePreferences = .shared, + managementDialogModel: ManagementDialogModel = ManagementDialogModel() + ) { self.syncService = syncService self.syncBookmarksAdapter = syncBookmarksAdapter self.appearancePreferences = appearancePreferences + self.syncFeatureFlags = syncService.featureFlags self.isFaviconsFetchingEnabled = syncBookmarksAdapter.isFaviconsFetchingEnabled self.isUnifiedFavoritesEnabled = appearancePreferences.favoritesDisplayMode.isDisplayUnified @@ -106,11 +130,19 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { self.managementDialogModel = managementDialogModel self.managementDialogModel.delegate = self + updateSyncFeatureFlags(self.syncFeatureFlags) setUpObservables() setUpSyncOptionsObservables(apperancePreferences: appearancePreferences) } private func setUpObservables() { + syncService.featureFlagsPublisher + .dropFirst() + .removeDuplicates() + .receive(on: DispatchQueue.main) + .assign(to: \.syncFeatureFlags, onWeaklyHeld: self) + .store(in: &cancellables) + syncService.authStatePublisher .removeDuplicates() .asVoid() diff --git a/DuckDuckGo/Preferences/View/PreferencesViewController.swift b/DuckDuckGo/Preferences/View/PreferencesViewController.swift index 0289ac6afc..0a9e3823d6 100644 --- a/DuckDuckGo/Preferences/View/PreferencesViewController.swift +++ b/DuckDuckGo/Preferences/View/PreferencesViewController.swift @@ -19,6 +19,7 @@ import AppKit import SwiftUI import Combine +import DDGSync #if NETWORK_PROTECTION import NetworkProtection @@ -28,12 +29,21 @@ final class PreferencesViewController: NSViewController { weak var delegate: BrowserTabSelectionDelegate? - let model = PreferencesSidebarModel(includeDuckPlayer: DuckPlayer.shared.isAvailable) + let model: PreferencesSidebarModel private var selectedTabIndexCancellable: AnyCancellable? private var selectedPreferencePaneCancellable: AnyCancellable? private var bitwardenManager: BWManagement = BWManager.shared + init(syncService: DDGSyncing, duckPlayer: DuckPlayer = DuckPlayer.shared) { + model = PreferencesSidebarModel(syncService: syncService, includeDuckPlayer: duckPlayer.isAvailable) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func loadView() { view = NSView() } diff --git a/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift b/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift index 2f28741993..3cd482ced8 100644 --- a/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift +++ b/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift @@ -68,7 +68,8 @@ final class FaviconsFetcherOnboarding { } private var shouldPresentOnboarding: Bool { - !didPresentFaviconsFetchingOnboarding + syncService.featureFlags.contains(.userInterface) + && !didPresentFaviconsFetchingOnboarding && !syncBookmarksAdapter.isFaviconsFetchingEnabled && syncBookmarksAdapter.isEligibleForFaviconsFetcherOnboarding } diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index 872b4fb27e..1192e49e25 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -546,7 +546,10 @@ final class BrowserTabViewController: NSViewController { var preferencesViewController: PreferencesViewController? private func preferencesViewControllerCreatingIfNeeded() -> PreferencesViewController { return preferencesViewController ?? { - let preferencesViewController = PreferencesViewController() + guard let syncService = NSApp.delegateTyped.syncService else { + fatalError("Sync service is nil") + } + let preferencesViewController = PreferencesViewController(syncService: syncService) preferencesViewController.delegate = self self.preferencesViewController = preferencesViewController return preferencesViewController diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index 9062d1b12f..b32006c664 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 046edbc6a8..ec47443f0f 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index c98d6bb942..54a86db801 100644 --- a/LocalPackages/LoginItems/Package.swift +++ b/LocalPackages/LoginItems/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 337ecff4d0..e7fb30c89e 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index a9cbf81607..aaaa2d9e09 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -20,7 +20,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift index b2a61f6b19..6ebc16624c 100644 --- a/LocalPackages/Purchase/Package.swift +++ b/LocalPackages/Purchase/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index e95d8aba1d..e9b73b884a 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(path: "../Account"), .package(path: "../Purchase"), .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index 1574244e15..cc43577d0b 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift index 928b489ddc..0fadaa2d19 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift @@ -24,9 +24,9 @@ public struct PreferencePaneSection: View where Content: View { public let verticalPadding: CGFloat @ViewBuilder public let content: () -> Content - public init(spacing: CGFloat = 12, vericalPadding: CGFloat = 20, @ViewBuilder content: @escaping () -> Content) { + public init(spacing: CGFloat = 12, verticalPadding: CGFloat = 20, @ViewBuilder content: @escaping () -> Content) { self.spacing = spacing - self.verticalPadding = vericalPadding + self.verticalPadding = verticalPadding self.content = content } diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift index 329cb46b85..1e73626837 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift @@ -21,16 +21,19 @@ import SwiftUI public struct TextButton: View { public let title: String + public let fontWeight: Font.Weight public let action: () -> Void - public init(_ title: String, action: @escaping () -> Void) { + public init(_ title: String, weight: Font.Weight = .regular, action: @escaping () -> Void) { self.title = title + self.fontWeight = weight self.action = action } public var body: some View { Button(action: action) { Text(title) + .fontWeight(fontWeight) .foregroundColor(Color("LinkBlueColor")) } .buttonStyle(.plain) diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 1f60d74fb1..49c6f30303 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift index 89cb583ab2..4e1d1de021 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift @@ -20,6 +20,11 @@ import Foundation public protocol ManagementViewModel: ObservableObject { + var isDataSyncingAvailable: Bool { get } + var isConnectingDevicesAvailable: Bool { get } + var isAccountCreationAvailable: Bool { get } + var isAccountRecoveryAvailable: Bool { get } + var isSyncEnabled: Bool { get } var isCreatingAccount: Bool { get } var shouldShowErrorMessage: Bool { get set } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift index 2f17ab011d..3878a7d693 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift @@ -25,6 +25,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { var body: some View { // Errors VStack(alignment: .leading, spacing: 16) { + syncUnavailableView() if model.isSyncBookmarksPaused { syncPaused(for: .bookmarks) } @@ -34,14 +35,14 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Sync Enabled - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { SyncStatusView() .environmentObject(model) .frame(width: 513, alignment: .topLeading) } // Synced Devices - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.syncedDevices) .font(Const.Fonts.preferencePaneSectionHeader) .padding(.horizontal, 16) @@ -51,7 +52,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Options - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.optionsSectionTitle) .font(Const.Fonts.preferencePaneSectionHeader) .padding(.horizontal, 16) @@ -94,7 +95,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Recovery - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { VStack(alignment: .leading, spacing: 6) { Text(UserText.recovery) .font(Const.Fonts.preferencePaneSectionHeader) @@ -112,7 +113,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Turn Off and Delate Data - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Button(UserText.turnOffAndDeleteServerData) { model.presentDeleteAccount() } @@ -138,28 +139,23 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { return UserText.credentialsLimitExceededAction } } - PreferencePaneSection(vericalPadding: 16) { - HStack(alignment: .top, spacing: 8) { - Text("⚠️") - VStack(alignment: .leading, spacing: 8) { - Text(UserText.syncLimitExceededTitle) - .bold() - Text(description) - Button(actionTitle) { - switch itemType { - case .bookmarks: - model.manageBookmarks() - case .credentials: - model.manageLogins() - } - } - .padding(.top, 8) - } + SyncWarningMessage(title: UserText.syncLimitExceededTitle, message: description, buttonTitle: actionTitle) { + switch itemType { + case .bookmarks: + model.manageBookmarks() + case .credentials: + model.manageLogins() } - .padding(.horizontal, 16) } - .frame(width: 512, alignment: .leading) - .background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AlertBubbleBackground"))) + } + + @ViewBuilder + fileprivate func syncUnavailableView() -> some View { + if model.isDataSyncingAvailable { + EmptyView() + } else { + SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + } } enum LimitedItemType { diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift index 471fb6a546..1ea3bf81a7 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift @@ -22,34 +22,10 @@ import SwiftUIExtensions struct SyncSetupView: View where ViewModel: ManagementViewModel { @EnvironmentObject var model: ViewModel - fileprivate func syncWithAnotherDeviceView() -> some View { - return VStack(alignment: .center, spacing: 16) { - Image("Sync-Pair-96") - VStack(alignment: .center, spacing: 8) { - SyncUIViews.TextHeader(text: UserText.beginSyncTitle) - SyncUIViews.TextDetailSecondary(text: UserText.beginSyncDescription) - } - .padding(.bottom, 16) - ZStack { - RoundedRectangle(cornerRadius: 8) - .fill(Color("LinkBlueColor")) - .frame(width: 220, height: 32) - Text(UserText.beginSyncButton) - .foregroundColor(.white) - .bold() - } - .onTapGesture { - model.syncWithAnotherDevicePressed() - } - } - .frame(width: 512, height: 254) - .roundedBorder() - .padding(.top, 20) - } - var body: some View { VStack(alignment: .leading, spacing: 24) { VStack(spacing: 8) { + syncUnavailableView() syncWithAnotherDeviceView() SyncUIViews.TextDetailSecondary(text: UserText.beginSyncFooter) .padding(.bottom, 24) @@ -59,16 +35,67 @@ struct SyncSetupView: View where ViewModel: ManagementViewModel { VStack(alignment: .leading, spacing: 12) { SyncUIViews.TextHeader2(text: UserText.otherOptionsSectionTitle) VStack(alignment: .leading, spacing: 8) { - SyncUIViews.TextLink(text: UserText.syncThisDeviceLink) - .onTapGesture { - model.syncWithServerPressed() - } - SyncUIViews.TextLink(text: UserText.recoverDataLink) - .onTapGesture { - model.recoverDataPressed() - } + TextButton(UserText.syncThisDeviceLink, weight: .semibold, action: model.syncWithServerPressed) + .disabled(!model.isAccountCreationAvailable) + TextButton(UserText.recoverDataLink, weight: .semibold, action: model.recoverDataPressed) + .disabled(!model.isAccountRecoveryAvailable) } } } } + + fileprivate func syncWithAnotherDeviceView() -> some View { + VStack(alignment: .center, spacing: 16) { + Image("Sync-Pair-96") + VStack(alignment: .center, spacing: 8) { + SyncUIViews.TextHeader(text: UserText.beginSyncTitle) + SyncUIViews.TextDetailSecondary(text: UserText.beginSyncDescription) + } + .padding(.bottom, 16) + Button(UserText.beginSyncButton, action: model.syncWithAnotherDevicePressed) + .buttonStyle(SyncWithAnotherDeviceButtonStyle(enabled: model.isConnectingDevicesAvailable)) + .disabled(!model.isConnectingDevicesAvailable) + } + .frame(width: 512, height: 254) + .roundedBorder() + .padding(.top, 20) + } + + @ViewBuilder + fileprivate func syncUnavailableView() -> some View { + if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable { + SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + .padding(.top, 16) + } else if !model.isAccountCreationAvailable { + SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningAccountCreationDisabled) + .padding(.top, 16) + } else { + EmptyView() + } + } +} + +private struct SyncWithAnotherDeviceButtonStyle: ButtonStyle { + + public let enabled: Bool + + public init(enabled: Bool) { + self.enabled = enabled + } + + public func makeBody(configuration: Self.Configuration) -> some View { + + let enabledBackgroundColor = configuration.isPressed ? Color(NSColor.controlAccentColor).opacity(0.5) : Color(NSColor.controlAccentColor) + let disabledBackgroundColor = Color.gray.opacity(0.1) + let labelColor = enabled ? Color.white : Color.primary.opacity(0.3) + + configuration.label + .lineLimit(1) + .font(.body.bold()) + .frame(width: 220, height: 32) + .background(enabled ? enabledBackgroundColor : disabledBackgroundColor) + .foregroundColor(labelColor) + .cornerRadius(8) + + } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift new file mode 100644 index 0000000000..cf9a80ed0b --- /dev/null +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift @@ -0,0 +1,53 @@ +// +// SyncWarningMessage.swift +// +// Copyright © 2023 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 SwiftUI +import SwiftUIExtensions + +struct SyncWarningMessage: View { + let title: String + let message: String + let buttonTitle: String? + let buttonAction: (() -> Void)? + + init(title: String, message: String, buttonTitle: String? = nil, buttonAction: (() -> Void)? = nil) { + self.title = title + self.message = message + self.buttonTitle = buttonTitle + self.buttonAction = buttonAction + } + + var body: some View { + PreferencePaneSection(verticalPadding: 16) { + HStack(alignment: .top, spacing: 8) { + Text("⚠️") + VStack(alignment: .leading, spacing: 8) { + Text(title).bold() + Text(message) + if let buttonTitle, let buttonAction { + Button(buttonTitle, action: buttonAction) + .padding(.top, 8) + } + } + } + .padding(.horizontal, 16) + } + .frame(width: 512, alignment: .leading) + .background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AlertBubbleBackground"))) + } +} diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift index c026ad51ce..766f8596ca 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift @@ -27,7 +27,7 @@ struct SyncedDevicesView: View where ViewModel: ManagementViewModel { let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() var body: some View { - VStack { + VStack(alignment: .leading) { SyncedDevicesList(devices: model.devices, presentDeviceDetails: model.presentDeviceDetails, presentRemoveDevice: model.presentRemoveDevice) @@ -41,15 +41,14 @@ struct SyncedDevicesView: View where ViewModel: ManagementViewModel { .onDisappear { isVisible = false } - SyncPreferencesRow { - } centerContent: { - Button { - model.syncWithAnotherDevicePressed() - } label: { - Text("Sync with Another Device") - .frame(height: 24) - } + Button { + model.syncWithAnotherDevicePressed() + } label: { + Text(UserText.beginSyncButton) } + .disabled(!model.isConnectingDevicesAvailable) + .padding(.horizontal, 10) + .padding(.bottom, 8) } .roundedBorder() } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index 928aa1123f..2b7d2a1059 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -157,4 +157,9 @@ enum UserText { static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog") static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog") static let keepFaviconsUpdated = NSLocalizedString("prefrences.sync.keep-favicons-updated", value: "Keep Bookmarks Icons Updated", comment: "Title of the confirmation button for favicons fetching") + + // Sync Feature Flags + static let serviceUnavailable = NSLocalizedString("sync.warning.service-unavailable", value: "Service Unavailable", comment: "Title of the warning message") + static let warningSyncDisabled = NSLocalizedString("sync.warning.sync-disabled", value: "We apologize, but the service is currently unavailable. Please try again later.", comment: "Sync unavailable warning message") + static let warningAccountCreationDisabled = NSLocalizedString("sync.warning.account-creation-disabled", value: "We apologize, but new account creation is currently unavailable for this service. Please try again later.", comment: "Sync unavailable warning message") } diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index 63be2bf115..e27bd650a8 100644 --- a/LocalPackages/SystemExtensionManager/Package.swift +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift index a272ed0be1..1424527204 100644 --- a/LocalPackages/XPCHelper/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/UnitTests/Preferences/PreferencesSidebarModelTests.swift b/UnitTests/Preferences/PreferencesSidebarModelTests.swift index f601cb90f4..1d6666ae91 100644 --- a/UnitTests/Preferences/PreferencesSidebarModelTests.swift +++ b/UnitTests/Preferences/PreferencesSidebarModelTests.swift @@ -31,7 +31,12 @@ final class PreferencesSidebarModelTests: XCTestCase { } private func PreferencesSidebarModel(loadSections: [PreferencesSection]? = nil, tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes) -> DuckDuckGo_Privacy_Browser.PreferencesSidebarModel { - return DuckDuckGo_Privacy_Browser.PreferencesSidebarModel(loadSections: { loadSections ?? PreferencesSection.defaultSections(includingDuckPlayer: false, includingVPN: false) }, tabSwitcherTabs: tabSwitcherTabs, privacyConfigurationManager: MockPrivacyConfigurationManager()) + return DuckDuckGo_Privacy_Browser.PreferencesSidebarModel( + loadSections: { loadSections ?? PreferencesSection.defaultSections(includingDuckPlayer: false, includingSync: false, includingVPN: false) }, + tabSwitcherTabs: tabSwitcherTabs, + privacyConfigurationManager: MockPrivacyConfigurationManager(), + syncService: MockDDGSyncing(authState: .inactive, isSyncInProgress: false) + ) } func testWhenInitializedThenFirstPaneInFirstSectionIsSelected() throws { diff --git a/UnitTests/Sync/SyncPreferencesTests.swift b/UnitTests/Sync/SyncPreferencesTests.swift index f3a9a4f8fb..5317d03c02 100644 --- a/UnitTests/Sync/SyncPreferencesTests.swift +++ b/UnitTests/Sync/SyncPreferencesTests.swift @@ -143,6 +143,12 @@ class MockDDGSyncing: DDGSyncing { var dataProvidersSource: DataProvidersSource? + @Published var featureFlags: SyncFeatureFlags = .all + + var featureFlagsPublisher: AnyPublisher { + $featureFlags.eraseToAnyPublisher() + } + @Published var authState: SyncAuthState = .inactive var authStatePublisher: AnyPublisher { @@ -161,7 +167,7 @@ class MockDDGSyncing: DDGSyncing { $isSyncInProgress.eraseToAnyPublisher() } - init(dataProvidersSource: DataProvidersSource? = nil, authState: SyncAuthState, account: SyncAccount? = nil, scheduler: Scheduling, isSyncInProgress: Bool) { + init(dataProvidersSource: DataProvidersSource? = nil, authState: SyncAuthState, account: SyncAccount? = nil, scheduler: Scheduling = CapturingScheduler(), isSyncInProgress: Bool) { self.dataProvidersSource = dataProvidersSource self.authState = authState self.account = account From 6389927cd4c2b62746c25f9d16d1f864a43e5451 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 21 Dec 2023 15:23:36 +0600 Subject: [PATCH 05/10] Improve bookmarks html reader (#1986) Task/Issue URL: https://app.asana.com/0/0/1206196320966558/f --- DuckDuckGo.xcodeproj/project.pbxproj | 63 +++++- .../xcshareddata/swiftpm/Package.resolved | 18 ++ .../Chromium/ImportedBookmarks.swift | 17 +- .../Bookmarks/HTML/BookmarkHTMLReader.swift | 106 +++++------ .../DataImport/Bookmarks/HTML/README.md | 17 +- .../DataImport/BookmarksHTMLReaderTests.swift | 98 +++------- .../bookmarks_chrome_dirty.html | 10 + .../bookmarks_firefox_dirty.html | 13 ++ .../bookmarks_netscape_dirty.html | 11 ++ .../bookmarks_safari_tp_dirty.html | 38 ++++ .../TestBookmarksData/bookmarks_vivaldi.html | 46 +++++ .../snapshot.bookmarks_brave.json | 150 +++++++++++++++ .../snapshot.bookmarks_chrome.json | 150 +++++++++++++++ .../snapshot.bookmarks_chrome_dirty.json | 31 +++ .../snapshot.bookmarks_ddg_android.json | 160 ++++++++++++++++ .../snapshot.bookmarks_ddg_ios.json | 75 ++++++++ .../snapshot.bookmarks_ddg_macos.json | 155 +++++++++++++++ .../snapshot.bookmarks_firefox.json | 180 ++++++++++++++++++ .../snapshot.bookmarks_firefox_dirty.json | 42 ++++ .../snapshot.bookmarks_netscape_dirty.json | 31 +++ .../snapshot.bookmarks_safari.json | 172 +++++++++++++++++ .../snapshot.bookmarks_safari_tp_dirty.json | 105 ++++++++++ .../snapshot.bookmarks_vivaldi.json | 136 +++++++++++++ 23 files changed, 1678 insertions(+), 146 deletions(-) create mode 100644 UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_chrome_dirty.html create mode 100644 UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_firefox_dirty.html create mode 100644 UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_netscape_dirty.html create mode 100644 UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_safari_tp_dirty.html create mode 100644 UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_vivaldi.html create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_brave.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome_dirty.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_android.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_ios.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_macos.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox_dirty.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_netscape_dirty.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari_tp_dirty.json create mode 100644 UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_vivaldi.json diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 38fb77252b..6c065c61a0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2715,6 +2715,13 @@ B65783E725F8AAFB00D8DB33 /* String+Punycode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65783E625F8AAFB00D8DB33 /* String+Punycode.swift */; }; B657841A25FA484B00D8DB33 /* NSException+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = B657841925FA484B00D8DB33 /* NSException+Catch.m */; }; B657841F25FA497600D8DB33 /* NSException+Catch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B657841E25FA497600D8DB33 /* NSException+Catch.swift */; }; + B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CA2B316DF100A595BB /* SnapshotTesting */; }; + B65CD8CD2B316DFC00A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CC2B316DFC00A595BB /* SnapshotTesting */; }; + B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CE2B316E0200A595BB /* SnapshotTesting */; }; + B65CD8D12B316E0C00A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8D02B316E0C00A595BB /* SnapshotTesting */; }; + B65CD8D32B316E1700A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8D22B316E1700A595BB /* SnapshotTesting */; }; + B65CD8D52B316FCA00A595BB /* __Snapshots__ in Resources */ = {isa = PBXBuildFile; fileRef = B65CD8D42B316FCA00A595BB /* __Snapshots__ */; }; + B65CD8D62B316FCA00A595BB /* __Snapshots__ in Resources */ = {isa = PBXBuildFile; fileRef = B65CD8D42B316FCA00A595BB /* __Snapshots__ */; }; B65DA5EF2A77CC3A00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E81F2A1E2603006D261F /* Bundle+NetworkProtectionExtensions.swift */; }; B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E81F2A1E2603006D261F /* Bundle+NetworkProtectionExtensions.swift */; }; B65DA5F12A77D2BC00CBEE8D /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; @@ -4058,6 +4065,8 @@ B657841825FA484B00D8DB33 /* NSException+Catch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSException+Catch.h"; sourceTree = ""; }; B657841925FA484B00D8DB33 /* NSException+Catch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSException+Catch.m"; sourceTree = ""; }; B657841E25FA497600D8DB33 /* NSException+Catch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSException+Catch.swift"; sourceTree = ""; }; + B65CD8D42B316FCA00A595BB /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = ""; }; + B65CD8D72B341FD300A595BB /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; B65E6B9D26D9EC0800095F96 /* CircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; }; B65E6B9F26D9F10600095F96 /* NSBezierPathExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSBezierPathExtension.swift; sourceTree = ""; }; B66260DC29AC5D4300E9E3EE /* NavigationProtectionTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProtectionTabExtension.swift; sourceTree = ""; }; @@ -4280,6 +4289,7 @@ buildActionMask = 2147483647; files = ( 3706FE88293F661700E42796 /* OHHTTPStubs in Frameworks */, + B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */, 3706FE89293F661700E42796 /* OHHTTPStubsSwift in Frameworks */, 4B81AD372B29513100706C96 /* PixelKitTestingUtilities in Frameworks */, ); @@ -4289,6 +4299,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8D12B316E0C00A595BB /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4303,6 +4314,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8CD2B316DFC00A595BB /* SnapshotTesting in Frameworks */, B6AE39F329374AEC00C37AA4 /* OHHTTPStubs in Frameworks */, B6AE39F529374AEC00C37AA4 /* OHHTTPStubsSwift in Frameworks */, ); @@ -4413,6 +4425,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8D32B316E1700A595BB /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4475,6 +4488,7 @@ buildActionMask = 2147483647; files = ( B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */, + B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */, B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */, 4B81AD352B29512B00706C96 /* PixelKitTestingUtilities in Frameworks */, ); @@ -4709,6 +4723,7 @@ 373A1AA6283ECC8000586521 /* HTML */ = { isa = PBXGroup; children = ( + B65CD8D72B341FD300A595BB /* README.md */, 373A1AA7283ED1B900586521 /* BookmarkHTMLReader.swift */, 373A1AAF2842C4EA00586521 /* BookmarkHTMLImporter.swift */, ); @@ -5411,6 +5426,8 @@ 4B723DFE26B0003E00E14D75 /* DataImport */ = { isa = PBXGroup; children = ( + 37A803DA27FD69D300052F4C /* DataImportResources */, + B65CD8D42B316FCA00A595BB /* __Snapshots__ */, 373A1AB128451ED400586521 /* BookmarksHTMLImporterTests.swift */, 373A1AA9283ED86C00586521 /* BookmarksHTMLReaderTests.swift */, 4B3F641D27A8D3BD00E0C118 /* BrowserProfileTests.swift */, @@ -5427,7 +5444,6 @@ 4B8AC93C26B49BE600879451 /* FirefoxLoginReaderTests.swift */, 4BB99D0E26FE1A84001E4761 /* SafariBookmarksReaderTests.swift */, 4BF4951726C08395000547B8 /* ThirdPartyBrowserTests.swift */, - 37A803DA27FD69D300052F4C /* DataImportResources */, ); path = DataImport; sourceTree = ""; @@ -8014,6 +8030,7 @@ 3706FDD6293F661700E42796 /* OHHTTPStubs */, 3706FDD8293F661700E42796 /* OHHTTPStubsSwift */, 4B81AD362B29513100706C96 /* PixelKitTestingUtilities */, + B65CD8CE2B316E0200A595BB /* SnapshotTesting */, ); productName = DuckDuckGoTests; productReference = 3706FE99293F661700E42796 /* Unit Tests App Store.xctest */; @@ -8035,6 +8052,9 @@ 37079A95294236FA0031BB3C /* PBXTargetDependency */, ); name = "Integration Tests App Store"; + packageProductDependencies = ( + B65CD8D02B316E0C00A595BB /* SnapshotTesting */, + ); productName = "Integration Tests"; productReference = 3706FEB2293F662100E42796 /* Integration Tests App Store.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -8076,6 +8096,7 @@ packageProductDependencies = ( B6AE39F229374AEC00C37AA4 /* OHHTTPStubs */, B6AE39F429374AEC00C37AA4 /* OHHTTPStubsSwift */, + B65CD8CC2B316DFC00A595BB /* SnapshotTesting */, ); productName = "Integration Tests"; productReference = 4B1AD89D25FC27E200261379 /* Integration Tests.xctest */; @@ -8292,6 +8313,9 @@ 7B4CE8E026F02108009134B1 /* PBXTargetDependency */, ); name = "UI Tests"; + packageProductDependencies = ( + B65CD8D22B316E1700A595BB /* SnapshotTesting */, + ); productName = "UI Tests"; productReference = 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; @@ -8414,6 +8438,7 @@ B6DA44162616C13800DD1EC2 /* OHHTTPStubs */, B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */, 4B81AD342B29512B00706C96 /* PixelKitTestingUtilities */, + B65CD8CA2B316DF100A595BB /* SnapshotTesting */, ); productName = DuckDuckGoTests; productReference = AA585D90248FD31400E9A3E2 /* Unit Tests.xctest */; @@ -8515,6 +8540,7 @@ B6EC37F529B5DAAC001ACE79 /* XCRemoteSwiftPackageReference "swifter" */, 371D00DF29D8509400EC8598 /* XCRemoteSwiftPackageReference "OpenSSL-XCFramework" */, 4B2AAAF329E70DEA0026AFC0 /* XCRemoteSwiftPackageReference "lottie-ios" */, + B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, ); productRefGroup = AA585D7F248FD31100E9A3E2 /* Products */; projectDirPath = ""; @@ -8620,6 +8646,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8D62B316FCA00A595BB /* __Snapshots__ in Resources */, 3706FE8B293F661700E42796 /* empty in Resources */, 376E2D282942843D001CD31B /* privacy-reference-tests in Resources */, 3706FE8C293F661700E42796 /* atb-with-update.json in Resources */, @@ -8860,6 +8887,7 @@ 31E163C0293A581900963C10 /* privacy-reference-tests in Resources */, B69B50542726CD8100758A2B /* atb-with-update.json in Resources */, 37A803DB27FD69D300052F4C /* DataImportResources in Resources */, + B65CD8D52B316FCA00A595BB /* __Snapshots__ in Resources */, B69B50522726CD8100758A2B /* atb.json in Resources */, 4B70C00127B0793D000386ED /* DuckDuckGo-ExampleCrash.ips in Resources */, 4BCF15ED2ABB9B180083F6DF /* network-protection-messages.json in Resources */, @@ -12953,6 +12981,14 @@ version = 2.5.1; }; }; + B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.15.1; + }; + }; B6DA44152616C13800DD1EC2 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/AliSoftware/OHHTTPStubs.git"; @@ -13457,6 +13493,31 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = "plugin:SwiftLintPlugin"; }; + B65CD8CA2B316DF100A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8CC2B316DFC00A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8CE2B316E0200A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8D02B316E0C00A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8D22B316E1700A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; B692D0DE2B209FB7003F2548 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 24e8e97f71..9080829d51 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -108,6 +108,24 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "59b663f68e69f27a87b45de48cb63264b8194605", + "version" : "1.15.1" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", + "version" : "509.0.2" + } + }, { "identity" : "swifter", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift b/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift index 67358f32e6..62275a5c10 100644 --- a/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift +++ b/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift @@ -18,13 +18,13 @@ import Foundation -struct ImportedBookmarks: Decodable { +struct ImportedBookmarks: Codable, Equatable { - struct BookmarkOrFolder: Decodable { + struct BookmarkOrFolder: Codable, Equatable { let name: String let type: String let urlString: String? - let isDDGFavorite: Bool + var isDDGFavorite: Bool = false let children: [BookmarkOrFolder]? @@ -62,15 +62,6 @@ struct ImportedBookmarks: Decodable { case children } - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - name = try container.decode(String.self, forKey: .name) - type = try container.decode(String.self, forKey: .type) - urlString = try container.decodeIfPresent(String.self, forKey: .urlString) - children = try container.decodeIfPresent([BookmarkOrFolder].self, forKey: .children) - isDDGFavorite = false - } - init(name: String, type: String, urlString: String?, children: [BookmarkOrFolder]?, isDDGFavorite: Bool = false) { self.name = name.trimmingWhitespace() self.type = type @@ -80,7 +71,7 @@ struct ImportedBookmarks: Decodable { } } - struct TopLevelFolders: Decodable { + struct TopLevelFolders: Codable, Equatable { let bookmarkBar: BookmarkOrFolder let otherBookmarks: BookmarkOrFolder diff --git a/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift b/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift index 7ab2b8e5a8..31dbf918f7 100644 --- a/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift +++ b/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift @@ -26,15 +26,14 @@ struct HTMLImportedBookmarks { final class BookmarkHTMLReader { struct ImportError: DataImportError { + // !! do not change the order + // cases 2,3 and 6 are reserved for removed errors enum OperationType: Int { - case parseXml - case validationBody - case validationH1 - case findTopLevelFolder - case proceedToTopLevelFolder - case readFolder - case bookmarkRead - case unknown + case parseXml = 0 + case validationBody = 1 + case proceedToTopLevelFolder = 4 + case readFolder = 5 + case unknown = 7 } var action: DataImportAction { .bookmarks } @@ -118,10 +117,6 @@ final class BookmarkHTMLReader { // MARK: - Private - private enum ReaderError: Error { - case noTopLevelFolder - } - private func determineImportSource(_ cursor: inout XMLNode?) throws -> BookmarkImportSource? { let isInSafariFormat = try findTopLevelFolderNameNode(&cursor) @@ -139,56 +134,51 @@ final class BookmarkHTMLReader { private func validateHTMLBookmarksDocument(_ document: XMLDocument) throws -> XMLNode? { let root = document.rootElement() guard let body = root?.child(at: 1) else { throw ImportError(type: .validationBody, underlyingError: nil) } - + // get /html/body/*[0] let cursor = body.child(at: 0) - guard cursor?.htmlTag == .h1 else { throw ImportError(type: .validationH1, underlyingError: nil) } return cursor } private func findTopLevelFolderNameNode(_ cursor: inout XMLNode?) throws -> Bool { var isInSafariFormat = false - cursor = cursor?.nextSibling - switch cursor?.htmlTag { - case .dl: - do { - try proceedToTopLevelFolderNameNode(&cursor) - } catch ReaderError.noTopLevelFolder { + rootLoop: while cursor != nil { + switch cursor?.htmlTag { + case .dl: + let originalCursorValue = cursor + cursor = cursor?.child(at: 0) + dlLoop: while cursor != nil { + switch cursor?.htmlTag { + case .dd: + if cursor?.child(at: 0)?.htmlTag == .h3 { + cursor = cursor?.child(at: 0) + break dlLoop + } + cursor = cursor?.nextSibling + case .dt: + // There is no "top-level" folder, and the first item + // in the bookmarks file is a regular bookmark, not a folder. + // This is specific to iOS and MacOS DuckDuckGo apps. + cursor = originalCursorValue + isInSafariFormat = true + break dlLoop + default: + throw ImportError(type: .proceedToTopLevelFolder, underlyingError: nil) + } + } + break rootLoop + case .h3: isInSafariFormat = true + break rootLoop + default: + cursor = cursor?.nextSibling } - case .h3: - isInSafariFormat = true - default: - throw ImportError(type: .findTopLevelFolder, underlyingError: nil) } return isInSafariFormat } - private func proceedToTopLevelFolderNameNode(_ cursor: inout XMLNode?) throws { - let originalCursorValue = cursor - cursor = cursor?.child(at: 0) - while cursor != nil { - guard cursor?.htmlTag == .dd else { - if cursor?.htmlTag == .dt { - // There is no "top-level" folder, and the first item - // in the bookmarks file is a regular bookmark, not a folder. - // This is specific to iOS and MacOS DuckDuckGo apps. - cursor = originalCursorValue - throw ReaderError.noTopLevelFolder - } else { - throw ImportError(type: .proceedToTopLevelFolder, underlyingError: nil) - } - } - if cursor?.child(at: 0)?.htmlTag == .h3 { - cursor = cursor?.child(at: 0) - return - } - cursor = cursor?.nextSibling - } - } - private func findNextItem(_ cursor: inout XMLNode?) -> XMLNode.BookmarkItemType? { var itemType: XMLNode.BookmarkItemType? cursor = cursor?.parent?.nextSibling @@ -220,7 +210,7 @@ final class BookmarkHTMLReader { private func readItem(_ item: XMLNode.BookmarkItemType, at cursor: XMLNode?) throws -> [ImportedBookmarks.BookmarkOrFolder] { switch item { case .bookmark: - return [try readBookmark(cursor)] + return readBookmark(cursor).map { [$0] } ?? [] case .folder: return [try readFolder(cursor)] case .safariTopLevelBookmarks: @@ -255,7 +245,9 @@ final class BookmarkHTMLReader { case (.dd, .h3): children.append(try readFolder(firstChild)) case (.dt, .a): - children.append(try readBookmark(firstChild)) + if let bookmark = readBookmark(firstChild) { + children.append(bookmark) + } default: break } @@ -266,9 +258,8 @@ final class BookmarkHTMLReader { return children } - private func readBookmark(_ node: XMLNode?) throws -> ImportedBookmarks.BookmarkOrFolder { - guard let bookmark = node?.bookmark else { throw ImportError(type: .bookmarkRead, underlyingError: nil) } - return bookmark + private func readBookmark(_ node: XMLNode?) -> ImportedBookmarks.BookmarkOrFolder? { + return node?.bookmark } private let bookmarksFileURL: URL @@ -369,14 +360,19 @@ private extension XMLNode { } var bookmark: ImportedBookmarks.BookmarkOrFolder? { - guard htmlTag == .a, let name = text else { + guard htmlTag == .a, + let name = text, + let element = self as? XMLElement else { return nil } + + if element.stringValue == "---" && element.attribute(forName: "href")?.stringValue == "http://bookmark.placeholder.url/" { + // vivaldi separator markup return nil } return .bookmark( name: name, - urlString: (self as? XMLElement)?.attribute(forName: "href")?.stringValue, - isDDGFavorite: (self as? XMLElement)?.attribute(forName: "duckduckgo:favorite")?.stringValue == "true" + urlString: element.attribute(forName: "href")?.stringValue, + isDDGFavorite: element.attribute(forName: "duckduckgo:favorite")?.stringValue == "true" ) } } diff --git a/DuckDuckGo/DataImport/Bookmarks/HTML/README.md b/DuckDuckGo/DataImport/Bookmarks/HTML/README.md index a44dbc4a4a..a8944c84e6 100644 --- a/DuckDuckGo/DataImport/Bookmarks/HTML/README.md +++ b/DuckDuckGo/DataImport/Bookmarks/HTML/README.md @@ -4,15 +4,24 @@ ```mermaid graph TD - A[cursor = document root node] -->|validateHTMLBookmarksDocument| B[cursor = h1 tag] + A[cursor = document root node] -->|validateHTMLBookmarksDocument| B[cursor = first body child] B --> C[determineImportSource
findTopLevelFolderNameNode] - C --> D[cursor => nextSibling] + C --> D{cursor.htmlTag} D -->|`dl` tag| E[proceedToTopLevel
FolderNameNode] - E --> F[store cursor
cursor => firstChild] - F -->|`dd` tag| G[cursor => firstChild] + E --> E1[store cursor
cursor => firstChild] + E1 --> F{cursor.htmlTag} + F -->|`dd` tag| F1[cursor => firstChild] + F1 --> G{cursor.htmlTag} G -->|`h3` tag| H[cursor = top level folder name
source = other] + G -->|other tag| G1[cursor => nextSibling] + G -->|`nil`| RET + G1 --> F F -->|`dt` tag| I[restore cursor
no top level folder] + F -->|other tag| ERR D -->|`h3` tag| J{isDDGBookmarks?} + D -->|other tag| NXT[cursor => nextSibling] + NXT --> D + D -->|`nil`| RET I --> K{isDDGBookmarks?} K -->|no| L[cursor = first item in folder
source = Safari] K -->|yes| M[cursor = first item in folder
source = DDG WebKit] diff --git a/UnitTests/DataImport/BookmarksHTMLReaderTests.swift b/UnitTests/DataImport/BookmarksHTMLReaderTests.swift index 543fe86c89..d83a094db3 100644 --- a/UnitTests/DataImport/BookmarksHTMLReaderTests.swift +++ b/UnitTests/DataImport/BookmarksHTMLReaderTests.swift @@ -16,87 +16,39 @@ // limitations under the License. // +import SnapshotTesting import XCTest @testable import DuckDuckGo_Privacy_Browser -class BookmarksHTMLReaderTests: XCTestCase { +final class BookmarksHTMLReaderTests: XCTestCase { - var reader: BookmarkHTMLReader! + let bookmarksHTMLReaderTestFilesURL = Bundle(for: BookmarksHTMLReaderTests.self) + .resourceURL! + .appendingPathComponent("DataImportResources/TestBookmarksData") - func bookmarksFileURL(_ name: String) -> URL { - let bundle = Bundle(for: ChromiumLoginReaderTests.self) - return bundle.resourceURL! - .appendingPathComponent("DataImportResources/TestBookmarksData") - .appendingPathComponent(name) - } - - func test_WhenParseChromeHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_chrome.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 12) - } - - func test_WhenParseSafariHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_safari.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 14) - } - - func test_WhenParseFirefoxHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_firefox.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 17) - } - - func test_WhenParseBraveHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_brave.html")) - let result = reader.readBookmarks() + @MainActor + func testBookmarksHTMLReaderSnapshot() throws { + let expectedToThrow: Set = [ + "bookmarks_invalid.html" + ] - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 12) - } + for fileName in try FileManager.default.contentsOfDirectory(atPath: bookmarksHTMLReaderTestFilesURL.path) { + let fileNameWithoutExtension = fileName.dropping(suffix: "html") + let fileURL = bookmarksHTMLReaderTestFilesURL.appendingPathComponent(fileName) + let reader = BookmarkHTMLReader(bookmarksFileURL: fileURL) + let result = reader.readBookmarks() - func test_WhenParseDDGAndroidHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_ddg_android.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 13) - } - - func test_WhenParseDDGiOSHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_ddg_ios.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 8) - } - - func test_WhenParseDDGMacOSHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_ddg_macos.html")) - let result = reader.readBookmarks() + if expectedToThrow.contains(fileName) { + XCTAssertThrowsError(try result.get(), fileNameWithoutExtension) + continue + } + guard case .success(let importResult) = result else { + XCTFail("unexpected failure in \(fileNameWithoutExtension): \(result)") + continue + } - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 13) + assertSnapshot(of: importResult.bookmarks, as: .json, named: fileNameWithoutExtension, testName: "snapshot") + } } - func test_WhenParseInvalidHtml_ThenImportFail() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_invalid.html")) - let result = reader.readBookmarks() - - XCTAssertThrowsError(try result.get(), "", { error in - guard let error = error as? BookmarkHTMLReader.ImportError else { - XCTFail("Unexpected error type: \(String(reflecting: error))") - return - } - XCTAssertEqual(error.type, .parseXml) - XCTAssertEqual((error.underlyingError as NSError?)?.domain, XMLParser.errorDomain) - }) - } } diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_chrome_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_chrome_dirty.html new file mode 100644 index 0000000000..a27442bcd2 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_chrome_dirty.html @@ -0,0 +1,10 @@ +

+

Bookmarks Bar

+

+

Bookmark 1 +
+
Bookmark 2 +
+

+

Bookmark 3 +

diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_firefox_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_firefox_dirty.html new file mode 100644 index 0000000000..5edc0e29da --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_firefox_dirty.html @@ -0,0 +1,13 @@ +

+

Folder Name 1

+
+
Help and Tutorials +
Customize Firefox +
+ +

Folder Name B

+
+
Get Involved +
About Us +
+
diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_netscape_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_netscape_dirty.html new file mode 100644 index 0000000000..d5a18a55b0 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_netscape_dirty.html @@ -0,0 +1,11 @@ + + +

+

Folder 1

+

+

Bookmark 1 +
+
Bookmark 2 +

+

Bookmark 3 +

diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_safari_tp_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_safari_tp_dirty.html new file mode 100644 index 0000000000..9fc2f276b0 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_safari_tp_dirty.html @@ -0,0 +1,38 @@ + +] + +Bookmarks +

Bookmarks

+

Favorites

+

+

Apple +
Yahoo +
Google +
Wikipedia +
Facebook +
Twitter +
LinkedIn +
The Weather Channel +
Yelp +
+
catz - Google Search +
dogz - +Google Search +
ab +yrvalg - Google Search +

+

Bookmarks Menu

+

+

+

Tab Group Favorites

+

+

Tab Group Favorites

+

+

abyrvalg - Google Search +

+

diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_vivaldi.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_vivaldi.html new file mode 100644 index 0000000000..1739091c81 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_vivaldi.html @@ -0,0 +1,46 @@ + + + +Bookmarks +

Bookmarks

+

+

Bookmarks

+

+

Speed Dial

+

+

Vivaldi Social +
Vivaldi Community +
Booking.com +
Amazon +
eBay +
AliExpress +
Eneba +
AccuWeather +
Vivaldia Games +

+

Bookmarks

+

+

Vivaldi

+

+

Vivaldi Features +
Vivaldi Webmail +
--- +
Vivaldi Help +

+

Booking.com +
Kayak +
AliExpress +
Amazon +
--- +
eBay +
Expedia +

+

Get Firefox for desktop — Mozilla (US) +
--- +

+

Trash

+

+

+

diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_brave.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_brave.json new file mode 100644 index 0000000000..b9d3bdbff9 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_brave.json @@ -0,0 +1,150 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "Bookmarks bar", + "type" : "folder" + }, + "other" : { + "children" : [ + + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome.json new file mode 100644 index 0000000000..f854ba036d --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome.json @@ -0,0 +1,150 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + + ], + "name" : "Bookmarks Bar", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome_dirty.json new file mode 100644 index 0000000000..7a782dfb5f --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome_dirty.json @@ -0,0 +1,31 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Bookmark 1", + "type" : "bookmark", + "url" : "https:\/\/example.com" + }, + { + "name" : "Bookmark 2", + "type" : "bookmark", + "url" : "https:\/\/example2.com" + } + ], + "name" : "Bookmarks Bar", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "name" : "Bookmark 3", + "type" : "bookmark", + "url" : "https:\/\/example3.com" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_android.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_android.json new file mode 100644 index 0000000000..a07bec85d1 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_android.json @@ -0,0 +1,160 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "DuckDuckGo Bookmarks", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "name" : "Apple (United Kingdom)", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/uk\/" + } + ], + "name" : "DuckDuckGo Favorites", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_ios.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_ios.json new file mode 100644 index 0000000000..1681f87cc5 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_ios.json @@ -0,0 +1,75 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + + ], + "name" : "", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com" + }, + { + "name" : "Hacker News", + "type" : "bookmark", + "url" : "https:\/\/news.ycombinator.com\/" + }, + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com" + }, + { + "name" : "The Linux Programming Interface: A Linux and UNIX System Programming Handbook: Amazon.co.uk: Michael Kerrisk: 9781593272203: Books", + "type" : "bookmark", + "url" : "https:\/\/www.amazon.co.uk\/Linux-Programming-Interface-System-Handbook\/dp\/1593272200" + }, + { + "children" : [ + { + "children" : [ + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com" + } + ], + "name" : "FolderA-2", + "type" : "folder" + } + ], + "name" : "FolderA", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/macrumors.com" + } + ], + "name" : "FolderB", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/wikipedia.org" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_macos.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_macos.json new file mode 100644 index 0000000000..8ea93679c0 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_macos.json @@ -0,0 +1,155 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + + ], + "name" : "", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "name" : "Apple (United Kingdom)", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/uk\/" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox.json new file mode 100644 index 0000000000..58729ad404 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox.json @@ -0,0 +1,180 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Get Help", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/products\/firefox" + }, + { + "name" : "Customize Firefox", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/kb\/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize" + }, + { + "name" : "Get Involved", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/contribute\/" + }, + { + "name" : "About Us", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/about\/" + } + ], + "name" : "Mozilla Firefox", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Getting Started", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/firefox\/central\/" + } + ], + "name" : "Bookmarks Toolbar", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox_dirty.json new file mode 100644 index 0000000000..7294c1c9d9 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox_dirty.json @@ -0,0 +1,42 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Help and Tutorials", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/products\/firefox" + }, + { + "name" : "Customize Firefox", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/kb\/customize-firefox-controls-buttons-and-toolbars" + } + ], + "name" : "Folder Name 1", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "name" : "Get Involved", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/contribute\/" + }, + { + "name" : "About Us", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/about\/" + } + ], + "name" : "Folder Name B", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_netscape_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_netscape_dirty.json new file mode 100644 index 0000000000..ecff3960c3 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_netscape_dirty.json @@ -0,0 +1,31 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Bookmark 1", + "type" : "bookmark", + "url" : "https:\/\/example.com" + }, + { + "name" : "Bookmark 2", + "type" : "bookmark", + "url" : "https:\/\/example2.com" + } + ], + "name" : "Folder 1", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "name" : "Bookmark 3", + "type" : "bookmark", + "url" : "https:\/\/example3.com" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari.json new file mode 100644 index 0000000000..b7a07d9471 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari.json @@ -0,0 +1,172 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Apple", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/uk" + } + ], + "name" : "Favourites", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + + ], + "name" : "Bookmarks Menu", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "How Danny Trejo Built a Decades-Long Film Career After Prison – Texas Monthly", + "type" : "bookmark", + "url" : "https:\/\/www.texasmonthly.com\/the-culture\/how-danny-trejo-built-decades-long-film-career-after-prison\/" + } + ], + "name" : "Reading List", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari_tp_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari_tp_dirty.json new file mode 100644 index 0000000000..2a5c599c51 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari_tp_dirty.json @@ -0,0 +1,105 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Apple", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/" + }, + { + "name" : "Yahoo", + "type" : "bookmark", + "url" : "https:\/\/www.icloud.com\/'%3EiCloud%3C\/A%3E%3CD%3E%3CA%20HREF=" + }, + { + "name" : "Google", + "type" : "bookmark", + "url" : "https:\/\/www.bing.com\/'%3EBing%3C\/A%3E%3CDT%3E%3CA%20HREF=" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + }, + { + "name" : "Facebook", + "type" : "bookmark", + "url" : "https:\/\/www.facebook.com\/" + }, + { + "name" : "Twitter", + "type" : "bookmark", + "url" : "https:\/\/twitter.com\/" + }, + { + "name" : "LinkedIn", + "type" : "bookmark", + "url" : "https:\/\/www.linkedin.com\/" + }, + { + "name" : "The Weather Channel", + "type" : "bookmark", + "url" : "https:\/\/www.weather.com\/" + }, + { + "name" : "Yelp", + "type" : "bookmark", + "url" : "https:\/\/www.yelp.com\/" + }, + { + "name" : "", + "type" : "bookmark", + "url" : "https:\/\/www.tripadvisor.com\/" + }, + { + "name" : "catz - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?q=catz&client=safari&sca_esv=570874343&rls=en&tbm=isch&source=1nms&sa=X&ved=2ahUKEwiQ60DeoN6BAxXJOhAIHbYCD2gQ_AUoAXoECAEQAw&biw=1324&bih=888" + }, + { + "name" : "dogz - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?client=safari&rls=en&q=dogz&ie=UTF-8&oe=UTF-8" + }, + { + "name" : "ab yrvalg - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?client=safari&rls=en&q=abyrvalg&ie=UTF-8&oe=UTF-8" + } + ], + "name" : "Favorites", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + + ], + "name" : "Bookmarks Menu", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "name" : "abyrvalg - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?client=safari&rls=en&q=abyrvalg&ie=UTF-8&oe=UTF-8" + } + ], + "name" : "Tab Group Favorites", + "type" : "folder" + } + ], + "name" : "Tab Group Favorites", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_vivaldi.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_vivaldi.json new file mode 100644 index 0000000000..3f933b6bb6 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_vivaldi.json @@ -0,0 +1,136 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "children" : [ + { + "name" : "Vivaldi Social", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-social" + }, + { + "name" : "Vivaldi Community", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-community" + }, + { + "name" : "Booking.com", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/bookingcom-en-us" + }, + { + "name" : "Amazon", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/amazon" + }, + { + "name" : "eBay", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/ebay-en-us" + }, + { + "name" : "AliExpress", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/aliexpresscom-us" + }, + { + "name" : "Eneba", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/eneba-en" + }, + { + "name" : "AccuWeather", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-accuweather" + }, + { + "name" : "Vivaldia Games", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/vivaldia-game" + } + ], + "name" : "Speed Dial", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "name" : "Vivaldi Features", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-browser-features" + }, + { + "name" : "Vivaldi Webmail", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-webmail" + }, + { + "name" : "Vivaldi Help", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-vivaldi-help-en" + } + ], + "name" : "Vivaldi", + "type" : "folder" + }, + { + "name" : "Booking.com", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/booking-us-bk" + }, + { + "name" : "Kayak", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/kayak-en-us" + }, + { + "name" : "AliExpress", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/ali-us-bk" + }, + { + "name" : "Amazon", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/amazon-us-bk" + }, + { + "name" : "eBay", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/ebay-us-bk" + }, + { + "name" : "Expedia", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/expedia-eu" + } + ], + "name" : "Bookmarks", + "type" : "folder" + }, + { + "name" : "Get Firefox for desktop — Mozilla (US)", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/firefox\/new\/?redirect_source=firefox-com" + } + ], + "name" : "Bookmarks", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + + ], + "name" : "Trash", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file From 187b319b803e4bb69ca5a6a95905c7aa851b1ae8 Mon Sep 17 00:00:00 2001 From: Juan Manuel Pereira Date: Thu, 21 Dec 2023 10:10:15 -0300 Subject: [PATCH 06/10] DBP: Integrate subscription account authentication to DBP (#1995) --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++ DuckDuckGo/Application/AppDelegate.swift | 8 +++ ...erProtectionSubscriptionEventHandler.swift | 55 +++++++++++++++++++ .../NavigationBar/View/MoreOptionsMenu.swift | 6 +- DuckDuckGo/Statistics/PixelEvent.swift | 5 ++ DuckDuckGo/Statistics/PixelParameters.swift | 1 + .../Sources/Account/AccountManager.swift | 8 ++- .../InputFilesChecker/InputFilesChecker.swift | 3 +- 8 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6c065c61a0..aa8833b98c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2933,6 +2933,8 @@ B6FA893D269C423100588ECD /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */; }; B6FA893F269C424500588ECD /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */; }; B6FA8941269C425400588ECD /* PrivacyDashboardPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */; }; + BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; }; + BB5789732B2CC0300009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; }; BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; }; BBDFDC5C2B2B8D7000F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; }; BBDFDC5D2B2B8E2100F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; }; @@ -4222,6 +4224,7 @@ B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PrivacyDashboard.storyboard; sourceTree = ""; }; B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = ""; }; B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPopover.swift; sourceTree = ""; }; + BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionSubscriptionEventHandler.swift; sourceTree = ""; }; BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionExternalWaitlistPixels.swift; sourceTree = ""; }; CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationURLProvider.swift; sourceTree = ""; }; CB6BCDF827C6BEFF00CC76DC /* PrivacyFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyFeatures.swift; sourceTree = ""; }; @@ -4673,6 +4676,7 @@ 3199C6F82AF94F5B002A7BA1 /* DataBrokerProtectionFeatureDisabler.swift */, 3199C6FC2AF97367002A7BA1 /* DataBrokerProtectionAppEvents.swift */, BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */, + BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */, ); path = DBP; sourceTree = ""; @@ -10907,6 +10911,7 @@ 4B957B8F2AC7AE700062CA31 /* Assertions.swift in Sources */, 4B957B902AC7AE700062CA31 /* BookmarkViewModel.swift in Sources */, 4B957B912AC7AE700062CA31 /* DaxSpeech.swift in Sources */, + BB5789732B2CC0300009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */, 4B957B922AC7AE700062CA31 /* DuckPlayerSchemeHandler.swift in Sources */, 4B957B932AC7AE700062CA31 /* FirePopoverViewModel.swift in Sources */, 4B957B942AC7AE700062CA31 /* BWCommand.swift in Sources */, @@ -11473,6 +11478,7 @@ 4B9DB04A2A983B24000927DB /* NotificationService.swift in Sources */, 3775912D29AAC72700E26367 /* SyncPreferences.swift in Sources */, 1DB9618329F67F6200CF5568 /* FaviconNullStore.swift in Sources */, + BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */, B693954F26F04BEB0015B914 /* PaddedImageButton.swift in Sources */, 4BA1A6B8258B081600F6F690 /* EncryptionKeyStoring.swift in Sources */, B65783E725F8AAFB00D8DB33 /* String+Punycode.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index bf83796dcf..c636f37de9 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -74,6 +74,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel private var emailCancellables = Set() let bookmarksManager = LocalBookmarkManager.shared +#if DBP && SUBSCRIPTION + private let dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler() +#endif + private var didFinishLaunching = false #if SPARKLE @@ -243,6 +247,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel UNUserNotificationCenter.current().delegate = self #endif +#if DBP && SUBSCRIPTION + dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() +#endif + #if DBP DataBrokerProtectionAppEvents().applicationDidFinishLaunching() #endif diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift new file mode 100644 index 0000000000..783c900d12 --- /dev/null +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -0,0 +1,55 @@ +// +// DataBrokerProtectionSubscriptionEventHandler.swift +// +// Copyright © 2023 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. +// +#if DBP && SUBSCRIPTION + +import Foundation +import Account +import DataBrokerProtection + +final class DataBrokerProtectionSubscriptionEventHandler { + + private let accountManager: Account.AccountManaging + private let authRepository: AuthenticationRepository + + init(accountManager: Account.AccountManaging = Account.AccountManager(), + authRepository: AuthenticationRepository = KeychainAuthenticationData()) { + self.accountManager = accountManager + self.authRepository = authRepository + } + + func registerForSubscriptionAccountManagerEvents() { + NotificationCenter.default.addObserver(self, selector: #selector(handleAccountDidSignIn), name: .accountDidSignIn, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleAccountDidSignOut), name: .accountDidSignOut, object: nil) + } + + @objc private func handleAccountDidSignIn() { + guard let token = accountManager.token else { + Pixel.fire(.dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn) + assertionFailure("[DBP Subscription] AccountManager signed in but token could not be retrieved") + return + } + + authRepository.save(accessToken: token) + } + + @objc private func handleAccountDidSignOut() { + // Going to be defined here: https://app.asana.com/0/1204006570077678/1206206961074916/f + } +} + +#endif diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index f4d0faec88..1b1aad17d4 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -149,11 +149,15 @@ final class MoreOptionsMenu: NSMenu { #if DBP @objc func openDataBrokerProtection(_ sender: NSMenuItem) { - if !DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist && DataBrokerProtectionWaitlistViewControllerPresenter.shouldPresentWaitlist() { + #if SUBSCRIPTION + actionDelegate?.optionsButtonMenuRequestedDataBrokerProtection(self) + #else + if !DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist && DataBrokerProtectionWaitlistViewControllerPresenter.shouldPresentWaitlist() { DataBrokerProtectionWaitlistViewControllerPresenter.show() } else { actionDelegate?.optionsButtonMenuRequestedDataBrokerProtection(self) } + #endif } #endif // DBP diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index 5694bb4432..407567a01b 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -197,6 +197,9 @@ extension Pixel { case dataBrokerProtectionWaitlistTermsAndConditionsDisplayed case dataBrokerProtectionWaitlistTermsAndConditionsAccepted + // DataBrokerProtection Other + case dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn + // 28-day Home Button case homeButtonHidden case homeButtonLeft @@ -538,6 +541,8 @@ extension Pixel.Event { return "m_mac_dbp_imp_terms" case .dataBrokerProtectionWaitlistTermsAndConditionsAccepted: return "m_mac_dbp_ev_terms_accepted" + case .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn: + return "m_mac_dbp_error_when_fetching_subscription_auth_token_after_sign_in" // 28-day Home Button case .homeButtonHidden: diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index 1bdc5b26d6..a2c30e5bd7 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -149,6 +149,7 @@ extension Pixel.Event { .dataBrokerProtectionWaitlistCardUITapped, .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, .dataBrokerProtectionWaitlistTermsAndConditionsAccepted, + .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn, .homeButtonLeft, .homeButtonRight, .homeButtonHidden: diff --git a/LocalPackages/Account/Sources/Account/AccountManager.swift b/LocalPackages/Account/Sources/Account/AccountManager.swift index b03c409908..8c3fe49ba3 100644 --- a/LocalPackages/Account/Sources/Account/AccountManager.swift +++ b/LocalPackages/Account/Sources/Account/AccountManager.swift @@ -29,7 +29,13 @@ public protocol AccountManagerKeychainAccessDelegate: AnyObject { func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) } -public class AccountManager { +public protocol AccountManaging { + + var token: String? { get } + +} + +public class AccountManager: AccountManaging { private let storage: AccountStorage public weak var delegate: AccountManagerKeychainAccessDelegate? diff --git a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift index ad2b491fc6..0db71533d6 100644 --- a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift +++ b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift @@ -44,7 +44,8 @@ let nonSandboxedExtraInputFiles: Set = [ .init("VPNMetadataCollector.swift", .source), .init("VPNFeedbackCategory.swift", .source), .init("VPNFeedbackSender.swift", .source), - .init("DuckDuckGoDBPBackgroundAgent.app", .unknown) + .init("DuckDuckGoDBPBackgroundAgent.app", .unknown), + .init("DataBrokerProtectionSubscriptionEventHandler.swift", .source) ] /** From b7581929cd84edab31c8d7210469bcf609c690f9 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Thu, 21 Dec 2023 14:27:04 +0100 Subject: [PATCH 07/10] Show alert and display warning icon in Sync Settings when data syncing is disabled (#1996) Task/Issue URL: https://app.asana.com/0/0/1206213106333863/f --- DuckDuckGo/Application/AppDelegate.swift | 29 +++++++++++++++++++ .../Common/Extensions/NSAlertExtension.swift | 12 ++++++++ DuckDuckGo/Common/Localizables/UserText.swift | 3 ++ .../Utilities/UserDefaultsWrapper.swift | 1 + .../Model/PreferencesSection.swift | 5 +++- .../ManagementView/SyncEnabledView.swift | 2 +- .../Views/ManagementView/SyncSetupView.swift | 7 ++--- .../Sources/SyncUI/internal/UserText.swift | 7 +++-- 8 files changed, 56 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index c636f37de9..545b0e8095 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -379,6 +379,35 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } subscribeSyncQueueToScreenLockedNotifications() + subscribeToSyncFeatureFlags(syncService) + } + + @UserDefaultsWrapper(key: .syncDidShowSyncPausedByFeatureFlagAlert, defaultValue: false) + private var syncDidShowSyncPausedByFeatureFlagAlert: Bool + + private func subscribeToSyncFeatureFlags(_ syncService: DDGSync) { + syncFeatureFlagsCancellable = syncService.featureFlagsPublisher + .dropFirst() + .map { $0.contains(.dataSyncing) } + .receive(on: DispatchQueue.main) + .sink { [weak self, weak syncService] isDataSyncingAvailable in + if isDataSyncingAvailable { + self?.syncDidShowSyncPausedByFeatureFlagAlert = false + } else if syncService?.authState == .active, self?.syncDidShowSyncPausedByFeatureFlagAlert == false { + let isSyncUIVisible = syncService?.featureFlags.contains(.userInterface) == true + let alert = NSAlert.dataSyncingDisabledByFeatureFlag(showLearnMore: isSyncUIVisible) + let response = alert.runModal() + self?.syncDidShowSyncPausedByFeatureFlagAlert = true + + switch response { + case .alertSecondButtonReturn: + alert.window.sheetParent?.endSheet(alert.window) + WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .sync) + default: + break + } + } + } } private func subscribeSyncQueueToScreenLockedNotifications() { diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 06d86aa2fc..628ddeddfc 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -221,6 +221,18 @@ extension NSAlert { return alert } + static func dataSyncingDisabledByFeatureFlag(showLearnMore: Bool, upgradeRequired: Bool = false) -> NSAlert { + let alert = NSAlert() + alert.messageText = UserText.syncPausedTitle + alert.informativeText = upgradeRequired ? UserText.syncUnavailableMessageUpgradeRequired : UserText.syncUnavailableMessage + alert.alertStyle = .warning + alert.addButton(withTitle: UserText.ok) + if showLearnMore { + alert.addButton(withTitle: UserText.learnMore) + } + return alert + } + static func customConfigurationAlert(configurationUrl: String) -> NSAlert { let alert = NSAlert() alert.messageText = "Set custom configuration URL:" diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index d4084d6d47..0eb91fe989 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -503,6 +503,9 @@ struct UserText { static let syncBookmarkPausedAlertDescription = NSLocalizedString("alert.sync-bookmarks-paused-description", value: "You have exceeded the bookmarks sync limit. Try deleting some bookmarks. Until this is resolved your bookmarks will not be backed up.", comment: "Description for alert shown when sync bookmarks paused for too many items") static let syncCredentialsPausedAlertTitle = NSLocalizedString("alert.sync-credentials-paused-title", value: "Passwords Sync is Paused", comment: "Title for alert shown when sync credentials paused for too many items") static let syncCredentialsPausedAlertDescription = NSLocalizedString("alert.sync-credentials-paused-description", value: "You have exceeded the passwords sync limit. Try deleting some passwords. Until this is resolved your passwords will not be backed up.", comment: "Description for alert shown when sync credentials paused for too many items") + static let syncPausedTitle = NSLocalizedString("alert.sync.warning.sync-paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") + static let syncUnavailableMessage = NSLocalizedString("alert.sync.warning.sync-unavailable-message", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") + static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("alert.sync.warning.data-syncing-disabled-upgrade-required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message") 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") diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 7bcf40d4b8..9ce0d13a89 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -176,6 +176,7 @@ public struct UserDefaultsWrapper { case syncIsEligibleForFaviconsFetcherOnboarding = "sync.is-eligible-for-favicons-fetcher-onboarding" case syncDidPresentFaviconsFetcherOnboarding = "sync.did-present-favicons-fetcher-onboarding" case syncDidMigrateToImprovedListsHandling = "sync.did-migrate-to-improved-lists-handling" + case syncDidShowSyncPausedByFeatureFlagAlert = "sync.did-show-sync-paused-by-feature-flag-alert" } enum RemovedKeys: String, CaseIterable { diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index ad061c7ed0..4e32a5af5f 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -92,6 +92,7 @@ enum PreferencePaneIdentifier: String, Equatable, Hashable, Identifiable { self.init(rawValue: path) } + @MainActor var displayName: String { switch self { case .general: @@ -99,7 +100,9 @@ enum PreferencePaneIdentifier: String, Equatable, Hashable, Identifiable { case .sync: let isSyncBookmarksPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) let isSyncCredentialsPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) - if isSyncBookmarksPaused || isSyncCredentialsPaused { + let syncService = NSApp.delegateTyped.syncService + let isDataSyncingDisabled = syncService?.featureFlags.contains(.dataSyncing) == false && syncService?.authState == .active + if isSyncBookmarksPaused || isSyncCredentialsPaused || isDataSyncingDisabled { return UserText.sync + " ⚠️" } return UserText.sync diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift index 3878a7d693..53ffb0513e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift @@ -154,7 +154,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { if model.isDataSyncingAvailable { EmptyView() } else { - SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + SyncWarningMessage(title: UserText.syncPausedTitle, message: UserText.syncUnavailableMessage) } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift index 1ea3bf81a7..133f1b4b2a 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift @@ -63,11 +63,8 @@ struct SyncSetupView: View where ViewModel: ManagementViewModel { @ViewBuilder fileprivate func syncUnavailableView() -> some View { - if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable { - SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) - .padding(.top, 16) - } else if !model.isAccountCreationAvailable { - SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningAccountCreationDisabled) + if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable || !model.isAccountCreationAvailable { + SyncWarningMessage(title: UserText.syncUnavailableTitle, message: UserText.syncUnavailableMessage) .padding(.top, 16) } else { EmptyView() diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index 2b7d2a1059..57d66f925e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -159,7 +159,8 @@ enum UserText { static let keepFaviconsUpdated = NSLocalizedString("prefrences.sync.keep-favicons-updated", value: "Keep Bookmarks Icons Updated", comment: "Title of the confirmation button for favicons fetching") // Sync Feature Flags - static let serviceUnavailable = NSLocalizedString("sync.warning.service-unavailable", value: "Service Unavailable", comment: "Title of the warning message") - static let warningSyncDisabled = NSLocalizedString("sync.warning.sync-disabled", value: "We apologize, but the service is currently unavailable. Please try again later.", comment: "Sync unavailable warning message") - static let warningAccountCreationDisabled = NSLocalizedString("sync.warning.account-creation-disabled", value: "We apologize, but new account creation is currently unavailable for this service. Please try again later.", comment: "Sync unavailable warning message") + static let syncUnavailableTitle = NSLocalizedString("sync.warning.sync-unavailable", value: "Sync & Backup is Unavailable", comment: "Title of the warning message") + static let syncPausedTitle = NSLocalizedString("sync.warning.sync-paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") + static let syncUnavailableMessage = NSLocalizedString("sync.warning.sync-unavailable-message", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") + static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("sync.warning.data-syncing-disabled-upgrade-required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message") } From 85de0dc3a84a8d8ce3a80b43a54d6a64255eab1b Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Thu, 21 Dec 2023 17:31:13 +0100 Subject: [PATCH 08/10] Fix internal user toggling (#2000) Task/Issue URL: https://app.asana.com/0/414235014887631/1206217864822184/f Tech Design URL: CC: **Description**: Fix internal user toggling. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../Mocks/MockPrivacyConfiguration.swift | 11 +++++++++++ DuckDuckGoDBPBackgroundAgent/DBPMocks.swift | 8 +++++--- LocalPackages/Account/Package.swift | 2 +- LocalPackages/DataBrokerProtection/Package.swift | 2 +- .../CCF/DataBrokerProtectionUtils.swift | 2 ++ .../Tests/DataBrokerProtectionTests/Mocks.swift | 6 ++++++ LocalPackages/LoginItems/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/PixelKit/Package.swift | 2 +- LocalPackages/Purchase/Package.swift | 2 +- LocalPackages/Subscription/Package.swift | 2 +- LocalPackages/SwiftUIExtensions/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/SystemExtensionManager/Package.swift | 2 +- LocalPackages/XPCHelper/Package.swift | 2 +- 17 files changed, 38 insertions(+), 17 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index aa8833b98c..3eacedf348 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12976,7 +12976,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 97.0.0; + version = 98.0.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 9080829d51..805101ac61 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" : "d671accf1bf7097c4e7f5cd55cd1c6dfa005cf92", - "version" : "97.0.0" + "revision" : "064ed560f19fbfd36eb02decebf944689818ed35", + "version" : "98.0.0" } }, { diff --git a/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift index df918a336e..f8dc30a6ae 100644 --- a/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift +++ b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift @@ -51,6 +51,16 @@ final class MockPrivacyConfiguration: PrivacyConfiguration { func userDisabledProtection(forDomain: String) {} } +final class MockInternalUserStoring: InternalUserStoring { + var isInternalUser: Bool = false +} + +extension DefaultInternalUserDecider { + convenience init(mockedStore: MockInternalUserStoring = MockInternalUserStoring()) { + self.init(store: mockedStore) + } +} + final class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManaging { var embeddedConfigData: BrowserServicesKit.PrivacyConfigurationManager.ConfigurationData { fatalError("not implemented") @@ -70,6 +80,7 @@ final class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManag var updatesPublisher: AnyPublisher = Just(()).eraseToAnyPublisher() var privacyConfig: PrivacyConfiguration = MockPrivacyConfiguration() + var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider() } #endif diff --git a/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift b/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift index 87b6e010a6..b184a883df 100644 --- a/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift +++ b/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift @@ -56,21 +56,23 @@ final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { guard let privacyConfigurationData = try? PrivacyConfigurationData(data: data) else { fatalError("Could not retrieve privacy configuration data") } - let privacyConfig = privacyConfiguration(withData: privacyConfigurationData) + let privacyConfig = privacyConfiguration(withData: privacyConfigurationData, internalUserDecider: internalUserDecider) return privacyConfig } + var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()) + func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult { .downloaded } } -func privacyConfiguration(withData data: PrivacyConfigurationData) -> PrivacyConfiguration { +func privacyConfiguration(withData data: PrivacyConfigurationData, internalUserDecider: InternalUserDecider) -> PrivacyConfiguration { let domain = MockDomainsProtectionStore() return AppPrivacyConfiguration(data: data, identifier: UUID().uuidString, localProtection: domain, - internalUserDecider: DefaultInternalUserDecider(store: InternalUserDeciderStoreMock())) + internalUserDecider: internalUserDecider) } final class MockDomainsProtectionStore: DomainsProtectionStore { diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index b32006c664..54ad9d3e9b 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: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index ec47443f0f..3852ed9842 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: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift index 5177325d43..d65b1653b2 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift @@ -163,6 +163,7 @@ private class PrivacyConfigurationDataBrokerProtectionConfigOverride: PrivacyCon var base: Data var updatesPublisher: AnyPublisher<(), Never> var privacyConfig: PrivacyConfiguration + var internalUserDecider: InternalUserDecider var currentConfig: Data { return updateConfigWithBrokerProtection() @@ -176,6 +177,7 @@ private class PrivacyConfigurationDataBrokerProtectionConfigOverride: PrivacyCon base = manager.currentConfig updatesPublisher = manager.updatesPublisher privacyConfig = manager.privacyConfig + internalUserDecider = manager.internalUserDecider } private func updateConfigWithBrokerProtection() -> Data { diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index d33f4b6e5b..b9699bd4e2 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -58,6 +58,10 @@ extension DataBrokerScheduleConfig { } } +final class InternalUserDeciderStoreMock: InternalUserStoring { + var isInternalUser: Bool = false +} + final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { var currentConfig: Data = Data() @@ -65,6 +69,8 @@ final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { var privacyConfig: BrowserServicesKit.PrivacyConfiguration = PrivacyConfigurationMock() + var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()) + func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult { .downloaded } diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index 54a86db801..df220e7be9 100644 --- a/LocalPackages/LoginItems/Package.swift +++ b/LocalPackages/LoginItems/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index e7fb30c89e..55b684a01e 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index aaaa2d9e09..93cfe4bf75 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -20,7 +20,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift index 6ebc16624c..1bca7b0dd1 100644 --- a/LocalPackages/Purchase/Package.swift +++ b/LocalPackages/Purchase/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index e9b73b884a..77a66800a4 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(path: "../Account"), .package(path: "../Purchase"), .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index cc43577d0b..90a50b4864 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 49c6f30303..7ecccd197d 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index e27bd650a8..58e8ba4070 100644 --- a/LocalPackages/SystemExtensionManager/Package.swift +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift index 1424527204..dfdef8911f 100644 --- a/LocalPackages/XPCHelper/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. From 9823186de513d52f02fb190d947d518a5ae79b00 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 21 Dec 2023 09:47:43 -0800 Subject: [PATCH 09/10] Add PixelKit source parameter (#1989) Task/Issue URL: https://app.asana.com/0/0/1206203334750907/f Tech Design URL: CC: Description: This PR adds a source parameter to the PixelKit setUp function, to allow us to include which target is sending the pixel. --- .../MacPacketTunnelProvider.swift | 9 ++++++ ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 1 + DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 7 +++- .../PixelKit/PixelKit+Parameters.swift | 1 + .../PixelKit/Sources/PixelKit/PixelKit.swift | 32 ++++++++++++++++--- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 87c5614ecb..6bc1dfa909 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -379,8 +379,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { dryRun = false #endif + let source: String + +#if NETP_SYSTEM_EXTENSION + source = "vpnSystemExtension" +#else + source = "vpnAppExtension" +#endif + PixelKit.setUp(dryRun: dryRun, appVersion: AppVersion.shared.versionNumber, + source: source, defaultHeaders: defaultHeaders, log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index f2ac32192a..9c8daa0d36 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -41,6 +41,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { PixelKit.setUp(dryRun: dryRun, appVersion: AppVersion.shared.versionNumber, + source: nil, defaultHeaders: [:], log: .dbpBackgroundAgentPixel, defaults: .standard) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping (Bool, Error?) -> Void) in diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index bc3af58b03..71f37ea3ba 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -173,7 +173,12 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { dryRun = false #endif - PixelKit.setUp(dryRun: dryRun, appVersion: AppVersion.shared.versionNumber, defaultHeaders: [:], log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in + PixelKit.setUp(dryRun: dryRun, + appVersion: AppVersion.shared.versionNumber, + source: "vpnAgent", + defaultHeaders: [:], + log: .networkProtectionPixel, + defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) let apiHeaders = APIRequest.Headers(additionalHeaders: headers) // workaround - Pixel class should really handle APIRequest.Headers by itself diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift index 9673f0dae0..70bf4aae8a 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift @@ -24,6 +24,7 @@ public extension PixelKit { public static let duration = "duration" public static let test = "test" public static let appVersion = "appVersion" + public static let pixelSource = "pixelSource" public static let osMajorVersion = "osMajorVersion" public static let errorCode = "e" diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index c8d165ad5f..b13aea7b17 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -87,10 +87,26 @@ public final class PixelKit { private let log: OSLog private let fireRequest: FireRequest - /// `dryRun`: if `true`, simulate requests and "send" them at an accelerated rate (once every 2 minutes instead of once a day) - /// `fireRequest`: this is not triggered when `dryRun` is `true` - public static func setUp(dryRun: Bool = false, appVersion: String, defaultHeaders: [String: String], log: OSLog, defaults: UserDefaults, fireRequest: @escaping FireRequest) { - shared = PixelKit(dryRun: dryRun, appVersion: appVersion, defaultHeaders: defaultHeaders, log: log, defaults: defaults, fireRequest: fireRequest) + /// Sets up PixelKit for the entire app. + /// + /// - Parameters: + /// - `dryRun`: if `true`, simulate requests and "send" them at an accelerated rate (once every 2 minutes instead of once a day) + /// - `source`: if set, adds a `pixelSource` parameter to the pixel call; this can be used to specify which target is sending the pixel + /// - `fireRequest`: this is not triggered when `dryRun` is `true` + public static func setUp(dryRun: Bool = false, + appVersion: String, + source: String? = nil, + defaultHeaders: [String: String], + log: OSLog, + defaults: UserDefaults, + fireRequest: @escaping FireRequest) { + shared = PixelKit(dryRun: dryRun, + appVersion: appVersion, + source: source, + defaultHeaders: defaultHeaders, + log: log, + defaults: defaults, + fireRequest: fireRequest) } static func tearDown() { @@ -98,10 +114,12 @@ public final class PixelKit { } private var dryRun: Bool + private let source: String? private let pixelCalendar: Calendar init(dryRun: Bool, appVersion: String, + source: String? = nil, defaultHeaders: [String: String], log: OSLog, dailyPixelCalendar: Calendar? = nil, @@ -111,6 +129,7 @@ public final class PixelKit { self.dryRun = dryRun self.appVersion = appVersion + self.source = source self.defaultHeaders = defaultHeaders self.log = log self.pixelCalendar = dailyPixelCalendar ?? Self.defaultDailyPixelCalendar @@ -119,6 +138,7 @@ public final class PixelKit { self.fireRequest = fireRequest } + // swiftlint:disable:next cyclomatic_complexity private func fire(pixelNamed pixelName: String, frequency: Frequency, withHeaders headers: [String: String]?, @@ -134,6 +154,10 @@ public final class PixelKit { newParams[Parameters.appVersion] = appVersion } + if let source { + newParams[Parameters.pixelSource] = source + } + if let error { newParams.appendErrorPixelParams(error: error) } From c0523ac8e1c15652199828d17ff1a06d03f42226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Thu, 21 Dec 2023 19:20:13 +0100 Subject: [PATCH 10/10] Make sure when we set custom config url, we don't expect etag in return (#1994) Task/Issue URL: https://app.asana.com/0/72649045549333/1205661297103826/f **Description**: Enable custom configuration url debugging (no need to get etags in response) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- .../Common/Utilities/UserDefaultsWrapper.swift | 2 ++ .../Configuration/ConfigurationManager.swift | 16 ++++++++++------ DuckDuckGo/Menus/MainMenu.swift | 10 ++++++++-- DuckDuckGo/Menus/MainMenuActions.swift | 4 ++-- LocalPackages/Account/Package.swift | 2 +- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/LoginItems/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/PixelKit/Package.swift | 2 +- LocalPackages/Purchase/Package.swift | 2 +- LocalPackages/Subscription/Package.swift | 2 +- LocalPackages/SwiftUIExtensions/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- .../SystemExtensionManager/Package.swift | 2 +- LocalPackages/XPCHelper/Package.swift | 2 +- 17 files changed, 37 insertions(+), 25 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3eacedf348..6f8a942aaf 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12976,7 +12976,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 98.0.0; + version = 98.0.1; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 805101ac61..13547f23d7 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" : "064ed560f19fbfd36eb02decebf944689818ed35", - "version" : "98.0.0" + "revision" : "ea133abe237b6cb57a4237e0373318a40c10afc2", + "version" : "98.0.1" } }, { @@ -147,7 +147,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 9ce0d13a89..3204cde614 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -41,6 +41,8 @@ public struct UserDefaultsWrapper { case configStoragePrivacyConfigurationEtag = "config.storage.privacyconfiguration.etag" case configFBConfigEtag = "config.storage.fbconfig.etag" + case configLastInstalled = "config.last.installed" + case fireproofDomains = "com.duckduckgo.fireproofing.allowedDomains" case areDomainsMigratedToETLDPlus1 = "com.duckduckgo.are-domains-migrated-to-etldplus1" case unprotectedDomains = "com.duckduckgo.contentblocker.unprotectedDomains" diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index a486bb4365..a64673dfc0 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -61,6 +61,9 @@ final class ConfigurationManager { @UserDefaultsWrapper(key: .configLastUpdated, defaultValue: .distantPast) private(set) var lastUpdateTime: Date + @UserDefaultsWrapper(key: .configLastInstalled, defaultValue: nil) + private(set) var lastConfigurationInstallDate: Date? + private var timerCancellable: AnyCancellable? private var lastRefreshCheckTime: Date = Date() @@ -97,9 +100,9 @@ final class ConfigurationManager { os_log("last refresh check %{public}s", log: .config, type: .default, String(describing: lastRefreshCheckTime)) } - private func refreshNow() async { + private func refreshNow(isDebug: Bool = false) async { let updateTrackerBlockingDependenciesTask = Task { - let didFetchAnyTrackerBlockingDependencies = await fetchTrackerBlockingDependencies() + let didFetchAnyTrackerBlockingDependencies = await fetchTrackerBlockingDependencies(isDebug: isDebug) if didFetchAnyTrackerBlockingDependencies { updateTrackerBlockingDependencies() tryAgainLater() @@ -134,13 +137,13 @@ final class ConfigurationManager { log() } - private func fetchTrackerBlockingDependencies() async -> Bool { + private func fetchTrackerBlockingDependencies(isDebug: Bool) async -> Bool { var didFetchAnyTrackerBlockingDependencies = false var tasks = [Configuration: Task<(), Swift.Error>]() tasks[.trackerDataSet] = Task { try await fetcher.fetch(.trackerDataSet) } tasks[.surrogates] = Task { try await fetcher.fetch(.surrogates) } - tasks[.privacyConfiguration] = Task { try await fetcher.fetch(.privacyConfiguration) } + tasks[.privacyConfiguration] = Task { try await fetcher.fetch(.privacyConfiguration, isDebug: isDebug) } for (configuration, task) in tasks { do { @@ -184,9 +187,9 @@ final class ConfigurationManager { private var isReadyToRefresh: Bool { Date().timeIntervalSince(lastUpdateTime) > Constants.refreshPeriodSeconds } - public func forceRefresh() { + public func forceRefresh(isDebug: Bool = false) { Task { - await refreshNow() + await refreshNow(isDebug: isDebug) } } @@ -200,6 +203,7 @@ final class ConfigurationManager { } private func updateTrackerBlockingDependencies() { + lastConfigurationInstallDate = Date() ContentBlocking.shared.trackerDataManager.reload(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), data: ConfigurationStore.shared.loadData(for: .trackerDataSet)) ContentBlocking.shared.privacyConfigurationManager.reload(etag: ConfigurationStore.shared.loadEtag(for: .privacyConfiguration), diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index dc532e19f3..9c3fdd9ac3 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -643,8 +643,14 @@ import Subscription } private func updateRemoteConfigurationInfo() { - let dateString = DateFormatter.localizedString(from: ConfigurationManager.shared.lastUpdateTime, dateStyle: .short, timeStyle: .medium) - configurationDateAndTimeMenuItem.title = "Last Update Time: \(dateString)" + var dateString: String + if let date = ConfigurationManager.shared.lastConfigurationInstallDate { + dateString = DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .medium) + configurationDateAndTimeMenuItem.title = "Last Update Time: \(dateString)" + } else { + dateString = "Last Update Time: -" + } + configurationDateAndTimeMenuItem.title = dateString customConfigurationUrlMenuItem.title = "Configuration URL: \(AppConfigurationURLProvider().url(for: .privacyConfiguration).absoluteString)" } diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 8a30695b31..4a4c4e1f7d 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -739,7 +739,7 @@ extension MainViewController { @objc func reloadConfigurationNow(_ sender: Any?) { OSLog.loggingCategories.insert(OSLog.AppCategories.config.rawValue) - ConfigurationManager.shared.forceRefresh() + ConfigurationManager.shared.forceRefresh(isDebug: true) } private func setConfigurationUrl(_ configurationUrl: URL?) { @@ -750,7 +750,7 @@ extension MainViewController { configurationProvider.resetToDefaultConfigurationUrl() } Configuration.setURLProvider(configurationProvider) - ConfigurationManager.shared.forceRefresh() + ConfigurationManager.shared.forceRefresh(isDebug: true) if let configurationUrl { os_log("New configuration URL set to \(configurationUrl.absoluteString)", type: .info) } else { diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index 54ad9d3e9b..49f48da084 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: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 3852ed9842..435391c9df 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: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index df220e7be9..53da2ff762 100644 --- a/LocalPackages/LoginItems/Package.swift +++ b/LocalPackages/LoginItems/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 55b684a01e..cd031f9386 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index 93cfe4bf75..f4230052ea 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -20,7 +20,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift index 1bca7b0dd1..a61123f147 100644 --- a/LocalPackages/Purchase/Package.swift +++ b/LocalPackages/Purchase/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index 77a66800a4..02148554e7 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(path: "../Account"), .package(path: "../Purchase"), .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index 90a50b4864..7ca05a42ec 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 7ecccd197d..6f485526ca 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index 58e8ba4070..722d4ae0a1 100644 --- a/LocalPackages/SystemExtensionManager/Package.swift +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift index dfdef8911f..74bc880ffe 100644 --- a/LocalPackages/XPCHelper/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite.