diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f4436e8f53..4c3ed2208f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -89,7 +89,6 @@ 37054FC92873301700033B6F /* PinnedTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37054FC82873301700033B6F /* PinnedTabView.swift */; }; 37054FCE2876472D00033B6F /* WebViewSnapshotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37054FCD2876472D00033B6F /* WebViewSnapshotView.swift */; }; 3706FA7B293F65D500E42796 /* FaviconUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA0CC562539EBC90079BC96 /* FaviconUserScript.swift */; }; - 3706FA7D293F65D500E42796 /* ApiRequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E457261460340067D1B9 /* ApiRequestError.swift */; }; 3706FA7E293F65D500E42796 /* LottieAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AADCBF3926F7C2CE00EF67A8 /* LottieAnimationCache.swift */; }; 3706FA7F293F65D500E42796 /* TabIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D23779287EB8CA00BCE03B /* TabIndex.swift */; }; 3706FA80293F65D500E42796 /* TabLazyLoaderDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534CA2281132CB002621E7 /* TabLazyLoaderDataSource.swift */; }; @@ -167,7 +166,7 @@ 3706FAD2293F65D500E42796 /* Atb.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50352726A11F00758A2B /* Atb.swift */; }; 3706FAD3293F65D500E42796 /* DownloadsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */; }; 3706FAD4293F65D500E42796 /* DataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3AF625D5DBFD00C7D2AA /* DataExtension.swift */; }; - 3706FAD6293F65D500E42796 /* ConfigurationStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FCE25D1AA22009424E3 /* ConfigurationStoring.swift */; }; + 3706FAD6293F65D500E42796 /* ConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */; }; 3706FAD7293F65D500E42796 /* Feedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D531A27A2F57E00074EC1 /* Feedback.swift */; }; 3706FAD8293F65D500E42796 /* RequestFilePermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99D0526FE1979001E4761 /* RequestFilePermissionViewController.swift */; }; 3706FAD9293F65D500E42796 /* FirefoxFaviconsReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A63E7289DB58E00378EF7 /* FirefoxFaviconsReader.swift */; }; @@ -318,11 +317,9 @@ 3706FB75293F65D500E42796 /* WebsiteBreakageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD8078427B3F3BE00CF7703 /* WebsiteBreakageSender.swift */; }; 3706FB76293F65D500E42796 /* ASN1Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AC93A26B48ADF00879451 /* ASN1Parser.swift */; }; 3706FB77293F65D500E42796 /* View+Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54B227EE509700F1F7B9 /* View+Cursor.swift */; }; - 3706FB79293F65D500E42796 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E4602614608B0067D1B9 /* AppVersion.swift */; }; 3706FB7A293F65D500E42796 /* FileDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 856C98DE257014BD00A22F1F /* FileDownloadManager.swift */; }; 3706FB7B293F65D500E42796 /* BookmarkImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99CF626FE191E001E4761 /* BookmarkImport.swift */; }; 3706FB7C293F65D500E42796 /* KeySetDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68503A6279141CD00893A05 /* KeySetDictionary.swift */; }; - 3706FB7D293F65D500E42796 /* ConfigurationDownloading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FBA25D181CB009424E3 /* ConfigurationDownloading.swift */; }; 3706FB7E293F65D500E42796 /* FireCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEEC6A827088ADB008445F7 /* FireCoordinator.swift */; }; 3706FB7F293F65D500E42796 /* GeolocationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B655369A268442EE00085A79 /* GeolocationProvider.swift */; }; 3706FB80293F65D500E42796 /* NSAlert+ActiveDownloadsTermination.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23B26E87D900031CB7F /* NSAlert+ActiveDownloadsTermination.swift */; }; @@ -425,7 +422,6 @@ 3706FBE7293F65D500E42796 /* PasswordManagementItemListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CC1D7A26A05ECF0062F04E /* PasswordManagementItemListModel.swift */; }; 3706FBE8293F65D500E42796 /* SuggestionTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABEE6A824AB4B910043105B /* SuggestionTableCellView.swift */; }; 3706FBE9293F65D500E42796 /* FireViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6820F025503DA9005ED0D5 /* FireViewModel.swift */; }; - 3706FBEB293F65D500E42796 /* APIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E459261460350067D1B9 /* APIRequest.swift */; }; 3706FBEC293F65D500E42796 /* EditableTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE65473271FCD40008D1D63 /* EditableTextView.swift */; }; 3706FBED293F65D500E42796 /* TabCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9FF95C24A1FA1C0039E328 /* TabCollection.swift */; }; 3706FBEE293F65D500E42796 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B688B4D9273E6D3B0087BEAF /* MainView.swift */; }; @@ -561,7 +557,6 @@ 3706FC77293F65D500E42796 /* PageObserverUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853014D525E671A000FB8205 /* PageObserverUserScript.swift */; }; 3706FC78293F65D500E42796 /* SecureVaultErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B642738127B65BAC0005DFD1 /* SecureVaultErrorReporter.swift */; }; 3706FC79293F65D500E42796 /* NSImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B139AFC26B60BD800894F82 /* NSImageExtensions.swift */; }; - 3706FC7A293F65D500E42796 /* APIHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E458261460340067D1B9 /* APIHeaders.swift */; }; 3706FC7B293F65D500E42796 /* PasswordManagementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85625995269C953C00EE44BC /* PasswordManagementViewController.swift */; }; 3706FC7C293F65D500E42796 /* ImportedBookmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99CFA26FE191E001E4761 /* ImportedBookmarks.swift */; }; 3706FC7D293F65D500E42796 /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6EF9B2250785D5004754E6 /* NSMenuExtension.swift */; }; @@ -810,7 +805,6 @@ 3706FE5D293F661700E42796 /* FileSystemDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF09222830812900EE1418 /* FileSystemDSL.swift */; }; 3706FE5E293F661700E42796 /* DataImportMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DFF26B0003E00E14D75 /* DataImportMocks.swift */; }; 3706FE5F293F661700E42796 /* CrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B70C00027B0793D000386ED /* CrashReportTests.swift */; }; - 3706FE60293F661700E42796 /* ConfigurationDownloaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B1625D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift */; }; 3706FE61293F661700E42796 /* PinnedTabsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D23786287F5C2900BCE03B /* PinnedTabsViewModelTests.swift */; }; 3706FE62293F661700E42796 /* PasswordManagementListSectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF4EA4F27C71F26004E57C4 /* PasswordManagementListSectionTests.swift */; }; 3706FE63293F661700E42796 /* RecentlyClosedCoordinatorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E9175286DB05D00AB6B62 /* RecentlyClosedCoordinatorMock.swift */; }; @@ -1209,8 +1203,7 @@ 85378DA2274E7F25007C5CBF /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85378DA1274E7F25007C5CBF /* EmailManagerRequestDelegate.swift */; }; 8546DE6225C03056000CA5E1 /* UserAgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8546DE6125C03056000CA5E1 /* UserAgentTests.swift */; }; 85480F8A25CDC360009424E3 /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85480F8925CDC360009424E3 /* MainMenu.storyboard */; }; - 85480FBB25D181CB009424E3 /* ConfigurationDownloading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FBA25D181CB009424E3 /* ConfigurationDownloading.swift */; }; - 85480FCF25D1AA22009424E3 /* ConfigurationStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FCE25D1AA22009424E3 /* ConfigurationStoring.swift */; }; + 85480FCF25D1AA22009424E3 /* ConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */; }; 85589E7F27BBB8630038AD11 /* AddEditFavoriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7927BBB8620038AD11 /* AddEditFavoriteViewController.swift */; }; 85589E8027BBB8630038AD11 /* AddEditFavoriteWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7A27BBB8620038AD11 /* AddEditFavoriteWindow.swift */; }; 85589E8127BBB8630038AD11 /* HomePage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E7B27BBB8630038AD11 /* HomePage.storyboard */; }; @@ -1261,7 +1254,6 @@ 85AC3AEF25D5CE9800C7D2AA /* UserScripts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3AEE25D5CE9800C7D2AA /* UserScripts.swift */; }; 85AC3AF725D5DBFD00C7D2AA /* DataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3AF625D5DBFD00C7D2AA /* DataExtension.swift */; }; 85AC3B0525D6B1D800C7D2AA /* ScriptSourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B0425D6B1D800C7D2AA /* ScriptSourceProviding.swift */; }; - 85AC3B1725D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B1625D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift */; }; 85AC3B3525DA82A600C7D2AA /* DataTaskProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B3425DA82A600C7D2AA /* DataTaskProviding.swift */; }; 85AC3B4925DAC9BD00C7D2AA /* ConfigurationStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B4825DAC9BD00C7D2AA /* ConfigurationStorageTests.swift */; }; 85AC7AD927BD625000FFB69B /* HomePageAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85AC7AD827BD625000FFB69B /* HomePageAssets.xcassets */; }; @@ -1653,10 +1645,6 @@ B6A924D92664C72E001A28CA /* WebKitDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */; }; B6A924DE2664CA09001A28CA /* LegacyWebKitDownloadDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A924DD2664CA08001A28CA /* LegacyWebKitDownloadDelegate.swift */; }; B6A9E45326142B070067D1B9 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; - B6A9E45A261460350067D1B9 /* ApiRequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E457261460340067D1B9 /* ApiRequestError.swift */; }; - B6A9E45B261460350067D1B9 /* APIHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E458261460340067D1B9 /* APIHeaders.swift */; }; - B6A9E45C261460350067D1B9 /* APIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E459261460350067D1B9 /* APIRequest.swift */; }; - B6A9E4612614608B0067D1B9 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E4602614608B0067D1B9 /* AppVersion.swift */; }; B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */; }; B6A9E47026146A250067D1B9 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46F26146A250067D1B9 /* DateExtension.swift */; }; B6A9E47726146A570067D1B9 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; @@ -1750,7 +1738,13 @@ 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 */; }; + CB24F70C29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */; }; + CB24F70D29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */; }; CB6BCDF927C6BEFF00CC76DC /* PrivacyFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB6BCDF827C6BEFF00CC76DC /* PrivacyFeatures.swift */; }; + CBC83E3629B63D380008E19C /* Configuration in Frameworks */ = {isa = PBXBuildFile; productRef = CBC83E3529B63D380008E19C /* Configuration */; }; + CBC83E3829B63D400008E19C /* Configuration in Frameworks */ = {isa = PBXBuildFile; productRef = CBC83E3729B63D400008E19C /* Configuration */; }; + CBDD5DE329A67F2700832877 /* MockConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE229A67F2700832877 /* MockConfigurationStore.swift */; }; + CBDD5DE429A6800300832877 /* MockConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE229A67F2700832877 /* MockConfigurationStore.swift */; }; EA0BA3A9272217E6002A0B6C /* ClickToLoadUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0BA3A8272217E6002A0B6C /* ClickToLoadUserScript.swift */; }; EA18D1CA272F0DC8006DC101 /* social_images in Resources */ = {isa = PBXBuildFile; fileRef = EA18D1C9272F0DC8006DC101 /* social_images */; }; EA1E52B52798CF98002EC53C /* ClickToLoadModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1E52B42798CF98002EC53C /* ClickToLoadModelTests.swift */; }; @@ -2228,8 +2222,7 @@ 85378DA1274E7F25007C5CBF /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; 8546DE6125C03056000CA5E1 /* UserAgentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentTests.swift; sourceTree = ""; }; 85480F8925CDC360009424E3 /* MainMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainMenu.storyboard; sourceTree = ""; }; - 85480FBA25D181CB009424E3 /* ConfigurationDownloading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationDownloading.swift; sourceTree = ""; }; - 85480FCE25D1AA22009424E3 /* ConfigurationStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationStoring.swift; sourceTree = ""; }; + 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationStore.swift; sourceTree = ""; }; 8553FF51257523760029327F /* URLSuggestedFilenameTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSuggestedFilenameTests.swift; sourceTree = ""; }; 85589E7927BBB8620038AD11 /* AddEditFavoriteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEditFavoriteViewController.swift; sourceTree = ""; }; 85589E7A27BBB8620038AD11 /* AddEditFavoriteWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEditFavoriteWindow.swift; sourceTree = ""; }; @@ -2282,7 +2275,6 @@ 85AC3AEE25D5CE9800C7D2AA /* UserScripts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserScripts.swift; sourceTree = ""; }; 85AC3AF625D5DBFD00C7D2AA /* DataExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtension.swift; sourceTree = ""; }; 85AC3B0425D6B1D800C7D2AA /* ScriptSourceProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptSourceProviding.swift; sourceTree = ""; }; - 85AC3B1625D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationDownloaderTests.swift; sourceTree = ""; }; 85AC3B3425DA82A600C7D2AA /* DataTaskProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTaskProviding.swift; sourceTree = ""; }; 85AC3B4825DAC9BD00C7D2AA /* ConfigurationStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationStorageTests.swift; sourceTree = ""; }; 85AC7AD827BD625000FFB69B /* HomePageAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = HomePageAssets.xcassets; sourceTree = ""; }; @@ -2661,10 +2653,6 @@ B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebKitDownloadTask.swift; sourceTree = ""; }; B6A924DD2664CA08001A28CA /* LegacyWebKitDownloadDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyWebKitDownloadDelegate.swift; sourceTree = ""; }; B6A9E45226142B070067D1B9 /* Pixel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pixel.swift; sourceTree = ""; }; - B6A9E457261460340067D1B9 /* ApiRequestError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequestError.swift; sourceTree = ""; }; - B6A9E458261460340067D1B9 /* APIHeaders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIHeaders.swift; sourceTree = ""; }; - B6A9E459261460350067D1B9 /* APIRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIRequest.swift; sourceTree = ""; }; - B6A9E4602614608B0067D1B9 /* AppVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersionExtension.swift; sourceTree = ""; }; B6A9E46F26146A250067D1B9 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; B6A9E47626146A570067D1B9 /* PixelEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelEvent.swift; sourceTree = ""; }; @@ -2738,7 +2726,9 @@ 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 = ""; }; + CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationURLProvider.swift; sourceTree = ""; }; CB6BCDF827C6BEFF00CC76DC /* PrivacyFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyFeatures.swift; sourceTree = ""; }; + CBDD5DE229A67F2700832877 /* MockConfigurationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConfigurationStore.swift; sourceTree = ""; }; EA0BA3A8272217E6002A0B6C /* ClickToLoadUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClickToLoadUserScript.swift; sourceTree = ""; }; EA18D1C9272F0DC8006DC101 /* social_images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = social_images; sourceTree = ""; }; EA1E52B42798CF98002EC53C /* ClickToLoadModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickToLoadModelTests.swift; sourceTree = ""; }; @@ -2777,6 +2767,7 @@ 3706FCAE293F65D500E42796 /* Lottie in Frameworks */, 3706FCAF293F65D500E42796 /* PrivacyDashboard in Frameworks */, 3706FCB0293F65D500E42796 /* Common in Frameworks */, + CBC83E3829B63D400008E19C /* Configuration in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2822,6 +2813,7 @@ 1E950E432912A10D0051A99B /* UserScript in Frameworks */, 4B82E9B325B69E3E00656FE7 /* TrackerRadarKit in Frameworks */, 1D02633328D898E1005CBB41 /* OpenSSL in Frameworks */, + CBC83E3629B63D380008E19C /* Configuration in Frameworks */, AA06B6B72672AF8100F541C5 /* Sparkle in Frameworks */, B6B77BE8297973D4001E68A1 /* Navigation in Frameworks */, 85FF55C825F82E4F00E2AB99 /* Lottie in Frameworks */, @@ -3600,6 +3592,7 @@ B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */, B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */, B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */, + CBDD5DE229A67F2700832877 /* MockConfigurationStore.swift */, ); path = "Content Blocker"; sourceTree = ""; @@ -3975,7 +3968,6 @@ 85AC3B1525D9BBFA00C7D2AA /* Configuration */ = { isa = PBXGroup; children = ( - 85AC3B1625D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift */, 85AC3B4825DAC9BD00C7D2AA /* ConfigurationStorageTests.swift */, ); path = Configuration; @@ -4072,9 +4064,8 @@ 85D33F1025C82E93002B91A6 /* Configuration */ = { isa = PBXGroup; children = ( - 85480FBA25D181CB009424E3 /* ConfigurationDownloading.swift */, 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */, - 85480FCE25D1AA22009424E3 /* ConfigurationStoring.swift */, + 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */, ); path = Configuration; sourceTree = ""; @@ -4223,6 +4214,7 @@ AAD86E51267A0DFF005C11BE /* UpdateController.swift */, 858A798226A8B75F00A75A42 /* CopyHandler.swift */, 1D36E65A298ACD2900AA485D /* AppIconChanger.swift */, + CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */, ); path = "App Delegate"; sourceTree = ""; @@ -4272,7 +4264,6 @@ EEAEA3F4294D05CF00D04DF3 /* JSAlert */, B31055BB27A1BA0E001AC618 /* Autoconsent */, 7B1E819A27C8874900FF0E60 /* Autofill */, - B6A9E47526146A440067D1B9 /* API */, AA4D700525545EDE00C3411E /* App Delegate */, AAC5E4C025D6A6A9007F5990 /* Bookmarks */, 4BFD356E283ADE8B00CE9234 /* Bookmarks Bar */, @@ -4625,7 +4616,6 @@ AA86491324D831B9001BABEE /* Common */ = { isa = PBXGroup; children = ( - B6A9E4602614608B0067D1B9 /* AppVersion.swift */, 4B67743D255DBEEA00025BD8 /* Database */, AADC60E92493B305008F8EF7 /* Extensions */, 4BA1A691258B06F600F6F690 /* File System */, @@ -5507,16 +5497,6 @@ path = Statistics; sourceTree = ""; }; - B6A9E47526146A440067D1B9 /* API */ = { - isa = PBXGroup; - children = ( - B6A9E458261460340067D1B9 /* APIHeaders.swift */, - B6A9E459261460350067D1B9 /* APIRequest.swift */, - B6A9E457261460340067D1B9 /* ApiRequestError.swift */, - ); - path = API; - sourceTree = ""; - }; B6AE74322609AFBB005B9B1A /* Progress */ = { isa = PBXGroup; children = ( @@ -5709,6 +5689,7 @@ 3706FA77293F65D500E42796 /* PrivacyDashboard */, 3706FA78293F65D500E42796 /* UserScript */, 37A5E2EF298AA1B20047046B /* Persistence */, + CBC83E3729B63D400008E19C /* Configuration */, 37F44A5E298C17830025E7FE /* Navigation */, 984FD3BE299ACF35007334DD /* Bookmarks */, ); @@ -5826,6 +5807,7 @@ 1E950E402912A10D0051A99B /* PrivacyDashboard */, 1E950E422912A10D0051A99B /* UserScript */, 98A50963294B691800D10880 /* Persistence */, + CBC83E3529B63D380008E19C /* Configuration */, B6B77BE7297973D4001E68A1 /* Navigation */, 987799EC299998B1005D8EB6 /* Bookmarks */, ); @@ -6307,7 +6289,6 @@ buildActionMask = 2147483647; files = ( 3706FA7B293F65D500E42796 /* FaviconUserScript.swift in Sources */, - 3706FA7D293F65D500E42796 /* ApiRequestError.swift in Sources */, 3706FA7E293F65D500E42796 /* LottieAnimationCache.swift in Sources */, 3706FA7F293F65D500E42796 /* TabIndex.swift in Sources */, 3706FA80293F65D500E42796 /* TabLazyLoaderDataSource.swift in Sources */, @@ -6390,7 +6371,7 @@ 3706FAD2293F65D500E42796 /* Atb.swift in Sources */, 3706FAD3293F65D500E42796 /* DownloadsViewController.swift in Sources */, 3706FAD4293F65D500E42796 /* DataExtension.swift in Sources */, - 3706FAD6293F65D500E42796 /* ConfigurationStoring.swift in Sources */, + 3706FAD6293F65D500E42796 /* ConfigurationStore.swift in Sources */, 3706FAD7293F65D500E42796 /* Feedback.swift in Sources */, 3707C722294B5D2900682A9F /* WKWebViewExtension.swift in Sources */, 3706FAD8293F65D500E42796 /* RequestFilePermissionViewController.swift in Sources */, @@ -6442,6 +6423,7 @@ 3706FB02293F65D500E42796 /* Onboarding.swift in Sources */, 3706FEB8293F6EFB00E42796 /* ConnectBitwardenView.swift in Sources */, 3706FB03293F65D500E42796 /* PopUpWindow.swift in Sources */, + CB24F70D29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */, 3706FB04293F65D500E42796 /* PersistentAppInterfaceSettings.swift in Sources */, 3706FB05293F65D500E42796 /* Favicons.xcdatamodeld in Sources */, 3706FB06293F65D500E42796 /* HoverButton.swift in Sources */, @@ -6564,11 +6546,9 @@ 3706FB76293F65D500E42796 /* ASN1Parser.swift in Sources */, 987799F42999993C005D8EB6 /* LegacyBookmarksStoreMigration.swift in Sources */, 3706FB77293F65D500E42796 /* View+Cursor.swift in Sources */, - 3706FB79293F65D500E42796 /* AppVersion.swift in Sources */, 3706FB7A293F65D500E42796 /* FileDownloadManager.swift in Sources */, 3706FB7B293F65D500E42796 /* BookmarkImport.swift in Sources */, 3706FB7C293F65D500E42796 /* KeySetDictionary.swift in Sources */, - 3706FB7D293F65D500E42796 /* ConfigurationDownloading.swift in Sources */, 3706FB7E293F65D500E42796 /* FireCoordinator.swift in Sources */, 3706FB7F293F65D500E42796 /* GeolocationProvider.swift in Sources */, 3706FB80293F65D500E42796 /* NSAlert+ActiveDownloadsTermination.swift in Sources */, @@ -6684,7 +6664,6 @@ 3706FBE8293F65D500E42796 /* SuggestionTableCellView.swift in Sources */, 3706FBE9293F65D500E42796 /* FireViewModel.swift in Sources */, 3706FEC6293F6F0600E42796 /* BWKeyStorage.swift in Sources */, - 3706FBEB293F65D500E42796 /* APIRequest.swift in Sources */, 3706FBEC293F65D500E42796 /* EditableTextView.swift in Sources */, 3706FBED293F65D500E42796 /* TabCollection.swift in Sources */, 3706FBEE293F65D500E42796 /* MainView.swift in Sources */, @@ -6835,7 +6814,6 @@ 3706FC78293F65D500E42796 /* SecureVaultErrorReporter.swift in Sources */, 3706FC79293F65D500E42796 /* NSImageExtensions.swift in Sources */, 3706FEBD293F6EFF00E42796 /* BWCommand.swift in Sources */, - 3706FC7A293F65D500E42796 /* APIHeaders.swift in Sources */, 3706FC7B293F65D500E42796 /* PasswordManagementViewController.swift in Sources */, 3706FC7C293F65D500E42796 /* ImportedBookmarks.swift in Sources */, 3706FC7D293F65D500E42796 /* NSMenuExtension.swift in Sources */, @@ -6937,6 +6915,7 @@ 3706FE0A293F661700E42796 /* UserAgentTests.swift in Sources */, 3706FE0B293F661700E42796 /* AVCaptureDeviceMock.swift in Sources */, 3706FE0C293F661700E42796 /* GeolocationProviderMock.swift in Sources */, + CBDD5DE429A6800300832877 /* MockConfigurationStore.swift in Sources */, 3706FE0D293F661700E42796 /* MainMenuTests.swift in Sources */, 3706FE0E293F661700E42796 /* FirefoxDataImporterTests.swift in Sources */, 3706FE0F293F661700E42796 /* CSVLoginExporterTests.swift in Sources */, @@ -7029,7 +7008,6 @@ 3706FE5D293F661700E42796 /* FileSystemDSL.swift in Sources */, 3706FE5E293F661700E42796 /* DataImportMocks.swift in Sources */, 3706FE5F293F661700E42796 /* CrashReportTests.swift in Sources */, - 3706FE60293F661700E42796 /* ConfigurationDownloaderTests.swift in Sources */, B60C6F7F29B1B41D007BFAA8 /* TestRunHelperInitializer.m in Sources */, 3706FE61293F661700E42796 /* PinnedTabsViewModelTests.swift in Sources */, 3706FE62293F661700E42796 /* PasswordManagementListSectionTests.swift in Sources */, @@ -7144,7 +7122,6 @@ files = ( AAA0CC572539EBC90079BC96 /* FaviconUserScript.swift in Sources */, 1D2DC0082901679E008083A1 /* BWResponse.swift in Sources */, - B6A9E45A261460350067D1B9 /* ApiRequestError.swift in Sources */, AADCBF3A26F7C2CE00EF67A8 /* LottieAnimationCache.swift in Sources */, 37D2377A287EB8CA00BCE03B /* TabIndex.swift in Sources */, 37534CA3281132CB002621E7 /* TabLazyLoaderDataSource.swift in Sources */, @@ -7233,7 +7210,7 @@ B69B503B2726A12500758A2B /* Atb.swift in Sources */, B6B1E88026D5DA9B0062C350 /* DownloadsViewController.swift in Sources */, 85AC3AF725D5DBFD00C7D2AA /* DataExtension.swift in Sources */, - 85480FCF25D1AA22009424E3 /* ConfigurationStoring.swift in Sources */, + 85480FCF25D1AA22009424E3 /* ConfigurationStore.swift in Sources */, AA3D531B27A2F57E00074EC1 /* Feedback.swift in Sources */, 4BB99D0626FE1979001E4761 /* RequestFilePermissionViewController.swift in Sources */, 4B0A63E8289DB58E00378EF7 /* FirefoxFaviconsReader.swift in Sources */, @@ -7323,6 +7300,7 @@ B6AAAC2D260330580029438D /* PublishedAfter.swift in Sources */, 37054FCE2876472D00033B6F /* WebViewSnapshotView.swift in Sources */, 4BBC16A027C4859400E00A38 /* DeviceAuthenticationService.swift in Sources */, + CB24F70C29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */, 3776582F27F82E62009A6B35 /* AutofillPreferences.swift in Sources */, AAD8078727B3F45600CF7703 /* WebsiteBreakage.swift in Sources */, 1D074B272909A433006E4AC3 /* PasswordManagerCoordinator.swift in Sources */, @@ -7408,11 +7386,9 @@ 4B8AC93B26B48ADF00879451 /* ASN1Parser.swift in Sources */, 37CD54B327EE509700F1F7B9 /* View+Cursor.swift in Sources */, B66E9DD22670EB2A00E53BB5 /* _WKDownload+WebKitDownload.swift in Sources */, - B6A9E4612614608B0067D1B9 /* AppVersion.swift in Sources */, 856C98DF257014BD00A22F1F /* FileDownloadManager.swift in Sources */, 4BB99CFF26FE191E001E4761 /* BookmarkImport.swift in Sources */, B68503A7279141CD00893A05 /* KeySetDictionary.swift in Sources */, - 85480FBB25D181CB009424E3 /* ConfigurationDownloading.swift in Sources */, AAEEC6A927088ADB008445F7 /* FireCoordinator.swift in Sources */, B655369B268442EE00085A79 /* GeolocationProvider.swift in Sources */, B6C0B23C26E87D900031CB7F /* NSAlert+ActiveDownloadsTermination.swift in Sources */, @@ -7528,7 +7504,6 @@ AABEE6A924AB4B910043105B /* SuggestionTableCellView.swift in Sources */, AA6820F125503DA9005ED0D5 /* FireViewModel.swift in Sources */, AAA0CC6A253CC43C0079BC96 /* WKUserContentControllerExtension.swift in Sources */, - B6A9E45C261460350067D1B9 /* APIRequest.swift in Sources */, 4BE65479271FCD41008D1D63 /* EditableTextView.swift in Sources */, AA9FF95D24A1FA1C0039E328 /* TabCollection.swift in Sources */, B688B4DA273E6D3B0087BEAF /* MainView.swift in Sources */, @@ -7675,7 +7650,6 @@ 853014D625E671A000FB8205 /* PageObserverUserScript.swift in Sources */, B642738227B65BAC0005DFD1 /* SecureVaultErrorReporter.swift in Sources */, 4B139AFD26B60BD800894F82 /* NSImageExtensions.swift in Sources */, - B6A9E45B261460350067D1B9 /* APIHeaders.swift in Sources */, 85625996269C953C00EE44BC /* PasswordManagementViewController.swift in Sources */, 4BB99D0226FE191E001E4761 /* ImportedBookmarks.swift in Sources */, B626A75A29921FAA00053070 /* NavigationActionPolicyExtension.swift in Sources */, @@ -7818,6 +7792,7 @@ B6106BAF26A7C6180013B453 /* PermissionStoreMock.swift in Sources */, 4B98D27A28D95F1A003C2B6F /* ChromiumFaviconsReaderTests.swift in Sources */, AA652CD325DDA6E9009059CC /* LocalBookmarkManagerTests.swift in Sources */, + CBDD5DE329A67F2700832877 /* MockConfigurationStore.swift in Sources */, B63ED0DC26AE7B1E00A9DAD1 /* WebViewMock.swift in Sources */, 4B4F72EC266B2ED300814C60 /* CollectionExtension.swift in Sources */, AAE39D1B24F44885008EF28B /* TabCollectionViewModelDelegateMock.swift in Sources */, @@ -7875,7 +7850,6 @@ 4BBF09232830812900EE1418 /* FileSystemDSL.swift in Sources */, 4B723E0526B0003E00E14D75 /* DataImportMocks.swift in Sources */, 4B70C00227B0793D000386ED /* CrashReportTests.swift in Sources */, - 85AC3B1725D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift in Sources */, 37D23787287F5C2900BCE03B /* PinnedTabsViewModelTests.swift in Sources */, 4BF4EA5027C71F26004E57C4 /* PasswordManagementListSectionTests.swift in Sources */, AA7E9176286DB05D00AB6B62 /* RecentlyClosedCoordinatorMock.swift in Sources */, @@ -8441,7 +8415,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 47.2.0; + version = 48.0.0; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { @@ -8619,6 +8593,11 @@ package = B6DA44152616C13800DD1EC2 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; productName = OHHTTPStubsSwift; }; + CBC83E3529B63D380008E19C /* Configuration */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Configuration; + }; B6EC37F829B5DAD7001ACE79 /* Swifter */ = { isa = XCSwiftPackageProductDependency; package = B6EC37F529B5DAAC001ACE79 /* XCRemoteSwiftPackageReference "swifter" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cbe9b39509..02ea57028a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "86a7a3a68f53a37aa292ff3193b2dd587f943527", - "version" : "47.2.0" + "revision" : "c10bf5256ba0b286fc62bd557e9d9100e7ea8c8f", + "version" : "48.0.0" } }, { diff --git a/DuckDuckGo/API/APIHeaders.swift b/DuckDuckGo/API/APIHeaders.swift deleted file mode 100644 index 274066a79b..0000000000 --- a/DuckDuckGo/API/APIHeaders.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// APIHeaders.swift -// -// Copyright © 2018 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 Foundation - -typealias HTTPHeaders = [String: String] - -final class APIHeaders { - - enum Name { - static let acceptEncoding = "Accept-Encoding" - static let acceptLanguage = "Accept-Language" - static let userAgent = "User-Agent" - static let etag = "ETag" - static let ifNoneMatch = "If-None-Match" - static let moreInfo = "X-DuckDuckGo-MoreInfo" - } - - private let appVersion: AppVersion - - init(appVersion: AppVersion = AppVersion.shared) { - self.appVersion = appVersion - } - - var defaultHeaders: HTTPHeaders { - let acceptEncoding = "gzip;q=1.0, compress;q=0.5" - let languages = Locale.preferredLanguages.prefix(6) - let acceptLanguage = languages.enumerated().map { index, language in - let q = 1.0 - (Double(index) * 0.1) - return "\(language);q=\(q)" - }.joined(separator: ", ") - - return [ - Name.acceptEncoding: acceptEncoding, - Name.acceptLanguage: acceptLanguage, - Name.userAgent: userAgent - ] - } - - var userAgent: String { - let osVersion = ProcessInfo.processInfo.operatingSystemVersion - return "ddg_mac/\(appVersion.versionNumber) (\(appVersion.identifier); macOS \(osVersion))" - } - - func defaultHeaders(with etag: String?) -> HTTPHeaders { - guard let etag = etag else { - return defaultHeaders - } - - return defaultHeaders.merging([Name.ifNoneMatch: etag]) { (_, new) in new } - } - - func addHeaders(to request: inout URLRequest) { - request.addValue(Name.userAgent, forHTTPHeaderField: userAgent) - } - -} diff --git a/DuckDuckGo/API/APIRequest.swift b/DuckDuckGo/API/APIRequest.swift deleted file mode 100644 index cd1aa99e7f..0000000000 --- a/DuckDuckGo/API/APIRequest.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// APIRequest.swift -// -// Copyright © 2017 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 Foundation -import Navigation -import os.log - -typealias APIRequestCompletion = (APIRequest.Response?, Error?) -> Void - -enum APIRequest { - - private static var defaultCallbackQueue: OperationQueue = { - let queue = OperationQueue() - queue.name = "APIRequest default callback queue" - queue.qualityOfService = .userInitiated - queue.maxConcurrentOperationCount = 1 - return queue - }() - - private static let defaultCallbackSession = URLSession(configuration: .default, delegate: nil, delegateQueue: defaultCallbackQueue) - private static let defaultCallbackEphemeralSession = URLSession(configuration: .ephemeral, delegate: nil, delegateQueue: defaultCallbackQueue) - - private static let mainThreadCallbackSession = URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue.main) - private static let mainThreadCallbackEphemeralSession = URLSession(configuration: .ephemeral, delegate: nil, delegateQueue: OperationQueue.main) - - struct Response { - - var data: Data? - var etag: String? - var urlResponse: URLResponse? - - } - - enum HTTPMethod: String { - case get = "GET" - case head = "HEAD" - case post = "POST" - case put = "PUT" - case delete = "DELETE" - case connect = "CONNECT" - case options = "OPTIONS" - case trace = "TRACE" - case patch = "PATCH" - } - - @discardableResult - static func request(url: URL, - method: HTTPMethod = .get, - parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - headers: HTTPHeaders = APIHeaders().defaultHeaders, - timeoutInterval: TimeInterval = 60.0, - useEphemeralURLSession: Bool = true, // URL requests must opt into using shared storage - callBackOnMainThread: Bool = false, - completion: @escaping APIRequestCompletion) -> URLSessionDataTask { - - let urlRequest = urlRequestFor( - url: url, - method: method, - parameters: parameters, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - headers: headers, - timeoutInterval: timeoutInterval - ) - let session = session(useMainThreadCallbackQueue: callBackOnMainThread, ephemeral: useEphemeralURLSession) - - let task = session.dataTask(with: urlRequest) { (data, response, error) in - - let httpResponse = response as? HTTPURLResponse - - if let error = error { - completion(nil, error) - } else if let error = httpResponse?.validateStatusCode() { - completion(nil, error) - } else { - var etag = httpResponse?.headerValue(for: APIHeaders.Name.etag) - - // Handle weak etags - etag = etag?.dropping(prefix: "W/") - completion(Response(data: data, etag: etag, urlResponse: response), nil) - } - } - task.resume() - return task - } - - static func urlRequestFor(url: URL, - method: HTTPMethod = .get, - parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - headers: HTTPHeaders = APIHeaders().defaultHeaders, - timeoutInterval: TimeInterval = 60.0) -> URLRequest { - let url = url.appendingParameters(parameters ?? [:], allowedReservedCharacters: allowedQueryReservedCharacters) - var urlRequest = URLRequest(url: url, timeoutInterval: timeoutInterval) - urlRequest.allHTTPHeaderFields = headers - urlRequest.httpMethod = method.rawValue - return urlRequest - } - - private static func session(useMainThreadCallbackQueue: Bool, ephemeral: Bool) -> URLSession { - if useMainThreadCallbackQueue { - return ephemeral ? mainThreadCallbackEphemeralSession : mainThreadCallbackSession - } else { - return ephemeral ? defaultCallbackEphemeralSession : defaultCallbackSession - } - } - -} - -extension HTTPURLResponse { - - fileprivate func headerValue(for name: String) -> String? { - let lname = name.lowercased() - return allHeaderFields.filter { ($0.key as? String)?.lowercased() == lname }.first?.value as? String - } - -} diff --git a/DuckDuckGo/API/ApiRequestError.swift b/DuckDuckGo/API/ApiRequestError.swift deleted file mode 100644 index 3119b591ee..0000000000 --- a/DuckDuckGo/API/ApiRequestError.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// ApiRequestError.swift -// -// Copyright © 2017 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 Foundation - -enum ApiRequestError: Error { - case noData -} diff --git a/DuckDuckGo/App Delegate/AppConfigurationURLProvider.swift b/DuckDuckGo/App Delegate/AppConfigurationURLProvider.swift new file mode 100644 index 0000000000..e26716fd45 --- /dev/null +++ b/DuckDuckGo/App Delegate/AppConfigurationURLProvider.swift @@ -0,0 +1,37 @@ +// +// AppConfigurationURLProvider.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 Foundation +import Configuration + +struct AppConfigurationURLProvider: ConfigurationURLProviding { + + func url(for configuration: Configuration) -> URL { + switch configuration { + case .bloomFilterBinary: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom.bin")! + case .bloomFilterSpec: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom-spec.json")! + case .bloomFilterExcludedDomains: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-false-positives.json")! + case .privacyConfiguration: return URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/config/v2/macos-config.json")! + case .surrogates: return URL(string: "https://duckduckgo.com/contentblocking.js?l=surrogates")! + case .trackerDataSet: return URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/v3/apple-tds.json")! + // In archived repo, to be refactored shortly (https://staticcdn.duckduckgo.com/useragents/social_ctp_configuration.json) + case .FBConfig: return URL(string: "https://staticcdn.duckduckgo.com/useragents/")! + } + } + +} diff --git a/DuckDuckGo/App Delegate/AppDelegate.swift b/DuckDuckGo/App Delegate/AppDelegate.swift index 992b10e3a3..feded610e2 100644 --- a/DuckDuckGo/App Delegate/AppDelegate.swift +++ b/DuckDuckGo/App Delegate/AppDelegate.swift @@ -21,6 +21,8 @@ import Combine import os.log import BrowserServicesKit import Persistence +import Configuration +import Networking import Bookmarks @NSApplicationMain @@ -61,6 +63,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel // swiftlint:disable:next function_body_length func applicationWillFinishLaunching(_ notification: Notification) { + APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) + Configuration.setURLProvider(AppConfigurationURLProvider()) + if !NSApp.isRunningUnitTests { #if DEBUG Pixel.setUp(dryRun: true) diff --git a/DuckDuckGo/Browser Tab/Model/UserContentUpdating.swift b/DuckDuckGo/Browser Tab/Model/UserContentUpdating.swift index 2611d91507..88624991d4 100644 --- a/DuckDuckGo/Browser Tab/Model/UserContentUpdating.swift +++ b/DuckDuckGo/Browser Tab/Model/UserContentUpdating.swift @@ -21,6 +21,7 @@ import Combine import Common import BrowserServicesKit import UserScript +import Configuration final class UserContentUpdating { diff --git a/DuckDuckGo/Common/AppVersion.swift b/DuckDuckGo/Common/AppVersion.swift deleted file mode 100644 index 56c0a84d84..0000000000 --- a/DuckDuckGo/Common/AppVersion.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// AppVersion.swift -// -// Copyright © 2017 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 Foundation - -struct AppVersion { - - static let shared = AppVersion() - - private let bundle: Bundle - - init(bundle: Bundle = .main) { - self.bundle = bundle - } - - var name: String { - return bundle.object(forInfoDictionaryKey: Bundle.Keys.name) as? String ?? "" - } - - var identifier: String { - return bundle.object(forInfoDictionaryKey: Bundle.Keys.identifier) as? String ?? "" - } - - var majorVersionNumber: String { - return String(versionNumber.split(separator: ".").first ?? "") - } - - var versionNumber: String { - return bundle.object(forInfoDictionaryKey: Bundle.Keys.versionNumber) as? String ?? "" - } - - var buildNumber: String { - return bundle.object(forInfoDictionaryKey: Bundle.Keys.buildNumber) as? String ?? "" - } - -} diff --git a/DuckDuckGo/Configuration/ConfigurationDownloading.swift b/DuckDuckGo/Configuration/ConfigurationDownloading.swift deleted file mode 100644 index e5ad1822a7..0000000000 --- a/DuckDuckGo/Configuration/ConfigurationDownloading.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// ConfigurationDownloading.swift -// -// Copyright © 2021 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 Foundation -import Combine -import BrowserServicesKit - -protocol ConfigurationDownloading { - - func refreshDataThenUpdate(for locations: [ConfigurationLocation], _ updater: @escaping () throws -> Void) - -> AnyPublisher<[ConfigurationDownloadMeta?], Swift.Error> - func cancelAll() - -} - -struct ConfigurationDownloadMeta { - - var etag: String - var data: Data - -} - -enum ConfigurationLocation: String, CaseIterable { - - case bloomFilterSpec = "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom-spec.json" - case bloomFilterBinary = "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom.bin" - case bloomFilterExcludedDomains = "https://staticcdn.duckduckgo.com/https/https-mobile-v2-false-positives.json" - case surrogates = "https://duckduckgo.com/contentblocking.js?l=surrogates" - case trackerRadar = "https://staticcdn.duckduckgo.com/trackerblocking/v3/apple-tds.json" - case privacyConfiguration = "https://staticcdn.duckduckgo.com/trackerblocking/config/v2/macos-config.json" - // In archived repo, to be refactored shortly (https://staticcdn.duckduckgo.com/useragents/social_ctp_configuration.json) - case FBConfig = "https://staticcdn.duckduckgo.com/useragents/" - -} - -final class DefaultConfigurationDownloader: ConfigurationDownloading { - - enum Error: Swift.Error { - - case urlSessionError(error: Swift.Error) - case noEtagInResponse - case invalidResponse - case savingData - case savingEtag - - } - - struct Constants { - static let ifNoneMatchField = "If-None-Match" - static let etagField = "Etag" - static let notModifiedResponseCode = 304 - static let successResponseCode = 200 - } - - let storage: ConfigurationStoring - let dataTaskProvider: DataTaskProviding - - private var cancellables = Set() - private let deliveryQueue: DispatchQueue - - init(storage: ConfigurationStoring = DefaultConfigurationStorage.shared, - dataTaskProvider: DataTaskProviding = SharedURLSessionDataTaskProvider(), - deliveryQueue: DispatchQueue) { - self.storage = storage - self.dataTaskProvider = dataTaskProvider - self.deliveryQueue = deliveryQueue - } - - func download(_ config: ConfigurationLocation, embeddedEtag: String?) -> AnyPublisher { - - let url = URL(string: config.rawValue)! - - return Future { promise in - var request = URLRequest.defaultRequest(with: url) - request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData - - let storedEtag = self.storage.loadEtag(for: config) - - if let embeddedEtag = embeddedEtag, storedEtag == nil { - request.addValue(embeddedEtag, forHTTPHeaderField: Constants.ifNoneMatchField) - } else if self.storage.loadData(for: config) != nil, let etag = storedEtag { - request.addValue(etag, forHTTPHeaderField: Constants.ifNoneMatchField) - } - - self.dataTaskProvider.dataTaskPublisher(for: request) - .tryMap { result -> ConfigurationDownloadMeta? in - guard let response = result.response as? HTTPURLResponse else { - throw Error.invalidResponse - } - - if response.statusCode == Constants.notModifiedResponseCode { - return nil - } - - guard let etag = response.value(forHTTPHeaderField: Constants.etagField) else { - throw Error.noEtagInResponse - } - - try self.storage.saveData(result.data, for: config) - try self.storage.saveEtag(etag, for: config) - - return ConfigurationDownloadMeta(etag: etag, data: result.data) - } - .sink(receiveCompletion: { completion in - - if case .failure(let error) = completion { - promise(.failure(error)) - } - - }) { value in - - promise(.success((value))) - - }.store(in: &self.cancellables) - - }.eraseToAnyPublisher() - - } - - func cancelAll() { - - let cancellables = self.cancellables - self.cancellables.removeAll() - cancellables.forEach { $0.cancel() } - - } - - func embeddedEtag(for config: ConfigurationLocation) -> String? { - switch config { - case .trackerRadar: return AppTrackerDataSetProvider.Constants.embeddedDataETag - case .privacyConfiguration: return AppPrivacyConfigurationDataProvider.Constants.embeddedDataSHA - default: return nil - } - } - - func refreshDataThenUpdate(for locations: [ConfigurationLocation], _ updater: @escaping () throws -> Void) - -> AnyPublisher<[ConfigurationDownloadMeta?], Swift.Error> { - - Publishers.MergeMany( - locations.map { - download($0, embeddedEtag: embeddedEtag(for: $0)) - } - ) - .receive(on: self.deliveryQueue) - .collect() - .tryMap { result -> [ConfigurationDownloadMeta?] in - if !result.compactMap({$0}).isEmpty { - try updater() - } - return result - }.eraseToAnyPublisher() - - } - -} diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index 63f84ff1ff..1b4053e24f 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -20,47 +20,44 @@ import Foundation import Combine import os import BrowserServicesKit +import Configuration +@MainActor final class ConfigurationManager { enum Error: Swift.Error { + case timeout case bloomFilterSpecNotFound case bloomFilterBinaryNotFound case bloomFilterPersistenceFailed case bloomFilterExclusionsNotFound case bloomFilterExclusionsPersistenceFailed + } - struct Constants { + enum Constants { + static let downloadTimeoutSeconds = 60.0 * 5 #if DEBUG - static let refreshPeriodSeconds = 60.0 * 2 // 2 minutes when in debug mode + static let refreshPeriodSeconds = 60.0 * 2 // 2 minutes #else static let refreshPeriodSeconds = 60.0 * 30 // 30 minutes #endif static let retryDelaySeconds = 60.0 * 60 * 1 // 1 hour delay before checking again if something went wrong last time - static let refreshCheckIntervalSeconds = 60.0 // Check if we need a refresh every minute + static let refreshCheckIntervalSeconds = 60.0 // check if we need a refresh every minute + } static let shared = ConfigurationManager() - static let queue: DispatchQueue = DispatchQueue(label: "Configuration Manager") @UserDefaultsWrapper(key: .configLastUpdated, defaultValue: .distantPast) - var lastUpdateTime: Date + private var lastUpdateTime: Date private var timerCancellable: AnyCancellable? - private var refreshCancellable: AnyCancellable? private var lastRefreshCheckTime: Date = Date() - private let configDownloader: ConfigurationDownloading - - /// Use the shared instance if subscribing to events. Only use the constructor for testing. - init(configDownloader: ConfigurationDownloading = DefaultConfigurationDownloader(deliveryQueue: ConfigurationManager.queue)) { - self.configDownloader = configDownloader - } - func start() { os_log("Starting configuration refresh timer", log: .config, type: .debug) timerCancellable = Timer.publish(every: Constants.refreshCheckIntervalSeconds, on: .main, in: .default) @@ -70,7 +67,9 @@ final class ConfigurationManager { self.lastRefreshCheckTime = Date() self.refreshIfNeeded() }) - refreshNow() + Task { + await refreshNow() + } } func log() { @@ -78,63 +77,93 @@ final class ConfigurationManager { os_log("last refresh check %{public}s", log: .config, type: .default, String(describing: lastRefreshCheckTime)) } - private func refreshNow() { - - refreshCancellable = + private func refreshNow() async { + let fetcher = ConfigurationFetcher(store: ConfigurationStore.shared, log: .config) - Publishers.MergeMany( - - configDownloader.refreshDataThenUpdate(for: [ - .trackerRadar, - .surrogates, - .privacyConfiguration - ], self.updateTrackerBlockingDependencies), - - configDownloader.refreshDataThenUpdate(for: [ - .bloomFilterBinary, - .bloomFilterSpec - ], self.updateBloomFilter), - - configDownloader.refreshDataThenUpdate(for: [ - .bloomFilterExcludedDomains - ], self.updateBloomFilterExclusions) - - ) - .collect() - .timeout(.seconds(Constants.downloadTimeoutSeconds), scheduler: Self.queue, options: nil, customError: { Error.timeout }) - .sink { [self] completion in + let updateTrackerBlockingDependenciesTask = Task { + let didFetchAnyTrackerBlockingDependencies = await fetchTrackerBlockingDependencies() + if didFetchAnyTrackerBlockingDependencies { + updateTrackerBlockingDependencies() + tryAgainLater() + } + } - if case .failure(let error) = completion { - os_log("Failed to complete configuration update %s", log: .config, type: .error, error.localizedDescription) - Pixel.fire(.debug(event: .configurationFetchError, error: error)) + let updateBloomFilterTask = Task { + do { + try await fetcher.fetch(all: [.bloomFilterBinary, .bloomFilterSpec]) + try updateBloomFilter() + tryAgainLater() + } catch { + handleRefreshError(error) + } + } - tryAgainSoon() - } else { - tryAgainLater() - } + let updateBloomFilterExclusionsTask = Task { + do { + try await fetcher.fetch(.bloomFilterExcludedDomains) + try updateBloomFilterExclusions() + tryAgainLater() + } catch { + handleRefreshError(error) + } + } - refreshCancellable = nil - configDownloader.cancelAll() + await updateTrackerBlockingDependenciesTask.value + await updateBloomFilterTask.value + await updateBloomFilterExclusionsTask.value - DefaultConfigurationStorage.shared.log() - log() + ConfigurationStore.shared.log() + log() + } - } receiveValue: { _ in - // no-op - if you want to do something more globally if any of the files were downloaded, this is the place + private func fetchTrackerBlockingDependencies() async -> Bool { + var didFetchAnyTrackerBlockingDependencies = false + let fetcher = ConfigurationFetcher(store: ConfigurationStore.shared, log: .config) + + 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) } + + for (configuration, task) in tasks { + do { + try await task.value + didFetchAnyTrackerBlockingDependencies = true + } catch { + os_log("Failed to complete configuration update to %@: %@", + log: .config, + type: .error, + configuration.rawValue, + error.localizedDescription) + tryAgainSoon() } + } + return didFetchAnyTrackerBlockingDependencies + } + + private func handleRefreshError(_ error: Swift.Error) { + os_log("Failed to complete configuration update %@", log: .config, type: .error, error.localizedDescription) + Pixel.fire(.debug(event: .configurationFetchError, error: error)) + tryAgainSoon() } public func refreshIfNeeded() { - guard self.isReadyToRefresh(), refreshCancellable == nil else { + guard isReadyToRefresh else { os_log("Configuration refresh is not needed at this time", log: .config, type: .debug) return } - refreshNow() + Task { + await refreshNow() + } } - private func isReadyToRefresh() -> Bool { - return Date().timeIntervalSince(lastUpdateTime) > Constants.refreshPeriodSeconds + private var isReadyToRefresh: Bool { Date().timeIntervalSince(lastUpdateTime) > Constants.refreshPeriodSeconds } + + public func forceRefresh() { + Task { + await refreshNow() + } } private func tryAgainLater() { @@ -146,54 +175,36 @@ final class ConfigurationManager { lastUpdateTime = Date(timeIntervalSinceNow: Constants.refreshPeriodSeconds - Constants.retryDelaySeconds) } - private func updateTrackerBlockingDependencies() throws { - - let tdsEtag = DefaultConfigurationStorage.shared.loadEtag(for: .trackerRadar) - let tdsData = DefaultConfigurationStorage.shared.loadData(for: .trackerRadar) - ContentBlocking.shared.trackerDataManager.reload(etag: tdsEtag, data: tdsData) - - let configEtag = DefaultConfigurationStorage.shared.loadEtag(for: .privacyConfiguration) - let configData = DefaultConfigurationStorage.shared.loadData(for: .privacyConfiguration) - _=ContentBlocking.shared.privacyConfigurationManager.reload(etag: configEtag, data: configData) - - _=ContentBlocking.shared.contentBlockingManager.scheduleCompilation() + private func updateTrackerBlockingDependencies() { + 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), + data: ConfigurationStore.shared.loadData(for: .privacyConfiguration)) + ContentBlocking.shared.contentBlockingManager.scheduleCompilation() } private func updateBloomFilter() throws { - - let configStore = DefaultConfigurationStorage.shared - guard let specData = configStore.loadData(for: .bloomFilterSpec) else { + guard let specData = ConfigurationStore.shared.loadData(for: .bloomFilterSpec) else { throw Error.bloomFilterSpecNotFound } - - guard let bloomFilterData = configStore.loadData(for: .bloomFilterBinary) else { + guard let bloomFilterData = ConfigurationStore.shared.loadData(for: .bloomFilterBinary) else { throw Error.bloomFilterBinaryNotFound } - let spec = try JSONDecoder().decode(HTTPSBloomFilterSpecification.self, from: specData) - - let httpsStore = AppHTTPSUpgradeStore() - guard httpsStore.persistBloomFilter(specification: spec, data: bloomFilterData) else { + guard AppHTTPSUpgradeStore().persistBloomFilter(specification: spec, data: bloomFilterData) else { throw Error.bloomFilterPersistenceFailed } - PrivacyFeatures.httpsUpgrade.loadData() } private func updateBloomFilterExclusions() throws { - - let configStore = DefaultConfigurationStorage.shared - guard let bloomFilterExclusions = configStore.loadData(for: .bloomFilterExcludedDomains) else { + guard let bloomFilterExclusions = ConfigurationStore.shared.loadData(for: .bloomFilterExcludedDomains) else { throw Error.bloomFilterExclusionsNotFound } - let excludedDomains = try JSONDecoder().decode(HTTPSExcludedDomains.self, from: bloomFilterExclusions).data - - let httpsStore = AppHTTPSUpgradeStore() - guard httpsStore.persistExcludedDomains(excludedDomains) else { + guard AppHTTPSUpgradeStore().persistExcludedDomains(excludedDomains) else { throw Error.bloomFilterExclusionsPersistenceFailed } - PrivacyFeatures.httpsUpgrade.loadData() } diff --git a/DuckDuckGo/Configuration/ConfigurationStoring.swift b/DuckDuckGo/Configuration/ConfigurationStore.swift similarity index 66% rename from DuckDuckGo/Configuration/ConfigurationStoring.swift rename to DuckDuckGo/Configuration/ConfigurationStore.swift index 8781b7fa89..81786161aa 100644 --- a/DuckDuckGo/Configuration/ConfigurationStoring.swift +++ b/DuckDuckGo/Configuration/ConfigurationStore.swift @@ -18,30 +18,21 @@ import Foundation import os +import Configuration -protocol ConfigurationStoring { +final class ConfigurationStore: ConfigurationStoring { - func loadData(for: ConfigurationLocation) -> Data? - func loadEtag(for: ConfigurationLocation) -> String? - func saveData(_ data: Data, for: ConfigurationLocation) throws - func saveEtag(_ etag: String, for: ConfigurationLocation) throws - func log() - -} - -final class DefaultConfigurationStorage: ConfigurationStoring { - - private static let fileLocations: [ConfigurationLocation: String] = [ + private static let fileLocations: [Configuration: String] = [ .bloomFilterBinary: "smarterEncryption.bin", .bloomFilterExcludedDomains: "smarterEncryptionExclusions.json", .bloomFilterSpec: "smarterEncryptionSpec.json", .surrogates: "surrogates.txt", .privacyConfiguration: "macos-config.json", - .trackerRadar: "tracker-radar.json", + .trackerDataSet: "tracker-radar.json", .FBConfig: "social_ctp_configuration.json" ] - static let shared = DefaultConfigurationStorage() + static let shared = ConfigurationStore() @UserDefaultsWrapper(key: .configStorageTrackerRadarEtag, defaultValue: nil) private var trackerRadarEtag: String? @@ -66,57 +57,39 @@ final class DefaultConfigurationStorage: ConfigurationStoring { private init() { } - func loadEtag(for config: ConfigurationLocation) -> String? { - switch config { - case .bloomFilterSpec: - return bloomFilterSpecEtag - - case .bloomFilterBinary: - return bloomFilterBinaryEtag - - case .bloomFilterExcludedDomains: - return bloomFilterExcludedDomainsEtag - - case .surrogates: - return surrogatesEtag - - case .trackerRadar: - return trackerRadarEtag - - case .privacyConfiguration: - return privacyConfigurationEtag - - case .FBConfig: - return FBConfigEtag + func loadEtag(for configuration: Configuration) -> String? { + switch configuration { + case .bloomFilterSpec: return bloomFilterSpecEtag + case .bloomFilterBinary: return bloomFilterBinaryEtag + case .bloomFilterExcludedDomains: return bloomFilterExcludedDomainsEtag + case .surrogates: return surrogatesEtag + case .trackerDataSet: return trackerRadarEtag + case .privacyConfiguration: return privacyConfigurationEtag + case .FBConfig: return FBConfigEtag } } - func saveEtag(_ etag: String, for config: ConfigurationLocation) throws { - switch config { - case .bloomFilterSpec: - bloomFilterSpecEtag = etag - - case .bloomFilterBinary: - bloomFilterBinaryEtag = etag - - case .bloomFilterExcludedDomains: - bloomFilterExcludedDomainsEtag = etag - - case .surrogates: - surrogatesEtag = etag - - case .trackerRadar: - trackerRadarEtag = etag - - case .privacyConfiguration: - privacyConfigurationEtag = etag + func loadEmbeddedEtag(for configuration: Configuration) -> String? { + switch configuration { + case .trackerDataSet: return AppTrackerDataSetProvider.Constants.embeddedDataETag + case .privacyConfiguration: return AppPrivacyConfigurationDataProvider.Constants.embeddedDataSHA + default: return nil + } + } - case .FBConfig: - return FBConfigEtag = etag + func saveEtag(_ etag: String, for configuration: Configuration) throws { + switch configuration { + case .bloomFilterSpec: bloomFilterSpecEtag = etag + case .bloomFilterBinary: bloomFilterBinaryEtag = etag + case .bloomFilterExcludedDomains: bloomFilterExcludedDomainsEtag = etag + case .surrogates: surrogatesEtag = etag + case .trackerDataSet: trackerRadarEtag = etag + case .privacyConfiguration: privacyConfigurationEtag = etag + case .FBConfig: FBConfigEtag = etag } } - func loadData(for config: ConfigurationLocation) -> Data? { + func loadData(for config: Configuration) -> Data? { let file = fileUrl(for: config) do { return try Data(contentsOf: file) @@ -128,7 +101,7 @@ final class DefaultConfigurationStorage: ConfigurationStoring { } } - func saveData(_ data: Data, for config: ConfigurationLocation) throws { + func saveData(_ data: Data, for config: Configuration) throws { let file = fileUrl(for: config) try data.write(to: file, options: .atomic) } @@ -143,7 +116,7 @@ final class DefaultConfigurationStorage: ConfigurationStoring { os_log("FBConfigEtag %{public}s", log: .config, type: .default, FBConfigEtag ?? "") } - func fileUrl(for config: ConfigurationLocation) -> URL { + func fileUrl(for config: Configuration) -> URL { let fm = FileManager.default let dir = URL.sandboxApplicationSupportURL diff --git a/DuckDuckGo/Content Blocker/ContentBlocking.swift b/DuckDuckGo/Content Blocker/ContentBlocking.swift index 5ebc005538..8cbba7d0dd 100644 --- a/DuckDuckGo/Content Blocker/ContentBlocking.swift +++ b/DuckDuckGo/Content Blocker/ContentBlocking.swift @@ -59,15 +59,15 @@ final class AppContentBlocking { // keeping whole ContentBlocking state initialization in one place to avoid races between updates publishing and rules storing init() { - let configStorage = DefaultConfigurationStorage.shared + let configStorage = ConfigurationStore.shared privacyConfigurationManager = PrivacyConfigurationManager(fetchedETag: configStorage.loadEtag(for: .privacyConfiguration), fetchedData: configStorage.loadData(for: .privacyConfiguration), embeddedDataProvider: AppPrivacyConfigurationDataProvider(), localProtection: LocalUnprotectedDomains.shared, errorReporting: Self.debugEvents) - trackerDataManager = TrackerDataManager(etag: DefaultConfigurationStorage.shared.loadEtag(for: .trackerRadar), - data: DefaultConfigurationStorage.shared.loadData(for: .trackerRadar), + trackerDataManager = TrackerDataManager(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), + data: ConfigurationStore.shared.loadData(for: .trackerDataSet), embeddedDataProvider: AppTrackerDataSetProvider(), errorReporting: Self.debugEvents) @@ -193,7 +193,7 @@ final class AppContentBlocking { protocol ContentBlockerRulesManagerProtocol: CompiledRuleListsSource { var updatesPublisher: AnyPublisher { get } var currentRules: [ContentBlockerRulesManager.Rules] { get } - func scheduleCompilation() -> ContentBlockerRulesManager.CompletionToken + @discardableResult func scheduleCompilation() -> ContentBlockerRulesManager.CompletionToken } extension ContentBlockerRulesManager: ContentBlockerRulesManagerProtocol {} diff --git a/DuckDuckGo/Content Blocker/ScriptSourceProviding.swift b/DuckDuckGo/Content Blocker/ScriptSourceProviding.swift index eda8e99dae..4d9d47e3b1 100644 --- a/DuckDuckGo/Content Blocker/ScriptSourceProviding.swift +++ b/DuckDuckGo/Content Blocker/ScriptSourceProviding.swift @@ -20,6 +20,7 @@ import Foundation import Combine import Common import BrowserServicesKit +import Configuration protocol ScriptSourceProviding { @@ -36,7 +37,7 @@ protocol ScriptSourceProviding { // refactor: ScriptSourceProvider to be passed to init methods as `some ScriptSourceProviding`, DefaultScriptSourceProvider to be killed // swiftlint:disable:next identifier_name func DefaultScriptSourceProvider() -> ScriptSourceProviding { - ScriptSourceProvider(configStorage: DefaultConfigurationStorage.shared, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, privacySettings: PrivacySecurityPreferences.shared, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, trackerDataManager: ContentBlocking.shared.trackerDataManager, tld: ContentBlocking.shared.tld) + ScriptSourceProvider(configStorage: ConfigurationStore.shared, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, privacySettings: PrivacySecurityPreferences.shared, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, trackerDataManager: ContentBlocking.shared.trackerDataManager, tld: ContentBlocking.shared.tld) } struct ScriptSourceProvider: ScriptSourceProviding { diff --git a/DuckDuckGo/Feedback and Breakage/Model/FeedbackSender.swift b/DuckDuckGo/Feedback and Breakage/Model/FeedbackSender.swift index c189c86c7c..51d3df15b4 100644 --- a/DuckDuckGo/Feedback and Breakage/Model/FeedbackSender.swift +++ b/DuckDuckGo/Feedback and Breakage/Model/FeedbackSender.swift @@ -18,6 +18,7 @@ import Foundation import os.log +import Networking final class FeedbackSender { @@ -37,7 +38,9 @@ final class FeedbackSender { "appversion": appVersion ] - APIRequest.request(url: Self.feedbackURL, method: .post, parameters: parameters) { _, error in + let configuration = APIRequest.Configuration(url: Self.feedbackURL, method: .post, queryParameters: parameters) + let request = APIRequest(configuration: configuration, urlSession: URLSession.session()) + request.fetch { _, error in if let error = error { os_log("FeedbackSender: Failed to submit feedback %s", type: .error, error.localizedDescription) Pixel.fire(.debug(event: .feedbackReportingFailed, error: error)) diff --git a/DuckDuckGo/Feedback and Breakage/View/FeedbackViewController.swift b/DuckDuckGo/Feedback and Breakage/View/FeedbackViewController.swift index c9008bd009..613b7923f5 100644 --- a/DuckDuckGo/Feedback and Breakage/View/FeedbackViewController.swift +++ b/DuckDuckGo/Feedback and Breakage/View/FeedbackViewController.swift @@ -18,6 +18,7 @@ import Cocoa import Combine +import Common final class FeedbackViewController: NSViewController { diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 6565bad7f6..974ad30e2f 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -654,8 +654,7 @@ extension MainViewController { } @IBAction func fetchConfigurationNow(_ sender: Any?) { - ConfigurationManager.shared.lastUpdateTime = .distantPast - ConfigurationManager.shared.refreshIfNeeded() + ConfigurationManager.shared.forceRefresh() } // MARK: - Developer Tools diff --git a/DuckDuckGo/Preferences/Model/AboutModel.swift b/DuckDuckGo/Preferences/Model/AboutModel.swift index 7a5a70a4e3..c9929a2a97 100644 --- a/DuckDuckGo/Preferences/Model/AboutModel.swift +++ b/DuckDuckGo/Preferences/Model/AboutModel.swift @@ -17,6 +17,7 @@ // import SwiftUI +import Common final class AboutModel: ObservableObject { let appVersion = AppVersion() diff --git a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift index 5e59df69e3..b2c2f027ca 100644 --- a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift @@ -19,6 +19,7 @@ import Foundation import SwiftUI import Combine +import Common protocol DefaultBrowserProvider { var bundleIdentifier: String { get } diff --git a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift index 4b7b6f8402..5f5a0493cb 100644 --- a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift +++ b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift @@ -19,6 +19,7 @@ import Foundation import BrowserServicesKit import os.log +import Networking final class StatisticsLoader { @@ -78,22 +79,22 @@ final class StatisticsLoader { os_log("Requesting install statistics", log: .atb, type: .debug) - APIRequest.request(url: URL.initialAtb) { response, error in - DispatchQueue.main.async { - self.isAppRetentionRequestInProgress = false - if let error = error { - os_log("Initial atb request failed with error %s", type: .error, error.localizedDescription) - completion() - return - } + let configuration = APIRequest.Configuration(url: URL.initialAtb) + let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) + request.fetch { response, error in + self.isAppRetentionRequestInProgress = false + if let error = error { + os_log("Initial atb request failed with error %s", type: .error, error.localizedDescription) + completion() + return + } - os_log("Install statistics request succeeded", log: .atb, type: .debug) + os_log("Install statistics request succeeded", log: .atb, type: .debug) - if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { - self.requestExti(atb: atb, completion: completion) - } else { - completion() - } + if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { + self.requestExti(atb: atb, completion: completion) + } else { + completion() } } } @@ -107,25 +108,25 @@ final class StatisticsLoader { os_log("Requesting exti", log: .atb, type: .debug) let installAtb = atb.version + (statisticsStore.variant ?? "") - let url = URL.exti(forAtb: installAtb) - APIRequest.request(url: url) { _, error in - DispatchQueue.main.async { - self.isAppRetentionRequestInProgress = false - if let error = error { - os_log("Exti request failed with error %s", type: .error, error.localizedDescription) - completion() - return - } + + let configuration = APIRequest.Configuration(url: URL.exti(forAtb: installAtb)) + let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) + request.fetch { _, error in + self.isAppRetentionRequestInProgress = false + if let error = error { + os_log("Exti request failed with error %s", type: .error, error.localizedDescription) + completion() + return + } - os_log("Exti request succeeded", log: .atb, type: .debug) + os_log("Exti request succeeded", log: .atb, type: .debug) - assert(self.statisticsStore.atb == nil) - assert(self.statisticsStore.installDate == nil) + assert(self.statisticsStore.atb == nil) + assert(self.statisticsStore.installDate == nil) - self.statisticsStore.installDate = Date() - self.statisticsStore.atb = atb.version - completion() - } + self.statisticsStore.installDate = Date() + self.statisticsStore.atb = atb.version + completion() } } @@ -140,25 +141,25 @@ final class StatisticsLoader { } os_log("Requesting search retention ATB", log: .atb, type: .debug) - + let url = URL.searchAtb(atbWithVariant: atbWithVariant, setAtb: searchRetentionAtb, isSignedIntoEmailProtection: emailManager.isSignedIn) - APIRequest.request(url: url) { response, error in - DispatchQueue.main.async { - if let error = error { - os_log("Search atb request failed with error %s", type: .error, error.localizedDescription) - completion() - return - } - - os_log("Search retention ATB request succeeded", log: .atb, type: .debug) - - if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { - self.statisticsStore.searchRetentionAtb = atb.version - self.storeUpdateVersionIfPresent(atb) - } - + let configuration = APIRequest.Configuration(url: url) + let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) + request.fetch { (response, error) in + if let error = error { + os_log("Search atb request failed with error %s", type: .error, error.localizedDescription) completion() + return } + + os_log("Search retention ATB request succeeded", log: .atb, type: .debug) + + if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { + self.statisticsStore.searchRetentionAtb = atb.version + self.storeUpdateVersionIfPresent(atb) + } + + completion() } } @@ -178,29 +179,29 @@ final class StatisticsLoader { isAppRetentionRequestInProgress = true let url = URL.appRetentionAtb(atbWithVariant: atbWithVariant, setAtb: appRetentionAtb) - APIRequest.request(url: url) { response, error in - DispatchQueue.main.async { - self.isAppRetentionRequestInProgress = false - - if let error = error { - os_log("App atb request failed with error %s", type: .error, error.localizedDescription) - completion() - return - } - - os_log("App retention ATB request succeeded", log: .atb, type: .debug) - - if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { - self.statisticsStore.appRetentionAtb = atb.version - self.statisticsStore.lastAppRetentionRequestDate = Date() - self.storeUpdateVersionIfPresent(atb) - } - + let configuration = APIRequest.Configuration(url: url) + let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) + request.fetch { response, error in + self.isAppRetentionRequestInProgress = false + + if let error = error { + os_log("App atb request failed with error %s", type: .error, error.localizedDescription) completion() + return } + + os_log("App retention ATB request succeeded", log: .atb, type: .debug) + + if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { + self.statisticsStore.appRetentionAtb = atb.version + self.statisticsStore.lastAppRetentionRequestDate = Date() + self.storeUpdateVersionIfPresent(atb) + } + + completion() } } - + func storeUpdateVersionIfPresent(_ atb: Atb) { dispatchPrecondition(condition: .onQueue(.main)) diff --git a/DuckDuckGo/Statistics/Pixel.swift b/DuckDuckGo/Statistics/Pixel.swift index 64846ee92a..fe0fad6dc4 100644 --- a/DuckDuckGo/Statistics/Pixel.swift +++ b/DuckDuckGo/Statistics/Pixel.swift @@ -18,6 +18,8 @@ import Foundation import os.log +import Networking +import Common final class Pixel { @@ -41,7 +43,7 @@ final class Pixel { withAdditionalParameters params: [String: String]? = nil, allowedQueryReservedCharacters: CharacterSet? = nil, includeAppVersionParameter: Bool = true, - withHeaders headers: HTTPHeaders = APIHeaders().defaultHeaders, + withHeaders headers: HTTPHeaders = APIRequest.Headers().default, onComplete: @escaping (Error?) -> Void = {_ in }) { var newParams = params ?? [:] @@ -53,7 +55,7 @@ final class Pixel { #endif var headers = headers - headers[APIHeaders.Name.moreInfo] = "See " + URL.duckDuckGoMorePrivacyInfo.absoluteString + headers[APIRequest.HTTPHeaderField.moreInfo] = "See " + URL.duckDuckGoMorePrivacyInfo.absoluteString guard !dryRun else { let params = params?.filter { key, _ in !["appVersion", "test"].contains(key) } ?? [:] @@ -66,14 +68,12 @@ final class Pixel { return } - let url = URL.pixelUrl(forPixelNamed: pixelName) - APIRequest.request( - url: url, - parameters: newParams, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - headers: headers, - callBackOnMainThread: true - ) { (_, error) in + let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: pixelName), + queryParameters: newParams, + allowedQueryReservedCharacters: allowedQueryReservedCharacters, + headers: headers) + let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) + request.fetch { (_, error) in onComplete(error) } } @@ -101,7 +101,7 @@ final class Pixel { includeAppVersionParameter: includeAppVersionParameter, onComplete: onComplete) } - + } public func pixelAssertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #fileID, line: UInt = #line) { diff --git a/Integration Tests/Autoconsent/AutoconsentBackgroundTests.swift b/Integration Tests/Autoconsent/AutoconsentBackgroundTests.swift index b11c4d2085..9271afa981 100644 --- a/Integration Tests/Autoconsent/AutoconsentBackgroundTests.swift +++ b/Integration Tests/Autoconsent/AutoconsentBackgroundTests.swift @@ -20,6 +20,7 @@ import XCTest import Common import BrowserServicesKit import TrackerRadarKit +import Configuration @testable import DuckDuckGo_Privacy_Browser @available(macOS 11, *) @@ -36,8 +37,8 @@ class AutoconsentBackgroundTests: XCTestCase { privacyConfigurationManager: MockPrivacyConfigurationManager(), privacySettings: preferences, contentBlockingManager: ContentBlockerRulesManagerMock(), - trackerDataManager: TrackerDataManager(etag: DefaultConfigurationStorage.shared.loadEtag(for: .trackerRadar), - data: DefaultConfigurationStorage.shared.loadData(for: .trackerRadar), + trackerDataManager: TrackerDataManager(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), + data: ConfigurationStore.shared.loadData(for: .trackerDataSet), embeddedDataProvider: AppTrackerDataSetProvider(), errorReporting: nil), @@ -83,8 +84,8 @@ class AutoconsentBackgroundTests: XCTestCase { privacyConfigurationManager: MockPrivacyConfigurationManager(), privacySettings: preferences, contentBlockingManager: ContentBlockerRulesManagerMock(), - trackerDataManager: TrackerDataManager(etag: DefaultConfigurationStorage.shared.loadEtag(for: .trackerRadar), - data: DefaultConfigurationStorage.shared.loadData(for: .trackerRadar), + trackerDataManager: TrackerDataManager(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), + data: ConfigurationStore.shared.loadData(for: .trackerDataSet), embeddedDataProvider: AppTrackerDataSetProvider(), errorReporting: nil), @@ -132,20 +133,20 @@ class MockStorage: ConfigurationStoring { var errorOnStoreEtag = false var data: Data? - var dataConfig: ConfigurationLocation? + var dataConfig: Configuration? var etag: String? - var etagConfig: ConfigurationLocation? + var etagConfig: Configuration? - func loadData(for: ConfigurationLocation) -> Data? { + func loadData(for: Configuration) -> Data? { return data } - func loadEtag(for: ConfigurationLocation) -> String? { + func loadEtag(for: Configuration) -> String? { return etag } - func saveData(_ data: Data, for config: ConfigurationLocation) throws { + func saveData(_ data: Data, for config: Configuration) throws { if errorOnStoreData { throw Error.mockError } @@ -154,7 +155,7 @@ class MockStorage: ConfigurationStoring { self.dataConfig = config } - func saveEtag(_ etag: String, for config: ConfigurationLocation) throws { + func saveEtag(_ etag: String, for config: Configuration) throws { if errorOnStoreEtag { throw Error.mockError } @@ -165,4 +166,6 @@ class MockStorage: ConfigurationStoring { func log() { } + func loadEmbeddedEtag(for configuration: Configuration) -> String? { nil } + } diff --git a/Unit Tests/Autoconsent/AutoconsentMessageProtocolTests.swift b/Unit Tests/Autoconsent/AutoconsentMessageProtocolTests.swift index a6f4037cb2..b19eef720b 100644 --- a/Unit Tests/Autoconsent/AutoconsentMessageProtocolTests.swift +++ b/Unit Tests/Autoconsent/AutoconsentMessageProtocolTests.swift @@ -25,15 +25,14 @@ import XCTest class AutoconsentMessageProtocolTests: XCTestCase { let userScript = AutoconsentUserScript( - scriptSource: ScriptSourceProvider(configStorage: ConfigurationDownloaderTests.MockStorage(), + scriptSource: ScriptSourceProvider(configStorage: MockConfigurationStore(), privacyConfigurationManager: MockPrivacyConfigurationManager(), privacySettings: PrivacySecurityPreferences.shared, // todo: mock contentBlockingManager: ContentBlockerRulesManagerMock(), - trackerDataManager: TrackerDataManager(etag: DefaultConfigurationStorage.shared.loadEtag(for: .trackerRadar), - data: DefaultConfigurationStorage.shared.loadData(for: .trackerRadar), + trackerDataManager: TrackerDataManager(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), + data: ConfigurationStore.shared.loadData(for: .trackerDataSet), embeddedDataProvider: AppTrackerDataSetProvider(), errorReporting: nil), - tld: TLD()), config: MockPrivacyConfiguration() ) diff --git a/Unit Tests/Configuration/ConfigurationDownloaderTests.swift b/Unit Tests/Configuration/ConfigurationDownloaderTests.swift deleted file mode 100644 index b42b2c13e5..0000000000 --- a/Unit Tests/Configuration/ConfigurationDownloaderTests.swift +++ /dev/null @@ -1,358 +0,0 @@ -// -// ConfigurationDownloaderTests.swift -// -// Copyright © 2021 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 XCTest -import Combine -@testable import DuckDuckGo_Privacy_Browser - -final class ConfigurationDownloaderTests: XCTestCase { - - static let resultData = "test".data(using: .utf8)! - - var cancellables = Set() - - func test_urls_do_not_contain_localhost() { - for url in ConfigurationLocation.allCases { - XCTAssertFalse(url.rawValue.contains("localhost")) - XCTAssertFalse(url.rawValue.contains("127.0.0.1")) - } - } - - func test_when_store_etag_fails_then_failure_returned_and_no_etag_stored() { - let response = HTTPURLResponse.success - let storageMock = MockStorage() - storageMock.errorOnStoreEtag = true - - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - - var completionResult: Subscribers.Completion? - downloader.download(.bloomFilterBinary, embeddedEtag: nil).sink { completion in - completionResult = completion - } receiveValue: { _ in - XCTFail("expected value") - }.store(in: &cancellables) - - XCTAssertNotNil(completionResult) - if case .failure = completionResult! { - // we good - } else { - XCTFail("completion was not expected failure") - } - - // Data may have been stored by this point, nothing we can do about that - XCTAssertNil(storageMock.etag) - XCTAssertNil(storageMock.etagConfig) - } - - func test_when_store_data_fails_then_failure_returned_and_no_data_or_etag_stored() { - let response = HTTPURLResponse.success - let storageMock = MockStorage() - storageMock.errorOnStoreData = true - - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - - var completionResult: Subscribers.Completion? - downloader.download(.bloomFilterBinary, embeddedEtag: nil).sink { completion in - completionResult = completion - } receiveValue: { _ in - XCTFail("expected value") - }.store(in: &cancellables) - - XCTAssertNotNil(completionResult) - if case .failure = completionResult! { - // we good - } else { - XCTFail("completion was not expected failure") - } - - XCTAssertNil(storageMock.data) - XCTAssertNil(storageMock.etag) - XCTAssertNil(storageMock.dataConfig) - XCTAssertNil(storageMock.etagConfig) - } - - func test_when_response_is_success_and_no_etag_then_error_returned() { - let response = HTTPURLResponse.successNoEtag - let storageMock = MockStorage() - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - - var completionResult: Subscribers.Completion? - downloader.download(.bloomFilterBinary, embeddedEtag: nil).sink { completion in - completionResult = completion - } receiveValue: { _ in - XCTFail("expected value") - }.store(in: &cancellables) - - XCTAssertNotNil(completionResult) - if case .failure = completionResult! { - // we good - } else { - XCTFail("completion was not expected failure") - } - - XCTAssertNil(storageMock.data) - XCTAssertNil(storageMock.etag) - XCTAssertNil(storageMock.dataConfig) - XCTAssertNil(storageMock.etagConfig) - } - - func test_when_response_is_error_then_error_returned() { - let response = HTTPURLResponse.internalServerError - let storageMock = MockStorage() - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - - var completionResult: Subscribers.Completion? - downloader.download(.bloomFilterBinary, embeddedEtag: nil).sink { completion in - completionResult = completion - } receiveValue: { _ in - XCTFail("expected value") - }.store(in: &cancellables) - - XCTAssertNotNil(completionResult) - if case .failure = completionResult! { - // we good - } else { - XCTFail("completion was not expected failure") - } - - XCTAssertNil(storageMock.data) - XCTAssertNil(storageMock.etag) - XCTAssertNil(storageMock.dataConfig) - XCTAssertNil(storageMock.etagConfig) - } - - func test_when_response_is_not_modified_and_valid_etag_then_nil_meta_returned_and_no_data_stored() { - let response = HTTPURLResponse.notModified - let storageMock = MockStorage() - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - var meta: ConfigurationDownloadMeta? - downloader.download(.bloomFilterBinary, embeddedEtag: nil).sink { completion in - if case .failure = completion { - XCTFail("unexpected failure") - } - } receiveValue: { value in - meta = value - }.store(in: &cancellables) - - XCTAssertNil(meta) - XCTAssertNil(storageMock.data) - XCTAssertNil(storageMock.etag) - XCTAssertNil(storageMock.dataConfig) - XCTAssertNil(storageMock.etagConfig) - } - - func test_when_etag_and_data_stored_then_etag_added_to_request() { - - let requestedEtag = UUID().uuidString - - let response = HTTPURLResponse.success - let storageMock = MockStorage() - storageMock.data = Data() - storageMock.etag = requestedEtag - - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - _ = downloader.download(.bloomFilterSpec, embeddedEtag: nil) - - XCTAssertEqual(requestedEtag, networkingMock.request?.value(forHTTPHeaderField: HTTPURLResponse.ifNoneMatchHeader)) - } - - func test_when_no_etag_stored_then_no_etag_added_to_request() { - - let response = HTTPURLResponse.success - let storageMock = MockStorage() - storageMock.data = Data() - - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - _ = downloader.download(.bloomFilterSpec, embeddedEtag: nil) - - XCTAssertNil(networkingMock.request?.value(forHTTPHeaderField: HTTPURLResponse.ifNoneMatchHeader)) - } - - func test_when_no_data_stored_then_no_etag_added_to_request() { - - let response = HTTPURLResponse.success - let storageMock = MockStorage() - storageMock.etag = "" - - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - _ = downloader.download(.bloomFilterSpec, embeddedEtag: nil) - - XCTAssertNil(networkingMock.request?.value(forHTTPHeaderField: HTTPURLResponse.ifNoneMatchHeader)) - } - - func test_when_embedded_etag_and_external_etag_provided_then_external_included_in_request() { - let embeddedEtag = UUID().uuidString - let externalEtag = UUID().uuidString - - let response = HTTPURLResponse.success - let storageMock = MockStorage() - storageMock.data = Data() - storageMock.etag = externalEtag - - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - _ = downloader.download(.bloomFilterSpec, embeddedEtag: embeddedEtag) - - XCTAssertEqual(externalEtag, networkingMock.request?.value(forHTTPHeaderField: HTTPURLResponse.ifNoneMatchHeader)) - } - - func test_when_embedded_etag_provided_then_is_included_in_request() { - let embeddedEtag = UUID().uuidString - - let response = HTTPURLResponse.success - let storageMock = MockStorage() - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - _ = downloader.download(.bloomFilterSpec, embeddedEtag: embeddedEtag) - - XCTAssertEqual(embeddedEtag, networkingMock.request?.value(forHTTPHeaderField: HTTPURLResponse.ifNoneMatchHeader)) - } - - func test_when_response_is_success_and_valid_etag_then_meta_returned_and_data_stored() { - - for config in ConfigurationLocation.allCases { - - let response = HTTPURLResponse.success - let storageMock = MockStorage() - let networkingMock = MockNetworking(result: (Self.resultData, response)) - let downloader = DefaultConfigurationDownloader(storage: storageMock, dataTaskProvider: networkingMock, deliveryQueue: DispatchQueue.main) - var meta: ConfigurationDownloadMeta? - downloader.download(config, embeddedEtag: nil).sink { completion in - if case .failure = completion { - XCTFail("unexpected failure for \(config.rawValue)") - } - } receiveValue: { value in - meta = value - }.store(in: &cancellables) - - XCTAssertEqual(meta?.etag, HTTPURLResponse.etagValue) - XCTAssertEqual(meta?.data, Self.resultData) - XCTAssertNotNil(storageMock.data) - XCTAssertNotNil(storageMock.etag) - XCTAssertEqual(storageMock.dataConfig, config) - XCTAssertNotNil(storageMock.etagConfig, HTTPURLResponse.etagValue) - - } - - } - - class MockNetworking: DataTaskProviding { - - let result: (Data, URLResponse) - var publisher: CurrentValueSubject<(data: Data, response: URLResponse), URLError>? - var request: URLRequest? - - init(result: (Data, URLResponse)) { - self.result = result - } - - func send(_ data: Data, _ response: URLResponse) { - publisher?.send((data: data, response: response)) - } - - func dataTaskPublisher(for request: URLRequest) -> AnyPublisher<(data: Data, response: URLResponse), URLError> { - self.request = request - let publisher = CurrentValueSubject<(data: Data, response: URLResponse), URLError>(result) - self.publisher = publisher - return publisher.eraseToAnyPublisher() - } - - } - - class MockStorage: ConfigurationStoring { - - enum Error: Swift.Error { - case mockError - } - - var errorOnStoreData = false - var errorOnStoreEtag = false - - var data: Data? - var dataConfig: ConfigurationLocation? - - var etag: String? - var etagConfig: ConfigurationLocation? - - func loadData(for: ConfigurationLocation) -> Data? { - return data - } - - func loadEtag(for: ConfigurationLocation) -> String? { - return etag - } - - func saveData(_ data: Data, for config: ConfigurationLocation) throws { - if errorOnStoreData { - throw Error.mockError - } - - self.data = data - self.dataConfig = config - } - - func saveEtag(_ etag: String, for config: ConfigurationLocation) throws { - if errorOnStoreEtag { - throw Error.mockError - } - - self.etag = etag - self.etagConfig = config - } - - func log() { } - - } - -} - -fileprivate extension HTTPURLResponse { - - static let etagHeader = "Etag" - static let ifNoneMatchHeader = "If-None-Match" - static let etagValue = "test-etag" - - static let success = HTTPURLResponse(url: URL.blankPage, - statusCode: 200, - httpVersion: nil, - headerFields: [HTTPURLResponse.etagHeader: HTTPURLResponse.etagValue])! - - static let notModified = HTTPURLResponse(url: URL.blankPage, - statusCode: 304, - httpVersion: nil, - headerFields: [HTTPURLResponse.etagHeader: HTTPURLResponse.etagValue])! - - static let internalServerError = HTTPURLResponse(url: URL.blankPage, - statusCode: 500, - httpVersion: nil, - headerFields: [:])! - - static let successNoEtag = HTTPURLResponse(url: URL.blankPage, - statusCode: 200, - httpVersion: nil, - headerFields: [:])! - -} diff --git a/Unit Tests/Configuration/ConfigurationStorageTests.swift b/Unit Tests/Configuration/ConfigurationStorageTests.swift index 9f3dfe4ff6..81d4b7a890 100644 --- a/Unit Tests/Configuration/ConfigurationStorageTests.swift +++ b/Unit Tests/Configuration/ConfigurationStorageTests.swift @@ -18,33 +18,32 @@ import XCTest import Combine +import Configuration @testable import DuckDuckGo_Privacy_Browser final class ConfigurationStorageTests: XCTestCase { override func tearDown() { super.tearDown() - - for config in ConfigurationLocation.allCases { - let url = DefaultConfigurationStorage.shared.fileUrl(for: config) + for config in Configuration.allCases { + let url = ConfigurationStore.shared.fileUrl(for: config) try? FileManager.default.removeItem(at: url) } - } func test_when_data_is_saved_for_config_then_it_can_be_loaded_correctly() { - for config in ConfigurationLocation.allCases { + for config in Configuration.allCases { let uuid = UUID().uuidString - try? DefaultConfigurationStorage.shared.saveData(uuid.data(using: .utf8)!, for: config) - XCTAssertEqual(uuid, DefaultConfigurationStorage.shared.loadData(for: config)?.utf8String()) + try? ConfigurationStore.shared.saveData(uuid.data(using: .utf8)!, for: config) + XCTAssertEqual(uuid, ConfigurationStore.shared.loadData(for: config)?.utf8String()) } } func test_when_etag_is_saved_for_config_then_it_can_be_loaded_correctly() { - for config in ConfigurationLocation.allCases { + for config in Configuration.allCases { let etag = UUID().uuidString - try? DefaultConfigurationStorage.shared.saveEtag(etag, for: config) - XCTAssertEqual(etag, DefaultConfigurationStorage.shared.loadEtag(for: config)) + try? ConfigurationStore.shared.saveEtag(etag, for: config) + XCTAssertEqual(etag, ConfigurationStore.shared.loadEtag(for: config)) } } diff --git a/Unit Tests/Content Blocker/AppPrivacyConfigurationTests.swift b/Unit Tests/Content Blocker/AppPrivacyConfigurationTests.swift index 673fcc19c1..e5ae91381e 100644 --- a/Unit Tests/Content Blocker/AppPrivacyConfigurationTests.swift +++ b/Unit Tests/Content Blocker/AppPrivacyConfigurationTests.swift @@ -44,13 +44,12 @@ class AppPrivacyConfigurationTests: XCTestCase { "Error: please update SHA and ETag when changing embedded config") } - func testWhenEmbeddedDataIsUsedThenItCanBeParsed() { + func testWhenEmbeddedDataIsUsedThenItCanBeParsed() throws { let provider = AppPrivacyConfigurationDataProvider() let jsonData = provider.embeddedData - let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] - let configData = PrivacyConfigurationData(json: json!) + let configData = try PrivacyConfigurationData(data: jsonData) let config = AppPrivacyConfiguration(data: configData, identifier: "", diff --git a/Unit Tests/Content Blocker/ContentBlockingMock.swift b/Unit Tests/Content Blocker/ContentBlockingMock.swift index bea30d639d..a140fab228 100644 --- a/Unit Tests/Content Blocker/ContentBlockingMock.swift +++ b/Unit Tests/Content Blocker/ContentBlockingMock.swift @@ -29,8 +29,8 @@ final class ContentBlockingMock: NSObject, ContentBlockingProtocol, AdClickAttri var embeddedDataEtag: String = "" var embeddedData: Data = .init() } - var trackerDataManager = TrackerDataManager(etag: DefaultConfigurationStorage.shared.loadEtag(for: .trackerRadar), - data: DefaultConfigurationStorage.shared.loadData(for: .trackerRadar), + var trackerDataManager = TrackerDataManager(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), + data: ConfigurationStore.shared.loadData(for: .trackerDataSet), embeddedDataProvider: AppTrackerDataSetProvider(), errorReporting: nil) diff --git a/Unit Tests/Content Blocker/ContentBlockingUpdatingTests.swift b/Unit Tests/Content Blocker/ContentBlockingUpdatingTests.swift index 4b9446a30b..caaed6b2b5 100644 --- a/Unit Tests/Content Blocker/ContentBlockingUpdatingTests.swift +++ b/Unit Tests/Content Blocker/ContentBlockingUpdatingTests.swift @@ -23,8 +23,8 @@ import TrackerRadarKit import BrowserServicesKit @testable import DuckDuckGo_Privacy_Browser -class ContentBlockingUpdatingTests: XCTestCase { - // todo: mock +final class ContentBlockingUpdatingTests: XCTestCase { + let preferences = PrivacySecurityPreferences.shared let rulesManager = ContentBlockerRulesManagerMock() var updating: UserContentUpdating! @@ -32,11 +32,11 @@ class ContentBlockingUpdatingTests: XCTestCase { override func setUp() { updating = UserContentUpdating(contentBlockerRulesManager: rulesManager, privacyConfigurationManager: MockPrivacyConfigurationManager(), - trackerDataManager: TrackerDataManager(etag: DefaultConfigurationStorage.shared.loadEtag(for: .trackerRadar), - data: DefaultConfigurationStorage.shared.loadData(for: .trackerRadar), + trackerDataManager: TrackerDataManager(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), + data: ConfigurationStore.shared.loadData(for: .trackerDataSet), embeddedDataProvider: AppTrackerDataSetProvider(), errorReporting: nil), - configStorage: ConfigurationDownloaderTests.MockStorage(), + configStorage: MockConfigurationStore(), privacySecurityPreferences: preferences, tld: TLD()) } diff --git a/Unit Tests/Content Blocker/MockConfigurationStore.swift b/Unit Tests/Content Blocker/MockConfigurationStore.swift new file mode 100644 index 0000000000..f7d19af95d --- /dev/null +++ b/Unit Tests/Content Blocker/MockConfigurationStore.swift @@ -0,0 +1,63 @@ +// +// MockConfigurationStore.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 Foundation +import Configuration + +final class MockConfigurationStore: ConfigurationStoring { + + enum Error: Swift.Error { + case mockError + } + + var errorOnStoreData = false + var errorOnStoreEtag = false + + var data: Data? + var dataConfig: Configuration? + + var etag: String? + var etagConfig: Configuration? + + func loadData(for: Configuration) -> Data? { data } + + func loadEtag(for: Configuration) -> String? { etag } + + func loadEmbeddedEtag(for configuration: Configuration) -> String? { nil } + + func saveData(_ data: Data, for config: Configuration) throws { + if errorOnStoreData { + throw Error.mockError + } + + self.data = data + self.dataConfig = config + } + + func saveEtag(_ etag: String, for config: Configuration) throws { + if errorOnStoreEtag { + throw Error.mockError + } + + self.etag = etag + self.etagConfig = config + } + + func log() { } + +} diff --git a/Unit Tests/Privacy Reference Tests/BrokenSiteReportingReferenceTests.swift b/Unit Tests/Privacy Reference Tests/BrokenSiteReportingReferenceTests.swift index 7d0b08b92c..3691923f4a 100644 --- a/Unit Tests/Privacy Reference Tests/BrokenSiteReportingReferenceTests.swift +++ b/Unit Tests/Privacy Reference Tests/BrokenSiteReportingReferenceTests.swift @@ -20,6 +20,7 @@ import XCTest import os.log import BrowserServicesKit +@testable import Networking @testable import DuckDuckGo_Privacy_Browser final class BrokenSiteReportingReferenceTests: XCTestCase { @@ -30,14 +31,11 @@ final class BrokenSiteReportingReferenceTests: XCTestCase { } private func makeURLRequest(with parameters: [String: String]) -> URLRequest { - APIRequest.urlRequestFor( - url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), - method: .get, - parameters: parameters, - allowedQueryReservedCharacters: WebsiteBreakageSender.allowedQueryReservedCharacters, - headers: [:], - timeoutInterval: 60 - ) + APIRequest.Headers.setUserAgent("") + let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), + queryParameters: parameters, + allowedQueryReservedCharacters: WebsiteBreakageSender.allowedQueryReservedCharacters) + return configuration.request } func testBrokenSiteReporting() { diff --git a/Unit Tests/Privacy Reference Tests/PrivacyReferenceTestHelper.swift b/Unit Tests/Privacy Reference Tests/PrivacyReferenceTestHelper.swift index 4d0dec5e1d..e818932475 100644 --- a/Unit Tests/Privacy Reference Tests/PrivacyReferenceTestHelper.swift +++ b/Unit Tests/Privacy Reference Tests/PrivacyReferenceTestHelper.swift @@ -44,16 +44,11 @@ struct PrivacyReferenceTestHelper { } } - func privacyConfigurationData(withConfigPath path: String, bundle: Bundle) -> PrivacyConfigurationData { + func privacyConfigurationData(withConfigPath path: String, bundle: Bundle) throws -> PrivacyConfigurationData { guard let configData = try? data(for: path, in: bundle) else { fatalError("Can't decode \(path)") } - - guard let json = try? JSONSerialization.jsonObject(with: configData, options: []) as? [String: Any] else { - fatalError("Can't decode \(path)") - } - - return PrivacyConfigurationData(json: json) + return try PrivacyConfigurationData(data: configData) } func privacyConfiguration(withData data: PrivacyConfigurationData) -> PrivacyConfiguration { diff --git a/Unit Tests/Statistics/PixelTests.swift b/Unit Tests/Statistics/PixelTests.swift index 45c9a48b6f..b5768d360a 100644 --- a/Unit Tests/Statistics/PixelTests.swift +++ b/Unit Tests/Statistics/PixelTests.swift @@ -19,6 +19,7 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift +import Networking @testable import DuckDuckGo_Privacy_Browser class PixelTests: XCTestCase { @@ -73,7 +74,7 @@ class PixelTests: XCTestCase { return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) } - var headers = APIHeaders().defaultHeaders + var headers = APIRequest.Headers().default headers[userAgentName] = testAgent Pixel.shared!.fire(pixelNamed: "test", withHeaders: headers) diff --git a/Unit Tests/Website Breakage Report/WebsiteBreakageReportTests.swift b/Unit Tests/Website Breakage Report/WebsiteBreakageReportTests.swift index 545c609aa9..9069b93806 100644 --- a/Unit Tests/Website Breakage Report/WebsiteBreakageReportTests.swift +++ b/Unit Tests/Website Breakage Report/WebsiteBreakageReportTests.swift @@ -17,6 +17,7 @@ // import XCTest +@testable import Networking @testable import DuckDuckGo_Privacy_Browser class WebsiteBreakageReportTests: XCTestCase { @@ -100,14 +101,11 @@ class WebsiteBreakageReportTests: XCTestCase { } func makeURLRequest(with parameters: [String: String]) -> URLRequest { - APIRequest.urlRequestFor( - url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), - method: .get, - parameters: parameters, - allowedQueryReservedCharacters: WebsiteBreakageSender.allowedQueryReservedCharacters, - headers: [:], - timeoutInterval: 60 - ) + APIRequest.Headers.setUserAgent("") + let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), + queryParameters: parameters, + allowedQueryReservedCharacters: WebsiteBreakageSender.allowedQueryReservedCharacters) + return configuration.request } }