diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c3b9abef26..6e91b8d6da 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -288,7 +288,6 @@ 3706FAB2293F65D500E42796 /* TabInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4F25B7BA2B006F6B06 /* TabInstrumentation.swift */; }; 3706FAB5293F65D500E42796 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 3706FAB6293F65D500E42796 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; - 3706FAB7293F65D500E42796 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; 3706FAB8293F65D500E42796 /* FaviconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA696275F90C400DCE9C9 /* FaviconImageCache.swift */; }; 3706FAB9293F65D500E42796 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1430DFF424D0580F00B8978C /* TabBarViewController.swift */; }; 3706FABA293F65D500E42796 /* BookmarkOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */; }; @@ -426,7 +425,6 @@ 3706FB60293F65D500E42796 /* PasswordManagementIdentityItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6546E271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift */; }; 3706FB61293F65D500E42796 /* ProgressExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F41030264D2B23003DA42C /* ProgressExtension.swift */; }; 3706FB62293F65D500E42796 /* CSVParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DF626B0002B00E14D75 /* CSVParser.swift */; }; - 3706FB64293F65D500E42796 /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; 3706FB65293F65D500E42796 /* PrivacyDashboardWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63BDF7D27FDAA640072D75B /* PrivacyDashboardWebView.swift */; }; 3706FB66293F65D500E42796 /* AppearancePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C727F2FDD100F1F7B9 /* AppearancePreferences.swift */; }; 3706FB67293F65D500E42796 /* DownloadListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */; }; @@ -545,9 +543,6 @@ 3706FBF0293F65D500E42796 /* PasswordManagementItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CC1D7C26A05F250062F04E /* PasswordManagementItemModel.swift */; }; 3706FBF2293F65D500E42796 /* FindInPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A0118125AF60E700FA6A0C /* FindInPageModel.swift */; }; 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929826670D2A00AD2C21 /* PseudoFolder.swift */; }; - 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; - 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; - 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; 3706FBF8293F65D500E42796 /* TabBarFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2CB1342587C29500AA6FBE /* TabBarFooter.swift */; }; 3706FBF9293F65D500E42796 /* BookmarksBarCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */; }; 3706FBFA293F65D500E42796 /* FileDownloadError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23826E742610031CB7F /* FileDownloadError.swift */; }; @@ -613,7 +608,6 @@ 3706FC3F293F65D500E42796 /* ApplicationDockMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA97BF4525135DD30014931A /* ApplicationDockMenu.swift */; }; 3706FC40293F65D500E42796 /* SaveIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */; }; 3706FC41293F65D500E42796 /* FileStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1A69A258B076900F6F690 /* FileStore.swift */; }; - 3706FC42293F65D500E42796 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; 3706FC43293F65D500E42796 /* PinnedTabsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF3F1E286F0A7A00BD9014 /* PinnedTabsViewModel.swift */; }; 3706FC44293F65D500E42796 /* BookmarkList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CF25D6A709007F5990 /* BookmarkList.swift */; }; 3706FC45293F65D500E42796 /* BookmarkTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C92667123700AD2C21 /* BookmarkTableRowView.swift */; }; @@ -655,7 +649,6 @@ 3706FC6F293F65D500E42796 /* FirePopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA13DCB3271480B0006D48D3 /* FirePopoverViewModel.swift */; }; 3706FC71293F65D500E42796 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F525D6BF2C007F5990 /* AddressBarButtonsViewController.swift */; }; - 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; 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 */; }; @@ -758,7 +751,6 @@ 3706FDDF293F661700E42796 /* BookmarkSidebarTreeControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B22667103000AD2C21 /* BookmarkSidebarTreeControllerTests.swift */; }; 3706FDE0293F661700E42796 /* TabIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D2377B287EBDA300BCE03B /* TabIndexTests.swift */; }; 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534CA42811987D002621E7 /* AdjacentItemEnumeratorTests.swift */; }; - 3706FDE2293F661700E42796 /* PixelArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */; }; 3706FDE4293F661700E42796 /* TabLazyLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534C9D28104D9B002621E7 /* TabLazyLoaderTests.swift */; }; 3706FDE5293F661700E42796 /* URLEventHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1B0C825EF9759004792B6 /* URLEventHandlerTests.swift */; }; 3706FDE6293F661700E42796 /* BookmarkOutlineViewDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B32667103000AD2C21 /* BookmarkOutlineViewDataSourceTests.swift */; }; @@ -810,7 +802,6 @@ 3706FE1A293F661700E42796 /* BrowserProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F641D27A8D3BD00E0C118 /* BrowserProfileTests.swift */; }; 3706FE1B293F661700E42796 /* PermissionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9F26A7BE0B0013B453 /* PermissionManagerTests.swift */; }; 3706FE1C293F661700E42796 /* ConnectBitwardenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59CC8B290083240058F2F6 /* ConnectBitwardenViewModelTests.swift */; }; - 3706FE1D293F661700E42796 /* PixelStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */; }; 3706FE1E293F661700E42796 /* GeolocationProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BB426A809E60013B453 /* GeolocationProviderTests.swift */; }; 3706FE1F293F661700E42796 /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; 3706FE20293F661700E42796 /* CLLocationManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63ED0E226B3E7FA00A9DAD1 /* CLLocationManagerMock.swift */; }; @@ -852,7 +843,6 @@ 3706FE45293F661700E42796 /* ProgressEstimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE74332609AFCE005B9B1A /* ProgressEstimationTests.swift */; }; 3706FE46293F661700E42796 /* EncryptedValueTransformerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1A6FD258C5C1300F6F690 /* EncryptedValueTransformerTests.swift */; }; 3706FE47293F661700E42796 /* URLExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F69B3B25EDE81F00978E59 /* URLExtensionTests.swift */; }; - 3706FE48293F661700E42796 /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */; }; 3706FE49293F661700E42796 /* BookmarkNodePathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B02667103000AD2C21 /* BookmarkNodePathTests.swift */; }; 3706FE4A293F661700E42796 /* BookmarkManagedObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B62667103000AD2C21 /* BookmarkManagedObjectTests.swift */; }; 3706FE4B293F661700E42796 /* BookmarksHTMLImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A1AB128451ED400586521 /* BookmarksHTMLImporterTests.swift */; }; @@ -1022,7 +1012,6 @@ 376E2D2629428353001CD31B /* PrivacyReferenceTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E163BC293A579E00963C10 /* PrivacyReferenceTestHelper.swift */; }; 376E2D2729428353001CD31B /* BrokenSiteReportingReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E163B9293A56F400963C10 /* BrokenSiteReportingReferenceTests.swift */; }; 376E2D282942843D001CD31B /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = 31E163BF293A581900963C10 /* privacy-reference-tests */; }; - 376E2D29294286B8001CD31B /* PixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC2621C293996410087A482 /* PixelEventTests.swift */; }; 37716D8029707E5D00A9FC6D /* FireproofingReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310E79BE294A19A8007C49E8 /* FireproofingReferenceTests.swift */; }; 3775912D29AAC72700E26367 /* SyncPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3775912C29AAC72700E26367 /* SyncPreferences.swift */; }; 3775912E29AAC72700E26367 /* SyncPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3775912C29AAC72700E26367 /* SyncPreferences.swift */; }; @@ -1259,8 +1248,6 @@ 4B677433255DBEB800025BD8 /* httpsMobileV2Bloom.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B677428255DBEB800025BD8 /* httpsMobileV2Bloom.bin */; }; 4B677435255DBEB800025BD8 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; 4B677442255DBEEA00025BD8 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B677440255DBEEA00025BD8 /* Database.swift */; }; - 4B67853F2AA7C726008A5004 /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67853E2AA7C726008A5004 /* DailyPixel.swift */; }; - 4B6785402AA7C726008A5004 /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67853E2AA7C726008A5004 /* DailyPixel.swift */; }; 4B6785472AA8DE68008A5004 /* NetworkProtectionFeatureDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */; }; 4B6785482AA8DE69008A5004 /* NetworkProtectionFeatureDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */; }; 4B67854A2AA8DE75008A5004 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */; }; @@ -1396,7 +1383,6 @@ 4B9579842AC7AE700062CA31 /* TabInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4F25B7BA2B006F6B06 /* TabInstrumentation.swift */; }; 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; - 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; 4B95798B2AC7AE700062CA31 /* FaviconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA696275F90C400DCE9C9 /* FaviconImageCache.swift */; }; 4B95798C2AC7AE700062CA31 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1430DFF424D0580F00B8978C /* TabBarViewController.swift */; }; 4B95798D2AC7AE700062CA31 /* BookmarkOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */; }; @@ -1589,7 +1575,6 @@ 4B957A582AC7AE700062CA31 /* PasswordManagementIdentityItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6546E271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift */; }; 4B957A592AC7AE700062CA31 /* ProgressExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F41030264D2B23003DA42C /* ProgressExtension.swift */; }; 4B957A5A2AC7AE700062CA31 /* CSVParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DF626B0002B00E14D75 /* CSVParser.swift */; }; - 4B957A5B2AC7AE700062CA31 /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; 4B957A5C2AC7AE700062CA31 /* PrivacyDashboardWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63BDF7D27FDAA640072D75B /* PrivacyDashboardWebView.swift */; }; 4B957A5D2AC7AE700062CA31 /* AppearancePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C727F2FDD100F1F7B9 /* AppearancePreferences.swift */; }; 4B957A5E2AC7AE700062CA31 /* DownloadListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */; }; @@ -1731,7 +1716,6 @@ 4B957AEF2AC7AE700062CA31 /* String+Punycode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65783E625F8AAFB00D8DB33 /* String+Punycode.swift */; }; 4B957AF02AC7AE700062CA31 /* NSException+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = B657841925FA484B00D8DB33 /* NSException+Catch.m */; }; 4B957AF12AC7AE700062CA31 /* AppStateRestorationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684592E25C93FBF00DC17B6 /* AppStateRestorationManager.swift */; }; - 4B957AF22AC7AE700062CA31 /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67853E2AA7C726008A5004 /* DailyPixel.swift */; }; 4B957AF32AC7AE700062CA31 /* NavigationHotkeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */; }; 4B957AF42AC7AE700062CA31 /* ClickToLoadUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0BA3A8272217E6002A0B6C /* ClickToLoadUserScript.swift */; }; 4B957AF52AC7AE700062CA31 /* WindowControllersManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA892E9250A4CEF005B37B2 /* WindowControllersManager.swift */; }; @@ -1751,10 +1735,7 @@ 4B957B042AC7AE700062CA31 /* UpdateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD86E51267A0DFF005C11BE /* UpdateController.swift */; }; 4B957B052AC7AE700062CA31 /* FindInPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A0118125AF60E700FA6A0C /* FindInPageModel.swift */; }; 4B957B062AC7AE700062CA31 /* PseudoFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929826670D2A00AD2C21 /* PseudoFolder.swift */; }; - 4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; 4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00E2A983B24000927DB /* WaitlistStorage.swift */; }; - 4B957B0A2AC7AE700062CA31 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; - 4B957B0B2AC7AE700062CA31 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; 4B957B0C2AC7AE700062CA31 /* TabBarFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2CB1342587C29500AA6FBE /* TabBarFooter.swift */; }; 4B957B0D2AC7AE700062CA31 /* JSAlertViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC111E5294D06290086524F /* JSAlertViewModel.swift */; }; 4B957B0E2AC7AE700062CA31 /* BookmarksBarCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */; }; @@ -1832,7 +1813,6 @@ 4B957B582AC7AE700062CA31 /* SaveIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */; }; 4B957B592AC7AE700062CA31 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; 4B957B5A2AC7AE700062CA31 /* FileStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1A69A258B076900F6F690 /* FileStore.swift */; }; - 4B957B5B2AC7AE700062CA31 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; 4B957B5C2AC7AE700062CA31 /* PinnedTabsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF3F1E286F0A7A00BD9014 /* PinnedTabsViewModel.swift */; }; 4B957B5D2AC7AE700062CA31 /* BookmarkList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CF25D6A709007F5990 /* BookmarkList.swift */; }; 4B957B5E2AC7AE700062CA31 /* NEOnDemandRuleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E81C2A1E25B0006D261F /* NEOnDemandRuleExtension.swift */; }; @@ -1889,7 +1869,6 @@ 4B957B952AC7AE700062CA31 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; 4B957B972AC7AE700062CA31 /* AddressBarButtonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F525D6BF2C007F5990 /* AddressBarButtonsViewController.swift */; }; 4B957B982AC7AE700062CA31 /* BWError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF076028F815AD00EDFBE3 /* BWError.swift */; }; - 4B957B9A2AC7AE700062CA31 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; 4B957B9B2AC7AE700062CA31 /* PageObserverUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853014D525E671A000FB8205 /* PageObserverUserScript.swift */; }; 4B957B9C2AC7AE700062CA31 /* SecureVaultErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B642738127B65BAC0005DFD1 /* SecureVaultErrorReporter.swift */; }; 4B957B9D2AC7AE700062CA31 /* NSImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B139AFC26B60BD800894F82 /* NSImageExtensions.swift */; }; @@ -2106,7 +2085,6 @@ 4BBF0917282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0916282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift */; }; 4BBF09232830812900EE1418 /* FileSystemDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF09222830812900EE1418 /* FileSystemDSL.swift */; }; 4BBF0925283083EC00EE1418 /* FileSystemDSLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */; }; - 4BC2621D293996410087A482 /* PixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC2621C293996410087A482 /* PixelEventTests.swift */; }; 4BCBE4552BA7E16600FC75A1 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2F565B2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift */; }; 4BCBE4562BA7E16900FC75A1 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; }; 4BCBE4582BA7E17800FC75A1 /* SubscriptionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE4572BA7E17800FC75A1 /* SubscriptionUI */; }; @@ -2936,7 +2914,6 @@ B66260E629ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */; }; B66260E729ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */; }; B66260E829ACD0C900E9E3EE /* DuckPlayerTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C416A6294A4AE500C4F2E7 /* DuckPlayerTabExtension.swift */; }; - B662D3D92755D7AD0035D4D6 /* PixelStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */; }; B662D3DE275613BB0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */; }; B662D3DF275616FF0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */; }; B6656E0D2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; @@ -2992,7 +2969,6 @@ B689ECD526C247DB006FB0C5 /* BackForwardListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B689ECD426C247DB006FB0C5 /* BackForwardListItem.swift */; }; B68C2FB227706E6A00BF2C7D /* ProcessExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */; }; B68C92C1274E3EF4002AC6B0 /* PopUpWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C0274E3EF4002AC6B0 /* PopUpWindow.swift */; }; - B68C92C42750EF76002AC6B0 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; B68D21C32ACBC916002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; B68D21C42ACBC917002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; B68D21C82ACBC96D002DA3C2 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; @@ -3060,11 +3036,7 @@ B6A5A2A025B96E8300AA7ADA /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; B6A5A2A825BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */; }; B6A924D92664C72E001A28CA /* WebKitDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */; }; - B6A9E45326142B070067D1B9 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */; }; - B6A9E47726146A570067D1B9 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; - B6A9E47F26146A800067D1B9 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; - B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; B6AA64732994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; B6AAAC2D260330580029438D /* PublishedAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC2C260330580029438D /* PublishedAfter.swift */; }; @@ -3176,13 +3148,9 @@ B6DA06E42913ECEE00225DE2 /* ContextMenuManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E32913ECEE00225DE2 /* ContextMenuManager.swift */; }; B6DA06E62913F39400225DE2 /* MenuItemSelectors.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E52913F39400225DE2 /* MenuItemSelectors.swift */; }; B6DA06E8291401D700225DE2 /* WKMenuItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E7291401D700225DE2 /* WKMenuItemIdentifier.swift */; }; - B6DA44022616B28300DD1EC2 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; - B6DA44082616B30600DD1EC2 /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; - B6DA44112616C0FC00DD1EC2 /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */; }; B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B6DA44162616C13800DD1EC2 /* OHHTTPStubs */; }; B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */; }; B6DA441E2616C84600DD1EC2 /* PixelStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA441D2616C84600DD1EC2 /* PixelStoreMock.swift */; }; - B6DA44232616CABC00DD1EC2 /* PixelArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */; }; B6DB3AEF278D5C370024C5C4 /* URLSessionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DB3AEE278D5C370024C5C4 /* URLSessionExtension.swift */; }; B6DB3AF6278EA0130024C5C4 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; B6DB3CF926A00E2D00D459B7 /* AVCaptureDevice+SwizzledAuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DB3CF826A00E2D00D459B7 /* AVCaptureDevice+SwizzledAuthState.swift */; }; @@ -3382,6 +3350,24 @@ EEDE50122BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */; }; EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; }; EEF53E182950CED5002D78F4 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; }; + F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; + F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; + F188267E2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; + F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + F18826812BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + F18826822BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + F18826842BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; + F18826852BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; + F18826862BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; + F188268D2BBF01C300D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; + F188268E2BBF01C400D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; + F188268F2BBF01C500D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; + F18826902BC0105800D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F18826912BC0105800D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F18826922BC0105900D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F18826932BC0105900D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F18826942BC0105A00D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F18826952BC0105A00D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; F1B33DF22BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; @@ -3913,7 +3899,6 @@ 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = httpsMobileV2FalsePositives.json; sourceTree = ""; }; 4B677440255DBEEA00025BD8 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; 4B677454255DC18000025BD8 /* Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bridging.h; sourceTree = ""; }; - 4B67853E2AA7C726008A5004 /* DailyPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixel.swift; sourceTree = ""; }; 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureDisabler.swift; sourceTree = ""; }; 4B6B64832BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistThankYouPromptPresenter.swift; sourceTree = ""; }; 4B70BFFF27B0793D000386ED /* DuckDuckGo-ExampleCrash.ips */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "DuckDuckGo-ExampleCrash.ips"; sourceTree = ""; }; @@ -4043,7 +4028,6 @@ 4BBF0916282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryFileHandlerTests.swift; sourceTree = ""; }; 4BBF09222830812900EE1418 /* FileSystemDSL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemDSL.swift; sourceTree = ""; }; 4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemDSLTests.swift; sourceTree = ""; }; - 4BC2621C293996410087A482 /* PixelEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelEventTests.swift; sourceTree = ""; }; 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessaging.swift; sourceTree = ""; }; 4BCF15D82ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessage.swift; sourceTree = ""; }; 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessageTests.swift; sourceTree = ""; }; @@ -4595,7 +4579,6 @@ B66260DC29AC5D4300E9E3EE /* NavigationProtectionTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProtectionTabExtension.swift; sourceTree = ""; }; B66260DF29AC6EBD00E9E3EE /* HistoryTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryTabExtension.swift; sourceTree = ""; }; B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationHotkeyHandler.swift; sourceTree = ""; }; - B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelStoreTests.swift; sourceTree = ""; }; B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyStoreMock.swift; sourceTree = ""; }; B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImportViewLocalizationTests.swift; sourceTree = ""; }; B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBarTextEditor.swift; sourceTree = ""; }; @@ -4684,11 +4667,7 @@ B6A60E4F2B73C3B800FD4968 /* WKURLSchemeTask+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WKURLSchemeTask+Private.h"; sourceTree = ""; }; B6A60E502B73C46B00FD4968 /* IntegrationTestsBridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IntegrationTestsBridging.h; sourceTree = ""; }; B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebKitDownloadTask.swift; sourceTree = ""; }; - B6A9E45226142B070067D1B9 /* Pixel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pixel.swift; sourceTree = ""; }; B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersionExtension.swift; sourceTree = ""; }; - B6A9E47626146A570067D1B9 /* PixelEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelEvent.swift; sourceTree = ""; }; - B6A9E47E26146A800067D1B9 /* PixelArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelArguments.swift; sourceTree = ""; }; - B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelParameters.swift; sourceTree = ""; }; B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureExtensionTests.swift; sourceTree = ""; }; B6AAAC2C260330580029438D /* PublishedAfter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedAfter.swift; sourceTree = ""; }; B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollectionExtension.swift; sourceTree = ""; }; @@ -4757,9 +4736,7 @@ B6DA06E7291401D700225DE2 /* WKMenuItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKMenuItemIdentifier.swift; sourceTree = ""; }; B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelDataStore.swift; sourceTree = ""; }; B6DA44072616B30600DD1EC2 /* PixelDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = PixelDataModel.xcdatamodel; sourceTree = ""; }; - B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelTests.swift; sourceTree = ""; }; B6DA441D2616C84600DD1EC2 /* PixelStoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelStoreMock.swift; sourceTree = ""; }; - B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelArgumentsTests.swift; sourceTree = ""; }; B6DB3AEE278D5C370024C5C4 /* URLSessionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionExtension.swift; sourceTree = ""; }; B6DB3CF826A00E2D00D459B7 /* AVCaptureDevice+SwizzledAuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice+SwizzledAuthState.swift"; sourceTree = ""; }; B6DB3CFA26A17CB800D459B7 /* PermissionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionModel.swift; sourceTree = ""; }; @@ -4851,6 +4828,9 @@ EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+VPNAgentConvenienceInitializers.swift"; sourceTree = ""; }; EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = ""; }; EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; + F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; + F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; + F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; @@ -8579,6 +8559,9 @@ B69B50392726A12500758A2B /* LocalStatisticsStore.swift */, B69B50372726A12000758A2B /* VariantManager.swift */, B69B50562727D16900758A2B /* AtbAndVariantCleanup.swift */, + B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */, + B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */, + B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */, 9FBD84512BB3AACB00220859 /* AttributionOriginFileProvider.swift */, 9FBD84722BB3E15D00220859 /* InstallationAttributionPixelHandler.swift */, ); @@ -8630,17 +8613,12 @@ B6A9E44E26142AF90067D1B9 /* Statistics */ = { isa = PBXGroup; children = ( - 857E5AF32A79044900FC0FB4 /* Experiment */, B69B50332726A10700758A2B /* ATB */, - B6A9E45226142B070067D1B9 /* Pixel.swift */, - 4B67853E2AA7C726008A5004 /* DailyPixel.swift */, - B6A9E47626146A570067D1B9 /* PixelEvent.swift */, - B6A9E47E26146A800067D1B9 /* PixelArguments.swift */, - B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */, + 857E5AF32A79044900FC0FB4 /* Experiment */, B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */, - B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */, - B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */, - B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */, + F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */, + F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */, + F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */, ); path = Statistics; sourceTree = ""; @@ -8730,10 +8708,6 @@ children = ( 857E5AF72A79617100FC0FB4 /* PixelExperiment */, B69B50402726C3F400758A2B /* ATB */, - B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */, - 4BC2621C293996410087A482 /* PixelEventTests.swift */, - B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */, - B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */, B6DA441D2616C84600DD1EC2 /* PixelStoreMock.swift */, 4B117F7C276C0CB5002F3D8C /* LocalStatisticsStoreTests.swift */, B610F2E327A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift */, @@ -10414,7 +10388,6 @@ 3706FAB6293F65D500E42796 /* YoutubePlayerUserScript.swift in Sources */, 1D8057C92A83CB3C00F4FED6 /* SupportedOsChecker.swift in Sources */, 373D9B4929EEAC1B00381FDD /* SyncMetadataDatabase.swift in Sources */, - 3706FAB7293F65D500E42796 /* PixelParameters.swift in Sources */, 3706FAB8293F65D500E42796 /* FaviconImageCache.swift in Sources */, 3706FAB9293F65D500E42796 /* TabBarViewController.swift in Sources */, 4B9DB0332A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */, @@ -10503,6 +10476,7 @@ F1B33DF72BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */, 3706FEC0293F6EFF00E42796 /* BWRequest.swift in Sources */, 3706FAF7293F65D500E42796 /* FireproofDomainsViewController.swift in Sources */, + F18826852BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */, 4BF0E5062AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 3706FAF8293F65D500E42796 /* URLEventHandler.swift in Sources */, 9FBD84742BB3E15D00220859 /* InstallationAttributionPixelHandler.swift in Sources */, @@ -10554,6 +10528,7 @@ 3706FB1B293F65D500E42796 /* SafariDataImporter.swift in Sources */, 3706FB1D293F65D500E42796 /* StatisticsLoader.swift in Sources */, 3793FDD829535EBA00A2E28F /* Assertions.swift in Sources */, + F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */, B62B48572ADE730D000DECE5 /* FileImportView.swift in Sources */, B6676BE22AA986A700525A21 /* AddressBarTextEditor.swift in Sources */, 3706FB1F293F65D500E42796 /* DataClearingPreferences.swift in Sources */, @@ -10640,7 +10615,6 @@ 3706FB60293F65D500E42796 /* PasswordManagementIdentityItemView.swift in Sources */, 3706FB61293F65D500E42796 /* ProgressExtension.swift in Sources */, 3706FB62293F65D500E42796 /* CSVParser.swift in Sources */, - 3706FB64293F65D500E42796 /* PixelDataModel.xcdatamodeld in Sources */, B626A75B29921FAA00053070 /* NavigationActionPolicyExtension.swift in Sources */, 4B9DB02A2A983B24000927DB /* WaitlistStorage.swift in Sources */, 3706FB65293F65D500E42796 /* PrivacyDashboardWebView.swift in Sources */, @@ -10699,8 +10673,11 @@ 37197EA42942441D00394917 /* NewWindowPolicy.swift in Sources */, 3706FB84293F65D500E42796 /* NSWindowExtension.swift in Sources */, 3706FB85293F65D500E42796 /* AddBookmarkPopover.swift in Sources */, + F18826922BC0105900D9AC4F /* PixelDataRecord.swift in Sources */, + F18826932BC0105900D9AC4F /* PixelDataStore.swift in Sources */, 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */, 3706FB87293F65D500E42796 /* ProcessExtension.swift in Sources */, + F18826812BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */, 3706FB88293F65D500E42796 /* PermissionAuthorizationQuery.swift in Sources */, 3706FB89293F65D500E42796 /* BadgeAnimationView.swift in Sources */, 4B4D60C32A0C849100BCD287 /* EventMapping+NetworkProtectionError.swift in Sources */, @@ -10780,6 +10757,7 @@ 3706FBBE293F65D500E42796 /* DownloadListViewModel.swift in Sources */, 3706FBBF293F65D500E42796 /* BookmarkManagementDetailViewController.swift in Sources */, B6B4D1CB2B0C8C9200C26286 /* FirefoxCompatibilityPreferences.swift in Sources */, + F188268E2BBF01C400D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, 3706FBC0293F65D500E42796 /* CSVImporter.swift in Sources */, 3706FBC1293F65D500E42796 /* StartupPreferences.swift in Sources */, 3706FBC2293F65D500E42796 /* MainMenu.swift in Sources */, @@ -10859,10 +10837,7 @@ 9F56CFAE2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */, 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */, 1D26EBAD2B74BECB0002A93F /* NSImageSendable.swift in Sources */, - 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */, 1D220BFD2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */, - 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */, - 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */, 3706FBF8293F65D500E42796 /* TabBarFooter.swift in Sources */, B626A7612992407D00053070 /* CancellableExtension.swift in Sources */, 3706FBF9293F65D500E42796 /* BookmarksBarCollectionViewItem.swift in Sources */, @@ -10888,7 +10863,6 @@ 1DCFBC8B29ADF32B00313531 /* BurnerHomePageView.swift in Sources */, 3706FC06293F65D500E42796 /* OnboardingViewModel.swift in Sources */, 3706FC07293F65D500E42796 /* ScriptSourceProviding.swift in Sources */, - 4B6785402AA7C726008A5004 /* DailyPixel.swift in Sources */, 31EF1E832B63FFCA00E6DB17 /* LoginItem+DataBrokerProtection.swift in Sources */, B6619EFC2B111CC600CD9186 /* InstructionsFormatParser.swift in Sources */, 3706FC08293F65D500E42796 /* CoreDataBookmarkImporter.swift in Sources */, @@ -10977,7 +10951,6 @@ 3706FC40293F65D500E42796 /* SaveIdentityViewController.swift in Sources */, 3706FC41293F65D500E42796 /* FileStore.swift in Sources */, 1DB67F2E2B6FEFDB003DF243 /* ViewSnapshotRenderer.swift in Sources */, - 3706FC42293F65D500E42796 /* PixelArguments.swift in Sources */, 3706FC43293F65D500E42796 /* PinnedTabsViewModel.swift in Sources */, 85D0327C2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */, B6685E4329A61C470043D2EE /* DownloadsTabExtension.swift in Sources */, @@ -11048,7 +11021,6 @@ 3706FC71293F65D500E42796 /* NSColorExtension.swift in Sources */, 1DB9618229F67F6100CF5568 /* FaviconNullStore.swift in Sources */, 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */, - 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */, 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 9FDA6C222B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, C1372EF52BBC5BAD003F8793 /* SecureTextField.swift in Sources */, @@ -11132,7 +11104,6 @@ 3706FDE0293F661700E42796 /* TabIndexTests.swift in Sources */, 9F26060F2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */, 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */, - 3706FDE2293F661700E42796 /* PixelArgumentsTests.swift in Sources */, 4B9DB0572A983B55000927DB /* MockNotificationService.swift in Sources */, 3706FDE4293F661700E42796 /* TabLazyLoaderTests.swift in Sources */, 3706FDE5293F661700E42796 /* URLEventHandlerTests.swift in Sources */, @@ -11208,7 +11179,6 @@ 3706FE1B293F661700E42796 /* PermissionManagerTests.swift in Sources */, 3706FE1C293F661700E42796 /* ConnectBitwardenViewModelTests.swift in Sources */, 4B9DB0552A983B55000927DB /* MockWaitlistStorage.swift in Sources */, - 3706FE1D293F661700E42796 /* PixelStoreTests.swift in Sources */, 1D9FDEC72B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */, C13909F52B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift in Sources */, 857E44642A9F70F200ED77A7 /* CampaignVariantTests.swift in Sources */, @@ -11280,7 +11250,6 @@ 3706FE47293F661700E42796 /* URLExtensionTests.swift in Sources */, 1DB9617B29F1D06D00CF5568 /* InternalUserDeciderMock.swift in Sources */, 317295D52AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift in Sources */, - 3706FE48293F661700E42796 /* PixelTests.swift in Sources */, 3706FE49293F661700E42796 /* BookmarkNodePathTests.swift in Sources */, 1DE03425298BC7F000CAB3D7 /* InternalUserDeciderStoreMock.swift in Sources */, 3706FE4A293F661700E42796 /* BookmarkManagedObjectTests.swift in Sources */, @@ -11309,7 +11278,6 @@ 3706FE5A293F661700E42796 /* GeolocationServiceMock.swift in Sources */, 3706FE5B293F661700E42796 /* FirefoxLoginReaderTests.swift in Sources */, 1D1C36E429FAE8DA001FA40C /* FaviconManagerTests.swift in Sources */, - 376E2D29294286B8001CD31B /* PixelEventTests.swift in Sources */, 37716D8029707E5D00A9FC6D /* FireproofingReferenceTests.swift in Sources */, B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */, 3706FE5C293F661700E42796 /* DuckPlayerPreferencesTests.swift in Sources */, @@ -11675,7 +11643,6 @@ 4B9579842AC7AE700062CA31 /* TabInstrumentation.swift in Sources */, 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */, 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */, - 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */, 4B95798B2AC7AE700062CA31 /* FaviconImageCache.swift in Sources */, 4B95798C2AC7AE700062CA31 /* TabBarViewController.swift in Sources */, 4B95798D2AC7AE700062CA31 /* BookmarkOutlineViewDataSource.swift in Sources */, @@ -11796,6 +11763,7 @@ 4B9579EF2AC7AE700062CA31 /* ProductWaitlistRequest.swift in Sources */, 7BEC20442B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */, 4B9579F02AC7AE700062CA31 /* Bookmark.xcdatamodeld in Sources */, + F18826822BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */, 4B9579F12AC7AE700062CA31 /* DefaultBrowserPromptView.swift in Sources */, 4B9579F22AC7AE700062CA31 /* WaitlistActivationDateStore.swift in Sources */, 4B9579F42AC7AE700062CA31 /* FaviconManager.swift in Sources */, @@ -11859,6 +11827,8 @@ 4B957A272AC7AE700062CA31 /* PrivacyDashboardPopover.swift in Sources */, 4B957A282AC7AE700062CA31 /* TestsClosureNavigationResponder.swift in Sources */, 4B957A292AC7AE700062CA31 /* RootView.swift in Sources */, + F18826942BC0105A00D9AC4F /* PixelDataRecord.swift in Sources */, + F18826952BC0105A00D9AC4F /* PixelDataStore.swift in Sources */, 56BA1E772BAAF70F001CF69F /* SSLErrorPageTabExtension.swift in Sources */, 4B37EE7C2B4CFF8000A89A61 /* HomePageRemoteMessagingRequest.swift in Sources */, 4B957A2A2AC7AE700062CA31 /* AddressBarTextField.swift in Sources */, @@ -11919,7 +11889,6 @@ 4B957A592AC7AE700062CA31 /* ProgressExtension.swift in Sources */, 4B44FEF52B1FEF5A000619D8 /* FocusableTextEditor.swift in Sources */, 4B957A5A2AC7AE700062CA31 /* CSVParser.swift in Sources */, - 4B957A5B2AC7AE700062CA31 /* PixelDataModel.xcdatamodeld in Sources */, 4B957A5C2AC7AE700062CA31 /* PrivacyDashboardWebView.swift in Sources */, B6656E5B2B2ADB1C008798A1 /* RequestFilePermissionView.swift in Sources */, 4B957A5D2AC7AE700062CA31 /* AppearancePreferences.swift in Sources */, @@ -12022,6 +11991,7 @@ B65E5DB12B74E6AA00480415 /* TrackerNetwork.swift in Sources */, B6E6B9E52BA1F5F1008AA7E1 /* FilePresenter.swift in Sources */, 31F2D2022AF026D800BF0144 /* WaitlistTermsAndConditionsActionHandler.swift in Sources */, + F18826862BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */, 4B957AAC2AC7AE700062CA31 /* EncryptedValueTransformer.swift in Sources */, 4B957AAD2AC7AE700062CA31 /* Tab+Dialogs.swift in Sources */, 4B957AAE2AC7AE700062CA31 /* PasteboardBookmark.swift in Sources */, @@ -12103,7 +12073,6 @@ 4B957AEF2AC7AE700062CA31 /* String+Punycode.swift in Sources */, 4B957AF02AC7AE700062CA31 /* NSException+Catch.m in Sources */, 4B957AF12AC7AE700062CA31 /* AppStateRestorationManager.swift in Sources */, - 4B957AF22AC7AE700062CA31 /* DailyPixel.swift in Sources */, 9FDA6C232B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, 4B957AF32AC7AE700062CA31 /* NavigationHotkeyHandler.swift in Sources */, B6CC266E2BAD9CD800F53F8D /* FileProgressPresenter.swift in Sources */, @@ -12127,11 +12096,8 @@ 4B957B052AC7AE700062CA31 /* FindInPageModel.swift in Sources */, 4B957B062AC7AE700062CA31 /* PseudoFolder.swift in Sources */, 4B2F565D2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */, - 4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */, 4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */, B6ABD0D02BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */, - 4B957B0A2AC7AE700062CA31 /* Pixel.swift in Sources */, - 4B957B0B2AC7AE700062CA31 /* PixelEvent.swift in Sources */, 4B957B0C2AC7AE700062CA31 /* TabBarFooter.swift in Sources */, C168B9AE2B31DC7F001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */, 4B957B0D2AC7AE700062CA31 /* JSAlertViewModel.swift in Sources */, @@ -12172,6 +12138,7 @@ 4B957B2B2AC7AE700062CA31 /* TabViewModel.swift in Sources */, 4B957B2C2AC7AE700062CA31 /* TabDragAndDropManager.swift in Sources */, B677FC572B064A9C0099EB04 /* DataImportViewModel.swift in Sources */, + F188267E2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */, 4B957B2D2AC7AE700062CA31 /* NSNotificationName+Favicons.swift in Sources */, 4B957B2E2AC7AE700062CA31 /* PinningManager.swift in Sources */, 4B957B2F2AC7AE700062CA31 /* SyncMetadataDatabase.swift in Sources */, @@ -12226,7 +12193,6 @@ 4B957B592AC7AE700062CA31 /* AppLauncher.swift in Sources */, 4B957B5A2AC7AE700062CA31 /* FileStore.swift in Sources */, 1DB67F2F2B6FEFDB003DF243 /* ViewSnapshotRenderer.swift in Sources */, - 4B957B5B2AC7AE700062CA31 /* PixelArguments.swift in Sources */, 4B957B5C2AC7AE700062CA31 /* PinnedTabsViewModel.swift in Sources */, 4B957B5D2AC7AE700062CA31 /* BookmarkList.swift in Sources */, 4B957B5E2AC7AE700062CA31 /* NEOnDemandRuleExtension.swift in Sources */, @@ -12243,6 +12209,7 @@ 4B957B672AC7AE700062CA31 /* AtbAndVariantCleanup.swift in Sources */, 4B957B692AC7AE700062CA31 /* FeedbackWindow.swift in Sources */, 4B957B6A2AC7AE700062CA31 /* WorkspaceProtocol.swift in Sources */, + F188268F2BBF01C500D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, 4B957B6B2AC7AE700062CA31 /* RecentlyVisitedView.swift in Sources */, 4B957B6C2AC7AE700062CA31 /* MouseOverAnimationButton.swift in Sources */, 4B957B6D2AC7AE700062CA31 /* TabBarScrollView.swift in Sources */, @@ -12304,7 +12271,6 @@ 4B957B952AC7AE700062CA31 /* NSColorExtension.swift in Sources */, 4B957B972AC7AE700062CA31 /* AddressBarButtonsViewController.swift in Sources */, 4B957B982AC7AE700062CA31 /* BWError.swift in Sources */, - 4B957B9A2AC7AE700062CA31 /* PixelDataRecord.swift in Sources */, 4B957B9B2AC7AE700062CA31 /* PageObserverUserScript.swift in Sources */, 4B957B9C2AC7AE700062CA31 /* SecureVaultErrorReporter.swift in Sources */, 4B68DDFF2ACBA14100FB0973 /* FileLineError.swift in Sources */, @@ -12526,12 +12492,12 @@ 1D26EBAC2B74BECB0002A93F /* NSImageSendable.swift in Sources */, B6B4D1C52B0B3B5400C26286 /* DataImportReportModel.swift in Sources */, 4B92928D26670D1700AD2C21 /* BookmarkOutlineCellView.swift in Sources */, + F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */, B604085C274B8FBA00680351 /* UnprotectedDomains.xcdatamodeld in Sources */, 4BB88B5025B7BA2B006F6B06 /* TabInstrumentation.swift in Sources */, 85D33F1225C82EB3002B91A6 /* ConfigurationManager.swift in Sources */, 31F28C4F28C8EEC500119F70 /* YoutubePlayerUserScript.swift in Sources */, 4B41EDAE2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */, - B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */, AA5FA697275F90C400DCE9C9 /* FaviconImageCache.swift in Sources */, 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, B62B483E2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, @@ -12575,6 +12541,8 @@ 858A798326A8B75F00A75A42 /* CopyHandler.swift in Sources */, 1E7E2E9029029A2A00C01B54 /* ContentBlockingRulesUpdateObserver.swift in Sources */, 4B8AC93926B48A5100879451 /* FirefoxLoginReader.swift in Sources */, + F18826902BC0105800D9AC4F /* PixelDataRecord.swift in Sources */, + F18826912BC0105800D9AC4F /* PixelDataStore.swift in Sources */, B69B503E2726A12500758A2B /* AtbParser.swift in Sources */, 37F19A6528E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift in Sources */, 4B92929E26670D2A00AD2C21 /* BookmarkSidebarTreeController.swift in Sources */, @@ -12755,7 +12723,6 @@ B68412142B694BA10092F66A /* NSObject+performSelector.m in Sources */, B6F41031264D2B23003DA42C /* ProgressExtension.swift in Sources */, 4B723E0F26B0006500E14D75 /* CSVParser.swift in Sources */, - B6DA44082616B30600DD1EC2 /* PixelDataModel.xcdatamodeld in Sources */, B63BDF7E27FDAA640072D75B /* PrivacyDashboardWebView.swift in Sources */, 37CD54CF27F2FDD100F1F7B9 /* AppearancePreferences.swift in Sources */, B6B1E87B26D381710062C350 /* DownloadListCoordinator.swift in Sources */, @@ -12835,6 +12802,7 @@ 85707F2A276A35FE00DC0649 /* ActionSpeech.swift in Sources */, B6BE9FAA293F7955006363C6 /* ModalSheetCancellable.swift in Sources */, B6830963274CDEC7004B46BB /* FireproofDomainsStore.swift in Sources */, + F188268D2BBF01C300D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 1E7E2E942902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift in Sources */, AA9FF95F24A1FB690039E328 /* TabCollectionViewModel.swift in Sources */, @@ -12892,6 +12860,7 @@ AA6FFB4424DC33320028F4D0 /* NSViewExtension.swift in Sources */, B6C0B23E26E8BF1F0031CB7F /* DownloadListViewModel.swift in Sources */, 4B9292D52667123700AD2C21 /* BookmarkManagementDetailViewController.swift in Sources */, + F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */, 4B723E1026B0006700E14D75 /* CSVImporter.swift in Sources */, 37A4CEBA282E992F00D75B89 /* StartupPreferences.swift in Sources */, 4B41EDB12B168B1E001EEDF4 /* VPNFeedbackFormView.swift in Sources */, @@ -12941,7 +12910,6 @@ B65783E725F8AAFB00D8DB33 /* String+Punycode.swift in Sources */, B657841A25FA484B00D8DB33 /* NSException+Catch.m in Sources */, B684592F25C93FBF00DC17B6 /* AppStateRestorationManager.swift in Sources */, - 4B67853F2AA7C726008A5004 /* DailyPixel.swift in Sources */, B66260E629ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */, EA0BA3A9272217E6002A0B6C /* ClickToLoadUserScript.swift in Sources */, AAA892EA250A4CEF005B37B2 /* WindowControllersManager.swift in Sources */, @@ -12969,10 +12937,7 @@ 1DDD3EC42B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */, 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, 4B05265E2B1AE5C70054955A /* VPNMetadataCollector.swift in Sources */, - B6DA44022616B28300DD1EC2 /* PixelDataStore.swift in Sources */, 4B9DB0292A983B24000927DB /* WaitlistStorage.swift in Sources */, - B6A9E45326142B070067D1B9 /* Pixel.swift in Sources */, - B6A9E47726146A570067D1B9 /* PixelEvent.swift in Sources */, AA2CB1352587C29500AA6FBE /* TabBarFooter.swift in Sources */, EEC111E6294D06290086524F /* JSAlertViewModel.swift in Sources */, 4BE5336C286912D40019DBFD /* BookmarksBarCollectionViewItem.swift in Sources */, @@ -13074,7 +13039,6 @@ F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */, EEC589D92A4F1CE300BCD60C /* AppLauncher.swift in Sources */, 4BA1A69B258B076900F6F690 /* FileStore.swift in Sources */, - B6A9E47F26146A800067D1B9 /* PixelArguments.swift in Sources */, 1D01A3D02B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */, 37BF3F21286F0A7A00BD9014 /* PinnedTabsViewModel.swift in Sources */, EEC4A6692B2C87D300F7C0AA /* VPNLocationView.swift in Sources */, @@ -13148,8 +13112,8 @@ F41D174125CB131900472416 /* NSColorExtension.swift in Sources */, AAC5E4F625D6BF2C007F5990 /* AddressBarButtonsViewController.swift in Sources */, B6F9BDE42B45CD1900677B33 /* ModalView.swift in Sources */, + F18826842BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */, 1D2DC0072901679C008083A1 /* BWError.swift in Sources */, - B68C92C42750EF76002AC6B0 /* PixelDataRecord.swift in Sources */, 853014D625E671A000FB8205 /* PageObserverUserScript.swift in Sources */, B677FC4F2B06376B0099EB04 /* ReportFeedbackView.swift in Sources */, B642738227B65BAC0005DFD1 /* SecureVaultErrorReporter.swift in Sources */, @@ -13238,7 +13202,6 @@ 4B9DB05A2A983B55000927DB /* MockWaitlistRequest.swift in Sources */, 37D2377C287EBDA300BCE03B /* TabIndexTests.swift in Sources */, 37534CA52811987D002621E7 /* AdjacentItemEnumeratorTests.swift in Sources */, - B6DA44232616CABC00DD1EC2 /* PixelArgumentsTests.swift in Sources */, 56B234BF2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */, C13909F42B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift in Sources */, 37534C9E28104D9B002621E7 /* TabLazyLoaderTests.swift in Sources */, @@ -13321,7 +13284,6 @@ B6106BA026A7BE0B0013B453 /* PermissionManagerTests.swift in Sources */, 4B59CC8C290083240058F2F6 /* ConnectBitwardenViewModelTests.swift in Sources */, B68412202B6A30680092F66A /* StringExtensionTests.swift in Sources */, - B662D3D92755D7AD0035D4D6 /* PixelStoreTests.swift in Sources */, B6106BB526A809E60013B453 /* GeolocationProviderTests.swift in Sources */, B6E6BA232BA2EDDE008AA7E1 /* FileReadResult.swift in Sources */, B6A5A2A025B96E8300AA7ADA /* AppStateChangePublisherTests.swift in Sources */, @@ -13396,7 +13358,6 @@ C1E961EB2B879E79001760E1 /* MockAutofillActionPresenter.swift in Sources */, 4BA1A6FE258C5C1300F6F690 /* EncryptedValueTransformerTests.swift in Sources */, 85F69B3C25EDE81F00978E59 /* URLExtensionTests.swift in Sources */, - B6DA44112616C0FC00DD1EC2 /* PixelTests.swift in Sources */, 4B9292BA2667103100AD2C21 /* BookmarkNodePathTests.swift in Sources */, 4B9292C02667103100AD2C21 /* BookmarkManagedObjectTests.swift in Sources */, 373A1AB228451ED400586521 /* BookmarksHTMLImporterTests.swift in Sources */, @@ -13444,7 +13405,6 @@ B698E5042908011E00A746A8 /* AppKitPrivateMethodsAvailabilityTests.swift in Sources */, 56D145EE29E6DAD900E3488A /* DataImportProviderTests.swift in Sources */, 4BB99D0F26FE1A84001E4761 /* ChromiumBookmarksReaderTests.swift in Sources */, - 4BC2621D293996410087A482 /* PixelEventTests.swift in Sources */, 1D9FDEB72B9B5D150040B78C /* SearchPreferencesTests.swift in Sources */, 1D12F2E2298BC660009A65FD /* InternalUserDeciderStoreMock.swift in Sources */, 4BB99D1026FE1A84001E4761 /* FirefoxBookmarksReaderTests.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index b70be830d9..94e73502b1 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -115,6 +115,9 @@ + + diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 53ac49ff5b..aacd98014a 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -95,6 +95,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { var updateController: UpdateController! #endif + @UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: Date.monthAgo) + static var firstLaunchDate: Date + + static var isNewUser: Bool { + return firstLaunchDate >= Date.weekAgo + } + // swiftlint:disable:next function_body_length override init() { do { @@ -109,22 +116,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate { internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore) if NSApplication.runType.requiresEnvironment { -#if DEBUG - Pixel.setUp(dryRun: true) - Self.setUpPixelKit(dryRun: true) -#else - Pixel.setUp() - Self.setUpPixelKit(dryRun: false) -#endif + Self.configurePixelKit() Database.shared.loadStore { _, error in guard let error = error else { return } switch error { case CoreDataDatabase.Error.containerLocationCouldNotBePrepared(let underlyingError): - Pixel.fire(.debug(event: .dbContainerInitializationError, error: underlyingError)) + PixelKit.fire(DebugEvent(GeneralPixel.dbContainerInitializationError(error: underlyingError))) default: - Pixel.fire(.debug(event: .dbInitializationError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.dbInitializationError(error: error))) } // Give Pixel a chance to be sent, but not too long @@ -133,12 +134,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } let preMigrationErrorHandling = EventMapping { _, error, _, _ in - if let error = error { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase, error: error)) - } else { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase)) - } - + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCouldNotLoadDatabase(error: error))) Thread.sleep(forTimeInterval: 1) fatalError("Could not create Bookmarks database stack: \(error?.localizedDescription ?? "err")") } @@ -152,12 +148,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { BookmarkDatabase.shared.db.loadStore { context, error in guard let context = context else { - if let error = error { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase, error: error)) - } else { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase)) - } - + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCouldNotLoadDatabase(error: error))) Thread.sleep(forTimeInterval: 1) fatalError("Could not create Bookmarks database stack: \(error?.localizedDescription ?? "err")") } @@ -193,6 +184,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #endif } + static func configurePixelKit() { +#if DEBUG + Self.setUpPixelKit(dryRun: true) +#else + Self.setUpPixelKit(dryRun: false) +#endif + } + func applicationWillFinishLaunching(_ notification: Notification) { APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) Configuration.setURLProvider(AppConfigurationURLProvider()) @@ -236,11 +235,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { _ = RecentlyClosedCoordinator.shared // Clean up previous experiment - if PixelExperiment.allocatedCohortDoesNotMatchCurrentCohorts { - PixelExperiment.cleanup() - } +// if PixelExperiment.allocatedCohortDoesNotMatchCurrentCohorts { // Re-implement https://app.asana.com/0/0/1207002879349166/f +// PixelExperiment.cleanup() +// } + if LocalStatisticsStore().atb == nil { - Pixel.firstLaunchDate = Date() + AppDelegate.firstLaunchDate = Date() // MARK: Enable pixel experiments here } AtbAndVariantCleanup.cleanup() @@ -384,7 +384,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { appVersion: AppVersion.shared.versionNumber, source: source, defaultHeaders: [:], - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) @@ -442,9 +441,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { .filter { $0 } .asVoid() .sink { [weak syncService] in - Pixel.fire(.syncDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncDaily, frequency: .daily) syncService?.syncDailyStats.sendStatsIfNeeded(handler: { params in - Pixel.fire(.syncSuccessRateDaily, withAdditionalParameters: params) + PixelKit.fire(GeneralPixel.syncSuccessRateDaily, withAdditionalParameters: params) }) } @@ -525,9 +524,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } private func emailDidSignInNotification(_ notification: Notification) { - Pixel.fire(.emailEnabled) - if Pixel.isNewUser { - Pixel.fire(.emailEnabledInitial, limitTo: .initial) + PixelKit.fire(GeneralPixel.emailEnabled) + if AppDelegate.isNewUser { + PixelKit.fire(GeneralPixel.emailEnabledInitial, frequency: .legacyInitial) } if let object = notification.object as? EmailManager, let emailManager = syncDataProviders.settingsAdapter.emailManager, object !== emailManager { @@ -536,18 +535,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } private func emailDidSignOutNotification(_ notification: Notification) { - Pixel.fire(.emailDisabled) + PixelKit.fire(GeneralPixel.emailDisabled) if let object = notification.object as? EmailManager, let emailManager = syncDataProviders.settingsAdapter.emailManager, object !== emailManager { syncService?.scheduler.notifyDataChanged() } } @objc private func dataImportCompleteNotification(_ notification: Notification) { - if Pixel.isNewUser { - Pixel.fire(.importDataInitial, limitTo: .initial) + if AppDelegate.isNewUser { + PixelKit.fire(GeneralPixel.importDataInitial, frequency: .legacyInitial) } } - } func updateSubscriptionStatus() { @@ -559,7 +557,7 @@ func updateSubscriptionStatus() { if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { - DailyPixel.fire(pixel: .privacyProSubscriptionActive, frequency: .dailyOnly) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) } } diff --git a/DuckDuckGo/Application/URLEventHandler.swift b/DuckDuckGo/Application/URLEventHandler.swift index 696d2b59d7..b5621d69da 100644 --- a/DuckDuckGo/Application/URLEventHandler.swift +++ b/DuckDuckGo/Application/URLEventHandler.swift @@ -19,6 +19,7 @@ import Common import Foundation import AppKit +import PixelKit import NetworkProtectionUI @@ -61,15 +62,15 @@ final class URLEventHandler { @objc func handleUrlEvent(event: NSAppleEventDescriptor, reply: NSAppleEventDescriptor) { guard let stringValue = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else { os_log("UrlEventListener: unable to determine path", type: .error) - Pixel.fire(.debug(event: .appOpenURLFailed, - error: NSError(domain: "CouldNotGetPath", code: -1, userInfo: nil))) + let error = NSError(domain: "CouldNotGetPath", code: -1, userInfo: nil) + PixelKit.fire(DebugEvent(GeneralPixel.appOpenURLFailed, error: error)) return } guard let url = URL.makeURL(from: stringValue) else { os_log("UrlEventListener: failed to construct URL from path %s", type: .error, stringValue) - Pixel.fire(.debug(event: .appOpenURLFailed, - error: NSError(domain: "CouldNotConstructURL", code: -1, userInfo: nil))) + let error = NSError(domain: "CouldNotConstructURL", code: -1, userInfo: nil) + PixelKit.fire(DebugEvent(GeneralPixel.appOpenURLFailed, error: error)) return } @@ -153,7 +154,7 @@ final class URLEventHandler { #if SUBSCRIPTION case AppLaunchCommand.showPrivacyPro.launchURL: WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) #endif #if !APPSTORE && !DEBUG case AppLaunchCommand.moveAppToApplications.launchURL: diff --git a/DuckDuckGo/Application/UpdateController.swift b/DuckDuckGo/Application/UpdateController.swift index 13fcacd30d..56f8d5c0fb 100644 --- a/DuckDuckGo/Application/UpdateController.swift +++ b/DuckDuckGo/Application/UpdateController.swift @@ -21,6 +21,7 @@ import Combine import Sparkle import BrowserServicesKit import SwiftUIExtensions +import PixelKit #if SPARKLE @@ -107,7 +108,7 @@ extension UpdateController: SPUUpdaterDelegate { return } - Pixel.fire(.debug(event: .updaterAborted, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.updaterAborted, error: error)) } func updater(_ updater: SPUUpdater, @@ -116,11 +117,11 @@ extension UpdateController: SPUUpdaterDelegate { state: SPUUserUpdateState) { switch choice { case .skip: - Pixel.fire(.debug(event: .userSelectedToSkipUpdate)) + PixelKit.fire(DebugEvent(GeneralPixel.userSelectedToSkipUpdate)) case .install: - Pixel.fire(.debug(event: .userSelectedToInstallUpdate)) + PixelKit.fire(DebugEvent(GeneralPixel.userSelectedToInstallUpdate)) case .dismiss: - Pixel.fire(.debug(event: .userSelectedToDismissUpdate)) + PixelKit.fire(DebugEvent(GeneralPixel.userSelectedToDismissUpdate)) @unknown default: break } diff --git a/DuckDuckGo/Autofill/AutofillActionExecutor.swift b/DuckDuckGo/Autofill/AutofillActionExecutor.swift index f574c7e974..cbdc445d1f 100644 --- a/DuckDuckGo/Autofill/AutofillActionExecutor.swift +++ b/DuckDuckGo/Autofill/AutofillActionExecutor.swift @@ -20,6 +20,7 @@ import Foundation import BrowserServicesKit import DDGSync import AppKit +import PixelKit /// Conforming types provide an `execute` method which performs some action on autofill types (e.g delete all passwords) protocol AutofillActionExecutor { @@ -68,7 +69,7 @@ struct AutofillDeleteAllPasswordsExecutor: AutofillActionExecutor { syncService.scheduler.notifyDataChanged() onSuccess?() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } return diff --git a/DuckDuckGo/Autofill/ContentOverlayViewController.swift b/DuckDuckGo/Autofill/ContentOverlayViewController.swift index d90b35f71d..9e325ef8b0 100644 --- a/DuckDuckGo/Autofill/ContentOverlayViewController.swift +++ b/DuckDuckGo/Autofill/ContentOverlayViewController.swift @@ -22,6 +22,7 @@ import Combine import BrowserServicesKit import SecureStorage import Autofill +import PixelKit @MainActor public final class ContentOverlayViewController: NSViewController, EmailManagerRequestDelegate { @@ -191,7 +192,7 @@ public final class ContentOverlayViewController: NSViewController, EmailManagerR parameters["keychain_operation"] = "save" } - Pixel.fire(.debug(event: .emailAutofillKeychainError, error: error), withAdditionalParameters: parameters) + PixelKit.fire(DebugEvent(GeneralPixel.emailAutofillKeychainError), withAdditionalParameters: parameters) } private enum Constants { @@ -294,7 +295,7 @@ extension ContentOverlayViewController: SecureVaultManagerDelegate { } public func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: String) { - Pixel.fire(.formAutofilled(kind: type.formAutofillKind)) + PixelKit.fire(GeneralPixel.formAutofilled(kind: type.formAutofillKind)) if type.formAutofillKind == .password && passwordManagerCoordinator.isEnabled { @@ -320,9 +321,9 @@ extension ContentOverlayViewController: SecureVaultManagerDelegate { self.emailManager.updateLastUseDate() - Pixel.fire(.jsPixel(pixel), withAdditionalParameters: pixelParameters) + PixelKit.fire(GeneralPixel.jsPixel(pixel), withAdditionalParameters: pixelParameters) } else { - Pixel.fire(.jsPixel(pixel), withAdditionalParameters: pixel.pixelParameters) + PixelKit.fire(GeneralPixel.jsPixel(pixel), withAdditionalParameters: pixel.pixelParameters) } } diff --git a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift index 9634ca221f..ebfc15be43 100644 --- a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift @@ -19,6 +19,7 @@ import Foundation import CoreData import Cocoa +import PixelKit fileprivate extension UUID { @@ -171,7 +172,7 @@ extension LegacyBookmarkStore { try context.save() } catch { - Pixel.fire(.debug(event: .bookmarksStoreRootFolderMigrationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksStoreRootFolderMigrationFailed, error: error)) } } @@ -211,7 +212,7 @@ extension LegacyBookmarkStore { // 4. Save the migration: try context.save() } catch { - Pixel.fire(.debug(event: .bookmarksStoreRootFolderMigrationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksStoreRootFolderMigrationFailed, error: error)) } } } diff --git a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift index c5ecef1b7c..734275e1c5 100644 --- a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift +++ b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift @@ -20,6 +20,7 @@ import Foundation import CoreData import Bookmarks import Persistence +import PixelKit public class LegacyBookmarksStoreMigration { @@ -55,7 +56,7 @@ public class LegacyBookmarksStoreMigration { guard BookmarkUtils.fetchRootFolder(destination) == nil else { // There should be no data left as migration has been done already if !bookmarkRoots.isEmpty { - Pixel.fire(.debug(event: .bookmarksMigrationAlreadyPerformed)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksMigrationAlreadyPerformed)) cleanupOldData(in: source) } @@ -72,9 +73,9 @@ public class LegacyBookmarksStoreMigration { let newFavoritesRoot = BookmarkUtils.fetchLegacyFavoritesFolder(destination) else { if bookmarkRoots.isEmpty { - Pixel.fire(.debug(event: .bookmarksCouldNotPrepareDatabase)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCouldNotPrepareDatabase)) } else { - Pixel.fire(.debug(event: .bookmarksMigrationCouldNotPrepareDatabase)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksMigrationCouldNotPrepareDatabase)) } Thread.sleep(forTimeInterval: 2) @@ -157,7 +158,7 @@ public class LegacyBookmarksStoreMigration { } do { - try destination.save(onErrorFire: .bookmarksMigrationFailed) + try destination.save(onErrorFire: GeneralPixel.bookmarksMigrationFailed) cleanupOldData(in: source) } catch { @@ -165,7 +166,7 @@ public class LegacyBookmarksStoreMigration { BookmarkUtils.prepareLegacyFoldersStructure(in: destination) do { - try destination.save(onErrorFire: .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration) + try destination.save(onErrorFire: GeneralPixel.bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration) } catch { Thread.sleep(forTimeInterval: 2) fatalError("Could not write to Bookmarks DB") @@ -181,7 +182,7 @@ public class LegacyBookmarksStoreMigration { allObjects.forEach { context.delete($0) } if context.hasChanges { - try? context.save(onErrorFire: .bookmarksMigrationCouldNotRemoveOldStore) + try? context.save(onErrorFire: GeneralPixel.bookmarksMigrationCouldNotRemoveOldStore) } } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift b/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift index 815dc8ecf9..467af32bbd 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift @@ -20,18 +20,19 @@ import Foundation import Bookmarks import Common import Persistence +import PixelKit public class BookmarksCleanupErrorHandling: EventMapping { public init() { super.init { event, _, _, _ in if event.cleanupError is BookmarksCleanupCancelledError { - Pixel.fire(.debug(event: .bookmarksCleanupAttemptedWhileSyncWasEnabled)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCleanupAttemptedWhileSyncWasEnabled)) } else { let processedErrors = CoreDataErrorsParser.parse(error: event.cleanupError as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .bookmarksCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) } } } diff --git a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift index e2be507a44..fb21f3222f 100644 --- a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift @@ -199,10 +199,10 @@ final class LocalBookmarkStore: BookmarkStore { var params = processedErrors.errorPixelParameters params[PixelKit.Parameters.errorSource] = source - Pixel.fire(.debug(event: .bookmarksSaveFailed, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailed, error: error), withAdditionalParameters: params) } else { - Pixel.fire(.debug(event: .bookmarksSaveFailed, error: localError), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailed, error: localError), withAdditionalParameters: [PixelKit.Parameters.errorSource: source]) } } else { @@ -211,7 +211,7 @@ final class LocalBookmarkStore: BookmarkStore { var params = processedErrors.errorPixelParameters params[PixelKit.Parameters.errorSource] = source - Pixel.fire(.debug(event: .bookmarksSaveFailed, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailed, error: error), withAdditionalParameters: params) } } @@ -228,7 +228,7 @@ final class LocalBookmarkStore: BookmarkStore { ) if context.hasChanges { - try context.save(onErrorFire: .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders) + try context.save(onErrorFire: GeneralPixel.bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders) } } catch { Thread.sleep(forTimeInterval: 1) @@ -310,7 +310,7 @@ final class LocalBookmarkStore: BookmarkStore { } else if let root = bookmarksRoot(in: context) { parentEntity = root } else { - Pixel.fire(.debug(event: .missingParent)) + PixelKit.fire(DebugEvent(GeneralPixel.missingParent)) throw BookmarkStoreError.missingParent } @@ -506,7 +506,7 @@ final class LocalBookmarkStore: BookmarkStore { } else if let root = self.bookmarksRoot(in: context) { parentEntity = root } else { - Pixel.fire(.debug(event: .missingParent)) + PixelKit.fire(DebugEvent(GeneralPixel.missingParent)) throw BookmarkStoreError.missingParent } @@ -720,7 +720,7 @@ final class LocalBookmarkStore: BookmarkStore { let processedErrors = CoreDataErrorsParser.parse(error: error) if NSApp.runType.requiresEnvironment { - Pixel.fire(.debug(event: .bookmarksSaveFailedOnImport, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailedOnImport, error: error), withAdditionalParameters: processedErrors.errorPixelParameters) assertionFailure("LocalBookmarkStore: Saving of context failed, error: \(error.localizedDescription)") } @@ -856,7 +856,7 @@ final class LocalBookmarkStore: BookmarkStore { } if deletedEntityCount > 0 { - Pixel.fire(.debug(event: .removedInvalidBookmarkManagedObjects)) + PixelKit.fire(DebugEvent(GeneralPixel.removedInvalidBookmarkManagedObjects)) } } onError: { [weak self] error in self?.commonOnSaveErrorHandler(error) @@ -904,7 +904,7 @@ final class LocalBookmarkStore: BookmarkStore { let nsError = error as NSError let processedErrors = CoreDataErrorsParser.parse(error: nsError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .favoritesCleanupFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.favoritesCleanupFailed, error: error), withAdditionalParameters: params) } onDidSave: {} } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift index ecc643b33d..837af92acd 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift @@ -603,7 +603,7 @@ extension BookmarkManagementDetailViewController: NSMenuDelegate { } // If only one item is selected try to get the item and its parent folder otherwise show the menu for multiple items. - if tableView.selectedRowIndexes.contains(row), tableView.selectedRowIndexes.count > 1 { + if tableView.selectedRowIndexes.contains(row), tableView.selectedRowIndexes.count > 1 { return ContextualMenu.menu(for: self.selectedItems()) } diff --git a/DuckDuckGo/Common/Database/Database.swift b/DuckDuckGo/Common/Database/Database.swift index 10e116d3a7..dee8aef2d8 100644 --- a/DuckDuckGo/Common/Database/Database.swift +++ b/DuckDuckGo/Common/Database/Database.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import CoreData import Foundation import Persistence +import PixelKit final class Database { @@ -100,7 +101,7 @@ final class Database { // Fire the pixel once a day at max if lastPixelSentAt < Date.daysAgo(1) { lastDatabaseFactoryFailurePixelDate = Date() - Pixel.fire(.debug(event: .dbMakeDatabaseError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.dbMakeDatabaseError(error: error))) } } } @@ -185,14 +186,14 @@ extension NSManagedObjectModel { extension NSManagedObjectContext { - func save(onErrorFire event: Pixel.Event.Debug) throws { + func save(onErrorFire event: PixelKitEventV2) throws { do { try save() } catch { let nsError = error as NSError let processedErrors = CoreDataErrorsParser.parse(error: nsError) - Pixel.fire(.debug(event: event, error: error), + PixelKit.fire(DebugEvent(event, error: error), withAdditionalParameters: processedErrors.errorPixelParameters) throw error diff --git a/DuckDuckGo/Common/FileSystem/FileStore.swift b/DuckDuckGo/Common/FileSystem/FileStore.swift index 99b03e5a01..22dc52c7d1 100644 --- a/DuckDuckGo/Common/FileSystem/FileStore.swift +++ b/DuckDuckGo/Common/FileSystem/FileStore.swift @@ -18,6 +18,7 @@ import Foundation import CryptoKit +import PixelKit protocol FileStore { func persist(_ data: Data, url: URL) -> Bool @@ -57,7 +58,7 @@ final class EncryptedFileStore: FileStore { return true } catch { - Pixel.fire(.debug(event: .fileStoreWriteFailed, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.fileStoreWriteFailed, error: error), withAdditionalParameters: ["config": url.lastPathComponent]) return false } diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 0337f4f9a6..e9bdee32d4 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -66,54 +66,54 @@ struct UserText { } // MARK: - Main Menu -> DuckDuckGo - static let mainMenuAppPreferences = NSLocalizedString("main-menu.app.preferences" ,value:"Preferences…", comment: "Main Menu DuckDuckGo item") - static let mainMenuAppServices = NSLocalizedString("main-menu.app.services", value:"Services", comment: "Main Menu DuckDuckGo item") - static let mainMenuAppCheckforUpdates = NSLocalizedString("main-menu.app.check-for-updates", value:"Check for Updates…", comment: "Main Menu DuckDuckGo item") - static let mainMenuAppHideDuckDuckGo = NSLocalizedString("main-menu.app.hide-duck-duck-go", value:"Hide DuckDuckGo", comment: "Main Menu DuckDuckGo item") - static let mainMenuAppHideOthers = NSLocalizedString("main-menu.app.hide-others", value:"Hide Others", comment: "Main Menu DuckDuckGo item") - static let mainMenuAppShowAll = NSLocalizedString("main-menu.app.show-all", value:"Show All", comment: "Main Menu DuckDuckGo item") - static let mainMenuAppQuitDuckDuckGo = NSLocalizedString("main-menu.app.quit-duck-duck-go", value:"Quit DuckDuckGo", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppPreferences = NSLocalizedString("main-menu.app.preferences", value: "Preferences…", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppServices = NSLocalizedString("main-menu.app.services", value: "Services", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppCheckforUpdates = NSLocalizedString("main-menu.app.check-for-updates", value: "Check for Updates…", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppHideDuckDuckGo = NSLocalizedString("main-menu.app.hide-duck-duck-go", value: "Hide DuckDuckGo", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppHideOthers = NSLocalizedString("main-menu.app.hide-others", value: "Hide Others", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppShowAll = NSLocalizedString("main-menu.app.show-all", value: "Show All", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppQuitDuckDuckGo = NSLocalizedString("main-menu.app.quit-duck-duck-go", value: "Quit DuckDuckGo", comment: "Main Menu DuckDuckGo item") // MARK: - Main Menu -> -File - static let mainMenuFile = NSLocalizedString("main-menu.file", value:"File", comment: "Main Menu File") - static let mainMenuFileNewTab = NSLocalizedString("main-menu.file.new-tab", value:"New Tab", comment: "Main Menu File item") - static let mainMenuFileOpenLocation = NSLocalizedString("main-menu.file.open-location", value:"Open Location…", comment: "Main Menu File item- Menu option that allows the user to connect to an address (type an address) on click the address bar of the browser is selected and the user can type.") - static let mainMenuFileCloseWindow = NSLocalizedString("main-menu.file.close-window", value:"Close Window", comment: "Main Menu File item") - static let mainMenuFileCloseAllWindows = NSLocalizedString("main-menu.file.close-all-windows", value:"Close All Windows", comment: "Main Menu File item") - static let mainMenuFileSaveAs = NSLocalizedString("main-menu.file.save-as", value:"Save As…", comment: "Main Menu File item") - static let mainMenuFileImportBookmarksandPasswords = NSLocalizedString("main-menu.file.import-bookmarks-and-passwords", value:"Import Bookmarks and Passwords…", comment: "Main Menu File item") - static let mainMenuFileExport = NSLocalizedString("main-menu.file.export", value:"Export", comment: "Main Menu File item") - static let mainMenuFileExportPasswords = NSLocalizedString("main-menu.file.export-passwords", value:"Passwords…", comment: "Main Menu File-Export item") - static let mainMenuFileExportBookmarks = NSLocalizedString("main-menu.file.export-bookmarks", value:"Bookmarks…", comment: "Main Menu File-Export item") + static let mainMenuFile = NSLocalizedString("main-menu.file", value: "File", comment: "Main Menu File") + static let mainMenuFileNewTab = NSLocalizedString("main-menu.file.new-tab", value: "New Tab", comment: "Main Menu File item") + static let mainMenuFileOpenLocation = NSLocalizedString("main-menu.file.open-location", value: "Open Location…", comment: "Main Menu File item- Menu option that allows the user to connect to an address (type an address) on click the address bar of the browser is selected and the user can type.") + static let mainMenuFileCloseWindow = NSLocalizedString("main-menu.file.close-window", value: "Close Window", comment: "Main Menu File item") + static let mainMenuFileCloseAllWindows = NSLocalizedString("main-menu.file.close-all-windows", value: "Close All Windows", comment: "Main Menu File item") + static let mainMenuFileSaveAs = NSLocalizedString("main-menu.file.save-as", value: "Save As…", comment: "Main Menu File item") + static let mainMenuFileImportBookmarksandPasswords = NSLocalizedString("main-menu.file.import-bookmarks-and-passwords", value: "Import Bookmarks and Passwords…", comment: "Main Menu File item") + static let mainMenuFileExport = NSLocalizedString("main-menu.file.export", value: "Export", comment: "Main Menu File item") + static let mainMenuFileExportPasswords = NSLocalizedString("main-menu.file.export-passwords", value: "Passwords…", comment: "Main Menu File-Export item") + static let mainMenuFileExportBookmarks = NSLocalizedString("main-menu.file.export-bookmarks", value: "Bookmarks…", comment: "Main Menu File-Export item") // MARK: - Main Menu -> Edit - static let mainMenuEdit = NSLocalizedString("main-menu.edit", value:"Edit", comment: "Main Menu Edit") - static let mainMenuEditUndo = NSLocalizedString("main-menu.edit.undo", value:"Undo", comment: "Main Menu Edit item") - static let mainMenuEditRedo = NSLocalizedString("main-menu.edit.redo", value:"Redo", comment: "Main Menu Edit item") - static let mainMenuEditCut = NSLocalizedString("main-menu.edit.cut", value:"Cut", comment: "Main Menu Edit item") - static let mainMenuEditCopy = NSLocalizedString("main-menu.edit.copy", value:"Copy", comment: "Main Menu Edit item") - static let mainMenuEditPaste = NSLocalizedString("main-menu.edit.paste", value:"Paste", comment: "Main Menu Edit item") - static let mainMenuEditPasteAndMatchStyle = NSLocalizedString("main-menu.edit.paste-and-match-style", value:"Paste and Match Style", comment: "Main Menu Edit item - Action that allows the user to paste copy into a target document and the target document's style will be retained (instead of the source style)") - static let mainMenuEditDelete = NSLocalizedString("main-menu.edit.delete", value:"Delete", comment: "Main Menu Edit item") - static let mainMenuEditSelectAll = NSLocalizedString("main-menu.edit.select-all", value:"Select All", comment: "Main Menu Edit item") - - static let mainMenuEditFind = NSLocalizedString("main-menu.edit.find", value:"Find", comment: "Main Menu Edit item") + static let mainMenuEdit = NSLocalizedString("main-menu.edit", value: "Edit", comment: "Main Menu Edit") + static let mainMenuEditUndo = NSLocalizedString("main-menu.edit.undo", value: "Undo", comment: "Main Menu Edit item") + static let mainMenuEditRedo = NSLocalizedString("main-menu.edit.redo", value: "Redo", comment: "Main Menu Edit item") + static let mainMenuEditCut = NSLocalizedString("main-menu.edit.cut", value: "Cut", comment: "Main Menu Edit item") + static let mainMenuEditCopy = NSLocalizedString("main-menu.edit.copy", value: "Copy", comment: "Main Menu Edit item") + static let mainMenuEditPaste = NSLocalizedString("main-menu.edit.paste", value: "Paste", comment: "Main Menu Edit item") + static let mainMenuEditPasteAndMatchStyle = NSLocalizedString("main-menu.edit.paste-and-match-style", value: "Paste and Match Style", comment: "Main Menu Edit item - Action that allows the user to paste copy into a target document and the target document's style will be retained (instead of the source style)") + static let mainMenuEditDelete = NSLocalizedString("main-menu.edit.delete", value: "Delete", comment: "Main Menu Edit item") + static let mainMenuEditSelectAll = NSLocalizedString("main-menu.edit.select-all", value: "Select All", comment: "Main Menu Edit item") + + static let mainMenuEditFind = NSLocalizedString("main-menu.edit.find", value: "Find", comment: "Main Menu Edit item") // MARK: Main Menu -> Edit -> Find - static let mainMenuEditFindFindNext = NSLocalizedString("main-menu.edit.find.find-next", value:"Find Next", comment: "Main Menu Edit-Find item") - static let mainMenuEditFindFindPrevious = NSLocalizedString("main-menu.edit.find.find-previous", value:"Find Previous", comment: "Main Menu Edit-Find item") - static let mainMenuEditFindHideFind = NSLocalizedString("main-menu.edit.find.hide-find", value:"Hide Find", comment: "Main Menu Edit-Find item") + static let mainMenuEditFindFindNext = NSLocalizedString("main-menu.edit.find.find-next", value: "Find Next", comment: "Main Menu Edit-Find item") + static let mainMenuEditFindFindPrevious = NSLocalizedString("main-menu.edit.find.find-previous", value: "Find Previous", comment: "Main Menu Edit-Find item") + static let mainMenuEditFindHideFind = NSLocalizedString("main-menu.edit.find.hide-find", value: "Hide Find", comment: "Main Menu Edit-Find item") static let mainMenuEditSpellingandGrammar = NSLocalizedString("main-menu.edit.edit-spelling-and-grammar", value: "Spelling and Grammar", comment: "Main Menu Edit item") // MARK: Main Menu -> Edit -> Spellingand - static let mainMenuEditSpellingandShowSpellingandGrammar = NSLocalizedString("main-menu.edit.spelling-and.show-spelling-and-grammar", value:"Show Spelling and Grammar", comment: "Main Menu Edit-Spellingand item") - static let mainMenuEditSpellingandCheckDocumentNow = NSLocalizedString("main-menu.edit.spelling-and.check-document-now", value:"Check Document Now", comment: "Main Menu Edit-Spellingand item") - static let mainMenuEditSpellingandCheckSpellingWhileTyping = NSLocalizedString("main-menu.edit.spelling-and.check-spelling-while-typing", value:"Check Spelling While Typing", comment: "Main Menu Edit-Spellingand item") - static let mainMenuEditSpellingandCheckGrammarWithSpelling = NSLocalizedString("main-menu.edit.spelling-and.check-grammar-with-spelling", value:"Check Grammar With Spelling", comment: "Main Menu Edit-Spellingand item") - static let mainMenuEditSpellingandCorrectSpellingAutomatically = NSLocalizedString("main-menu.edit.spelling-and.correct-spelling-automatically", value:"Correct Spelling Automatically", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandShowSpellingandGrammar = NSLocalizedString("main-menu.edit.spelling-and.show-spelling-and-grammar", value: "Show Spelling and Grammar", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCheckDocumentNow = NSLocalizedString("main-menu.edit.spelling-and.check-document-now", value: "Check Document Now", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCheckSpellingWhileTyping = NSLocalizedString("main-menu.edit.spelling-and.check-spelling-while-typing", value: "Check Spelling While Typing", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCheckGrammarWithSpelling = NSLocalizedString("main-menu.edit.spelling-and.check-grammar-with-spelling", value: "Check Grammar With Spelling", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCorrectSpellingAutomatically = NSLocalizedString("main-menu.edit.spelling-and.correct-spelling-automatically", value: "Correct Spelling Automatically", comment: "Main Menu Edit-Spellingand item") - static let mainMenuEditSubstitutions = NSLocalizedString("main-menu.edit.subsitutions", value:"Substitutions", comment: "Main Menu Edit item") + static let mainMenuEditSubstitutions = NSLocalizedString("main-menu.edit.subsitutions", value: "Substitutions", comment: "Main Menu Edit item") // TODO: Done till here // MARK: Main Menu -> Edit -> Substitutions static let mainMenuEditSubstitutionsShowSubstitutions = NSLocalizedString("Show Substitutions", comment: "Main Menu Edit-Substitutions item") @@ -143,7 +143,7 @@ struct UserText { static let mainMenuViewReloadPage = NSLocalizedString("Reload Page", comment: "Main Menu View item") static let mainMenuViewHome = NSLocalizedString("Home", comment: "Main Menu View item") static let mainMenuHomeButton = NSLocalizedString("Home Button", comment: "Main Menu > View > Home Button item") - + static func mainMenuHomeButtonMode(for position: HomeButtonPosition) -> String { switch position { case .hidden: @@ -154,7 +154,7 @@ struct UserText { return NSLocalizedString("main.menu.home.button.mode.right", value: "Show Right of the Reload Button", comment: "Main Menu > View > Home Button > right position item") } } - + static let mainMenuViewShowAutofillShortcut = NSLocalizedString("Show Autofill Shortcut", comment: "Main Menu View item") static let mainMenuViewShowBookmarksShortcut = NSLocalizedString("Show Bookmarks Shortcut", comment: "Main Menu View item") static let mainMenuViewShowDownloadsShortcut = NSLocalizedString("Show Downloads Shortcut", comment: "Main Menu View item") @@ -391,7 +391,7 @@ struct UserText { static let restartBitwarden = NSLocalizedString("restart.bitwarden", value: "Restart Bitwarden", comment: "Button to restart Bitwarden application") static let restartBitwardenInfo = NSLocalizedString("restart.bitwarden.info", value: "Bitwarden is not responding. Please restart it to initiate the communication again", comment: "This string represents a message informing the user that Bitwarden is not responding and prompts them to restart the application to initiate communication again.") - static let autofillViewContentButton = NSLocalizedString("autofill.view-autofill-content", value: "View Autofill Content…", comment: "View Autofill Content Button name in the autofill settings") + static let autofillViewContentButton = NSLocalizedString("autofill.view-autofill-content", value: "View Autofill Content…", comment: "View Autofill Content Button name in the autofill settings") static let autofillAskToSave = NSLocalizedString("autofill.ask-to-save", value: "Save and Autofill", comment: "Autofill settings section title") static let autofillAskToSaveExplanation = NSLocalizedString("autofill.ask-to-save.explanation", value: "Receive prompts to save new information and autofill online forms.", comment: "Description of Autofill autosaving feature - used in settings") static let autofillUsernamesAndPasswords = NSLocalizedString("autofill.usernames-and-passwords", value: "Usernames and passwords", comment: "Autofill autosaved data type") @@ -400,14 +400,14 @@ struct UserText { static let autofillExcludedSites = NSLocalizedString("autofill.excluded-sites", value: "Excluded Sites", comment: "Autofill settings section title") static let autofillExcludedSitesExplanation = NSLocalizedString("autofill.excluded-sites.explanation", value: "Websites you selected to never ask to save your password.", comment: "Subtitle providing additional information about the excluded sites section") static let autofillExcludedSitesReset = NSLocalizedString("autofill.excluded-sites.reset", value: "Reset", comment: "Button title allowing users to reset their list of excluded sites") - static let autofillExcludedSitesResetActionTitle = NSLocalizedString("autofill.excluded-sites.reset.action.title", value:"Reset Excluded Sites?", comment: "Alert title") - static let autofillExcludedSitesResetActionMessage = NSLocalizedString("autofill.excluded-sites.reset.action.message", value:"If you reset excluded sites, you will be prompted to save your password next time you sign in to any of these sites.", comment: "Alert title") + static let autofillExcludedSitesResetActionTitle = NSLocalizedString("autofill.excluded-sites.reset.action.title", value: "Reset Excluded Sites?", comment: "Alert title") + static let autofillExcludedSitesResetActionMessage = NSLocalizedString("autofill.excluded-sites.reset.action.message", value: "If you reset excluded sites, you will be prompted to save your password next time you sign in to any of these sites.", comment: "Alert title") static let autofillAutoLock = NSLocalizedString("autofill.auto-lock", value: "Auto-lock", comment: "Autofill settings section title") static let autofillLockWhenIdle = NSLocalizedString("autofill.lock-when-idle", value: "Lock autofill after computer is idle for", comment: "Autofill auto-lock setting") static let autofillNeverLock = NSLocalizedString("autofill.never-lock", value: "Never lock autofill", comment: "Autofill auto-lock setting") static let autofillNeverLockWarning = NSLocalizedString("autofill.never-lock-warning", value: "If not locked, anyone with access to your device will be able to use and modify your autofill data. For security purposes, credit card form fill always requires authentication.", comment: "Autofill disabled auto-lock warning") static let autolockLocksFormFill = NSLocalizedString("autofill.autolock-locks-form-filling", value: "Also lock password form fill", comment: "Lock form filling when auto-lock is active text") - + static let downloadsLocation = NSLocalizedString("downloads.location", value: "Location", comment: "Downloads directory location") static let downloadsAlwaysAsk = NSLocalizedString("downloads.always-ask", value: "Always ask where to save files", comment: "Downloads preferences checkbox") static let downloadsChangeDirectory = NSLocalizedString("downloads.change", value: "Change…", comment: "Change downloads directory button") @@ -555,7 +555,7 @@ struct UserText { static let general = NSLocalizedString("preferences.general", value: "General", comment: "Title of the option to show the General preferences") static let sync = NSLocalizedString("preferences.sync", value: "Sync & Backup", comment: "Title of the option to show the Sync preferences") - static let syncAutoLockPrompt = NSLocalizedString("preferences.sync.auto-lock-prompt", value:"Unlock device to setup Sync & Backup", comment: "Reason for auth when setting up Sync") + static let syncAutoLockPrompt = NSLocalizedString("preferences.sync.auto-lock-prompt", value: "Unlock device to setup Sync & Backup", comment: "Reason for auth when setting up Sync") static let syncBookmarkPausedAlertTitle = NSLocalizedString("alert.sync-bookmarks-paused-title", value: "Bookmarks Sync is Paused", comment: "Title for alert shown when sync bookmarks paused for too many items") static let syncBookmarkPausedAlertDescription = NSLocalizedString("alert.sync-bookmarks-paused-description", value: "You have exceeded the bookmarks sync limit. Try deleting some bookmarks. Until this is resolved your bookmarks will not be backed up.", comment: "Description for alert shown when sync bookmarks paused for too many items") static let syncCredentialsPausedAlertTitle = NSLocalizedString("alert.sync-credentials-paused-title", value: "Passwords Sync is Paused", comment: "Title for alert shown when sync credentials paused for too many items") @@ -592,7 +592,7 @@ struct UserText { static let onStartup = NSLocalizedString("preferences.on-startup", value: "On Startup", comment: "Name of the preferences section related to app startup") static let reopenAllWindowsFromLastSession = NSLocalizedString("preferences.reopen-windows", value: "Reopen all windows from last session", comment: "Option to control session restoration") static let showHomePage = NSLocalizedString("preferences.show-home", value: "Open a new window", comment: "Option to control session startup") - + static let homePage = NSLocalizedString("preferences-homepage.title", value: "Homepage", comment: "Title for Homepage section in settings") static let homePageDescription = NSLocalizedString("preferences-homepage.description", value: "When navigating home or opening new windows.", comment: "Homepage behavior description") static let newTab = NSLocalizedString("preferences-homepage-newTab", value: "New Tab page", comment: "Option to open a new tab") @@ -639,7 +639,6 @@ struct UserText { static let aboutUnsupportedDeviceInfo2Part4 = "of DuckDuckGo. You can also keep using your current version of the browser, but it will not receive further updates." static let unsupportedDeviceInfoAlertHeader = NSLocalizedString("unsupported.device.info.alert.header", value: "Your version of macOS is no longer supported.", comment: "his string represents the header for an alert informing the user that their version of macOS is no longer supported") - static func moreAt(url: String) -> String { let localized = NSLocalizedString("preferences.about.more-at", value: "More at %@", comment: "Link to the about page") return String(format: localized, url) @@ -916,9 +915,9 @@ struct UserText { // MARK: - Tooltips static let autofillShortcutTooltip = NSLocalizedString("tooltip.autofill.shortcut", value: "Autofill", comment: "Tooltip for the autofill shortcut") - + static let homeButtonTooltip = NSLocalizedString("tooltip.home.button", value: "Home", comment: "Tooltip for the home button") - + static let bookmarksShortcutTooltip = NSLocalizedString("tooltip.bookmarks.shortcut", value: "Bookmarks", comment: "Tooltip for the bookmarks shortcut") static let downloadsShortcutTooltip = NSLocalizedString("tooltip.downloads.shortcut", value: "Downloads", comment: "Tooltip for the downloads shortcut") @@ -957,7 +956,7 @@ struct UserText { static let copyPasswordTooltip = NSLocalizedString("autofill.copy-password", value: "Copy password", comment: "Tooltip for the Autofill panel's Copy Password button") static let showPasswordTooltip = NSLocalizedString("autofill.show-password", value: "Show password", comment: "Tooltip for the Autofill panel's Show Password button") static let hidePasswordTooltip = NSLocalizedString("autofill.hide-password", value: "Hide password", comment: "Tooltip for the Autofill panel's Hide Password button") - + static let autofillShowCardCvvTooltip = NSLocalizedString("autofill.show-card-cvv", value: "Show CVV", comment: "Tooltip for the Autofill panel's Show CVV button") static let autofillHideCardCvvTooltip = NSLocalizedString("autofill.hide-card-cvv", value: "Hide CVV", comment: "Tooltip for the Autofill panel's Hide CVV button") @@ -975,17 +974,16 @@ struct UserText { let localized = NSLocalizedString("autofill.popover.password-manager-connected-to-user", value: "Connected to user %@", comment: "Label describing what user is connected to the password manager") return String(format: localized, user) } - + static func passwordManagerAutosavePopoverText(domain: String) -> String { let localized = NSLocalizedString("autofill.popover.autosave.text", value: "Password saved for %@", comment: "Text confirming a password has been saved for the %@ domain") return String(format: localized, domain) } - + static let passwordManagerAutosaveButtonText = NSLocalizedString("autofill.popover.autosave.button.text", value: "View", comment: "Button to view the recently autosaved password") - static func openPasswordManagerButton(managerName: String) -> String { let localized = NSLocalizedString("autofill.popover.open-password-manager", value: "Open %@", comment: "Open password manager button") return String(format: localized, managerName) @@ -993,7 +991,7 @@ struct UserText { static let passwordManagerLockedStatus = NSLocalizedString("autofill.manager.status.locked", value: "Locked", comment: "Locked status for password manager") static let passwordManagerUnlockedStatus = NSLocalizedString("autofill.manager.status.unlocked", value: "Unlocked", comment: "Unlocked status for password manager") - + static func alertTitle(from domain: String) -> String { let localized = NSLocalizedString("alert.title", value: "A message from %@", comment: "Title formatted with presenting domain") return String(format: localized, domain) @@ -1068,7 +1066,6 @@ struct UserText { // "tab.dbp.title" - Tab data broker protection title static let tabDataBrokerProtectionTitle = "Personal Information Removal" - // Bookmarks bar prompt static let bookmarksBarPromptTitle = NSLocalizedString("bookmarks.bar.prompt.title", value: "Show Bookmarks Bar?", comment: "Title for bookmarks bar prompt") static let bookmarksBarPromptMessage = NSLocalizedString("bookmarks.bar.prompt.message", value: "Show the Bookmarks Bar for quick access to your new bookmarks.", comment: "Message show for bookmarks bar prompt") diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index a64673dfc0..531fbe4985 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -22,6 +22,7 @@ import BrowserServicesKit import Configuration import Common import Networking +import PixelKit @MainActor final class ConfigurationManager { @@ -72,13 +73,13 @@ final class ConfigurationManager { eventMapping: Self.configurationDebugEvents) private static let configurationDebugEvents = EventMapping { event, error, _, _ in - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel switch event { case .invalidPayload(let configuration): domainEvent = .invalidPayload(configuration) } - Pixel.fire(.debug(event: domainEvent, error: error)) + PixelKit.fire(DebugEvent(domainEvent, error: error)) } func start() { @@ -170,7 +171,7 @@ final class ConfigurationManager { } os_log("Failed to complete configuration update %@", log: .config, type: .error, error.localizedDescription) - Pixel.fire(.debug(event: .configurationFetchError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.configurationFetchError(error: error))) tryAgainSoon() } diff --git a/DuckDuckGo/Configuration/ConfigurationStore.swift b/DuckDuckGo/Configuration/ConfigurationStore.swift index 179e5addd7..fe58ca3df2 100644 --- a/DuckDuckGo/Configuration/ConfigurationStore.swift +++ b/DuckDuckGo/Configuration/ConfigurationStore.swift @@ -19,6 +19,7 @@ import Common import Foundation import Configuration +import PixelKit final class ConfigurationStore: ConfigurationStoring { @@ -99,7 +100,7 @@ final class ConfigurationStore: ConfigurationStoring { let nserror = error as NSError if nserror.domain != NSCocoaErrorDomain || nserror.code != NSFileReadNoSuchFileError { - Pixel.fire(.debug(event: .trackerDataCouldNotBeLoaded, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.trackerDataCouldNotBeLoaded, error: error)) } return nil diff --git a/DuckDuckGo/ContentBlocker/ContentBlocking.swift b/DuckDuckGo/ContentBlocker/ContentBlocking.swift index 795f5ec581..d935081755 100644 --- a/DuckDuckGo/ContentBlocker/ContentBlocking.swift +++ b/DuckDuckGo/ContentBlocker/ContentBlocking.swift @@ -21,6 +21,7 @@ import WebKit import Combine import BrowserServicesKit import Common +import PixelKit protocol ContentBlockingProtocol { @@ -98,18 +99,18 @@ final class AppContentBlocking { } private let toggleProtectionsEvents = EventMapping { event, _, parameters, _ in - let domainEvent: Pixel.Event + let domainEvent: GeneralPixel switch event { case .toggleProtectionsCounterDaily: domainEvent = .toggleProtectionsDailyCount } - Pixel.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) + PixelKit.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) } private static let debugEvents = EventMapping { event, error, parameters, onComplete in guard NSApp.runType.requiresEnvironment else { return } - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel switch event { case .trackerDataParseFailed: domainEvent = .trackerDataParseFailed @@ -132,7 +133,7 @@ final class AppContentBlocking { case .contentBlockingCompilationFailed(let listName, let component): let defaultTDSListName = DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName - let listType: Pixel.Event.CompileRulesListType + let listType: GeneralPixel.CompileRulesListType switch listName { case defaultTDSListName: listType = .tds @@ -154,13 +155,15 @@ final class AppContentBlocking { return } - Pixel.fire(.debug(event: domainEvent, error: error), withAdditionalParameters: parameters, onComplete: onComplete) + PixelKit.fire(DebugEvent(domainEvent, error: error), withAdditionalParameters: parameters) { _, error in + onComplete(error) + } } // MARK: - Ad Click Attribution let attributionEvents: EventMapping? = .init { event, _, parameters, _ in - let domainEvent: Pixel.Event + let domainEvent: GeneralPixel switch event { case .adAttributionDetected: domainEvent = .adClickAttributionDetected @@ -170,11 +173,11 @@ final class AppContentBlocking { domainEvent = .adClickAttributionPageLoads } - Pixel.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) + PixelKit.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) } let attributionDebugEvents: EventMapping? = .init { event, _, _, _ in - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel switch event { case .adAttributionCompilationFailedForAttributedRulesList: domainEvent = .adAttributionCompilationFailedForAttributedRulesList @@ -198,8 +201,7 @@ final class AppContentBlocking { domainEvent = .adAttributionLogicWrongVendorOnFailedCompilation } - Pixel.fire(.debug(event: domainEvent, error: nil), - includeAppVersionParameter: false) + PixelKit.fire(DebugEvent(domainEvent), includeAppVersionParameter: false) } } diff --git a/DuckDuckGo/CrashReports/Model/CrashReporter.swift b/DuckDuckGo/CrashReports/Model/CrashReporter.swift index 39288b6043..df3d05fab4 100644 --- a/DuckDuckGo/CrashReports/Model/CrashReporter.swift +++ b/DuckDuckGo/CrashReports/Model/CrashReporter.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit final class CrashReporter { @@ -47,7 +48,7 @@ final class CrashReporter { return } - Pixel.fire(.crash) + PixelKit.fire(GeneralPixel.crash) latestCrashReport = latest promptPresenter.showPrompt(self, for: latest) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index c8d4c2b3ca..61389aba24 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -151,17 +151,15 @@ public class DataBrokerProtectionPixelsHandler: EventMapping (URL?, UTType?) @@ -274,7 +275,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable self.download.cancel() self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) - Pixel.fire(.debug(event: .fileGetDownloadLocationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileGetDownloadLocationFailed, error: error)) } return nil } @@ -334,7 +335,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable self.download.cancel() self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) - Pixel.fire(.debug(event: .fileDownloadCreatePresentersFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileDownloadCreatePresentersFailed, error: error)) } } @@ -393,7 +394,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable } catch { // fallback: move failed, keep the temp file in the original location os_log(.error, log: log, "🙁 fallback with \(error), will use \(tempURL.path)") - Pixel.fire(.debug(event: .fileAccessRelatedItemFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileAccessRelatedItemFailed, error: error)) return tempURL } } @@ -686,7 +687,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { self.finish(with: .success(destinationFile)) } catch { - Pixel.fire(.debug(event: .fileMoveToDownloadsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileMoveToDownloadsFailed, error: error)) os_log(.error, log: log, "fileMoveToDownloadsFailed: \(error)") self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) } diff --git a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift index 70cfda3f2d..55763a2c5c 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift @@ -20,6 +20,7 @@ import Combine import Common import Foundation import Navigation +import PixelKit @MainActor private func getFirstAvailableWebView() -> WKWebView? { @@ -482,7 +483,7 @@ final class DownloadListCoordinator { } } catch { assertionFailure("Resume data coding failed: \(error)") - Pixel.fire(.debug(event: .downloadResumeDataCodingFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.downloadResumeDataCodingFailed, error: error)) } webView.resumeDownload(fromResumeData: resumeData, diff --git a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift index cec1d0e0da..9af9ffb38a 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift @@ -21,6 +21,7 @@ import Common import CoreData import Foundation import UniformTypeIdentifiers +import PixelKit protocol DownloadListStoring { @@ -191,7 +192,7 @@ extension DownloadListItem { let modified = managedObject.modified, let url = managedObject.urlEncrypted as? URL else { - Pixel.fire(.debug(event: .downloadListItemDecryptionFailedUnique), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.downloadListItemDecryptionFailedUnique), frequency: .daily) assertionFailure("DownloadListItem: Failed to init from ManagedObject") return nil } diff --git a/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift b/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift index 798ebc2f7c..30314266da 100644 --- a/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift +++ b/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit protocol TabDataClearing { func prepareForDataClearing(caller: TabCleanupPreparer) @@ -65,7 +66,7 @@ final class TabCleanupPreparer: NSObject, WKNavigationDelegate { } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - Pixel.fire(.debug(event: .blankNavigationOnBurnFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.blankNavigationOnBurnFailed, error: error)) processedTabs += 1 notifyIfDone() diff --git a/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift b/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift index b10b728212..1221497f5f 100644 --- a/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift +++ b/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift @@ -20,6 +20,7 @@ import Cocoa import BrowserServicesKit import Common import History +import PixelKit @MainActor final class FirePopoverViewModel { @@ -188,7 +189,7 @@ final class FirePopoverViewModel { // MARK: - Burning func burn() { - Pixel.fire(.fireButtonFirstBurn, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.fireButtonFirstBurn, frequency: .daily) switch (clearingOption, areAllSelected) { case (.currentTab, _): @@ -197,7 +198,7 @@ final class FirePopoverViewModel { assertionFailure("No tab selected") return } - Pixel.fire(.fireButton(option: .tab)) + PixelKit.fire(GeneralPixel.fireButton(option: .tab)) let burningEntity = Fire.BurningEntity.tab(tabViewModel: tabViewModel, selectedDomains: selectedDomains, parentTabCollectionViewModel: tabCollectionViewModel) @@ -207,17 +208,17 @@ final class FirePopoverViewModel { assertionFailure("FirePopoverViewModel: TabCollectionViewModel is not present") return } - Pixel.fire(.fireButton(option: .window)) + PixelKit.fire(GeneralPixel.fireButton(option: .window)) let burningEntity = Fire.BurningEntity.window(tabCollectionViewModel: tabCollectionViewModel, selectedDomains: selectedDomains) fireViewModel.fire.burnEntity(entity: burningEntity) case (.allData, true): - Pixel.fire(.fireButton(option: .allSites)) + PixelKit.fire(GeneralPixel.fireButton(option: .allSites)) fireViewModel.fire.burnAll() case (.allData, false): - Pixel.fire(.fireButton(option: .allSites)) + PixelKit.fire(GeneralPixel.fireButton(option: .allSites)) fireViewModel.fire.burnEntity(entity: .allWindows(mainWindowControllers: WindowControllersManager.shared.mainWindowControllers, selectedDomains: selectedDomains)) } diff --git a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift index 36838c0539..a62dc501ce 100644 --- a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift +++ b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift @@ -21,6 +21,7 @@ import Foundation import CoreData import Combine import History +import PixelKit final class EncryptedHistoryStore: HistoryStoring { @@ -91,7 +92,7 @@ final class EncryptedHistoryStore: HistoryStoring { } os_log("%d items cleaned from history", log: .history, entriesToDelete.count) } catch { - Pixel.fire(.debug(event: .historyRemoveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveFailed, error: error)) self.context.reset() return .failure(error) } @@ -100,7 +101,7 @@ final class EncryptedHistoryStore: HistoryStoring { do { try context.save() } catch { - Pixel.fire(.debug(event: .historyRemoveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveFailed, error: error)) context.reset() return .failure(error) } @@ -117,7 +118,7 @@ final class EncryptedHistoryStore: HistoryStoring { let history = BrowsingHistory(historyEntries: historyEntries) return .success(history) } catch { - Pixel.fire(.debug(event: .historyReloadFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyReloadFailed, error: error)) return .failure(error) } } @@ -133,7 +134,7 @@ final class EncryptedHistoryStore: HistoryStoring { } try context.save() } catch { - Pixel.fire(.debug(event: .historyCleanEntriesFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyCleanEntriesFailed, error: error)) context.reset() return .failure(error) } @@ -149,7 +150,7 @@ final class EncryptedHistoryStore: HistoryStoring { try context.save() return .success(()) } catch { - Pixel.fire(.debug(event: .historyCleanVisitsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyCleanVisitsFailed, error: error)) context.reset() return .failure(error) } @@ -174,8 +175,8 @@ final class EncryptedHistoryStore: HistoryStoring { do { fetchedObjects = try self.context.fetch(fetchRequest) } catch { - Pixel.fire(.debug(event: .historySaveFailed, error: error)) - Pixel.fire(.debug(event: .historySaveFailedDaily, error: error), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailedDaily, error: error), frequency: .legacyDaily) promise(.failure(error)) return } @@ -203,16 +204,16 @@ final class EncryptedHistoryStore: HistoryStoring { context: self.context) switch insertionResult { case .failure(let error): - Pixel.fire(.debug(event: .historySaveFailed, error: error)) - Pixel.fire(.debug(event: .historySaveFailedDaily, error: error), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailedDaily, error: error), frequency: .legacyDaily) context.reset() promise(.failure(error)) case .success(let visitMOs): do { try self.context.save() } catch { - Pixel.fire(.debug(event: .historySaveFailed, error: error)) - Pixel.fire(.debug(event: .historySaveFailedDaily, error: error), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailedDaily, error: error), frequency: .legacyDaily) context.reset() promise(.failure(HistoryStoreError.savingFailed)) return @@ -262,7 +263,7 @@ final class EncryptedHistoryStore: HistoryStoring { context: NSManagedObjectContext) -> Result { let insertedObject = NSEntityDescription.insertNewObject(forEntityName: VisitManagedObject.className(), into: context) guard let visitMO = insertedObject as? VisitManagedObject else { - Pixel.fire(.debug(event: .historyInsertVisitFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.historyInsertVisitFailed)) context.reset() return .failure(HistoryStoreError.savingFailed) } @@ -310,7 +311,7 @@ final class EncryptedHistoryStore: HistoryStoring { context.delete(visit) } } catch { - Pixel.fire(.debug(event: .historyRemoveVisitsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveVisitsFailed, error: error)) return .failure(error) } } @@ -318,7 +319,7 @@ final class EncryptedHistoryStore: HistoryStoring { do { try context.save() } catch { - Pixel.fire(.debug(event: .historyRemoveVisitsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveVisitsFailed, error: error)) context.reset() return .failure(error) } @@ -346,7 +347,7 @@ fileprivate extension HistoryEntry { guard let url = historyEntryMO.urlEncrypted as? URL, let identifier = historyEntryMO.identifier, let lastVisit = historyEntryMO.lastVisit else { - Pixel.fire(.debug(event: .historyEntryDecryptionFailedUnique), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.historyEntryDecryptionFailedUnique), frequency: .daily) assertionFailure("HistoryEntry: Failed to init HistoryEntry from HistoryEntryManagedObject") return nil } diff --git a/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift b/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift index 7c729d99ad..4d65b77c05 100644 --- a/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift +++ b/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift @@ -19,6 +19,7 @@ import Foundation import Bookmarks import BrowserServicesKit +import PixelKit protocol DataImportStatusProviding { var didImport: Bool { get } @@ -80,7 +81,7 @@ final class BookmarksAndPasswordsImportStatusProvider: DataImportStatusProviding let identitiesDates = try secureVault.identities().map(\.created) dates.append(contentsOf: identitiesDates) } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } guard dates.count >= 2 else { return false diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index eb693541b9..31a029cbc7 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -20,6 +20,8 @@ import AppKit import BrowserServicesKit import Common import Foundation +import PixelKit + import NetworkProtection import NetworkProtectionUI @@ -164,7 +166,7 @@ extension HomePage.Models { switch featureType { case .defaultBrowser: do { - Pixel.fire(.defaultRequestedFromHomepageSetupView) + PixelKit.fire(GeneralPixel.defaultRequestedFromHomepageSetupView) try defaultBrowserProvider.presentDefaultBrowserPrompt() } catch { defaultBrowserProvider.openSystemPreferences() @@ -219,11 +221,11 @@ extension HomePage.Models { shouldShowSurveyDay14 = false case .networkProtectionRemoteMessage(let message): homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: message) - Pixel.fire(.networkProtectionRemoteMessageDismissed(messageID: message.id)) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: message.id)) case .dataBrokerProtectionRemoteMessage(let message): #if DBP homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: message) - Pixel.fire(.dataBrokerProtectionRemoteMessageDismissed(messageID: message.id)) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDismissed(messageID: message.id)) #endif case .dataBrokerProtectionWaitlistInvited: shouldShowDBPWaitlistInvitedCardUI = false @@ -245,19 +247,12 @@ extension HomePage.Models { for message in homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.presentableRemoteMessages() { features.append(.dataBrokerProtectionRemoteMessage(message)) - DailyPixel.fire( - pixel: .dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), - frequency: .dailyOnly - ) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .daily) } #endif for message in homePageRemoteMessaging.networkProtectionRemoteMessaging.presentableRemoteMessages() { - features.append(.networkProtectionRemoteMessage(message)) - DailyPixel.fire( - pixel: .networkProtectionRemoteMessageDisplayed(messageID: message.id), - frequency: .dailyOnly - ) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .daily) } if waitlistBetaThankYouPresenter.canShowVPNCard { @@ -439,7 +434,7 @@ extension HomePage.Models { @MainActor private func handle(remoteMessage: NetworkProtectionRemoteMessage) { guard let actionType = remoteMessage.action.actionType else { - Pixel.fire(.networkProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: remoteMessage) refreshFeaturesMatrix() return @@ -452,7 +447,7 @@ extension HomePage.Models { if let surveyURL = remoteMessage.presentableSurveyURL() { let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) tabCollectionViewModel.append(tab: tab) - Pixel.fire(.networkProtectionRemoteMessageOpened(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageOpened(messageID: remoteMessage.id)) // Dismiss the message after the user opens the URL, even if they just close the tab immediately afterwards. homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: remoteMessage) @@ -464,7 +459,7 @@ extension HomePage.Models { @MainActor private func handle(remoteMessage: DataBrokerProtectionRemoteMessage) { #if DBP guard let actionType = remoteMessage.action.actionType else { - Pixel.fire(.dataBrokerProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: remoteMessage) refreshFeaturesMatrix() return @@ -477,7 +472,7 @@ extension HomePage.Models { if let surveyURL = remoteMessage.presentableSurveyURL() { let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) tabCollectionViewModel.append(tab: tab) - Pixel.fire(.dataBrokerProtectionRemoteMessageOpened(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageOpened(messageID: remoteMessage.id)) // Dismiss the message after the user opens the URL, even if they just close the tab immediately afterwards. homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: remoteMessage) diff --git a/DuckDuckGo/HomePage/Model/HomePageRecentlyVisitedModel.swift b/DuckDuckGo/HomePage/Model/HomePageRecentlyVisitedModel.swift index 2e627932cc..a22162f63e 100644 --- a/DuckDuckGo/HomePage/Model/HomePageRecentlyVisitedModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageRecentlyVisitedModel.swift @@ -28,7 +28,7 @@ final class RecentlyVisitedModel: ObservableObject { let f = RelativeDateTimeFormatter() f.unitsStyle = .abbreviated return f - } () + }() private let fire: Fire diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index 96fb2aca47..9769333182 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -20,6 +20,7 @@ import Cocoa import Combine import SwiftUI import History +import PixelKit @MainActor final class HomePageViewController: NSViewController { @@ -106,8 +107,8 @@ final class HomePageViewController: NSViewController { override func viewWillAppear() { super.viewWillAppear() - if OnboardingViewModel.isOnboardingFinished && Pixel.isNewUser { - Pixel.fire(.newTabInitial, limitTo: .initial) + if OnboardingViewModel.isOnboardingFinished && AppDelegate.isNewUser { + PixelKit.fire(GeneralPixel.newTabInitial, frequency: .legacyInitial) } subscribeToHistory() } @@ -158,7 +159,7 @@ final class HomePageViewController: NSViewController { func createDefaultBrowserModel() -> HomePage.Models.DefaultBrowserModel { return .init(isDefault: DefaultBrowserPreferences.shared.isDefault, wasClosed: defaultBrowserDismissed, requestSetDefault: { [weak self] in - Pixel.fire(.defaultRequestedFromHomepage) + PixelKit.fire(GeneralPixel.defaultRequestedFromHomepage) let defaultBrowserPreferencesModel = DefaultBrowserPreferences.shared defaultBrowserPreferencesModel.becomeDefault { [weak self] isDefault in _ = defaultBrowserPreferencesModel diff --git a/DuckDuckGo/LoginItems/LoginItemsManager.swift b/DuckDuckGo/LoginItems/LoginItemsManager.swift index e0a44a4fb4..a4ccbba360 100644 --- a/DuckDuckGo/LoginItems/LoginItemsManager.swift +++ b/DuckDuckGo/LoginItems/LoginItemsManager.swift @@ -19,6 +19,7 @@ import Common import Foundation import LoginItems +import PixelKit /// Class to manage the login items for the VPN and DBP /// @@ -66,17 +67,12 @@ final class LoginItemsManager { } private func handleError(for item: LoginItem, action: Action, error: NSError) { - let event = Pixel.Event.Debug.loginItemUpdateError( - loginItemBundleID: item.agentBundleID, - action: "enable", - buildType: AppVersion.shared.buildType, - osVersion: AppVersion.shared.osVersion - ) - DailyPixel.fire(pixel: .debug(event: event, error: error), frequency: .dailyAndCount) - - os_log("🔴 Could not enable %{public}@: %{public}@", - item.debugDescription, - error.debugDescription) + let event = GeneralPixel.loginItemUpdateError(loginItemBundleID: item.agentBundleID, + action: "enable", + buildType: AppVersion.shared.buildType, + osVersion: AppVersion.shared.osVersion) + PixelKit.fire(DebugEvent(event, error: error), frequency: .dailyAndCount) + os_log("🔴 Could not enable %{public}@: %{public}@", item.debugDescription, error.debugDescription) } // MARK: - Debug Interactions diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 44c8b76408..e0d2a3bd48 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -577,7 +577,7 @@ import SubscriptionUI NSMenuItem(title: "More Than 15 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan15DaysAgo(_:))) } NSMenuItem(title: "Reset Email Protection InContext Signup Prompt", action: #selector(MainViewController.resetEmailProtectionInContextPrompt)) - NSMenuItem(title: "Reset Daily Pixels", action: #selector(MainViewController.resetDailyPixels)) + NSMenuItem(title: "Reset Pixels Storage", action: #selector(MainViewController.resetDailyPixels)) }.withAccessibilityIdentifier("MainMenu.resetData") NSMenuItem(title: "UI Triggers") { NSMenuItem(title: "Show Save Credentials Popover", action: #selector(MainViewController.showSaveCredentialsPopover)) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 4f2befbe8b..c94b028622 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -740,13 +740,13 @@ extension MainViewController { UserDefaults.netP.networkProtectionEntitlementsExpired = false // Clear pixel data - DailyPixel.clearLastFireDate(pixel: .privacyProFeatureEnabled) - Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouDBP) - Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouVPN) + PixelKit.shared?.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProFeatureEnabled) + PixelKit.shared?.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouDBP) + PixelKit.shared?.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouVPN) } @objc func resetDailyPixels(_ sender: Any?) { - UserDefaults.standard.removePersistentDomain(forName: DailyPixel.Constant.dailyPixelStorageIdentifier) + PixelKit.shared?.clearFrequencyHistoryForAllPixels() } @objc func in10PercentSurveyOn(_ sender: Any?) { diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index b6cf9fd9f0..566a7c413f 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -290,7 +290,7 @@ final class AddressBarButtonsViewController: NSViewController { bookmarkButton.isHidden = !showBookmarkButton } - func openBookmarkPopover(setFavorite: Bool, accessPoint: Pixel.Event.AccessPoint) { + func openBookmarkPopover(setFavorite: Bool, accessPoint: GeneralPixel.AccessPoint) { let result = bookmarkForCurrentUrl(setFavorite: setFavorite, accessPoint: accessPoint) guard let bookmark = result.bookmark else { assertionFailure("Failed to get a bookmark for the popover") @@ -920,7 +920,7 @@ final class AddressBarButtonsViewController: NSViewController { } } - private func bookmarkForCurrentUrl(setFavorite: Bool, accessPoint: Pixel.Event.AccessPoint) -> (bookmark: Bookmark?, isNew: Bool) { + private func bookmarkForCurrentUrl(setFavorite: Bool, accessPoint: GeneralPixel.AccessPoint) -> (bookmark: Bookmark?, isNew: Bool) { guard let tabViewModel, let url = tabViewModel.tab.content.url else { assertionFailure("No URL for bookmarking") diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index e784fccf14..1cb95e8458 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -20,6 +20,8 @@ import Cocoa import Combine import Common import BrowserServicesKit +import PixelKit + import NetworkProtection #if SUBSCRIPTION @@ -350,7 +352,7 @@ final class MoreOptionsMenu: NSMenu { items.append(dataBrokerProtectionItem) #if SUBSCRIPTION - if subscriptionFeatureAvailability.isFeatureAvailable && accountManager.isUserAuthenticated { + if subscriptionFeatureAvailability.isFeatureAvailable && accountManager.isUserAuthenticated { Task { let isMenuItemEnabled: Bool @@ -366,7 +368,7 @@ final class MoreOptionsMenu: NSMenu { } #endif - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndCount) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndCount) } else { DefaultDataBrokerProtectionFeatureVisibility().disableAndDeleteForWaitlistUsers() @@ -382,7 +384,7 @@ final class MoreOptionsMenu: NSMenu { .withImage(.itrIcon) items.append(identityTheftRestorationItem) - if subscriptionFeatureAvailability.isFeatureAvailable && accountManager.isUserAuthenticated { + if subscriptionFeatureAvailability.isFeatureAvailable && accountManager.isUserAuthenticated { Task { let isMenuItemEnabled: Bool @@ -536,7 +538,7 @@ final class EmailOptionsButtonSubMenu: NSMenu { let pixelParameters = self.emailManager.emailPixelParameters self.emailManager.updateLastUseDate() - Pixel.fire(.emailUserCreatedAlias, withAdditionalParameters: pixelParameters) + PixelKit.fire(GeneralPixel.emailUserCreatedAlias, withAdditionalParameters: pixelParameters) NSPasteboard.general.copy(address) NotificationCenter.default.post(name: NSNotification.Name.privateEmailCopiedToClipboard, object: nil) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift b/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift index 8eee150a17..763cf56628 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift @@ -105,7 +105,7 @@ final class NavigationBarPopovers: PopoverPresenter { } func passwordManagementButtonPressed(usingView view: NSView, withDelegate delegate: NSPopoverDelegate) { - if autofillPopoverPresenter.popoverIsShown == true && view.window == autofillPopoverPresenter.popoverPresentingWindow { + if autofillPopoverPresenter.popoverIsShown == true && view.window == autofillPopoverPresenter.popoverPresentingWindow { autofillPopoverPresenter.dismiss() } else { showPasswordManagementPopover(selectedCategory: nil, usingView: view, withDelegate: delegate) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 6b1af92c29..30646febcb 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -20,6 +20,8 @@ import Cocoa import Combine import Common import BrowserServicesKit +import PixelKit + import NetworkProtection import NetworkProtectionIPC import NetworkProtectionUI @@ -1055,7 +1057,7 @@ extension NavigationBarViewController: OptionsButtonMenuDelegate { #if SUBSCRIPTION func optionsButtonMenuRequestedSubscriptionPurchasePage(_ menu: NSMenu) { WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) } func optionsButtonMenuRequestedIdentityTheftRestoration(_ menu: NSMenu) { diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift index d1797d0e0c..2446ab0ac5 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift @@ -254,147 +254,87 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { switch self { case .networkProtectionKeychainErrorFailedToCastKeychainValueToData(let field): return [PixelKit.Parameters.keychainFieldName: field] - case .networkProtectionKeychainReadError(let field, let status): return [ PixelKit.Parameters.keychainFieldName: field, PixelKit.Parameters.errorCode: String(status) ] - case .networkProtectionKeychainWriteError(let field, let status): return [ PixelKit.Parameters.keychainFieldName: field, PixelKit.Parameters.errorCode: String(status) ] - case .networkProtectionKeychainUpdateError(let field, let status): return [ PixelKit.Parameters.keychainFieldName: field, PixelKit.Parameters.errorCode: String(status) ] - case .networkProtectionKeychainDeleteError(let status): - return [ - PixelKit.Parameters.errorCode: String(status) - ] - + return [PixelKit.Parameters.errorCode: String(status)] case .networkProtectionClientFailedToFetchServerList(let error): return error?.pixelParameters - case .networkProtectionClientFailedToFetchRegisteredServers(let error): return error?.pixelParameters - case .networkProtectionClientFailedToRedeemInviteCode(let error): return error?.pixelParameters - case .networkProtectionClientFailedToFetchLocations(let error): return error?.pixelParameters - case .networkProtectionClientFailedToParseLocationsResponse(let error): return error?.pixelParameters - case .networkProtectionUnhandledError(let function, let line, let error): var parameters = error.pixelParameters parameters[PixelKit.Parameters.function] = function parameters[PixelKit.Parameters.line] = String(line) return parameters - case .networkProtectionWireguardErrorCannotSetNetworkSettings(let error): return error.pixelParameters - case .networkProtectionWireguardErrorCannotStartWireguardBackend(code: let code): - return [ - PixelKit.Parameters.errorCode: String(code) - ] - + return [PixelKit.Parameters.errorCode: String(code)] case .networkProtectionWireguardErrorInvalidState(reason: let reason): - return [ - PixelKit.Parameters.reason: reason - ] - - case .networkProtectionTunnelConfigurationNoServerRegistrationInfo, - .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, - .networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, - .networkProtectionTunnelConfigurationCouldNotGetPeerHostName, - .networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, - .networkProtectionClientFailedToParseServerListResponse, - .networkProtectionClientFailedToEncodeRegisterKeyRequest, - .networkProtectionClientFailedToParseRegisteredServersResponse, - .networkProtectionClientFailedToParseRedeemResponse, - .networkProtectionClientInvalidInviteCode, - .networkProtectionClientFailedToEncodeRedeemRequest, - .networkProtectionClientInvalidAuthToken, - .networkProtectionNoAuthTokenFoundError, - .networkProtectionRekeyAttempt, - .networkProtectionRekeyCompleted, - .networkProtectionRekeyFailure, - .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, - .networkProtectionWireguardErrorFailedDNSResolution, - .networkProtectionSystemExtensionActivationFailure, - .networkProtectionActiveUser, - .networkProtectionNewUser, - .networkProtectionEnableAttemptConnecting, - .networkProtectionEnableAttemptSuccess, - .networkProtectionEnableAttemptFailure, - .networkProtectionTunnelFailureDetected, - .networkProtectionTunnelFailureRecovered, - .networkProtectionLatency, - .networkProtectionLatencyError, - .networkProtectionControllerStartAttempt, - .networkProtectionControllerStartSuccess, - .networkProtectionControllerStartFailure, - .networkProtectionTunnelStartAttempt, - .networkProtectionTunnelStartSuccess, - .networkProtectionTunnelStartFailure, - .networkProtectionTunnelUpdateAttempt, - .networkProtectionTunnelUpdateSuccess, - .networkProtectionTunnelUpdateFailure: - - return nil - } - } - - var error: (any Error)? { - switch self { + return [PixelKit.Parameters.reason: reason] case .networkProtectionActiveUser, .networkProtectionNewUser, .networkProtectionControllerStartAttempt, .networkProtectionControllerStartSuccess, + .networkProtectionControllerStartFailure, .networkProtectionTunnelStartAttempt, .networkProtectionTunnelStartSuccess, + .networkProtectionTunnelStartFailure, .networkProtectionTunnelUpdateAttempt, .networkProtectionTunnelUpdateSuccess, + .networkProtectionTunnelUpdateFailure, .networkProtectionEnableAttemptConnecting, .networkProtectionEnableAttemptSuccess, .networkProtectionEnableAttemptFailure, .networkProtectionTunnelFailureDetected, .networkProtectionTunnelFailureRecovered, - .networkProtectionLatencyError, .networkProtectionLatency, + .networkProtectionLatencyError, .networkProtectionTunnelConfigurationNoServerRegistrationInfo, + .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, .networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, .networkProtectionTunnelConfigurationCouldNotGetPeerHostName, .networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, .networkProtectionClientFailedToParseServerListResponse, .networkProtectionClientFailedToEncodeRegisterKeyRequest, .networkProtectionClientFailedToParseRegisteredServersResponse, - .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, .networkProtectionClientFailedToEncodeRedeemRequest, .networkProtectionClientInvalidInviteCode, + .networkProtectionClientFailedToParseRedeemResponse, .networkProtectionClientInvalidAuthToken, - .networkProtectionKeychainErrorFailedToCastKeychainValueToData, - .networkProtectionKeychainReadError, - .networkProtectionKeychainWriteError, - .networkProtectionKeychainUpdateError, - .networkProtectionKeychainDeleteError, .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, - .networkProtectionWireguardErrorInvalidState, .networkProtectionWireguardErrorFailedDNSResolution, - .networkProtectionWireguardErrorCannotStartWireguardBackend, .networkProtectionNoAuthTokenFoundError, .networkProtectionRekeyAttempt, - .networkProtectionRekeyCompleted: + .networkProtectionRekeyCompleted, + .networkProtectionRekeyFailure, + .networkProtectionSystemExtensionActivationFailure: return nil + } + } + + var error: (any Error)? { + switch self { case .networkProtectionClientFailedToRedeemInviteCode(let error), .networkProtectionClientFailedToFetchLocations(let error), .networkProtectionClientFailedToParseLocationsResponse(let error), @@ -410,6 +350,45 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { .networkProtectionUnhandledError(_, _, let error), .networkProtectionSystemExtensionActivationFailure(let error): return error + case .networkProtectionActiveUser, + .networkProtectionNewUser, + .networkProtectionControllerStartAttempt, + .networkProtectionControllerStartSuccess, + .networkProtectionTunnelStartAttempt, + .networkProtectionTunnelStartSuccess, + .networkProtectionTunnelUpdateAttempt, + .networkProtectionTunnelUpdateSuccess, + .networkProtectionEnableAttemptConnecting, + .networkProtectionEnableAttemptSuccess, + .networkProtectionEnableAttemptFailure, + .networkProtectionTunnelFailureDetected, + .networkProtectionTunnelFailureRecovered, + .networkProtectionLatency, + .networkProtectionLatencyError, + .networkProtectionTunnelConfigurationNoServerRegistrationInfo, + .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, + .networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, + .networkProtectionTunnelConfigurationCouldNotGetPeerHostName, + .networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, + .networkProtectionClientFailedToParseServerListResponse, + .networkProtectionClientFailedToEncodeRegisterKeyRequest, + .networkProtectionClientFailedToParseRegisteredServersResponse, + .networkProtectionClientFailedToEncodeRedeemRequest, + .networkProtectionClientInvalidInviteCode, + .networkProtectionClientInvalidAuthToken, + .networkProtectionKeychainErrorFailedToCastKeychainValueToData, + .networkProtectionKeychainReadError, + .networkProtectionKeychainWriteError, + .networkProtectionKeychainUpdateError, + .networkProtectionKeychainDeleteError, + .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, + .networkProtectionWireguardErrorInvalidState, + .networkProtectionWireguardErrorFailedDNSResolution, + .networkProtectionWireguardErrorCannotStartWireguardBackend, + .networkProtectionNoAuthTokenFoundError, + .networkProtectionRekeyAttempt, + .networkProtectionRekeyCompleted: + return nil } } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift index ed515e70e7..3a068bfed6 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift @@ -62,10 +62,10 @@ extension EventMapping where Event == NetworkProtectionError { frequency = .standard case .failedToFetchLocationList(let error): domainEvent = .networkProtectionClientFailedToFetchLocations(error) - frequency = .dailyAndContinuous + frequency = .dailyAndCount case .failedToParseLocationListResponse(let error): domainEvent = .networkProtectionClientFailedToParseLocationsResponse(error) - frequency = .dailyAndContinuous + frequency = .dailyAndCount case .noServerRegistrationInfo, .couldNotSelectClosestServer, .couldNotGetPeerPublicKey, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 911913504f..1c9c014a2e 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -485,7 +485,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr /// func start() async { PixelKit.fire(NetworkProtectionPixelEvent.networkProtectionControllerStartAttempt, - frequency: .dailyAndContinuous) + frequency: .dailyAndCount) controllerErrorStore.lastErrorMessage = nil do { @@ -526,11 +526,11 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr // in the packet tunnel provider side that can be used to debug additional logic. // PixelKit.fire(NetworkProtectionPixelEvent.networkProtectionControllerStartSuccess, - frequency: .dailyAndContinuous) + frequency: .dailyAndCount) } } catch { PixelKit.fire( - NetworkProtectionPixelEvent.networkProtectionControllerStartFailure(error), frequency: .dailyAndContinuous, includeAppVersionParameter: true + NetworkProtectionPixelEvent.networkProtectionControllerStartFailure(error), frequency: .dailyAndCount, includeAppVersionParameter: true ) await stop() @@ -582,7 +582,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionNewUser, - frequency: .justOnce, + frequency: .unique, includeAppVersionParameter: true) { [weak self] fired, error in guard let self, error == nil, fired else { return } self.defaults.vpnFirstEnabled = PixelKit.pixelLastFireDate(event: NetworkProtectionPixelEvent.networkProtectionNewUser) @@ -749,7 +749,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr private func fetchAuthToken() throws -> NSString? { #if SUBSCRIPTION - if let accessToken = accountManager.accessToken { + if let accessToken = accountManager.accessToken { os_log(.error, log: .networkProtection, "🟢 TunnelController found token: %{public}d", accessToken) return Self.adaptAccessTokenForVPN(accessToken) as NSString? } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift index 3368da5066..e2e26d0e67 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift @@ -19,6 +19,7 @@ import Foundation import Combine import NetworkProtection +import PixelKit final class VPNLocationViewModel: ObservableObject { private static var cachedLocations: [VPNCountryItemModel]? @@ -63,7 +64,7 @@ final class VPNLocationViewModel: ObservableObject { } func onViewAppeared() async { - Pixel.fire(.networkProtectionGeoswitchingOpened) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingOpened) await reloadList() } @@ -73,13 +74,13 @@ final class VPNLocationViewModel: ObservableObject { } func onNearestItemSelection() async { - DailyPixel.fire(pixel: .networkProtectionGeoswitchingSetNearest, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetNearest, frequency: .dailyAndCount) selectedLocation = .nearest await reloadList() } func onCountryItemSelection(id: String, cityId: String? = nil) async { - DailyPixel.fire(pixel: .networkProtectionGeoswitchingSetCustom, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetCustom, frequency: .dailyAndCount) let city = cityId == VPNCityItemModel.nearest.id ? nil : cityId let location = NetworkProtectionSelectedLocation(country: id, city: city) selectedLocation = .location(location) @@ -94,7 +95,7 @@ final class VPNLocationViewModel: ObservableObject { private func reloadList() async { guard let locations = try? await locationListRepository.fetchLocationList().sortedByName() else { return } if locations.isEmpty { - DailyPixel.fire(pixel: .networkProtectionGeoswitchingNoLocations, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingNoLocations, frequency: .dailyAndCount) } let isNearestSelected = selectedLocation == .nearest self.isNearestSelected = isNearestSelected diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift index 1f1ce5935b..739c7501ed 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift @@ -18,6 +18,7 @@ import Foundation import Networking +import PixelKit protocol NetworkProtectionRemoteMessaging { @@ -90,7 +91,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess try self.messageStorage.store(messages: messages) self.updateLastRefreshDate() // Update last refresh date on success, otherwise let the app try again next time } catch { - Pixel.fire(.debug(event: .networkProtectionRemoteMessageStorageFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageStorageFailed, error: error)) } case .failure(let error): // Ignore 403 errors, those happen when a file can't be found on S3 @@ -99,7 +100,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess return } - Pixel.fire(.debug(event: .networkProtectionRemoteMessageFetchingFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageFetchingFailed, error: error)) } } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 0689316a76..97d96e0ed8 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -129,7 +129,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { return } - PixelKit.fire(domainEvent, frequency: .dailyAndContinuous, includeAppVersionParameter: true) + PixelKit.fire(domainEvent, frequency: .dailyAndCount, includeAppVersionParameter: true) } } @@ -150,7 +150,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .userBecameActive: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionActiveUser, - frequency: .dailyOnly, + frequency: .legacyDaily, withAdditionalParameters: [PixelKit.Parameters.vpnCohort: PixelKit.cohort(from: defaults.vpnFirstEnabled)], includeAppVersionParameter: true) case .reportConnectionAttempt(attempt: let attempt): @@ -158,17 +158,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .connecting: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionEnableAttemptConnecting, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionEnableAttemptSuccess, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionEnableAttemptFailure, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .reportTunnelFailure(result: let result): @@ -176,12 +176,12 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .failureDetected: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelFailureDetected, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failureRecovered: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelFailureRecovered, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .networkPathChanged: break @@ -191,13 +191,13 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .error: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionLatencyError, - frequency: .dailyOnly, + frequency: .legacyDaily, includeAppVersionParameter: true) case .quality(let quality): guard quality != .unknown else { return } PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionLatency(quality: quality), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .rekeyAttempt(let step): @@ -205,17 +205,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .begin: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionRekeyAttempt, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure(let error): PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionRekeyFailure(error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionRekeyCompleted, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .tunnelStartAttempt(let step): @@ -223,17 +223,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .begin: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelStartAttempt, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure(let error): PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelStartFailure(error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelStartSuccess, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .tunnelUpdateAttempt(let step): @@ -241,17 +241,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .begin: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelUpdateAttempt, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure(let error): PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelUpdateFailure(error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelUpdateSuccess, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } } @@ -474,7 +474,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { appVersion: AppVersion.shared.versionNumber, source: source, defaultHeaders: defaultHeaders, - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift index d300309ec6..5d9b0c0fa4 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift @@ -73,7 +73,6 @@ final class MacTransparentProxyProvider: TransparentProxyProvider { appVersion: AppVersion.shared.versionNumber, source: "vpnProxyExtension", defaultHeaders: [:], - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift index 24be234ef2..cfc4a000dd 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift @@ -20,6 +20,7 @@ import Common import Foundation import SwiftUI import OpenSSL +import PixelKit final class BWManager: BWManagement, ObservableObject { @@ -121,7 +122,7 @@ final class BWManager: BWManagement, ObservableObject { try communicator.runProxyProcess() } catch { os_log("BWManagement: Running of proxy process failed", type: .error) - Pixel.fire(.debug(event: .bitwardenNotResponding)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenNotResponding)) status = .error(error: .runningOfProxyProcessFailed) scheduleConnectionAttempt() } @@ -191,7 +192,7 @@ final class BWManager: BWManagement, ObservableObject { if let sharedKey = sharedKey { guard let sharedKeyData = Data(base64Encoded: sharedKey), encryption.setSharedKey(sharedKeyData) else { - Pixel.fire(.debug(event: .bitwardenSharedKeyInjectionFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSharedKeyInjectionFailed)) status = .error(error: .sharedKeyInjectionFailed) return } @@ -222,7 +223,7 @@ final class BWManager: BWManagement, ObservableObject { self?.verificationTimer = nil if self?.status == .waitingForStatusResponse { - Pixel.fire(.debug(event: .bitwardenNotResponding)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenNotResponding)) BWNotRespondingAlert.show() } } @@ -232,10 +233,7 @@ final class BWManager: BWManagement, ObservableObject { switch error { case "cannot-decrypt": logOrAssertionFailure("BWManagement: Bitwarden error - cannot decrypt") - - if Pixel.Event.Repetition(key: "bitwardenRespondedCannotDecryptUnique", update: false) != .repetitive { - Pixel.fire(.debug(event: .bitwardenRespondedCannotDecryptUnique())) - } + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedCannotDecrypt), frequency: .daily) case "locked": if case let .connected(vault) = status { status = .connected(vault: vault.locked) @@ -244,20 +242,20 @@ final class BWManager: BWManagement, ObservableObject { } return default: logOrAssertionFailure("BWManager: Unhandled error") - Pixel.fire(.debug(event: .bitwardenRespondedWithError)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedWithError)) } } private func handleHandshakeResponse(encryptedSharedKey: String, status: String) { guard status == "success" else { - Pixel.fire(.debug(event: .bitwardenHandshakeFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenHandshakeFailed)) self.status = .error(error: .handshakeFailed) cancelConnectionAndScheduleNextAttempt() return } guard let sharedKey = encryption.decryptSharedKey(encryptedSharedKey) else { - Pixel.fire(.debug(event: .bitwardenDecryptionOfSharedKeyFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenDecryptionOfSharedKeyFailed)) self.status = .error(error: .decryptionOfSharedKeyFailed) cancelConnectionAndScheduleNextAttempt() return @@ -266,7 +264,7 @@ final class BWManager: BWManagement, ObservableObject { do { try keyStorage.save(sharedKey: sharedKey) } catch { - Pixel.fire(.debug(event: .bitwardenStoringOfTheSharedKeyFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenStoringOfTheSharedKeyFailed)) self.status = .error(error: .storingOfTheSharedKeyFailed) return } @@ -284,7 +282,7 @@ final class BWManager: BWManagement, ObservableObject { let hmacString = encryptedPayload.mac, let hmac = Data(base64Encoded: hmacString) else { - Pixel.fire(.debug(event: .bitwardenParsingFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenParsingFailed)) status = .error(error: .parsingFailed) return } @@ -292,20 +290,20 @@ final class BWManager: BWManagement, ObservableObject { // Compare HMAC let ourHmac = encryption.computeHmac(data, iv: ivData) guard ourHmac == hmac else { - Pixel.fire(.debug(event: .bitwardenHmacComparisonFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenHmacComparisonFailed)) logOrAssertionFailure("BWManager: HMAC comparison failed") return } let decryptedData = encryption.decryptData(data, andIv: ivData) guard decryptedData.count > 0 else { - Pixel.fire(.debug(event: .bitwardenDecryptionFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenDecryptionFailed)) status = .error(error: .decryptionOfDataFailed) return } guard let response = BWResponse(from: decryptedData) else { - Pixel.fire(.debug(event: .bitwardenParsingFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenParsingFailed)) status = .error(error: .parsingFailed) return } @@ -343,7 +341,7 @@ final class BWManager: BWManagement, ObservableObject { private func handleStatusResponse(payloadItemArray: [BWResponse.PayloadItem]) { // Find the active vault guard let activePayloadItem = payloadItemArray.filter({ $0.active ?? false }).first else { - Pixel.fire(.debug(event: .bitwardenNoActiveVault)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenNoActiveVault)) status = .error(error: .noActiveVault) return } @@ -369,7 +367,7 @@ final class BWManager: BWManagement, ObservableObject { } handleError(error) - Pixel.fire(.debug(event: .bitwardenCredentialRetrievalFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenCredentialRetrievalFailed)) completion([], BWError.credentialRetrievalFailed) } } @@ -385,7 +383,7 @@ final class BWManager: BWManagement, ObservableObject { if payloadItem.status == "success" { completion(nil) } else { - Pixel.fire(.debug(event: .bitwardenCredentialCreationFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenCredentialCreationFailed)) completion(BWError.credentialCreationFailed) } } @@ -401,7 +399,7 @@ final class BWManager: BWManagement, ObservableObject { if payloadItem.status == "success" { completion(nil) } else { - Pixel.fire(.debug(event: .bitwardenCredentialUpdateFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenCredentialUpdateFailed)) completion(BWError.credentialUpdateFailed) } } @@ -431,7 +429,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageIdGenerator.generateMessageId()).data else { logOrAssertionFailure("BWManager: Making the status message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -447,7 +445,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { logOrAssertionFailure("BWManager: Making the credential retrieval message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -467,7 +465,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { logOrAssertionFailure("BWManager: Making the credential creation message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -488,7 +486,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { logOrAssertionFailure("BWManager: Making the credential update message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -546,7 +544,7 @@ final class BWManager: BWManagement, ObservableObject { let email = payloadItem.email, let statusString = payloadItem.status, let status = BWVault.Status(rawValue: statusString) else { - Pixel.fire(.debug(event: .bitwardenStatusParsingFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenStatusParsingFailed)) self.status = .error(error: .statusParsingFailed) return } diff --git a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift index 39c5b222df..d4158aed5c 100644 --- a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift +++ b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift @@ -20,6 +20,7 @@ import Foundation import BrowserServicesKit import Combine import Common +import PixelKit protocol PasswordManagerCoordinating: BrowserServicesKit.PasswordManager { @@ -247,7 +248,7 @@ final class PasswordManagerCoordinator: PasswordManagerCoordinating { return } - Pixel.fire(.bitwardenPasswordAutofilled) + PixelKit.fire(GeneralPixel.bitwardenPasswordAutofilled) } func reportPasswordSave() { @@ -255,7 +256,7 @@ final class PasswordManagerCoordinator: PasswordManagerCoordinating { return } - Pixel.fire(.bitwardenPasswordSaved) + PixelKit.fire(GeneralPixel.bitwardenPasswordSaved) } // MARK: - Cache diff --git a/DuckDuckGo/Permissions/Model/StoredPermission.swift b/DuckDuckGo/Permissions/Model/StoredPermission.swift index 5fb8ee39ad..6a73f71f1e 100644 --- a/DuckDuckGo/Permissions/Model/StoredPermission.swift +++ b/DuckDuckGo/Permissions/Model/StoredPermission.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit enum PersistedPermissionDecision { case deny @@ -64,7 +65,7 @@ struct PermissionEntity: Equatable { guard let domain = managedObject.domainEncrypted as? String, let permissionTypeString = managedObject.permissionType, let permissionType = PermissionType(rawValue: permissionTypeString) else { - Pixel.fire(.debug(event: .permissionDecryptionFailedUnique), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.permissionDecryptionFailedUnique), frequency: .daily) assertionFailure("\(#file): Failed to create PermissionEntity from PermissionManagedObject") return nil } diff --git a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift index c5558b2637..c037a9c75c 100644 --- a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift +++ b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift @@ -20,6 +20,7 @@ import Foundation import AppKit import Bookmarks import Common +import PixelKit protocol AppearancePreferencesPersistor { var showFullURL: Bool { get set } @@ -164,7 +165,7 @@ final class AppearancePreferences: ObservableObject { persistor.isFavoriteVisible = isFavoriteVisible // Temporary Pixel if !isFavoriteVisible { - Pixel.fire(.favoriteSectionHidden) + PixelKit.fire(GeneralPixel.favoriteSectionHidden) } } } @@ -174,7 +175,7 @@ final class AppearancePreferences: ObservableObject { persistor.isContinueSetUpVisible = isContinueSetUpVisible // Temporary Pixel if !isContinueSetUpVisible { - Pixel.fire(.continueSetUpSectionHidden) + PixelKit.fire(GeneralPixel.continueSetUpSectionHidden) } } } @@ -184,7 +185,7 @@ final class AppearancePreferences: ObservableObject { persistor.isRecentActivityVisible = isRecentActivityVisible // Temporary Pixel if !isRecentActivityVisible { - Pixel.fire(.recentActivitySectionHidden) + PixelKit.fire(GeneralPixel.recentActivitySectionHidden) } } } diff --git a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift index a40e390510..a5495827fe 100644 --- a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift @@ -20,6 +20,7 @@ import Combine import Common import Foundation import SwiftUI +import PixelKit protocol DefaultBrowserProvider { var bundleIdentifier: String { get } @@ -76,11 +77,13 @@ final class DefaultBrowserPreferences: ObservableObject { @Published private(set) var isDefault: Bool = false { didSet { // Temporary pixel for first time user import data + DispatchQueue.main.async { #if DEBUG - guard NSApp.runType.requiresEnvironment else { return } + guard NSApp.runType.requiresEnvironment else { return } #endif - if Pixel.isNewUser && isDefault { - Pixel.fire(.setAsDefaultInitial, limitTo: .initial) + if AppDelegate.isNewUser && self.isDefault { + PixelKit.fire(GeneralPixel.setAsDefaultInitial, frequency: .legacyInitial) + } } } } diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index d631e55f21..a0a5591360 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -244,7 +244,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToTurnSyncOff, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncLogoutError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLogoutError(error: error))) } } } @@ -396,7 +396,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncDeleteAccountError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncDeleteAccountError(error: error))) } } } @@ -411,7 +411,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { mapDevices(devices) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToUpdateDeviceName, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncUpdateDeviceError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncUpdateDeviceError(error: error))) } syncService.scheduler.resumeSyncQueue() } @@ -427,7 +427,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { let device = deviceInfo() let devices = try await syncService.login(recoveryKey, deviceName: device.name, deviceType: device.type) mapDevices(devices) - Pixel.fire(.syncLogin) + PixelKit.fire(GeneralPixel.syncLogin) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if isRecovery { self.showDevicesSynced() @@ -449,11 +449,11 @@ extension SyncPreferences: ManagementDialogModelDelegate { let device = deviceInfo() presentDialog(for: .prepareToSync) try await syncService.createAccount(deviceName: device.name, deviceType: device.type) - Pixel.fire(.syncSignupDirect) + PixelKit.fire(GeneralPixel.syncSignupDirect) presentDialog(for: .saveRecoveryCode(recoveryCode ?? "")) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToSyncToServer, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncSignupError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncSignupError(error: error))) } } } @@ -489,7 +489,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { description: error.localizedDescription ) } - firePixelIfNeeded(event: .debug(event: .syncLoginError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLoginError(error: error))) } } } @@ -512,14 +512,14 @@ extension SyncPreferences: ManagementDialogModelDelegate { try await loginAndShowPresentedDialog(recoveryKey, isRecovery: fromRecoveryScreen) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToMergeTwoAccounts, description: "") - firePixelIfNeeded(event: .debug(event: .syncLoginExistingAccountError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLoginExistingAccountError(error: error))) } } else if let connectKey = syncCode.connect { do { if syncService.account == nil { let device = deviceInfo() try await syncService.createAccount(deviceName: device.name, deviceType: device.type) - Pixel.fire(.syncSignupConnect) + PixelKit.fire(GeneralPixel.syncSignupConnect) presentDialog(for: .saveRecoveryCode(recoveryCode)) } @@ -538,7 +538,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { type: .unableToSyncToOtherDevice, description: error.localizedDescription ) - firePixelIfNeeded(event: .debug(event: .syncLoginError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLoginError(error: error))) } } else { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .invalidCode, description: "") @@ -584,7 +584,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { } } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableCreateRecoveryPDF, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncCannotCreateRecoveryPDF, error: nil)) + PixelKit.fire(DebugEvent(GeneralPixel.syncCannotCreateRecoveryPDF)) } } @@ -599,7 +599,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { managementDialogModel.endFlow() } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToRemoveDevice, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncRemoveDeviceError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncRemoveDeviceError(error: error))) } } } @@ -688,16 +688,4 @@ extension SyncPreferences: ManagementDialogModelDelegate { func recoveryCodePasted(_ code: String, fromRecoveryScreen: Bool) { recoverDevice(recoveryCode: code, fromRecoveryScreen: fromRecoveryScreen) } - - private func firePixelIfNeeded(event: Pixel.Event) { - if case let .debug(_, debugError) = event { - if debugError == nil { - Pixel.fire(event) - } - if let syncError = debugError as? SyncError, !syncError.isServerError { - Pixel.fire(event, withAdditionalParameters: syncError.errorParameters) - } - } - } - } diff --git a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift index 63cdb989d5..b87117a34e 100644 --- a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift @@ -19,6 +19,7 @@ import PreferencesViews import SwiftUI import SwiftUIExtensions +import PixelKit fileprivate extension Preferences.Const { static let autoLockWarningOffset: CGFloat = { @@ -133,7 +134,7 @@ extension Preferences { Button(UserText.autofillExcludedSitesReset) { showingResetNeverPromptSitesSheet.toggle() if showingResetNeverPromptSitesSheet { - Pixel.fire(.autofillLoginsSettingsResetExcludedDisplayed) + PixelKit.fire(GeneralPixel.autofillLoginsSettingsResetExcludedDisplayed) } } }.sheet(isPresented: $showingResetNeverPromptSitesSheet) { @@ -348,7 +349,7 @@ struct ResetNeverPromptSitesSheet: View { Spacer() Button(UserText.cancel) { isSheetPresented.toggle() - Pixel.fire(.autofillLoginsSettingsResetExcludedDismissed) + PixelKit.fire(GeneralPixel.autofillLoginsSettingsResetExcludedDismissed) } Button(action: { saveChanges() @@ -365,7 +366,7 @@ struct ResetNeverPromptSitesSheet: View { private func saveChanges() { autofillPreferencesModel.resetNeverPromptWebsites() isSheetPresented.toggle() - Pixel.fire(.autofillLoginsSettingsResetExcludedConfirmed) + PixelKit.fire(GeneralPixel.autofillLoginsSettingsResetExcludedConfirmed) } } diff --git a/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift b/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift index cc7692b366..8cc530b171 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift @@ -21,6 +21,7 @@ import Combine import PreferencesViews import SwiftUI import SwiftUIExtensions +import PixelKit extension Preferences { @@ -52,7 +53,7 @@ extension Preferences { } .padding(.trailing, 8) Button(action: { - Pixel.fire(.defaultRequestedFromSettings) + PixelKit.fire(GeneralPixel.defaultRequestedFromSettings) defaultBrowserModel.becomeDefault() }) { Text(UserText.makeDefaultBrowser) diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index 1dde6f4bbf..87f9b6dc47 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -22,6 +22,7 @@ import SwiftUI import SwiftUIExtensions import SyncUI import BrowserServicesKit +import PixelKit #if SUBSCRIPTION import Subscription @@ -140,32 +141,32 @@ enum Preferences { DispatchQueue.main.async { switch event { case .openVPN: - Pixel.fire(.privacyProVPNSettings) + PixelKit.fire(PrivacyProPixel.privacyProVPNSettings) NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) case .openDB: - Pixel.fire(.privacyProPersonalInformationRemovalSettings) + PixelKit.fire(PrivacyProPixel.privacyProPersonalInformationRemovalSettings) WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .openITR: - Pixel.fire(.privacyProIdentityRestorationSettings) + PixelKit.fire(PrivacyProPixel.privacyProIdentityRestorationSettings) WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) case .iHaveASubscriptionClick: - Pixel.fire(.privacyProRestorePurchaseClick) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseClick) case .activateAddEmailClick: - DailyPixel.fire(pixel: .privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) case .postSubscriptionAddEmailClick: - Pixel.fire(.privacyProSubscriptionManagementEmail, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeAddDevice, frequency: .unique) case .restorePurchaseStoreClick: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreStart, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreStart, frequency: .dailyAndCount) case .addToAnotherDeviceClick: - Pixel.fire(.privacyProSettingsAddDevice) + PixelKit.fire(PrivacyProPixel.privacyProSettingsAddDevice) case .addDeviceEnterEmail: - Pixel.fire(.privacyProAddDeviceEnterEmail) + PixelKit.fire(PrivacyProPixel.privacyProAddDeviceEnterEmail) case .activeSubscriptionSettingsClick: - Pixel.fire(.privacyProSubscriptionSettings) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionSettings) case .changePlanOrBillingClick: - Pixel.fire(.privacyProSubscriptionManagementPlanBilling) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionManagementPlanBilling) case .removeSubscriptionClick: - Pixel.fire(.privacyProSubscriptionManagementRemoval) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionManagementRemoval) } } } diff --git a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift index 280d4420dd..41e1300195 100644 --- a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift @@ -22,6 +22,7 @@ import Combine import BrowserServicesKit import PrivacyDashboard import Common +import PixelKit protocol PrivacyDashboardViewControllerSizeDelegate: AnyObject { @@ -44,30 +45,27 @@ final class PrivacyDashboardViewController: NSViewController { private let brokenSiteReporter: BrokenSiteReporter = { BrokenSiteReporter(pixelHandler: { parameters in - Pixel.fire( - .brokenSiteReport, - withAdditionalParameters: parameters, - allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters - ) + PixelKit.fire(GeneralPixel.brokenSiteReport, + withAdditionalParameters: parameters, + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) }, keyValueStoring: UserDefaults.standard) }() private let toggleProtectionsOffReporter: BrokenSiteReporter = { BrokenSiteReporter(pixelHandler: { parameters in - Pixel.fire( - .protectionToggledOffBreakageReport, - withAdditionalParameters: parameters, - allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) + PixelKit.fire(GeneralPixel.protectionToggledOffBreakageReport, + withAdditionalParameters: parameters, + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) }, keyValueStoring: UserDefaults.standard) }() private let toggleReportEvents = EventMapping { event, _, parameters, _ in - let domainEvent: Pixel.Event + let domainEvent: GeneralPixel switch event { case .toggleReportDismiss: domainEvent = .toggleReportDismiss case .toggleReportDoNotSend: domainEvent = .toggleReportDoNotSend } - Pixel.fire(domainEvent, withAdditionalParameters: parameters) + PixelKit.fire(domainEvent, withAdditionalParameters: parameters) } private let permissionHandler = PrivacyDashboardPermissionHandler() @@ -177,10 +175,10 @@ final class PrivacyDashboardViewController: NSViewController { let configuration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig if state.isProtected && configuration.isUserUnprotected(domain: domain) { configuration.userEnabledProtection(forDomain: domain) - Pixel.fire(.dashboardProtectionAllowlistRemove(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.dashboardProtectionAllowlistRemove(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) } else { configuration.userDisabledProtection(forDomain: domain) - Pixel.fire(.dashboardProtectionAllowlistAdd(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.dashboardProtectionAllowlistAdd(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) } let completionToken = ContentBlocking.shared.contentBlockingManager.scheduleCompilation() @@ -193,7 +191,7 @@ final class PrivacyDashboardViewController: NSViewController { extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { func privacyDashboardControllerDidRequestShowReportBrokenSite(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController) { - // Not used in macOS: Pixel.fire(.privacyDashboardReportBrokenSite) + // Not used in macOS: PixelKit.fire(GeneralPixel.privacyDashboardReportBrokenSite) } func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, diff --git a/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift b/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift index 60d8eb0309..015f0606f3 100644 --- a/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift +++ b/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift @@ -18,6 +18,7 @@ import Foundation import BrowserServicesKit +import PixelKit final class AutofillNeverPromptWebsitesManager { @@ -48,7 +49,7 @@ final class AutofillNeverPromptWebsitesManager { fetchNeverPromptWebsites() return id } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) throw error } } @@ -64,7 +65,7 @@ final class AutofillNeverPromptWebsitesManager { fetchNeverPromptWebsites() return true } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) return false } } @@ -77,7 +78,7 @@ final class AutofillNeverPromptWebsitesManager { do { neverPromptWebsites = try secureVault.neverPromptWebsites() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) neverPromptWebsites = [] } } diff --git a/DuckDuckGo/SecureVault/Model/PasswordManagementCreditCardModel.swift b/DuckDuckGo/SecureVault/Model/PasswordManagementCreditCardModel.swift index 9330a76820..967069d21e 100644 --- a/DuckDuckGo/SecureVault/Model/PasswordManagementCreditCardModel.swift +++ b/DuckDuckGo/SecureVault/Model/PasswordManagementCreditCardModel.swift @@ -27,7 +27,7 @@ final class PasswordManagementCreditCardModel: ObservableObject, PasswordManagem let dateFormatter = DateFormatter() dateFormatter.dateFormat = "MMMM, yyyy" return dateFormatter - } () + }() var onDirtyChanged: (Bool) -> Void var onSaveRequested: (Model) -> Void diff --git a/DuckDuckGo/SecureVault/Model/PasswordManagementIdentityModel.swift b/DuckDuckGo/SecureVault/Model/PasswordManagementIdentityModel.swift index 6b656d3642..a4c0a9a597 100644 --- a/DuckDuckGo/SecureVault/Model/PasswordManagementIdentityModel.swift +++ b/DuckDuckGo/SecureVault/Model/PasswordManagementIdentityModel.swift @@ -28,7 +28,7 @@ final class PasswordManagementIdentityModel: ObservableObject, PasswordManagemen dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .none return dateFormatter - } () + }() var onDirtyChanged: (Bool) -> Void var onSaveRequested: (SecureVaultModels.Identity) -> Void diff --git a/DuckDuckGo/SecureVault/Model/PasswordManagementLoginModel.swift b/DuckDuckGo/SecureVault/Model/PasswordManagementLoginModel.swift index 12cc2ac135..defe6b5029 100644 --- a/DuckDuckGo/SecureVault/Model/PasswordManagementLoginModel.swift +++ b/DuckDuckGo/SecureVault/Model/PasswordManagementLoginModel.swift @@ -29,7 +29,7 @@ final class PasswordManagementLoginModel: ObservableObject, PasswordManagementIt dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .medium return dateFormatter - } () + }() var onSaveRequested: (SecureVaultModels.WebsiteCredentials) -> Void var onDeleteRequested: (SecureVaultModels.WebsiteCredentials) -> Void diff --git a/DuckDuckGo/SecureVault/Model/PasswordManagementNoteModel.swift b/DuckDuckGo/SecureVault/Model/PasswordManagementNoteModel.swift index 894c2a48ff..2c7a00364f 100644 --- a/DuckDuckGo/SecureVault/Model/PasswordManagementNoteModel.swift +++ b/DuckDuckGo/SecureVault/Model/PasswordManagementNoteModel.swift @@ -28,7 +28,7 @@ final class PasswordManagementNoteModel: ObservableObject, PasswordManagementIte dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .medium return dateFormatter - } () + }() var onDirtyChanged: (Bool) -> Void var onSaveRequested: (SecureVaultModels.Note) -> Void diff --git a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift index 5b5722bf34..0a76643d88 100644 --- a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift +++ b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift @@ -19,6 +19,7 @@ import Foundation import BrowserServicesKit import SecureStorage +import PixelKit final class SecureVaultErrorReporter: SecureVaultErrorReporting { static let shared = SecureVaultErrorReporter() @@ -29,9 +30,9 @@ final class SecureVaultErrorReporter: SecureVaultErrorReporting { switch error { case .initFailed, .failedToOpenDatabase: - Pixel.fire(.debug(event: .secureVaultInitError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultInitError(error: error))) default: - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/SecureVault/View/PasswordManagementBitwardenItemView.swift b/DuckDuckGo/SecureVault/View/PasswordManagementBitwardenItemView.swift index aad40477d2..3126331628 100644 --- a/DuckDuckGo/SecureVault/View/PasswordManagementBitwardenItemView.swift +++ b/DuckDuckGo/SecureVault/View/PasswordManagementBitwardenItemView.swift @@ -28,7 +28,7 @@ struct PasswordManagementBitwardenItemView: View { VStack(spacing: 2) { Text(UserText.passwordManagerPopoverTitle(managerName: manager.displayName)) - HStack (spacing: 3) { + HStack(spacing: 3) { Text(UserText.passwordManagerPopoverChangeInSettingsLabel) Button { WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .autofill) diff --git a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift index 9c9600f5b8..b5fd2cf4fc 100644 --- a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift +++ b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift @@ -24,6 +24,7 @@ import DDGSync import Foundation import SecureStorage import SwiftUI +import PixelKit protocol PasswordManagementDelegate: AnyObject { @@ -548,7 +549,7 @@ final class PasswordManagementViewController: NSViewController { if case SecureStorageError.duplicateRecord = error { showDuplicateAlert() } else { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } } @@ -571,7 +572,7 @@ final class PasswordManagementViewController: NSViewController { postChange() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } @@ -615,7 +616,7 @@ final class PasswordManagementViewController: NSViewController { postChange() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } @@ -634,7 +635,7 @@ final class PasswordManagementViewController: NSViewController { self.requestSync() self.refreshData() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } default: @@ -657,7 +658,7 @@ final class PasswordManagementViewController: NSViewController { try self.secureVault?.deleteIdentityFor(identityId: id) self.refreshData() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } default: @@ -699,7 +700,7 @@ final class PasswordManagementViewController: NSViewController { try self.secureVault?.deleteCreditCardFor(cardId: id) self.refreshData() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } default: @@ -753,7 +754,7 @@ final class PasswordManagementViewController: NSViewController { self?.syncModelsOnNote(note) } } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } @@ -857,7 +858,7 @@ final class PasswordManagementViewController: NSViewController { items = cards.map(SecureVaultItem.card) } } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } DispatchQueue.main.async { diff --git a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift index 4647fd79a8..fa4da02ff7 100644 --- a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift @@ -20,6 +20,7 @@ import AppKit import BrowserServicesKit import Combine import Common +import PixelKit protocol SaveCredentialsDelegate: AnyObject { @@ -227,10 +228,10 @@ final class SaveCredentialsViewController: NSViewController { } } catch { os_log("%s:%s: failed to store credentials %s", type: .error, className, #function, error.localizedDescription) - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } - Pixel.fire(.autofillItemSaved(kind: .password)) + PixelKit.fire(GeneralPixel.autofillItemSaved(kind: .password)) if passwordManagerCoordinator.isEnabled { passwordManagerCoordinator.reportPasswordSave() @@ -313,7 +314,7 @@ final class SaveCredentialsViewController: NSViewController { } catch { os_log("%: failed to save never prompt for website %s", type: .error, #function, error.localizedDescription) } - Pixel.fire(.autofillLoginsSaveLoginModalExcludeSiteConfirmed) + PixelKit.fire(GeneralPixel.autofillLoginsSaveLoginModalExcludeSiteConfirmed) onNotNowClicked(sender: nil) } diff --git a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift index 075e31befd..88006e5096 100644 --- a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift @@ -20,6 +20,7 @@ import AppKit import BrowserServicesKit import Combine import Common +import PixelKit protocol SaveIdentityDelegate: AnyObject { @@ -71,10 +72,10 @@ final class SaveIdentityViewController: NSViewController { do { try AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared).storeIdentity(identity) - Pixel.fire(.autofillItemSaved(kind: .identity)) + PixelKit.fire(GeneralPixel.autofillItemSaved(kind: .identity)) } catch { os_log("%s:%s: failed to store identity %s", type: .error, className, #function, error.localizedDescription) - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift index 652c0aa6ae..68275eeb5e 100644 --- a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift +++ b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift @@ -20,6 +20,7 @@ import Foundation import BrowserServicesKit import Combine import Common +import PixelKit protocol SavePaymentMethodDelegate: AnyObject { @@ -94,7 +95,7 @@ final class SavePaymentMethodViewController: NSViewController { try AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared).storeCreditCard(paymentMethod) } catch { os_log("%s:%s: failed to store payment method %s", type: .error, className, #function, error.localizedDescription) - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift index e76155b196..cb16b2bcef 100644 --- a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift +++ b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift @@ -20,6 +20,7 @@ import BrowserServicesKit import Common import Foundation import Persistence +import PixelKit protocol PrivacyFeaturesProtocol { var contentBlocking: AnyContentBlocking { get } @@ -42,22 +43,31 @@ final class AppPrivacyFeatures: PrivacyFeaturesProtocol { let httpsUpgrade: HTTPSUpgrade private static let httpsUpgradeDebugEvents = EventMapping { event, error, parameters, onComplete in - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel let dailyAndCount: Bool switch event { case .dbSaveBloomFilterError: - domainEvent = .dbSaveBloomFilterError + domainEvent = GeneralPixel.dbSaveBloomFilterError(error: error) dailyAndCount = true case .dbSaveExcludedHTTPSDomainsError: - domainEvent = .dbSaveExcludedHTTPSDomainsError + domainEvent = GeneralPixel.dbSaveExcludedHTTPSDomainsError(error: error) dailyAndCount = false } if dailyAndCount { - DailyPixel.fire(pixel: .debug(event: domainEvent, error: error), frequency: .dailyAndCount, includeAppVersionParameter: true, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + PixelKit.fire(DebugEvent(domainEvent, error: error), + frequency: .dailyAndCount, + withAdditionalParameters: parameters ?? [:], + includeAppVersionParameter: true) { _, error in + onComplete(error) + } } else { - Pixel.fire(.debug(event: domainEvent, error: error), withAdditionalParameters: parameters, onComplete: onComplete) + PixelKit.fire(DebugEvent(domainEvent, error: error), + frequency: .dailyAndCount, + withAdditionalParameters: parameters ?? [:]) { _, error in + onComplete(error) + } } } private static var embeddedBloomFilterResources: EmbeddedBloomFilterResources { diff --git a/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift b/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift index 521b01b490..e99fd44f8c 100644 --- a/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift +++ b/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift @@ -19,6 +19,7 @@ import Foundation import Combine import Common +import PixelKit @MainActor final class AppStateRestorationManager: NSObject { @@ -71,10 +72,8 @@ final class AppStateRestorationManager: NSObject { // ignore } catch { os_log("App state could not be decoded: %s", "\(error)") - Pixel.fire( - .debug(event: .appStateRestorationFailed, error: error), - withAdditionalParameters: ["interactive": String(interactive)] - ) + PixelKit.fire(DebugEvent(GeneralPixel.appStateRestorationFailed, error: error), + withAdditionalParameters: ["interactive": String(interactive)]) } return state @@ -140,7 +139,7 @@ final class AppStateRestorationManager: NSObject { // ignore } catch { os_log("Pinned tabs state could not be decoded: %s", "\(error)") - Pixel.fire(.debug(event: .appStateRestorationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.appStateRestorationFailed, error: error)) } } diff --git a/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift b/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift index 9a0baf2bff..b26f411d9c 100644 --- a/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift +++ b/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit /// A type that handles Pixels for acquisition attributions. protocol AttributionsPixelHandler: AnyObject { @@ -40,7 +41,7 @@ final class InstallationAttributionPixelHandler: AttributionsPixelHandler { /// - originProvider: A provider for the origin used to track the acquisition funnel. /// - locale: The locale of the device. init( - fireRequest: @escaping FireRequest = Pixel.fire, + fireRequest: @escaping FireRequest = PixelKit.fire, originProvider: AttributionOriginProvider = AttributionOriginFileProvider(), locale: Locale = .current ) { @@ -51,12 +52,13 @@ final class InstallationAttributionPixelHandler: AttributionsPixelHandler { func fireInstallationAttributionPixel() { fireRequest( - .installationAttribution, - .initial, + GeneralPixel.installationAttribution, + .legacyInitial, + [:], additionalParameters(origin: originProvider.origin, locale: locale.identifier), nil, - true, - { _ in } + nil, + true, { _, _ in } ) } } @@ -77,11 +79,13 @@ private extension InstallationAttributionPixelHandler { extension InstallationAttributionPixelHandler { typealias FireRequest = ( - _ event: Pixel.Event, - _ repetition: Pixel.Event.Repetition, + _ event: PixelKit.Event, + _ frequency: PixelKit.Frequency, + _ headers: [String: String], _ parameters: [String: String]?, + _ error: Error?, _ allowedQueryReservedCharacters: CharacterSet?, _ includeAppVersionParameter: Bool, - _ onComplete: @escaping (Error?) -> Void + _ onComplete: @escaping (Bool, Error?) -> Void ) -> Void } diff --git a/DuckDuckGo/Statistics/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents b/DuckDuckGo/Statistics/ATB/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents similarity index 100% rename from DuckDuckGo/Statistics/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents rename to DuckDuckGo/Statistics/ATB/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents diff --git a/DuckDuckGo/Statistics/PixelDataRecord.swift b/DuckDuckGo/Statistics/ATB/PixelDataRecord.swift similarity index 100% rename from DuckDuckGo/Statistics/PixelDataRecord.swift rename to DuckDuckGo/Statistics/ATB/PixelDataRecord.swift diff --git a/DuckDuckGo/Statistics/PixelDataStore.swift b/DuckDuckGo/Statistics/ATB/PixelDataStore.swift similarity index 100% rename from DuckDuckGo/Statistics/PixelDataStore.swift rename to DuckDuckGo/Statistics/ATB/PixelDataStore.swift diff --git a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift index 72a928c43b..744f4b6071 100644 --- a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift +++ b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift @@ -20,6 +20,7 @@ import Common import Foundation import BrowserServicesKit import Networking +import PixelKit final class StatisticsLoader { @@ -59,7 +60,7 @@ final class StatisticsLoader { completion() } } - Pixel.fire(.serp) + PixelKit.fire(GeneralPixel.serp) self.fireDailyOsVersionCounterPixel() } else if !self.statisticsStore.isAppRetentionFiredToday { self.refreshAppRetentionAtb(completion: completion) @@ -224,9 +225,9 @@ final class StatisticsLoader { let randomDelay = Double.random(in: 0.5...5) DispatchQueue.global().asyncAfter(deadline: .now() + randomDelay) { - Pixel.fire(.dailyOsVersionCounter, - limitTo: .dailyFirst, - includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.dailyOsVersionCounter, + frequency: .legacyDaily, + includeAppVersionParameter: false) } } diff --git a/DuckDuckGo/Statistics/DailyPixel.swift b/DuckDuckGo/Statistics/DailyPixel.swift deleted file mode 100644 index e436d453ba..0000000000 --- a/DuckDuckGo/Statistics/DailyPixel.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// DailyPixel.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 Common - -final class DailyPixel { - - public enum PixelFrequency { - /// Sent once per day. The last timestamp for this pixel is stored and compared to the current date. Pixels of this type will have `_d` appended to their name. - case dailyOnly - - /// Sent once per day with a `_d` suffix, in addition to every time it is called with a `_c` suffix. - /// This means a pixel will get sent twice the first time it is called per-day, and subsequent calls that day will only send the `_c` variant. - /// This is useful in situations where pixels receive spikes in volume, as the daily pixel can be used to determine how many users are actually affected. - case dailyAndCount - } - - enum Error: Swift.Error { - case alreadyFired - } - - enum Constant { - static let dailyPixelStorageIdentifier = "com.duckduckgo.daily.pixel.storage" - } - - private static let storage: UserDefaults = UserDefaults(suiteName: Constant.dailyPixelStorageIdentifier)! - - static func fire(pixel: Pixel.Event, - frequency: PixelFrequency, - includeAppVersionParameter includeAppVersion: Bool = true, - withAdditionalParameters params: [String: String] = [:], - onComplete: @escaping (Swift.Error?) -> Void = { _ in }) { - switch frequency { - case .dailyOnly: - if !pixel.hasBeenFiredToday(dailyPixelStorage: storage) { - Pixel.shared?.fire(.dailyPixel(pixel, isFirst: true), withAdditionalParameters: params, includeAppVersionParameter: includeAppVersion) - updatePixelLastFireDate(pixel: pixel) - } - case .dailyAndCount: - if !pixel.hasBeenFiredToday(dailyPixelStorage: storage) { - Pixel.shared?.fire(.dailyPixel(pixel, isFirst: true), withAdditionalParameters: params, includeAppVersionParameter: includeAppVersion) - updatePixelLastFireDate(pixel: pixel) - } - - Pixel.shared?.fire(.dailyPixel(pixel, isFirst: false), withAdditionalParameters: params, includeAppVersionParameter: includeAppVersion) - } - } - - private static func updatePixelLastFireDate(pixel: Pixel.Event) { - storage.set(Date(), forKey: pixel.name) - } - - static func clearLastFireDate(pixel: Pixel.Event) { - storage.removeObject(forKey: pixel.name) - } - -} - -private extension Pixel.Event { - - func hasBeenFiredToday(dailyPixelStorage: UserDefaults) -> Bool { - if let lastFireDate = dailyPixelStorage.object(forKey: name) as? Date { - return Calendar.current.isDate(Date(), inSameDayAs: lastFireDate) - } - - return false - } - -} diff --git a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift index 64af60e55b..eb0f07b592 100644 --- a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift +++ b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift @@ -16,7 +16,13 @@ // limitations under the License. // +/* + WARNING: This component doesn't work anymore, re-implementation needed + https://app.asana.com/0/0/1207002879349166/f + */ + import Foundation +import PixelKit enum PixelExperiment: String, CaseIterable { @@ -54,7 +60,7 @@ enum PixelExperiment: String, CaseIterable { case control } -/// These functions contain the business logic for determining if the pixel should be fired or not. +// These functions contain the business logic for determining if the pixel should be fired or not. extension PixelExperiment { static func fireEnrollmentPixel() { @@ -128,7 +134,7 @@ final internal class PixelExperimentLogic { isInstalled = true } - // You'll need additional pixels for your experiment. Pass the cohort as a paramter. + // You'll need additional pixels for your experiment. Pass the cohort as a parameter. func fireEnrollmentPixel() { // You'll probably need this at least. } @@ -136,15 +142,18 @@ final internal class PixelExperimentLogic { // Often used func fireFirstSerpPixel() { guard allocatedCohort != nil, let cohort else { return } - Pixel.fire(.serpInitial(cohort: cohort.rawValue), limitTo: .initial, includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) } // Often used for retention experiments func fireDay21To27SerpPixel() { guard allocatedCohort != nil, let cohort else { return } - if now() >= Pixel.firstLaunchDate.adding(.days(21)) && now() <= Pixel.firstLaunchDate.adding(.days(27)) { - Pixel.fire(.serpDay21to27(cohort: cohort.rawValue), limitTo: .initial, includeAppVersionParameter: false) + DispatchQueue.main.async { + let now = self.now() + if now >= AppDelegate.firstLaunchDate.adding(.days(21)) && now <= AppDelegate.firstLaunchDate.adding(.days(27)) { + PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) + } } } diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/GeneralPixel.swift similarity index 50% rename from DuckDuckGo/Statistics/PixelEvent.swift rename to DuckDuckGo/Statistics/GeneralPixel.swift index 902e5b0b5a..285a3bbdee 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -1,7 +1,7 @@ // -// PixelEvent.swift +// GeneralPixel.swift // -// Copyright © 2021 DuckDuckGo. All rights reserved. +// Copyright © 2024 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,427 +16,303 @@ // limitations under the License. // -import Foundation +import AppKit +import PixelKit import BrowserServicesKit -import Bookmarks -import Configuration import DDGSync -import PixelKit - -extension Pixel { - - indirect enum Event { - /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our - /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure - /// nothing breaks in the migration towards `PixelKit`. - case pixelKitEvent(_ event: PixelKitEvent) - - case crash - - case brokenSiteReport - - enum OnboardingShown: String, CustomStringConvertible { - var description: String { rawValue } - - init(_ value: Bool) { - if value { - self = .onboardingShown - } else { - self = .regularNavigation - } - } - case onboardingShown = "onboarding-shown" - case regularNavigation = "regular-nav" - } - - enum WaitResult: String, CustomStringConvertible { - var description: String { rawValue } - - case closed - case quit - case success - } - - enum CompileRulesWaitTime: String, CustomStringConvertible { - var description: String { rawValue } - - case noWait = "0" - case lessThan1s = "1" - case lessThan5s = "5" - case lessThan10s = "10" - case lessThan20s = "20" - case lessThan40s = "40" - case more = "more" - } - - case compileRulesWait(onboardingShown: OnboardingShown, waitTime: CompileRulesWaitTime, result: WaitResult) - static func compileRulesWait(onboardingShown: Bool, waitTime interval: TimeInterval, result: WaitResult) -> Event { - let waitTime: CompileRulesWaitTime - switch interval { - case 0: - waitTime = .noWait - case ...1: - waitTime = .lessThan1s - case ...5: - waitTime = .lessThan5s - case ...10: - waitTime = .lessThan10s - case ...20: - waitTime = .lessThan20s - case ...40: - waitTime = .lessThan40s - default: - waitTime = .more - } - return .compileRulesWait(onboardingShown: OnboardingShown(onboardingShown), - waitTime: waitTime, - result: result) - } - - case launchInitial(cohort: String) - - case serp - case serpInitial(cohort: String) - case serpDay21to27(cohort: String) - - case dailyOsVersionCounter - - case dataImportFailed(source: DataImport.Source, sourceVersion: String?, error: any DataImportError) - - case formAutofilled(kind: FormAutofillKind) - case autofillItemSaved(kind: FormAutofillKind) - - case autofillLoginsSaveLoginModalExcludeSiteConfirmed - case autofillLoginsSettingsResetExcludedDisplayed - case autofillLoginsSettingsResetExcludedConfirmed - case autofillLoginsSettingsResetExcludedDismissed - - case bitwardenPasswordAutofilled - case bitwardenPasswordSaved - - case ampBlockingRulesCompilationFailed - - case adClickAttributionDetected - case adClickAttributionActive - case adClickAttributionPageLoads - - case emailEnabled - case emailDisabled - case emailUserPressedUseAddress - case emailUserPressedUseAlias - case emailUserCreatedAlias - - case jsPixel(_ pixel: AutofillUserScript.JSPixel) - - case debug(event: Debug, error: Error? = nil) - - // Activation Points - case newTabInitial - case emailEnabledInitial - case watchInDuckPlayerInitial - case setAsDefaultInitial - case importDataInitial - - // New Tab section removed - case favoriteSectionHidden - case recentActivitySectionHidden - case continueSetUpSectionHidden - - // Fire Button - case fireButtonFirstBurn - case fireButton(option: FireButtonOption) - - // Duck Player - case duckPlayerDailyUniqueView - case duckPlayerViewFromYoutubeViaMainOverlay - case duckPlayerViewFromYoutubeViaHoverButton - case duckPlayerViewFromYoutubeAutomatic - case duckPlayerViewFromSERP - case duckPlayerViewFromOther - case duckPlayerSettingAlways - case duckPlayerSettingNever - case duckPlayerSettingBackToDefault - - // Dashboard - case dashboardProtectionAllowlistAdd(triggerOrigin: String?) - case dashboardProtectionAllowlistRemove(triggerOrigin: String?) - - // VPN - case vpnBreakageReport(category: String, description: String, metadata: String) - - // VPN - case networkProtectionRemoteMessageDisplayed(messageID: String) - case networkProtectionRemoteMessageDismissed(messageID: String) - case networkProtectionRemoteMessageOpened(messageID: String) - case networkProtectionEnabledOnSearch - case networkProtectionGeoswitchingOpened - case networkProtectionGeoswitchingSetNearest - case networkProtectionGeoswitchingSetCustom - case networkProtectionGeoswitchingNoLocations - - // Sync - case syncSignupDirect - case syncSignupConnect - case syncLogin - case syncDaily - case syncDuckAddressOverride - case syncSuccessRateDaily - case syncLocalTimestampResolutionTriggered(Feature) - case syncBookmarksCountLimitExceededDaily - case syncCredentialsCountLimitExceededDaily - case syncBookmarksRequestSizeLimitExceededDaily - case syncCredentialsRequestSizeLimitExceededDaily - - // DataBroker Protection Waitlist - case dataBrokerProtectionWaitlistUserActive - case dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed - case dataBrokerProtectionWaitlistIntroDisplayed - case dataBrokerProtectionWaitlistNotificationShown - case dataBrokerProtectionWaitlistNotificationTapped - case dataBrokerProtectionWaitlistCardUITapped - case dataBrokerProtectionWaitlistTermsAndConditionsDisplayed - case dataBrokerProtectionWaitlistTermsAndConditionsAccepted - case dataBrokerProtectionRemoteMessageDisplayed(messageID: String) - case dataBrokerProtectionRemoteMessageDismissed(messageID: String) - case dataBrokerProtectionRemoteMessageOpened(messageID: String) - - // Login Item events - case dataBrokerEnableLoginItemDaily - case dataBrokerDisableLoginItemDaily - case dataBrokerResetLoginItemDaily - case dataBrokerDisableAndDeleteDaily - - // DataBrokerProtection Other - case dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn - - // Subscription - case privacyProFeatureEnabled - case privacyProBetaUserThankYouVPN - case privacyProBetaUserThankYouDBP - case privacyProSubscriptionActive - case privacyProOfferScreenImpression - case privacyProPurchaseAttempt - case privacyProPurchaseFailure - case privacyProPurchaseFailureStoreError - case privacyProPurchaseFailureBackendError - case privacyProPurchaseFailureAccountNotCreated - case privacyProPurchaseSuccess - case privacyProRestorePurchaseOfferPageEntry - case privacyProRestorePurchaseClick - case privacyProRestorePurchaseSettingsMenuEntry - case privacyProRestorePurchaseEmailStart - case privacyProRestorePurchaseStoreStart - case privacyProRestorePurchaseEmailSuccess - case privacyProRestorePurchaseStoreSuccess - case privacyProRestorePurchaseStoreFailureNotFound - case privacyProRestorePurchaseStoreFailureOther - case privacyProRestoreAfterPurchaseAttempt - case privacyProSubscriptionActivated - case privacyProWelcomeAddDevice - case privacyProSettingsAddDevice - case privacyProAddDeviceEnterEmail - case privacyProWelcomeVPN - case privacyProWelcomePersonalInformationRemoval - case privacyProWelcomeIdentityRestoration - case privacyProSubscriptionSettings - case privacyProVPNSettings - case privacyProPersonalInformationRemovalSettings - case privacyProIdentityRestorationSettings - case privacyProSubscriptionManagementEmail - case privacyProSubscriptionManagementPlanBilling - case privacyProSubscriptionManagementRemoval - case privacyProPurchaseStripeSuccess - // Web pixels - case privacyProOfferMonthlyPriceClick - case privacyProOfferYearlyPriceClick - case privacyProAddEmailSuccess - case privacyProWelcomeFAQClick - - case dailyPixel(Event, isFirst: Bool) - - // Default Browser - case defaultRequestedFromHomepage - case defaultRequestedFromHomepageSetupView - case defaultRequestedFromSettings - case defaultRequestedFromOnboarding - - case protectionToggledOffBreakageReport - case toggleProtectionsDailyCount - case toggleReportDoNotSend - case toggleReportDismiss - - // Password Import Keychain Prompt - case passwordImportKeychainPrompt - case passwordImportKeychainPromptDenied - - // Tracks installation without tracking retention. - case installationAttribution - - enum Debug { - /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our - /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure - /// nothing breaks in the migration towards `PixelKit`. - case pixelKitEvent(_ event: PixelKitEvent) - - case assertionFailure(message: String, file: StaticString, line: UInt) - - case dbMakeDatabaseError - case dbContainerInitializationError - case dbInitializationError - case dbSaveExcludedHTTPSDomainsError - case dbSaveBloomFilterError - - case configurationFetchError - - case trackerDataParseFailed - case trackerDataReloadFailed - case trackerDataCouldNotBeLoaded - - case privacyConfigurationParseFailed - case privacyConfigurationReloadFailed - case privacyConfigurationCouldNotBeLoaded - - case fileStoreWriteFailed - case fileMoveToDownloadsFailed - case fileAccessRelatedItemFailed - case fileGetDownloadLocationFailed - case fileDownloadCreatePresentersFailed - case downloadResumeDataCodingFailed - - case suggestionsFetchFailed - case appOpenURLFailed - case appStateRestorationFailed - - case contentBlockingErrorReportingIssue - - case contentBlockingCompilationFailed(listType: CompileRulesListType, - component: ContentBlockerDebugEvents.Component) - - case contentBlockingCompilationTime - - case secureVaultInitError - case secureVaultError - - case feedbackReportingFailed - - case blankNavigationOnBurnFailed - - case historyRemoveFailed - case historyReloadFailed - case historyCleanEntriesFailed - case historyCleanVisitsFailed - case historySaveFailed - case historySaveFailedDaily - case historyInsertVisitFailed - case historyRemoveVisitsFailed - - case emailAutofillKeychainError - - case bookmarksStoreRootFolderMigrationFailed - case bookmarksStoreFavoritesFolderMigrationFailed - - case adAttributionCompilationFailedForAttributedRulesList - case adAttributionGlobalAttributedRulesDoNotExist - case adAttributionDetectionHeuristicsDidNotMatchDomain - case adAttributionLogicUnexpectedStateOnRulesCompiled - case adAttributionLogicUnexpectedStateOnInheritedAttribution - case adAttributionLogicUnexpectedStateOnRulesCompilationFailed - case adAttributionDetectionInvalidDomainInParameter - case adAttributionLogicRequestingAttributionTimedOut - case adAttributionLogicWrongVendorOnSuccessfulCompilation - case adAttributionLogicWrongVendorOnFailedCompilation - - case webKitDidTerminate - - case removedInvalidBookmarkManagedObjects - - case bitwardenNotResponding - case bitwardenRespondedCannotDecryptUnique(repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) - case bitwardenHandshakeFailed - case bitwardenDecryptionOfSharedKeyFailed - case bitwardenStoringOfTheSharedKeyFailed - case bitwardenCredentialRetrievalFailed - case bitwardenCredentialCreationFailed - case bitwardenCredentialUpdateFailed - case bitwardenRespondedWithError - case bitwardenNoActiveVault - case bitwardenParsingFailed - case bitwardenStatusParsingFailed - case bitwardenHmacComparisonFailed - case bitwardenDecryptionFailed - case bitwardenSendingOfMessageFailed - case bitwardenSharedKeyInjectionFailed - - case updaterAborted - case userSelectedToSkipUpdate - case userSelectedToInstallUpdate - case userSelectedToDismissUpdate - - case faviconDecryptionFailedUnique - case downloadListItemDecryptionFailedUnique - case historyEntryDecryptionFailedUnique - case permissionDecryptionFailedUnique - - // Errors from Bookmarks Module - case missingParent - case bookmarksSaveFailed - case bookmarksSaveFailedOnImport - - case bookmarksCouldNotLoadDatabase - case bookmarksCouldNotPrepareDatabase - case bookmarksMigrationAlreadyPerformed - case bookmarksMigrationFailed - case bookmarksMigrationCouldNotPrepareDatabase - case bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration - case bookmarksMigrationCouldNotRemoveOldStore - case bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders - - case syncSentUnauthenticatedRequest - case syncMetadataCouldNotLoadDatabase - case syncBookmarksProviderInitializationFailed - case syncBookmarksFailed - case syncCredentialsProviderInitializationFailed - case syncCredentialsFailed - case syncSettingsFailed - case syncSettingsMetadataUpdateFailed - case syncSignupError - case syncLoginError - case syncLogoutError - case syncUpdateDeviceError - case syncRemoveDeviceError - case syncDeleteAccountError - case syncLoginExistingAccountError - case syncCannotCreateRecoveryPDF - - case bookmarksCleanupFailed - case bookmarksCleanupAttemptedWhileSyncWasEnabled - case favoritesCleanupFailed - case bookmarksFaviconsFetcherStateStoreInitializationFailed - case bookmarksFaviconsFetcherFailed - - case credentialsDatabaseCleanupFailed - case credentialsCleanupAttemptedWhileSyncWasEnabled - - case invalidPayload(Configuration) - - case burnerTabMisplaced - - case networkProtectionRemoteMessageFetchingFailed - case networkProtectionRemoteMessageStorageFailed - case dataBrokerProtectionRemoteMessageFetchingFailed - case dataBrokerProtectionRemoteMessageStorageFailed - - case loginItemUpdateError(loginItemBundleID: String, action: String, buildType: String, osVersion: String) - } - } -} +import Configuration -extension Pixel.Event { +// swiftlint:disable:next type_body_length +enum GeneralPixel: PixelKitEventV2 { + + case crash + case brokenSiteReport + case compileRulesWait(onboardingShown: OnboardingShown, waitTime: CompileRulesWaitTime, result: WaitResult) + case launchInitial(cohort: String) + + case serp + case serpInitial(cohort: String) + case serpDay21to27(cohort: String) + + case dailyOsVersionCounter + + case dataImportFailed(source: DataImport.Source, sourceVersion: String?, error: any DataImportError) + + case formAutofilled(kind: FormAutofillKind) + case autofillItemSaved(kind: FormAutofillKind) + + case autofillLoginsSaveLoginModalExcludeSiteConfirmed + case autofillLoginsSettingsResetExcludedDisplayed + case autofillLoginsSettingsResetExcludedConfirmed + case autofillLoginsSettingsResetExcludedDismissed + + case bitwardenPasswordAutofilled + case bitwardenPasswordSaved + + case ampBlockingRulesCompilationFailed + + case adClickAttributionDetected + case adClickAttributionActive + case adClickAttributionPageLoads + + case emailEnabled + case emailDisabled + case emailUserPressedUseAddress + case emailUserPressedUseAlias + case emailUserCreatedAlias + + case jsPixel(_ pixel: AutofillUserScript.JSPixel) + + // Activation Points + case newTabInitial + case emailEnabledInitial + case watchInDuckPlayerInitial + case setAsDefaultInitial + case importDataInitial + + // New Tab section removed + case favoriteSectionHidden + case recentActivitySectionHidden + case continueSetUpSectionHidden + + // Fire Button + case fireButtonFirstBurn + case fireButton(option: FireButtonOption) + + // Duck Player + case duckPlayerDailyUniqueView + case duckPlayerViewFromYoutubeViaMainOverlay + case duckPlayerViewFromYoutubeViaHoverButton + case duckPlayerViewFromYoutubeAutomatic + case duckPlayerViewFromSERP + case duckPlayerViewFromOther + case duckPlayerSettingAlways + case duckPlayerSettingNever + case duckPlayerSettingBackToDefault + + // Dashboard + case dashboardProtectionAllowlistAdd(triggerOrigin: String?) + case dashboardProtectionAllowlistRemove(triggerOrigin: String?) + + // VPN + case vpnBreakageReport(category: String, description: String, metadata: String) + + // VPN + case networkProtectionRemoteMessageDisplayed(messageID: String) + case networkProtectionRemoteMessageDismissed(messageID: String) + case networkProtectionRemoteMessageOpened(messageID: String) + case networkProtectionEnabledOnSearch + case networkProtectionGeoswitchingOpened + case networkProtectionGeoswitchingSetNearest + case networkProtectionGeoswitchingSetCustom + case networkProtectionGeoswitchingNoLocations + + // Sync + case syncSignupDirect + case syncSignupConnect + case syncLogin + case syncDaily + case syncDuckAddressOverride + case syncSuccessRateDaily + case syncLocalTimestampResolutionTriggered(Feature) + case syncBookmarksCountLimitExceededDaily + case syncCredentialsCountLimitExceededDaily + case syncBookmarksRequestSizeLimitExceededDaily + case syncCredentialsRequestSizeLimitExceededDaily + + // DataBroker Protection Waitlist + case dataBrokerProtectionWaitlistUserActive + case dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed + case dataBrokerProtectionWaitlistIntroDisplayed + case dataBrokerProtectionWaitlistNotificationShown + case dataBrokerProtectionWaitlistNotificationTapped + case dataBrokerProtectionWaitlistCardUITapped + case dataBrokerProtectionWaitlistTermsAndConditionsDisplayed + case dataBrokerProtectionWaitlistTermsAndConditionsAccepted + case dataBrokerProtectionRemoteMessageDisplayed(messageID: String) + case dataBrokerProtectionRemoteMessageDismissed(messageID: String) + case dataBrokerProtectionRemoteMessageOpened(messageID: String) + + // Login Item events + case dataBrokerEnableLoginItemDaily + case dataBrokerDisableLoginItemDaily + case dataBrokerResetLoginItemDaily + case dataBrokerDisableAndDeleteDaily + + // DataBrokerProtection Other + case dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn + + // Default Browser + case defaultRequestedFromHomepage + case defaultRequestedFromHomepageSetupView + case defaultRequestedFromSettings + case defaultRequestedFromOnboarding + + case protectionToggledOffBreakageReport + case toggleProtectionsDailyCount + case toggleReportDoNotSend + case toggleReportDismiss + + // Password Import Keychain Prompt + case passwordImportKeychainPrompt + case passwordImportKeychainPromptDenied + + // MARK: - Debug + + case assertionFailure(message: String, file: StaticString, line: UInt) + + case dbMakeDatabaseError(error: Error?) + case dbContainerInitializationError(error: Error) + case dbInitializationError(error: Error) + case dbSaveExcludedHTTPSDomainsError(error: Error?) + case dbSaveBloomFilterError(error: Error?) + + case configurationFetchError(error: Error) + + case trackerDataParseFailed + case trackerDataReloadFailed + case trackerDataCouldNotBeLoaded + + case privacyConfigurationParseFailed + case privacyConfigurationReloadFailed + case privacyConfigurationCouldNotBeLoaded + + case fileStoreWriteFailed + case fileMoveToDownloadsFailed + case fileAccessRelatedItemFailed + case fileGetDownloadLocationFailed + case fileDownloadCreatePresentersFailed + case downloadResumeDataCodingFailed + + case suggestionsFetchFailed + case appOpenURLFailed + case appStateRestorationFailed + + case contentBlockingErrorReportingIssue + + case contentBlockingCompilationFailed(listType: CompileRulesListType, component: ContentBlockerDebugEvents.Component) + + case contentBlockingCompilationTime + + case secureVaultInitError(error: Error) + case secureVaultError(error: Error) + + case feedbackReportingFailed + + case blankNavigationOnBurnFailed + + case historyRemoveFailed + case historyReloadFailed + case historyCleanEntriesFailed + case historyCleanVisitsFailed + case historySaveFailed + case historySaveFailedDaily + case historyInsertVisitFailed + case historyRemoveVisitsFailed + + case emailAutofillKeychainError + + case bookmarksStoreRootFolderMigrationFailed + case bookmarksStoreFavoritesFolderMigrationFailed + + case adAttributionCompilationFailedForAttributedRulesList + case adAttributionGlobalAttributedRulesDoNotExist + case adAttributionDetectionHeuristicsDidNotMatchDomain + case adAttributionLogicUnexpectedStateOnRulesCompiled + case adAttributionLogicUnexpectedStateOnInheritedAttribution + case adAttributionLogicUnexpectedStateOnRulesCompilationFailed + case adAttributionDetectionInvalidDomainInParameter + case adAttributionLogicRequestingAttributionTimedOut + case adAttributionLogicWrongVendorOnSuccessfulCompilation + case adAttributionLogicWrongVendorOnFailedCompilation + + case webKitDidTerminate + + case removedInvalidBookmarkManagedObjects + + case bitwardenNotResponding + case bitwardenRespondedCannotDecrypt + case bitwardenHandshakeFailed + case bitwardenDecryptionOfSharedKeyFailed + case bitwardenStoringOfTheSharedKeyFailed + case bitwardenCredentialRetrievalFailed + case bitwardenCredentialCreationFailed + case bitwardenCredentialUpdateFailed + case bitwardenRespondedWithError + case bitwardenNoActiveVault + case bitwardenParsingFailed + case bitwardenStatusParsingFailed + case bitwardenHmacComparisonFailed + case bitwardenDecryptionFailed + case bitwardenSendingOfMessageFailed + case bitwardenSharedKeyInjectionFailed + + case updaterAborted + case userSelectedToSkipUpdate + case userSelectedToInstallUpdate + case userSelectedToDismissUpdate + + case faviconDecryptionFailedUnique + case downloadListItemDecryptionFailedUnique + case historyEntryDecryptionFailedUnique + case permissionDecryptionFailedUnique + + // Errors from Bookmarks Module + case missingParent + case bookmarksSaveFailed + case bookmarksSaveFailedOnImport + + case bookmarksCouldNotLoadDatabase(error: Error?) + case bookmarksCouldNotPrepareDatabase + case bookmarksMigrationAlreadyPerformed + case bookmarksMigrationFailed + case bookmarksMigrationCouldNotPrepareDatabase + case bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration + case bookmarksMigrationCouldNotRemoveOldStore + case bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders + + case syncSentUnauthenticatedRequest + case syncMetadataCouldNotLoadDatabase + case syncBookmarksProviderInitializationFailed + case syncBookmarksFailed + case syncCredentialsProviderInitializationFailed + case syncCredentialsFailed + case syncSettingsFailed + case syncSettingsMetadataUpdateFailed + case syncSignupError(error: Error) + case syncLoginError(error: Error) + case syncLogoutError(error: Error) + case syncUpdateDeviceError(error: Error) + case syncRemoveDeviceError(error: Error) + case syncDeleteAccountError(error: Error) + case syncLoginExistingAccountError(error: Error) + case syncCannotCreateRecoveryPDF + + case bookmarksCleanupFailed + case bookmarksCleanupAttemptedWhileSyncWasEnabled + case favoritesCleanupFailed + case bookmarksFaviconsFetcherStateStoreInitializationFailed + case bookmarksFaviconsFetcherFailed + + case credentialsDatabaseCleanupFailed + case credentialsCleanupAttemptedWhileSyncWasEnabled + + case invalidPayload(Configuration) // BSK>Configuration + + case burnerTabMisplaced + + case networkProtectionRemoteMessageFetchingFailed + case networkProtectionRemoteMessageStorageFailed + case dataBrokerProtectionRemoteMessageFetchingFailed + case dataBrokerProtectionRemoteMessageStorageFailed + + case loginItemUpdateError(loginItemBundleID: String, action: String, buildType: String, osVersion: String) + + // Tracks installation without tracking retention. + case installationAttribution var name: String { switch self { - case .pixelKitEvent(let event): - return event.name case .crash: return "m_mac_crash" @@ -479,9 +355,6 @@ extension Pixel.Event { case .bitwardenPasswordSaved: return "m_mac_bitwarden_save_password" - case .debug(event: let event, error: _): - return "m_mac_debug_\(event.name)" - case .ampBlockingRulesCompilationFailed: return "m_mac_amp_rules_compilation_failed" @@ -625,8 +498,6 @@ extension Pixel.Event { case .dataBrokerResetLoginItemDaily: return "m_mac_dbp_daily_login-item_reset" case .dataBrokerDisableAndDeleteDaily: return "m_mac_dbp_daily_disable-and-delete" - case .dailyPixel(let pixel, isFirst: let isFirst): - return pixel.name + (isFirst ? "_d" : "_c") case .networkProtectionGeoswitchingOpened: return "m_mac_netp_imp_geoswitching_c" case .networkProtectionGeoswitchingSetNearest: @@ -641,88 +512,16 @@ extension Pixel.Event { case .defaultRequestedFromSettings: return "m_mac_default_requested_from_settings" case .defaultRequestedFromOnboarding: return "m_mac_default_requested_from_onboarding" - // MARK: - Subscription - case .privacyProFeatureEnabled: return - "m_mac_\(appDistribution)_privacy-pro_feature_enabled" - case .privacyProBetaUserThankYouVPN: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_vpn" - case .privacyProBetaUserThankYouDBP: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_dbp" - case .privacyProSubscriptionActive: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_active" - case .privacyProOfferScreenImpression: return "m_mac_\(appDistribution)_privacy-pro_offer_screen_impression" - case .privacyProPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_terms-conditions_subscribe_click" - case .privacyProPurchaseFailure: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_other" - case .privacyProPurchaseFailureStoreError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_store" - case .privacyProPurchaseFailureBackendError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_backend" - case .privacyProPurchaseFailureAccountNotCreated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_account-creation" - case .privacyProPurchaseSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_success" - case .privacyProRestorePurchaseOfferPageEntry: return "m_mac_\(appDistribution)_privacy-pro_offer_restore-purchase_click" - case .privacyProRestorePurchaseClick: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" - case .privacyProRestorePurchaseSettingsMenuEntry: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" - case .privacyProRestorePurchaseEmailStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_enter-email_click" - case .privacyProRestorePurchaseStoreStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_restore-purchase_click" - case .privacyProRestorePurchaseEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-email_success" - case .privacyProRestorePurchaseStoreSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_success" - case .privacyProRestorePurchaseStoreFailureNotFound: return "m_mac_\(appDistribution)_privacy-pro_subscription-restore-using-store_failure_not-found" - case .privacyProRestorePurchaseStoreFailureOther: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_failure_other" - case .privacyProRestoreAfterPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-after-purchase-attempt_success" - case .privacyProSubscriptionActivated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_activated_u" - case .privacyProWelcomeAddDevice: return "m_mac_\(appDistribution)_privacy-pro_welcome_add-device_click_u" - case .privacyProSettingsAddDevice: return "m_mac_\(appDistribution)_privacy-pro_settings_add-device_click" - case .privacyProAddDeviceEnterEmail: return "m_mac_\(appDistribution)_privacy-pro_add-device_enter-email_click" - case .privacyProWelcomeVPN: return "m_mac_\(appDistribution)_privacy-pro_welcome_vpn_click_u" - case .privacyProWelcomePersonalInformationRemoval: return "m_mac_\(appDistribution)_privacy-pro_welcome_personal-information-removal_click_u" - case .privacyProWelcomeIdentityRestoration: return "m_mac_\(appDistribution)_privacy-pro_welcome_identity-theft-restoration_click_u" - case .privacyProSubscriptionSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_screen_impression" - case .privacyProVPNSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_vpn_click" - case .privacyProPersonalInformationRemovalSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_personal-information-removal_click" - case .privacyProIdentityRestorationSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_identity-theft-restoration_click" - case .privacyProSubscriptionManagementEmail: return "m_mac_\(appDistribution)_privacy-pro_manage-email_edit_click" - case .privacyProSubscriptionManagementPlanBilling: return "m_mac_\(appDistribution)_privacy-pro_settings_change-plan-or-billing_click" - case .privacyProSubscriptionManagementRemoval: return "m_mac_\(appDistribution)_privacy-pro_settings_remove-from-device_click" - case .privacyProPurchaseStripeSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_stripe_success" - // Web - case .privacyProOfferMonthlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_monthly-price_click" - case .privacyProOfferYearlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_yearly-price_click" - case .privacyProAddEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_add-email_success_u" - case .privacyProWelcomeFAQClick: return "m_mac_\(appDistribution)_privacy-pro_welcome_faq_click_u" - case .protectionToggledOffBreakageReport: return "m_mac_protection-toggled-off-breakage-report" case .toggleProtectionsDailyCount: return "m_mac_toggle-protections-daily-count" case .toggleReportDoNotSend: return "m_mac_toggle-report-do-not-send" case .toggleReportDismiss: return "m_mac_toggle-report-dismiss" - // Password Import Keychain Prompt + // Password Import Keychain Prompt case .passwordImportKeychainPrompt: return "m_mac_password_import_keychain_prompt" case .passwordImportKeychainPromptDenied: return "m_mac_password_import_keychain_prompt_denied" - // Installation Attribution - case .installationAttribution: return "m_mac_install" - } - } -} - -// swiftlint:disable private_over_fileprivate -#if APPSTORE -fileprivate let appDistribution = "store" -#else -fileprivate let appDistribution = "direct" -#endif -// swiftlint:enable private_over_fileprivate - -extension Pixel.Event: Equatable { - - static func == (lhs: Pixel.Event, rhs: Pixel.Event) -> Bool { - lhs.name == rhs.name && lhs.parameters == rhs.parameters - } - -} - -extension Pixel.Event.Debug { - - var name: String { - switch self { - case .pixelKitEvent(let event): - return event.name - + // DEBUG case .assertionFailure: return "assertion_failure" @@ -861,8 +660,8 @@ extension Pixel.Event.Debug { case .bitwardenNotResponding: return "bitwarden_not_responding" - case .bitwardenRespondedCannotDecryptUnique: - return "bitwarden_responded_cannot_decrypt_unique" + case .bitwardenRespondedCannotDecrypt: + return "bitwarden_responded_cannot_decrypt_d" case .bitwardenHandshakeFailed: return "bitwarden_handshake_failed" case .bitwardenDecryptionOfSharedKeyFailed: @@ -962,6 +761,176 @@ extension Pixel.Event.Debug { case .dataBrokerProtectionRemoteMessageStorageFailed: return "dbp_remote_message_storage_failed" case .loginItemUpdateError: return "login-item_update-error" + + // Installation Attribution + case .installationAttribution: return "m_mac_install" + } + } + + var error: (any Error)? { + switch self { + case .dbMakeDatabaseError(let error?), + .dbContainerInitializationError(let error), + .dbInitializationError(let error), + .dbSaveExcludedHTTPSDomainsError(let error?), + .dbSaveBloomFilterError(let error?), + .configurationFetchError(let error), + .secureVaultInitError(let error), + .secureVaultError(let error), + .syncSignupError(let error), + .syncLoginError(let error), + .syncLogoutError(let error), + .syncUpdateDeviceError(let error), + .syncRemoveDeviceError(let error), + .syncDeleteAccountError(let error), + .syncLoginExistingAccountError(let error), + .bookmarksCouldNotLoadDatabase(let error?): + return error + default: return nil + } + } + + var parameters: [String: String]? { + switch self { + case .loginItemUpdateError(let loginItemBundleID, let action, let buildType, let osVersion): + return ["loginItemBundleID": loginItemBundleID, "action": action, "buildType": buildType, "macosVersion": osVersion] + + case .dataImportFailed(source: _, sourceVersion: let version, error: let error): + var params = error.pixelParameters + + if let version { + params[PixelKit.Parameters.sourceBrowserVersion] = version + } + return params + + case .launchInitial(let cohort): + return [PixelKit.Parameters.experimentCohort: cohort] + + case .serpInitial(let cohort): + return [PixelKit.Parameters.experimentCohort: cohort] + + case .serpDay21to27(let cohort): + return [PixelKit.Parameters.experimentCohort: cohort] + + case .dailyOsVersionCounter: + return [PixelKit.Parameters.osMajorVersion: "\(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)"] + + case .dashboardProtectionAllowlistAdd(let triggerOrigin): + guard let trigger = triggerOrigin else { return nil } + return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] + + case .dashboardProtectionAllowlistRemove(let triggerOrigin): + guard let trigger = triggerOrigin else { return nil } + return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] + + case .syncSuccessRateDaily: + return nil + + case .vpnBreakageReport(let category, let description, let metadata): + return [ + PixelKit.Parameters.vpnBreakageCategory: category, + PixelKit.Parameters.vpnBreakageDescription: description, + PixelKit.Parameters.vpnBreakageMetadata: metadata + ] + default: return nil + } + } + + public enum CompileRulesListType: String, CustomStringConvertible { + + public var description: String { rawValue } + + case tds = "tracker_data" + case clickToLoad = "click_to_load" + case blockingAttribution = "blocking_attribution" + case attributed = "attributed" + case unknown = "unknown" + + } + + enum OnboardingShown: String, CustomStringConvertible { + var description: String { rawValue } + + init(_ value: Bool) { + if value { + self = .onboardingShown + } else { + self = .regularNavigation + } } + case onboardingShown = "onboarding-shown" + case regularNavigation = "regular-nav" + } + + enum WaitResult: String, CustomStringConvertible { + var description: String { rawValue } + + case closed + case quit + case success + } + + enum CompileRulesWaitTime: String, CustomStringConvertible { + var description: String { rawValue } + + case noWait = "0" + case lessThan1s = "1" + case lessThan5s = "5" + case lessThan10s = "10" + case lessThan20s = "20" + case lessThan40s = "40" + case more = "more" + } + + enum FormAutofillKind: String, CustomStringConvertible { + var description: String { rawValue } + + case password + case card + case identity + } + + enum FireButtonOption: String, CustomStringConvertible { + var description: String { rawValue } + + case tab + case window + case allSites = "all-sites" + } + + enum AccessPoint: String, CustomStringConvertible { + var description: String { rawValue } + + case button = "source-button" + case mainMenu = "source-menu" + case tabMenu = "source-tab-menu" + case hotKey = "source-keyboard" + case moreMenu = "source-more-menu" + case newTab = "source-new-tab" + + init(sender: Any, default: AccessPoint, mainMenuCheck: (NSMenu?) -> Bool = { $0 is MainMenu }) { + switch sender { + case let menuItem as NSMenuItem: + if mainMenuCheck(menuItem.topMenu) { + if let event = NSApp.currentEvent, + case .keyDown = event.type, + event.characters == menuItem.keyEquivalent { + + self = .hotKey + } else { + self = .mainMenu + } + } else { + self = `default` + } + + case is NSButton: + self = .button + + default: + self = `default` + } + } + } } diff --git a/DuckDuckGo/Statistics/Pixel.swift b/DuckDuckGo/Statistics/Pixel.swift deleted file mode 100644 index aa30eace74..0000000000 --- a/DuckDuckGo/Statistics/Pixel.swift +++ /dev/null @@ -1,168 +0,0 @@ -// -// Pixel.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 -import Networking -import Common -import PixelKit - -final class Pixel { - - static private(set) var shared: Pixel? - - static func setUp(dryRun: Bool = false) { -#if DEBUG - if dryRun { - shared = Pixel(store: LocalPixelDataStore.shared) { event, params, _, _, onComplete in - let params = params.filter { key, _ in !["appVersion", "test"].contains(key) } - os_log(.debug, log: .pixel, "Pixel dry fire: %{public}@ %{public}@", event.name.replacingOccurrences(of: "_", with: "."), params.isEmpty ? "" : params.debugDescription) - // simulate server response time for Dry Run mode - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - onComplete(nil) - } - } - return - } -#endif - shared = Pixel(store: LocalPixelDataStore.shared, requestSender: Pixel.defaultRequestSender) - } - -#if DEBUG - static func setUp(store: @escaping @autoclosure () -> PixelDataStore = { fatalError("provide test store") }(), - pixelFired: @escaping (Pixel.Event) -> Void) { - shared = Pixel(store: store()) { event, _, _, _, onComplete in - pixelFired(event) - onComplete(nil) - } - } -#endif - - static func tearDown() { - shared?.store = nil - shared = nil - } - - typealias RequestSender = (Pixel.Event, [String: String], CharacterSet?, APIRequest.Headers, @escaping (Error?) -> Void) -> Void - private let sendRequest: RequestSender - - private var store: (() -> PixelDataStore)! - - static var isNewUser: Bool { - let oneWeekAgo = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())! - return firstLaunchDate >= oneWeekAgo - } - - static var defaultRequestSender: RequestSender { - { event, params, allowedQueryReservedCharacters, headers, onComplete in - let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: event.name), - queryParameters: params, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - headers: headers) - let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) - request.fetch { (_, error) in - onComplete(error) - } - } - } - - private let appVersion: String - - init(appVersion: String = AppVersion.shared.versionNumber, - store: @escaping @autoclosure () -> PixelDataStore, - requestSender: @escaping RequestSender) { - - self.appVersion = appVersion - self.store = store - self.sendRequest = requestSender - } - - private static let pixelRequestHeaders: HTTPHeaders = [ - APIRequest.HTTPHeaderField.moreInfo: "See " + URL.duckDuckGoMorePrivacyInfo.absoluteString, - "X-DuckDuckGo-Client": "macOS" - ] - - // Temporary for activation pixels - static private var aMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date())! - @UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: aMonthAgo) - static var firstLaunchDate: Date - - func fire(_ event: Pixel.Event, - limitTo limit: Pixel.Event.Repetition = .repetitive, - withAdditionalParameters parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - includeAppVersionParameter: Bool = true, - withHeaders headers: APIRequest.Headers = APIRequest.Headers(additionalHeaders: pixelRequestHeaders), - onComplete: @escaping (Error?) -> Void = {_ in }) { - - func repetition() -> Event.Repetition { - Event.Repetition(key: event.name, store: self.store()) - } - switch limit { - case .initial: - if repetition() != .initial { return } - case .dailyFirst: - if repetition() == .repetitive { return } // Pixel already fired today - case .repetitive: break - } - - var newParams: [String: String] - switch (event.parameters, parameters) { - case (.some(let parameters), .none): - newParams = parameters - case (.none, .some(let parameters)): - newParams = parameters - case (.some(let params1), .some(let params2)): - newParams = params1.merging(params2) { $1 } - case (.none, .none): - newParams = [:] - } - - if includeAppVersionParameter { - newParams[PixelKit.Parameters.appVersion] = appVersion - } -#if DEBUG - newParams[PixelKit.Parameters.test] = PixelKit.Values.test -#endif - - sendRequest(event, newParams, allowedQueryReservedCharacters, headers, onComplete) - } - - static func fire(_ event: Pixel.Event, - limitTo limit: Pixel.Event.Repetition = .repetitive, - withAdditionalParameters parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - includeAppVersionParameter: Bool = true, - onComplete: @escaping (Error?) -> Void = {_ in }) { - - Self.shared?.fire(event, - limitTo: limit, - withAdditionalParameters: parameters, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - includeAppVersionParameter: includeAppVersionParameter, - onComplete: onComplete) - } - - func clearRepetitions(for event: Pixel.Event) { - store().removeValue(forKey: event.name) - } -} - -public func pixelAssertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #fileID, line: UInt = #line) { - Pixel.fire(.debug(event: Pixel.Event.Debug.assertionFailure(message: message(), file: file, line: line))) - Swift.assertionFailure(message(), file: file, line: line) -} diff --git a/DuckDuckGo/Statistics/PixelArguments.swift b/DuckDuckGo/Statistics/PixelArguments.swift deleted file mode 100644 index 987491bcf7..0000000000 --- a/DuckDuckGo/Statistics/PixelArguments.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// PixelArguments.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 AppKit - -extension Pixel.Event { - - enum Repetition: String, CustomStringConvertible { - var description: String { rawValue } - - /// fires once ever - case initial = "initial" - - /// fires at most once per day - case dailyFirst = "first-in-a-day" - - /// fires every time - case repetitive = "repetitive" - - init(key: String, store: PixelDataStore = LocalPixelDataStore.shared, now: Date = Date(), update: Bool = true) { - defer { - if update { - store.set(now.daySinceReferenceDate, forKey: key) - } - } - - guard let lastUsedDay: Int = store.value(forKey: key) else { - self = .initial - return - } - if lastUsedDay == now.daySinceReferenceDate { - self = .repetitive - return - } - self = .dailyFirst - } - } - - enum AccessPoint: String, CustomStringConvertible { - var description: String { rawValue } - - case button = "source-button" - case mainMenu = "source-menu" - case tabMenu = "source-tab-menu" - case hotKey = "source-keyboard" - case moreMenu = "source-more-menu" - case newTab = "source-new-tab" - - init(sender: Any, default: AccessPoint, mainMenuCheck: (NSMenu?) -> Bool = { $0 is MainMenu }) { - switch sender { - case let menuItem as NSMenuItem: - if mainMenuCheck(menuItem.topMenu) { - if let event = NSApp.currentEvent, - case .keyDown = event.type, - event.characters == menuItem.keyEquivalent { - - self = .hotKey - } else { - self = .mainMenu - } - } else { - self = `default` - } - - case is NSButton: - self = .button - - default: - assertionFailure("AccessPoint: Unexpected type of sender: \(type(of: sender))") - self = `default` - } - } - - } - - enum FormAutofillKind: String, CustomStringConvertible { - var description: String { rawValue } - - case password - case card - case identity - } - - public enum CompileRulesListType: String, CustomStringConvertible { - - public var description: String { rawValue } - - case tds = "tracker_data" - case clickToLoad = "click_to_load" - case blockingAttribution = "blocking_attribution" - case attributed = "attributed" - case unknown = "unknown" - - } - - enum FireButtonOption: String, CustomStringConvertible { - var description: String { rawValue } - - case tab - case window - case allSites = "all-sites" - } -} - -extension DataImportAction: CustomStringConvertible { - var description: String { - switch self { - case .bookmarks: return "bookmarks" - case .passwords: return "logins" - case .favicons: return "favicons" - case .generic: return "generic" - } - } -} - -extension DataImport.Source: CustomStringConvertible { - var description: String { - switch self { - case .brave: return "source-brave" - case .chrome: return "source-chrome" - case .chromium: return "source-chromium" - case .coccoc: return "source-coccoc" - case .csv: return "source-csv" - case .bitwarden: return "source-bitwarden" - case .lastPass: return "source-lastpass" - case .onePassword7: return "source-1password" - case .onePassword8: return "source-1password-8" - case .edge: return "source-edge" - case .firefox: return "source-firefox" - case .safari: return "source-safari" - case .safariTechnologyPreview: return "source-safari-technology-preview" - case .bookmarksHTML: return "source-bookmarks-html" - case .opera: return "source-opera" - case .operaGX: return "source-operagx" - case .tor: return "source-tor" - case .vivaldi: return "source-vivaldi" - case .yandex: return "source-yandex" - } - } -} diff --git a/DuckDuckGo/Statistics/PixelKit+Assertion.swift b/DuckDuckGo/Statistics/PixelKit+Assertion.swift new file mode 100644 index 0000000000..e76dd74ef2 --- /dev/null +++ b/DuckDuckGo/Statistics/PixelKit+Assertion.swift @@ -0,0 +1,25 @@ +// +// PixelKit+Assertion.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit + +public func pixelAssertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #fileID, line: UInt = #line) { + PixelKit.fire(DebugEvent(GeneralPixel.assertionFailure(message: message(), file: file, line: line))) + Swift.assertionFailure(message(), file: file, line: line) +} diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift deleted file mode 100644 index 2fad773b42..0000000000 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ /dev/null @@ -1,377 +0,0 @@ -// -// PixelParameters.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 PixelKit - -extension Pixel.Event { - - var parameters: [String: String]? { - switch self { - case .pixelKitEvent(let event): - return event.parameters - - case .debug(event: let debugEvent, error: let error): - var params = error?.pixelParameters ?? [:] - - if let debugParams = debugEvent.parameters { - params.merge(debugParams) { (current, _) in current } - } - - if case let .assertionFailure(message, file, line) = debugEvent { - params[PixelKit.Parameters.assertionMessage] = message - params[PixelKit.Parameters.assertionFile] = String(file) - params[PixelKit.Parameters.assertionLine] = String(line) - } - - return params - - case .dataImportFailed(source: _, sourceVersion: let version, error: let error): - var params = error.pixelParameters - - if let version { - params[PixelKit.Parameters.sourceBrowserVersion] = version - } - return params - - case .launchInitial(let cohort): - return [PixelKit.Parameters.experimentCohort: cohort] - - case .serpInitial(let cohort): - return [PixelKit.Parameters.experimentCohort: cohort] - - case .serpDay21to27(let cohort): - return [PixelKit.Parameters.experimentCohort: cohort] - - case .dailyPixel(let pixel, isFirst: _): - return pixel.parameters - - case .dailyOsVersionCounter: - return [PixelKit.Parameters.osMajorVersion: "\(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)"] - - case .dashboardProtectionAllowlistAdd(let triggerOrigin): - guard let trigger = triggerOrigin else { return nil } - return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] - - case .dashboardProtectionAllowlistRemove(let triggerOrigin): - guard let trigger = triggerOrigin else { return nil } - return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] - - case .syncSuccessRateDaily: - return nil - - case .vpnBreakageReport(let category, let description, let metadata): - return [ - PixelKit.Parameters.vpnBreakageCategory: category, - PixelKit.Parameters.vpnBreakageDescription: description, - PixelKit.Parameters.vpnBreakageMetadata: metadata - ] - - // Don't use default to force new items to be thought about - case .crash, - .brokenSiteReport, - .compileRulesWait, - .serp, - .formAutofilled, - .autofillItemSaved, - .autofillLoginsSaveLoginModalExcludeSiteConfirmed, - .autofillLoginsSettingsResetExcludedDisplayed, - .autofillLoginsSettingsResetExcludedConfirmed, - .autofillLoginsSettingsResetExcludedDismissed, - .bitwardenPasswordAutofilled, - .bitwardenPasswordSaved, - .ampBlockingRulesCompilationFailed, - .adClickAttributionDetected, - .adClickAttributionActive, - .adClickAttributionPageLoads, - .emailEnabled, - .emailDisabled, - .emailUserCreatedAlias, - .emailUserPressedUseAlias, - .emailUserPressedUseAddress, - .jsPixel, - .emailEnabledInitial, - .watchInDuckPlayerInitial, - .importDataInitial, - .newTabInitial, - .setAsDefaultInitial, - .favoriteSectionHidden, - .recentActivitySectionHidden, - .continueSetUpSectionHidden, - .fireButtonFirstBurn, - .fireButton, - .duckPlayerDailyUniqueView, - .duckPlayerViewFromYoutubeViaMainOverlay, - .duckPlayerViewFromYoutubeViaHoverButton, - .duckPlayerViewFromYoutubeAutomatic, - .duckPlayerViewFromSERP, - .duckPlayerViewFromOther, - .duckPlayerSettingAlways, - .duckPlayerSettingNever, - .duckPlayerSettingBackToDefault, - .networkProtectionRemoteMessageDisplayed, - .networkProtectionRemoteMessageDismissed, - .networkProtectionRemoteMessageOpened, - .networkProtectionEnabledOnSearch, - .networkProtectionGeoswitchingOpened, - .networkProtectionGeoswitchingSetNearest, - .networkProtectionGeoswitchingSetCustom, - .networkProtectionGeoswitchingNoLocations, - .syncSignupDirect, - .syncSignupConnect, - .syncLogin, - .syncDaily, - .syncDuckAddressOverride, - .syncLocalTimestampResolutionTriggered, - .syncBookmarksCountLimitExceededDaily, - .syncCredentialsCountLimitExceededDaily, - .syncBookmarksRequestSizeLimitExceededDaily, - .syncCredentialsRequestSizeLimitExceededDaily, - .dataBrokerProtectionWaitlistUserActive, - .dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, - .dataBrokerProtectionWaitlistIntroDisplayed, - .dataBrokerProtectionWaitlistNotificationShown, - .dataBrokerProtectionWaitlistNotificationTapped, - .dataBrokerProtectionWaitlistCardUITapped, - .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, - .dataBrokerProtectionWaitlistTermsAndConditionsAccepted, - .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn, - .dataBrokerProtectionRemoteMessageOpened, - .dataBrokerProtectionRemoteMessageDisplayed, - .dataBrokerProtectionRemoteMessageDismissed, - .dataBrokerDisableAndDeleteDaily, - .dataBrokerEnableLoginItemDaily, - .dataBrokerDisableLoginItemDaily, - .dataBrokerResetLoginItemDaily, - .defaultRequestedFromHomepage, - .defaultRequestedFromHomepageSetupView, - .defaultRequestedFromSettings, - .defaultRequestedFromOnboarding, - .privacyProFeatureEnabled, - .privacyProBetaUserThankYouVPN, - .privacyProBetaUserThankYouDBP, - .privacyProSubscriptionActive, - .privacyProOfferScreenImpression, - .privacyProPurchaseAttempt, - .privacyProPurchaseFailure, - .privacyProPurchaseFailureStoreError, - .privacyProPurchaseFailureBackendError, - .privacyProPurchaseFailureAccountNotCreated, - .privacyProPurchaseSuccess, - .privacyProRestorePurchaseOfferPageEntry, - .privacyProRestorePurchaseSettingsMenuEntry, - .privacyProRestorePurchaseEmailStart, - .privacyProRestorePurchaseStoreStart, - .privacyProRestorePurchaseEmailSuccess, - .privacyProRestorePurchaseStoreSuccess, - .privacyProRestorePurchaseStoreFailureNotFound, - .privacyProRestorePurchaseStoreFailureOther, - .privacyProRestoreAfterPurchaseAttempt, - .privacyProSubscriptionActivated, - .privacyProWelcomeAddDevice, - .privacyProSettingsAddDevice, - .privacyProAddDeviceEnterEmail, - .privacyProWelcomeVPN, - .privacyProWelcomePersonalInformationRemoval, - .privacyProWelcomeIdentityRestoration, - .privacyProSubscriptionSettings, - .privacyProVPNSettings, - .privacyProPersonalInformationRemovalSettings, - .privacyProIdentityRestorationSettings, - .privacyProSubscriptionManagementEmail, - .privacyProSubscriptionManagementPlanBilling, - .privacyProSubscriptionManagementRemoval, - .privacyProRestorePurchaseClick, - .protectionToggledOffBreakageReport, - .toggleProtectionsDailyCount, - .toggleReportDoNotSend, - .toggleReportDismiss, - .privacyProOfferMonthlyPriceClick, - .privacyProOfferYearlyPriceClick, - .privacyProAddEmailSuccess, - .privacyProWelcomeFAQClick, - .privacyProPurchaseStripeSuccess, - .passwordImportKeychainPrompt, - .passwordImportKeychainPromptDenied, - .installationAttribution: - return nil - } - } - -} - -extension Pixel.Event.Debug { - - var parameters: [String: String]? { - switch self { - case .loginItemUpdateError(let loginItemBundleID, let action, let buildType, let osVersion): - return ["loginItemBundleID": loginItemBundleID, "action": action, "buildType": buildType, "macosVersion": osVersion] - case .pixelKitEvent, - .assertionFailure, - .dbMakeDatabaseError, - .dbContainerInitializationError, - .dbInitializationError, - .dbSaveExcludedHTTPSDomainsError, - .dbSaveBloomFilterError, - .configurationFetchError, - .trackerDataParseFailed, - .trackerDataReloadFailed, - .trackerDataCouldNotBeLoaded, - .privacyConfigurationParseFailed, - .privacyConfigurationReloadFailed, - .privacyConfigurationCouldNotBeLoaded, - .fileStoreWriteFailed, - .fileMoveToDownloadsFailed, - .fileGetDownloadLocationFailed, - .fileAccessRelatedItemFailed, - .fileDownloadCreatePresentersFailed, - .downloadResumeDataCodingFailed, - .suggestionsFetchFailed, - .appOpenURLFailed, - .appStateRestorationFailed, - .contentBlockingErrorReportingIssue, - .contentBlockingCompilationFailed, - .contentBlockingCompilationTime, - .secureVaultInitError, - .secureVaultError, - .feedbackReportingFailed, - .blankNavigationOnBurnFailed, - .historyRemoveFailed, - .historyReloadFailed, - .historyCleanEntriesFailed, - .historyCleanVisitsFailed, - .historySaveFailed, - .historySaveFailedDaily, - .historyInsertVisitFailed, - .historyRemoveVisitsFailed, - .emailAutofillKeychainError, - .bookmarksStoreRootFolderMigrationFailed, - .bookmarksStoreFavoritesFolderMigrationFailed, - .adAttributionCompilationFailedForAttributedRulesList, - .adAttributionGlobalAttributedRulesDoNotExist, - .adAttributionDetectionHeuristicsDidNotMatchDomain, - .adAttributionLogicUnexpectedStateOnRulesCompiled, - .adAttributionLogicUnexpectedStateOnInheritedAttribution, - .adAttributionLogicUnexpectedStateOnRulesCompilationFailed, - .adAttributionDetectionInvalidDomainInParameter, - .adAttributionLogicRequestingAttributionTimedOut, - .adAttributionLogicWrongVendorOnSuccessfulCompilation, - .adAttributionLogicWrongVendorOnFailedCompilation, - .webKitDidTerminate, - .removedInvalidBookmarkManagedObjects, - .bitwardenNotResponding, - .bitwardenRespondedCannotDecryptUnique, - .bitwardenHandshakeFailed, - .bitwardenDecryptionOfSharedKeyFailed, - .bitwardenStoringOfTheSharedKeyFailed, - .bitwardenCredentialRetrievalFailed, - .bitwardenCredentialCreationFailed, - .bitwardenCredentialUpdateFailed, - .bitwardenRespondedWithError, - .bitwardenNoActiveVault, - .bitwardenParsingFailed, - .bitwardenStatusParsingFailed, - .bitwardenHmacComparisonFailed, - .bitwardenDecryptionFailed, - .bitwardenSendingOfMessageFailed, - .bitwardenSharedKeyInjectionFailed, - .updaterAborted, - .userSelectedToSkipUpdate, - .userSelectedToInstallUpdate, - .userSelectedToDismissUpdate, - .faviconDecryptionFailedUnique, - .downloadListItemDecryptionFailedUnique, - .historyEntryDecryptionFailedUnique, - .permissionDecryptionFailedUnique, - .missingParent, - .bookmarksSaveFailed, - .bookmarksSaveFailedOnImport, - .bookmarksCouldNotLoadDatabase, - .bookmarksCouldNotPrepareDatabase, - .bookmarksMigrationAlreadyPerformed, - .bookmarksMigrationFailed, - .bookmarksMigrationCouldNotPrepareDatabase, - .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration, - .bookmarksMigrationCouldNotRemoveOldStore, - .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders, - .syncSentUnauthenticatedRequest, - .syncMetadataCouldNotLoadDatabase, - .syncBookmarksProviderInitializationFailed, - .syncBookmarksFailed, - .syncCredentialsProviderInitializationFailed, - .syncCredentialsFailed, - .syncSettingsFailed, - .syncSettingsMetadataUpdateFailed, - .syncSignupError, - .syncLoginError, - .syncLogoutError, - .syncUpdateDeviceError, - .syncRemoveDeviceError, - .syncDeleteAccountError, - .syncLoginExistingAccountError, - .syncCannotCreateRecoveryPDF, - .bookmarksCleanupFailed, - .bookmarksCleanupAttemptedWhileSyncWasEnabled, - .favoritesCleanupFailed, - .bookmarksFaviconsFetcherStateStoreInitializationFailed, - .bookmarksFaviconsFetcherFailed, - .credentialsDatabaseCleanupFailed, - .credentialsCleanupAttemptedWhileSyncWasEnabled, - .invalidPayload, - .burnerTabMisplaced, - .networkProtectionRemoteMessageFetchingFailed, - .networkProtectionRemoteMessageStorageFailed, - .dataBrokerProtectionRemoteMessageFetchingFailed, - .dataBrokerProtectionRemoteMessageStorageFailed: - return nil - } - } - -} - -extension Error { - - var pixelParameters: [String: String] { - var params = [String: String]() - - if let errorWithUserInfo = self as? ErrorWithPixelParameters { - params = errorWithUserInfo.errorParameters - } - - let nsError = self as NSError - - params[PixelKit.Parameters.errorCode] = "\(nsError.code)" - params[PixelKit.Parameters.errorDomain] = nsError.domain - - if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { - params[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingError.code)" - params[PixelKit.Parameters.underlyingErrorDomain] = underlyingError.domain - } - - if let sqlErrorCode = nsError.userInfo["SQLiteResultCode"] as? NSNumber { - params[PixelKit.Parameters.underlyingErrorSQLiteCode] = "\(sqlErrorCode.intValue)" - } - - if let sqlExtendedErrorCode = nsError.userInfo["SQLiteExtendedResultCode"] as? NSNumber { - params[PixelKit.Parameters.underlyingErrorSQLiteExtendedCode] = "\(sqlExtendedErrorCode.intValue)" - } - - return params - } - -} diff --git a/DuckDuckGo/Statistics/PrivacyProPixel.swift b/DuckDuckGo/Statistics/PrivacyProPixel.swift new file mode 100644 index 0000000000..d50f06656a --- /dev/null +++ b/DuckDuckGo/Statistics/PrivacyProPixel.swift @@ -0,0 +1,128 @@ +// +// PrivacyProPixel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit + +// swiftlint:disable private_over_fileprivate +#if APPSTORE +fileprivate let appDistribution = "store" +#else +fileprivate let appDistribution = "direct" +#endif +// swiftlint:enable private_over_fileprivate + +enum PrivacyProPixel: PixelKitEventV2 { + // Subscription + case privacyProFeatureEnabled + case privacyProBetaUserThankYouVPN + case privacyProBetaUserThankYouDBP + case privacyProSubscriptionActive + case privacyProOfferScreenImpression + case privacyProPurchaseAttempt + case privacyProPurchaseFailure + case privacyProPurchaseFailureStoreError + case privacyProPurchaseFailureBackendError + case privacyProPurchaseFailureAccountNotCreated + case privacyProPurchaseSuccess + case privacyProRestorePurchaseOfferPageEntry + case privacyProRestorePurchaseClick + case privacyProRestorePurchaseSettingsMenuEntry + case privacyProRestorePurchaseEmailStart + case privacyProRestorePurchaseStoreStart + case privacyProRestorePurchaseEmailSuccess + case privacyProRestorePurchaseStoreSuccess + case privacyProRestorePurchaseStoreFailureNotFound + case privacyProRestorePurchaseStoreFailureOther + case privacyProRestoreAfterPurchaseAttempt + case privacyProSubscriptionActivated + case privacyProWelcomeAddDevice + case privacyProSettingsAddDevice + case privacyProAddDeviceEnterEmail + case privacyProWelcomeVPN + case privacyProWelcomePersonalInformationRemoval + case privacyProWelcomeIdentityRestoration + case privacyProSubscriptionSettings + case privacyProVPNSettings + case privacyProPersonalInformationRemovalSettings + case privacyProIdentityRestorationSettings + case privacyProSubscriptionManagementEmail + case privacyProSubscriptionManagementPlanBilling + case privacyProSubscriptionManagementRemoval + case privacyProPurchaseStripeSuccess + // Web pixels + case privacyProOfferMonthlyPriceClick + case privacyProOfferYearlyPriceClick + case privacyProAddEmailSuccess + case privacyProWelcomeFAQClick + + var name: String { + switch self { + case .privacyProFeatureEnabled: return + "m_mac_\(appDistribution)_privacy-pro_feature_enabled" + case .privacyProBetaUserThankYouVPN: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_vpn" + case .privacyProBetaUserThankYouDBP: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_dbp" + case .privacyProSubscriptionActive: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_active" + case .privacyProOfferScreenImpression: return "m_mac_\(appDistribution)_privacy-pro_offer_screen_impression" + case .privacyProPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_terms-conditions_subscribe_click" + case .privacyProPurchaseFailure: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_other" + case .privacyProPurchaseFailureStoreError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_store" + case .privacyProPurchaseFailureBackendError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_backend" + case .privacyProPurchaseFailureAccountNotCreated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_account-creation" + case .privacyProPurchaseSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_success" + case .privacyProRestorePurchaseOfferPageEntry: return "m_mac_\(appDistribution)_privacy-pro_offer_restore-purchase_click" + case .privacyProRestorePurchaseClick: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" + case .privacyProRestorePurchaseSettingsMenuEntry: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" + case .privacyProRestorePurchaseEmailStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_enter-email_click" + case .privacyProRestorePurchaseStoreStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_restore-purchase_click" + case .privacyProRestorePurchaseEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-email_success" + case .privacyProRestorePurchaseStoreSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_success" + case .privacyProRestorePurchaseStoreFailureNotFound: return "m_mac_\(appDistribution)_privacy-pro_subscription-restore-using-store_failure_not-found" + case .privacyProRestorePurchaseStoreFailureOther: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_failure_other" + case .privacyProRestoreAfterPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-after-purchase-attempt_success" + case .privacyProSubscriptionActivated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_activated_u" + case .privacyProWelcomeAddDevice: return "m_mac_\(appDistribution)_privacy-pro_welcome_add-device_click_u" + case .privacyProSettingsAddDevice: return "m_mac_\(appDistribution)_privacy-pro_settings_add-device_click" + case .privacyProAddDeviceEnterEmail: return "m_mac_\(appDistribution)_privacy-pro_add-device_enter-email_click" + case .privacyProWelcomeVPN: return "m_mac_\(appDistribution)_privacy-pro_welcome_vpn_click_u" + case .privacyProWelcomePersonalInformationRemoval: return "m_mac_\(appDistribution)_privacy-pro_welcome_personal-information-removal_click_u" + case .privacyProWelcomeIdentityRestoration: return "m_mac_\(appDistribution)_privacy-pro_welcome_identity-theft-restoration_click_u" + case .privacyProSubscriptionSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_screen_impression" + case .privacyProVPNSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_vpn_click" + case .privacyProPersonalInformationRemovalSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_personal-information-removal_click" + case .privacyProIdentityRestorationSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_identity-theft-restoration_click" + case .privacyProSubscriptionManagementEmail: return "m_mac_\(appDistribution)_privacy-pro_manage-email_edit_click" + case .privacyProSubscriptionManagementPlanBilling: return "m_mac_\(appDistribution)_privacy-pro_settings_change-plan-or-billing_click" + case .privacyProSubscriptionManagementRemoval: return "m_mac_\(appDistribution)_privacy-pro_settings_remove-from-device_click" + case .privacyProPurchaseStripeSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_stripe_success" + // Web + case .privacyProOfferMonthlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_monthly-price_click" + case .privacyProOfferYearlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_yearly-price_click" + case .privacyProAddEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_add-email_success_u" + case .privacyProWelcomeFAQClick: return "m_mac_\(appDistribution)_privacy-pro_welcome_faq_click_u" + } + } + + var error: (any Error)? { + return nil + } + + var parameters: [String: String]? { + return nil + } +} diff --git a/DuckDuckGo/Statistics/RulesCompilationMonitor.swift b/DuckDuckGo/Statistics/RulesCompilationMonitor.swift index 86e704f4d7..689a6c6174 100644 --- a/DuckDuckGo/Statistics/RulesCompilationMonitor.swift +++ b/DuckDuckGo/Statistics/RulesCompilationMonitor.swift @@ -54,14 +54,14 @@ final class AbstractContentBlockingAssetsCompilationTimeReporter Void) = { _ in }) { + private func report(waitTime: TimeInterval, result: GeneralPixel.WaitResult, completionHandler: @escaping ((Error?) -> Void) = { _ in }) { // report only once isFinished = true completionHandler(nil) // This is temporarily disabled: // - // Pixel.fire(.compileRulesWait(onboardingShown: self.onboardingShown, waitTime: waitTime, result: result), + // PixelKit.fire(GeneralPixel.compileRulesWait(onboardingShown: self.onboardingShown, waitTime: waitTime, result: result), // withAdditionalParameters: ["waitTime": String(waitTime)], // onComplete: completionHandler) diff --git a/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift b/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift index 9ca0f3aaa9..5d4435eb83 100644 --- a/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift +++ b/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift @@ -20,6 +20,7 @@ import Foundation import Suggestions import Common import History +import PixelKit final class SuggestionContainer { @@ -63,7 +64,7 @@ final class SuggestionContainer { os_log("Suggestions: Failed to get suggestions - %s", type: .error, "\(String(describing: error))") - Pixel.fire(.debug(event: .suggestionsFetchFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.suggestionsFetchFailed, error: error)) return } diff --git a/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift b/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift index cac2ff4cee..386dd306a5 100644 --- a/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift +++ b/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift @@ -20,18 +20,19 @@ import Foundation import BrowserServicesKit import Common import Persistence +import PixelKit public class CredentialsCleanupErrorHandling: EventMapping { public init() { super.init { event, _, _, _ in if event.cleanupError is CredentialsCleanupCancelledError { - Pixel.fire(.debug(event: .credentialsCleanupAttemptedWhileSyncWasEnabled)) + PixelKit.fire(DebugEvent(GeneralPixel.credentialsCleanupAttemptedWhileSyncWasEnabled)) } else { let processedErrors = CoreDataErrorsParser.parse(error: event.cleanupError as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .credentialsDatabaseCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.credentialsDatabaseCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) } } } diff --git a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift index d7a9651d14..54dae6e41d 100644 --- a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift +++ b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift @@ -22,12 +22,13 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit public class BookmarksFaviconsFetcherErrorHandler: EventMapping { public init() { super.init { event, _, _, _ in - Pixel.fire(.debug(event: .bookmarksFaviconsFetcherFailed, error: event.underlyingError)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksFaviconsFetcherFailed, error: event.underlyingError)) } } @@ -147,7 +148,7 @@ final class SyncBookmarksAdapter { do { stateStore = try BookmarksFaviconsFetcherStateStore(applicationSupportURL: URL.sandboxApplicationSupportURL) } catch { - Pixel.fire(.debug(event: .bookmarksFaviconsFetcherStateStoreInitializationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksFaviconsFetcherStateStoreInitializationFailed, error: error)) os_log(.error, log: OSLog.sync, "Failed to initialize BookmarksFaviconsFetcherStateStore: %{public}s", String(reflecting: error)) return nil } @@ -167,17 +168,17 @@ final class SyncBookmarksAdapter { .sink { [weak self] error in switch error { case let syncError as SyncError: - Pixel.fire(.debug(event: .syncBookmarksFailed, error: syncError)) + PixelKit.fire(DebugEvent(GeneralPixel.syncBookmarksFailed, error: syncError)) switch syncError { case .unexpectedStatusCode(409): // If bookmarks count limit has been exceeded self?.isSyncBookmarksPaused = true - Pixel.fire(.syncBookmarksCountLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncBookmarksCountLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() case .unexpectedStatusCode(413): // If bookmarks request size limit has been exceeded self?.isSyncBookmarksPaused = true - Pixel.fire(.syncBookmarksRequestSizeLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncBookmarksRequestSizeLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() default: break @@ -187,7 +188,7 @@ final class SyncBookmarksAdapter { if nsError.domain != NSURLErrorDomain { let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncBookmarksFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncBookmarksFailed, error: error), withAdditionalParameters: params) } } os_log(.error, log: OSLog.sync, "Bookmarks Sync error: %{public}s", String(reflecting: error)) diff --git a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift index 444e7a1872..571fa21afb 100644 --- a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift +++ b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift @@ -22,6 +22,7 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit final class SyncCredentialsAdapter { @@ -85,17 +86,17 @@ final class SyncCredentialsAdapter { .sink { [weak self] error in switch error { case let syncError as SyncError: - Pixel.fire(.debug(event: .syncCredentialsFailed, error: syncError)) + PixelKit.fire(DebugEvent(GeneralPixel.syncCredentialsFailed, error: syncError)) switch syncError { case .unexpectedStatusCode(409): // If credentials count limit has been exceeded self?.isSyncCredentialsPaused = true - Pixel.fire(.syncCredentialsCountLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncCredentialsCountLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() case .unexpectedStatusCode(413): // If credentials request size limit has been exceeded self?.isSyncCredentialsPaused = true - Pixel.fire(.syncCredentialsRequestSizeLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncCredentialsRequestSizeLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() default: break @@ -105,7 +106,7 @@ final class SyncCredentialsAdapter { if nsError.domain != NSURLErrorDomain { let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncCredentialsFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncCredentialsFailed, error: error), withAdditionalParameters: params) } } os_log(.error, log: OSLog.sync, "Credentials Sync error: %{public}s", String(reflecting: error)) @@ -116,7 +117,7 @@ final class SyncCredentialsAdapter { } catch let error as NSError { let processedErrors = CoreDataErrorsParser.parse(error: error) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncCredentialsProviderInitializationFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncCredentialsProviderInitializationFailed, error: error), withAdditionalParameters: params) } } diff --git a/DuckDuckGo/Sync/SyncDataProviders.swift b/DuckDuckGo/Sync/SyncDataProviders.swift index 2858e2664a..4da4a2d5c5 100644 --- a/DuckDuckGo/Sync/SyncDataProviders.swift +++ b/DuckDuckGo/Sync/SyncDataProviders.swift @@ -22,6 +22,7 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit final class SyncDataProviders: DataProvidersSource { public let bookmarksAdapter: SyncBookmarksAdapter @@ -109,9 +110,9 @@ final class SyncDataProviders: DataProvidersSource { syncMetadataDatabase.db.loadStore { context, error in guard context != nil else { if let error = error { - Pixel.fire(.debug(event: .syncMetadataCouldNotLoadDatabase, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncMetadataCouldNotLoadDatabase, error: error)) } else { - Pixel.fire(.debug(event: .syncMetadataCouldNotLoadDatabase)) + PixelKit.fire(DebugEvent(GeneralPixel.syncMetadataCouldNotLoadDatabase)) } Thread.sleep(forTimeInterval: 1) diff --git a/DuckDuckGo/Sync/SyncErrorHandler.swift b/DuckDuckGo/Sync/SyncErrorHandler.swift index 15f94ac550..036c041fb7 100644 --- a/DuckDuckGo/Sync/SyncErrorHandler.swift +++ b/DuckDuckGo/Sync/SyncErrorHandler.swift @@ -19,12 +19,13 @@ import Common import DDGSync import Foundation +import PixelKit public class SyncErrorHandler: EventMapping { public init() { super.init { event, _, _, _ in - Pixel.fire(.debug(event: .syncSentUnauthenticatedRequest, error: event)) + PixelKit.fire(DebugEvent(GeneralPixel.syncSentUnauthenticatedRequest, error: event)) } } diff --git a/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift b/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift index 2c94d341f6..f5c29f6e7d 100644 --- a/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift +++ b/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift @@ -19,6 +19,7 @@ import Common import SyncDataProviders import Foundation +import PixelKit public class SyncMetricsEventsHandler: EventMapping { @@ -26,9 +27,9 @@ public class SyncMetricsEventsHandler: EventMapping { super.init { event, _, _, _ in switch event { case .overrideEmailProtectionSettings: - Pixel.fire(.syncDuckAddressOverride) + PixelKit.fire(GeneralPixel.syncDuckAddressOverride) case .localTimestampResolutionTriggered(let feature): - Pixel.fire(.syncLocalTimestampResolutionTriggered(feature)) + PixelKit.fire(GeneralPixel.syncLocalTimestampResolutionTriggered(feature)) } } } diff --git a/DuckDuckGo/Sync/SyncSettingsAdapter.swift b/DuckDuckGo/Sync/SyncSettingsAdapter.swift index 96fc030a0d..f726673087 100644 --- a/DuckDuckGo/Sync/SyncSettingsAdapter.swift +++ b/DuckDuckGo/Sync/SyncSettingsAdapter.swift @@ -23,6 +23,7 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit extension SettingsProvider.Setting { static let favoritesDisplayMode = SettingsProvider.Setting(key: "favorites_display_mode") @@ -63,18 +64,18 @@ final class SyncSettingsAdapter { .sink { error in switch error { case let syncError as SyncError: - Pixel.fire(.debug(event: .syncSettingsFailed, error: syncError)) + PixelKit.fire(DebugEvent(GeneralPixel.syncSettingsFailed, error: syncError)) case let settingsMetadataError as SettingsSyncMetadataSaveError: let underlyingError = settingsMetadataError.underlyingError let processedErrors = CoreDataErrorsParser.parse(error: underlyingError as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncSettingsMetadataUpdateFailed, error: underlyingError), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncSettingsMetadataUpdateFailed, error: underlyingError), withAdditionalParameters: params) default: let nsError = error as NSError if nsError.domain != NSURLErrorDomain { let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncSettingsFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncSettingsFailed, error: error), withAdditionalParameters: params) } } os_log(.error, log: OSLog.sync, "Settings Sync error: %{public}s", String(reflecting: error)) diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index 3f9e9c207e..847638a230 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -24,6 +24,7 @@ import Navigation import UserScript import WebKit import History +import PixelKit // swiftlint:disable file_length @@ -1179,7 +1180,7 @@ extension Tab/*: NavigationResponder*/ { // to be moved to Tab+Navigation.swift func webContentProcessDidTerminate(with reason: WKProcessTerminationReason?) { let error = WKError(.webContentProcessTerminated, userInfo: [ WKProcessTerminationReason.userInfoKey: reason?.rawValue ?? -1, - NSLocalizedDescriptionKey: UserText.webProcessCrashPageMessage, + NSLocalizedDescriptionKey: UserText.webProcessCrashPageMessage ]) if case.url(let url, _, _) = content { @@ -1188,7 +1189,7 @@ extension Tab/*: NavigationResponder*/ { // to be moved to Tab+Navigation.swift loadErrorHTML(error, header: UserText.webProcessCrashPageHeader, forUnreachableURL: url, alternate: true) } - Pixel.fire(.debug(event: .webKitDidTerminate, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.webKitDidTerminate, error: error)) } @MainActor diff --git a/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift b/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift index ef950d286a..9e34d1aac8 100644 --- a/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift +++ b/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift @@ -117,7 +117,7 @@ final class TestTabExtensionsBuilder: TabExtensionsBuilderProtocol { else { fatalError("\(type(of: components[idx].buildingBlock)) has been already initialized at the moment of the `override` call") } - loader.state = TabExtensionLazyLoader.State.none { makeTabExtension().getPublicProtocol () } + loader.state = TabExtensionLazyLoader.State.none { makeTabExtension().getPublicProtocol() } return builderBlock } diff --git a/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift index 5a973d76cc..867702bd7e 100644 --- a/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift @@ -20,6 +20,7 @@ import BrowserServicesKit import Combine import Foundation import SecureStorage +import PixelKit final class AutofillTabExtension: TabExtension { @@ -148,7 +149,7 @@ extension AutofillTabExtension: SecureVaultManagerDelegate { } func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: String) { - Pixel.fire(.formAutofilled(kind: type.formAutofillKind)) + PixelKit.fire(GeneralPixel.formAutofilled(kind: type.formAutofillKind)) if type.formAutofillKind == .password && passwordManagerCoordinator.isEnabled { @@ -167,7 +168,7 @@ extension AutofillTabExtension: SecureVaultManagerDelegate { } public func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, didReceivePixel pixel: AutofillUserScript.JSPixel) { - Pixel.fire(.jsPixel(pixel)) + PixelKit.fire(GeneralPixel.jsPixel(pixel)) } public func secureVaultManager(_: SecureVaultManager, didRequestCreditCardsManagerForDomain domain: String) { @@ -206,7 +207,7 @@ extension AutofillTabExtension: SecureVaultManagerDelegate { } extension AutofillType { - var formAutofillKind: Pixel.Event.FormAutofillKind { + var formAutofillKind: GeneralPixel.FormAutofillKind { switch self { case .password: return .password case .card: return .card diff --git a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift index 65fd4a1370..ca6cec4c5a 100644 --- a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift @@ -21,6 +21,7 @@ import Common import ContentBlocking import Foundation import Navigation +import PixelKit protocol YoutubeScriptsProvider { var youtubeOverlayScript: YoutubeOverlayUserScript? { get } @@ -111,9 +112,9 @@ extension DuckPlayerTabExtension: YoutubeOverlayUserScriptDelegate { func youtubeOverlayUserScriptDidRequestDuckPlayer(with url: URL, in webView: WKWebView) { if duckPlayer.mode == .enabled { - Pixel.fire(.duckPlayerViewFromYoutubeAutomatic) + PixelKit.fire(GeneralPixel.duckPlayerViewFromYoutubeAutomatic) } else { - Pixel.fire(.duckPlayerViewFromYoutubeViaHoverButton) + PixelKit.fire(GeneralPixel.duckPlayerViewFromYoutubeViaHoverButton) } // to be standardised across the app let isRequestingNewTab = NSApp.isCommandPressed @@ -202,7 +203,7 @@ extension DuckPlayerTabExtension: NavigationResponder { // Always allow loading Private Player URLs (local HTML) if navigationAction.url.isDuckURLScheme || navigationAction.url.isDuckPlayer { if navigationAction.request.allHTTPHeaderFields?["Referer"] == URL.duckDuckGo.absoluteString { - Pixel.fire(.duckPlayerViewFromSERP) + PixelKit.fire(GeneralPixel.duckPlayerViewFromSERP) } return .allow } @@ -289,10 +290,10 @@ extension DuckPlayerTabExtension: NavigationResponder { switch navigationAction.navigationType { case .custom, .redirect(.server): - Pixel.fire(.duckPlayerViewFromOther) + PixelKit.fire(GeneralPixel.duckPlayerViewFromOther) case .other: if navigationAction.request.allHTTPHeaderFields?["Referer"] == URL.duckDuckGo.absoluteString { - Pixel.fire(.duckPlayerViewFromSERP) + PixelKit.fire(GeneralPixel.duckPlayerViewFromSERP) } default: break @@ -311,7 +312,7 @@ extension DuckPlayerTabExtension: NavigationResponder { return } if navigation.url.isDuckPlayer { - Pixel.fire(.duckPlayerDailyUniqueView, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.duckPlayerDailyUniqueView, frequency: .daily) } } diff --git a/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift index aa8fa230fc..e5a9a8d0e7 100644 --- a/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift @@ -22,6 +22,7 @@ import Common import Foundation import Navigation import WebKit +import PixelKit final class NavigationProtectionTabExtension { @@ -30,7 +31,7 @@ final class NavigationProtectionTabExtension { private static let debugEvents = EventMapping { event, _, _, _ in switch event { case .ampBlockingRulesCompilationFailed: - Pixel.fire(.ampBlockingRulesCompilationFailed) + PixelKit.fire(GeneralPixel.ampBlockingRulesCompilationFailed) } } diff --git a/DuckDuckGo/Tab/TabExtensions/NetworkProtectionControllerTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/NetworkProtectionControllerTabExtension.swift index 7378ec7a72..7f86b0f766 100644 --- a/DuckDuckGo/Tab/TabExtensions/NetworkProtectionControllerTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/NetworkProtectionControllerTabExtension.swift @@ -19,6 +19,7 @@ import Foundation import Navigation import NetworkProtection +import PixelKit final class NetworkProtectionControllerTabExtension { let tunnelController: NetworkProtectionIPCTunnelController @@ -31,7 +32,7 @@ final class NetworkProtectionControllerTabExtension { extension NetworkProtectionControllerTabExtension: NavigationResponder { func navigationDidFinish(_ navigation: Navigation) { if navigation.url.isDuckDuckGoSearch, tunnelController.isConnected == true { - DailyPixel.fire(pixel: .networkProtectionEnabledOnSearch, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionEnabledOnSearch, frequency: .dailyAndCount) } } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 1a3605a102..35661ff131 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -20,6 +20,7 @@ import Foundation import Subscription import SubscriptionUI import enum StoreKit.StoreKitError +import PixelKit @available(macOS 12.0, *) struct SubscriptionAppStoreRestorer { @@ -66,13 +67,12 @@ struct SubscriptionAppStoreRestorer { switch result { case .success: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) - + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) case .failure(let error): switch error { case .missingAccountOrTransactions: break default: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreFailureOther, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureOther, frequency: .dailyAndCount) } switch error { diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift index bdce936804..438997c0f7 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift @@ -18,6 +18,7 @@ import Foundation import Common +import PixelKit enum SubscriptionError: Error { case purchaseFailed, @@ -60,7 +61,7 @@ struct SubscriptionErrorReporter { case .failedToRestorePastPurchase: isStoreError = true case .subscriptionNotFound: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreFailureNotFound, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureNotFound, frequency: .dailyAndCount) isStoreError = true case .subscriptionExpired: isStoreError = true @@ -69,17 +70,17 @@ struct SubscriptionErrorReporter { isBackendError = true case .cancelledByUser: break case .accountCreationFailed: - DailyPixel.fire(pixel: .privacyProPurchaseFailureAccountNotCreated, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureAccountNotCreated, frequency: .dailyAndCount) case .activeSubscriptionAlreadyPresent: break case .generalError: break } if isStoreError { - DailyPixel.fire(pixel: .privacyProPurchaseFailureStoreError, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureStoreError, frequency: .dailyAndCount) } if isBackendError { - DailyPixel.fire(pixel: .privacyProPurchaseFailureBackendError, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureBackendError, frequency: .dailyAndCount) } } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index 76cdb94e7c..1b917bb9c6 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -27,6 +27,7 @@ import WebKit import UserScript import Subscription import SubscriptionUI +import PixelKit public extension Notification.Name { static let subscriptionPageCloseAndOpenPreferences = Notification.Name("com.duckduckgo.subscriptionPage.CloseAndOpenPreferences") @@ -152,7 +153,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - DailyPixel.fire(pixel: .privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") @@ -211,8 +212,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // swiftlint:disable:next function_body_length cyclomatic_complexity func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - DailyPixel.fire(pixel: .privacyProPurchaseAttempt, frequency: .dailyAndCount) - + PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndCount) struct SubscriptionSelection: Decodable { let id: String } @@ -242,9 +242,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // Check for active subscriptions if await PurchaseManager.hasActiveSubscription() { - - Pixel.fire(.privacyProRestoreAfterPurchaseAttempt) - + PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) await WindowControllersManager.shared.lastKeyMainWindowController?.showSubscriptionFoundAlert(originalMessage: message) @@ -290,8 +288,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, subscriptionAppGroup: subscriptionAppGroup) { case .success(let purchaseUpdate): os_log(.info, log: .subscription, "[Purchase] Purchase complete") - DailyPixel.fire(pixel: .privacyProPurchaseSuccess, frequency: .dailyAndCount) - Pixel.fire(.privacyProSubscriptionActivated, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .unique) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure(let error): switch error { @@ -344,7 +342,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - Pixel.fire(.privacyProRestorePurchaseOfferPageEntry) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseOfferPageEntry) guard let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController, let windowControllerManager = await WindowControllersManager.shared.lastKeyMainWindowController else { return nil @@ -365,7 +363,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { }, uiActionHandler: { event in switch event { case .activateAddEmailClick: - DailyPixel.fire(pixel: .privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) default: break } @@ -402,14 +400,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { case .appTrackingProtection: NotificationCenter.default.post(name: .openAppTrackingProtection, object: self, userInfo: nil) case .vpn: - Pixel.fire(.privacyProWelcomeVPN, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .unique) NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) case .personalInformationRemoval: - Pixel.fire(.privacyProWelcomePersonalInformationRemoval, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .unique) NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .identityTheftRestoration: - Pixel.fire(.privacyProWelcomeIdentityRestoration, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) } @@ -424,20 +422,19 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await StripePurchaseFlow.completeSubscriptionPurchase(subscriptionAppGroup: subscriptionAppGroup) await mainViewController?.dismiss(progressViewController) - DailyPixel.fire(pixel: .privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) - + PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) return [String: String]() // cannot be nil, the web app expect something back before redirecting the user to the final page } // MARK: Pixel related actions func subscriptionsMonthlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProOfferMonthlyPriceClick) + PixelKit.fire(PrivacyProPixel.privacyProOfferMonthlyPriceClick) return nil } func subscriptionsYearlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProOfferYearlyPriceClick) + PixelKit.fire(PrivacyProPixel.privacyProOfferYearlyPriceClick) return nil } @@ -447,12 +444,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProAddEmailSuccess, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .unique) return nil } func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProWelcomeFAQClick, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .unique) return nil } @@ -477,7 +474,7 @@ extension MainWindowController { @MainActor func showSomethingWentWrongAlert() { - DailyPixel.fire(pixel: .privacyProPurchaseFailure, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) guard let window else { return } window.show(.somethingWentWrongAlert()) @@ -489,7 +486,7 @@ extension MainWindowController { window.show(.subscriptionNotFoundAlert(), firstButtonAction: { WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } @@ -499,7 +496,7 @@ extension MainWindowController { window.show(.subscriptionInactiveAlert(), firstButtonAction: { WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } @@ -513,7 +510,7 @@ extension MainWindowController { let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) switch result { case .success: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) case .failure: break } originalMessage.webView?.reload() diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index dbf22e4e18..f45d9a12d6 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -23,6 +23,7 @@ import Common import SwiftUI import WebKit import Subscription +import PixelKit // swiftlint:disable file_length // swiftlint:disable:next type_body_length @@ -1133,7 +1134,7 @@ extension BrowserTabViewController: OnboardingDelegate { return } - Pixel.fire(.defaultRequestedFromOnboarding) + PixelKit.fire(GeneralPixel.defaultRequestedFromOnboarding) defaultBrowserPreferences.becomeDefault { _ in _ = defaultBrowserPreferences withAnimation { diff --git a/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift b/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift index e1fd1751c0..0317aed9b9 100644 --- a/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift +++ b/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift @@ -20,6 +20,7 @@ import Common import Foundation import Combine import History +import PixelKit /** * The delegate callbacks are triggered for events related to unpinned tabs only. @@ -657,7 +658,7 @@ final class TabCollectionViewModel: NSObject { // Make sure the tab is burner if it is supposed to be if newTabs.first(where: { $0.burnerMode != self.burnerMode }) != nil { - Pixel.fire(.debug(event: .burnerTabMisplaced)) + PixelKit.fire(DebugEvent(GeneralPixel.burnerTabMisplaced)) fatalError("Error in burner tab management") } } .store(in: &cancellables) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift index e799bbea54..9fe17e7a62 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit protocol VPNFeedbackSender { func send(metadata: VPNMetadata, category: VPNFeedbackCategory, userText: String) async throws @@ -27,10 +28,10 @@ struct DefaultVPNFeedbackSender: VPNFeedbackSender { func send(metadata: VPNMetadata, category: VPNFeedbackCategory, userText: String) async throws { let urlAllowed: CharacterSet = .alphanumerics.union(.init(charactersIn: "-._~")) let encodedUserText = userText.addingPercentEncoding(withAllowedCharacters: urlAllowed) ?? userText - let pixelEvent = Pixel.Event.vpnBreakageReport(category: category.rawValue, description: encodedUserText, metadata: metadata.toBase64()) + let pixelEvent = GeneralPixel.vpnBreakageReport(category: category.rawValue, description: encodedUserText, metadata: metadata.toBase64()) try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - Pixel.fire(pixelEvent) { error in + PixelKit.fire(pixelEvent) { _, error in if let error { continuation.resume(throwing: error) } else { diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 6ce1842ef4..e810339aef 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -215,7 +215,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return false } - PixelKit.fire(VPNPrivacyProPixel.vpnBetaStoppedWhenPrivacyProEnabled, frequency: .dailyAndContinuous) + PixelKit.fire(VPNPrivacyProPixel.vpnBetaStoppedWhenPrivacyProEnabled, frequency: .dailyAndCount) defaults.vpnLegacyUserAccessDisabledOnce = true await featureDisabler.disable(keepAuthToken: true, uninstallSystemExtension: false) return true diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift index 727156f946..52b5ec44cf 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift @@ -136,7 +136,7 @@ struct NetworkProtectionInvitedToWaitlistViewData: InvitedToWaitlistViewData { .init(imageName: "Card-16", title: UserText.networkProtectionWaitlistInvitedSection3Title, - subtitle: UserText.networkProtectionWaitlistInvitedSection3Subtitle), + subtitle: UserText.networkProtectionWaitlistInvitedSection3Subtitle) ] } diff --git a/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift b/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift index 2237c32222..4c895fcf36 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift @@ -19,6 +19,7 @@ import AppKit import Foundation import Subscription +import PixelKit final class WaitlistThankYouPromptPresenter { @@ -55,7 +56,7 @@ final class WaitlistThankYouPromptPresenter { // Wiring this here since it's mostly useful for rolling out PrivacyPro, and should // go away once PPro is fully rolled out. if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - DailyPixel.fire(pixel: .privacyProFeatureEnabled, frequency: .dailyOnly) + PixelKit.fire(PrivacyProPixel.privacyProFeatureEnabled, frequency: .daily) } guard canShowPromptCheck() else { @@ -64,11 +65,11 @@ final class WaitlistThankYouPromptPresenter { if isPIRBetaTester() { saveDidShowPromptCheck() - DailyPixel.fire(pixel: Pixel.Event.privacyProBetaUserThankYouDBP, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouDBP, frequency: .dailyAndCount) presentPIRThankYouPrompt(in: window) } else if isVPNBetaTester() { saveDidShowPromptCheck() - DailyPixel.fire(pixel: Pixel.Event.privacyProBetaUserThankYouVPN, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouVPN, frequency: .dailyAndCount) presentVPNThankYouPrompt(in: window) } } diff --git a/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift b/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift index 7cded8cb81..a97df7f1c6 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift @@ -91,7 +91,7 @@ struct DataBrokerProtectionWaitlistViewControllerPresenter: WaitlistViewControll guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController else { return } - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) // This is a hack to get around an issue with the waitlist notification screen showing the wrong state while it animates in, and then // jumping to the correct state as soon as the animation is complete. This works around that problem by providing the correct state up front, diff --git a/DuckDuckGo/Waitlist/Waitlist.swift b/DuckDuckGo/Waitlist/Waitlist.swift index 5a28b3f257..56c9f8cdd7 100644 --- a/DuckDuckGo/Waitlist/Waitlist.swift +++ b/DuckDuckGo/Waitlist/Waitlist.swift @@ -346,7 +346,7 @@ struct DataBrokerProtectionWaitlist: Waitlist { sendInviteCodeAvailableNotification { DispatchQueue.main.async { - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistNotificationShown, frequency: .dailyAndCount) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistNotificationShown, frequency: .dailyAndCount) } } } diff --git a/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift b/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift index c0bb48b726..ccde7fd9a7 100644 --- a/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift +++ b/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift @@ -18,6 +18,7 @@ import Foundation import UserNotifications +import PixelKit protocol WaitlistTermsAndConditionsActionHandler { var acceptedTermsAndConditions: Bool { get } @@ -47,7 +48,7 @@ struct DataBrokerProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTerm var acceptedTermsAndConditions: Bool func didShow() { - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndCount) } mutating func didAccept() { @@ -55,7 +56,7 @@ struct DataBrokerProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTerm // Remove delivered NetP notifications in case the user didn't click them. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [DataBrokerProtectionWaitlist.notificationIdentifier]) - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndCount) } } diff --git a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift index 53620c7236..abbd1fb71e 100644 --- a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift +++ b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift @@ -23,6 +23,7 @@ import Foundation import Navigation import WebKit import UserScript +import PixelKit enum DuckPlayerMode: Equatable, Codable { case enabled, alwaysAsk, disabled @@ -152,11 +153,11 @@ final class DuckPlayer { .handleEvents(receiveOutput: { mode in switch mode { case .enabled: - Pixel.fire(.duckPlayerSettingAlways) + PixelKit.fire(GeneralPixel.duckPlayerSettingAlways) case .alwaysAsk: - Pixel.fire(.duckPlayerSettingBackToDefault) + PixelKit.fire(GeneralPixel.duckPlayerSettingBackToDefault) case .disabled: - Pixel.fire(.duckPlayerSettingNever) + PixelKit.fire(GeneralPixel.duckPlayerSettingNever) } }) .prepend(preferences.duckPlayerMode) diff --git a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift index 60906938bc..e5b711db73 100644 --- a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift @@ -20,6 +20,7 @@ import Foundation import WebKit import Common import UserScript +import PixelKit protocol YoutubeOverlayUserScriptDelegate: AnyObject { func youtubeOverlayUserScriptDidRequestDuckPlayer(with url: URL, in webView: WKWebView) @@ -113,16 +114,16 @@ extension YoutubeOverlayUserScript { if pixelName == "play.use" || pixelName == "play.do_not_use" { duckPlayerPreferences.youtubeOverlayAnyButtonPressed = true if pixelName == "play.use" { - Pixel.fire(.duckPlayerViewFromYoutubeViaMainOverlay) + PixelKit.fire(GeneralPixel.duckPlayerViewFromYoutubeViaMainOverlay) } } // Temporary pixel for first time user uses Duck Player - if !Pixel.isNewUser { + if !AppDelegate.isNewUser { return nil } if pixelName == "play.use" { - Pixel.fire(.watchInDuckPlayerInitial, limitTo: .initial) + PixelKit.fire(GeneralPixel.watchInDuckPlayerInitial, frequency: .legacyInitial) } return nil } diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 522bef3306..5e3bf42951 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -43,7 +43,6 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { appVersion: AppVersion.shared.versionNumber, source: "dbpBackgroundAgent", defaultHeaders: [:], - log: .dbpBackgroundAgentPixel, defaults: .standard) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping (Bool, Error?) -> Void) in let url = URL.pixelUrl(forPixelNamed: pixelName) diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 67d2baeb1d..c63440c515 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -303,7 +303,6 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { appVersion: AppVersion.shared.versionNumber, source: pixelSource, defaultHeaders: [:], - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) @@ -372,7 +371,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { UserDefaults.netP.networkProtectionEntitlementsExpired = false case .invalidEntitlement: UserDefaults.netP.networkProtectionEntitlementsExpired = true - PixelKit.fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, frequency: .dailyAndContinuous) + PixelKit.fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, frequency: .dailyAndCount) guard let self else { return } Task { diff --git a/IntegrationTests/Tab/AddressBarTests.swift b/IntegrationTests/Tab/AddressBarTests.swift index 99dce527a4..3877942c87 100644 --- a/IntegrationTests/Tab/AddressBarTests.swift +++ b/IntegrationTests/Tab/AddressBarTests.swift @@ -737,12 +737,12 @@ class AddressBarTests: XCTestCase { // when activaing a Pinned Tab in another window its Web View should become the first responder viewModel2.select(at: .pinned(0)) - try await Task.sleep(interval: 0.01) + try await Task.sleep(interval: 0.1) XCTAssertEqual(window.firstResponder, window) XCTAssertEqual(window2.firstResponder, tab.webView) window.makeKeyAndOrderFront(nil) - try await Task.sleep(interval: 0.01) + try await Task.sleep(interval: 0.1) XCTAssertEqual(window.firstResponder, tab.webView) XCTAssertEqual(window2.firstResponder, window2) diff --git a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/Pixels/VPNPrivacyProPixelTests.swift b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/Pixels/VPNPrivacyProPixelTests.swift index 05e05a5827..21d5472f49 100644 --- a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/Pixels/VPNPrivacyProPixelTests.swift +++ b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/Pixels/VPNPrivacyProPixelTests.swift @@ -61,10 +61,12 @@ final class VPNPrivacyProPixelTests: XCTestCase { /// func testVPNPixelFireExpectations() { fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_vpn_access_revoked_dialog_shown"), file: #filePath, line: #line) fire(VPNPrivacyProPixel.vpnBetaStoppedWhenPrivacyProEnabled, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_vpn_beta_stopped_when_privacy_pro_enabled"), file: #filePath, line: #line) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index f55ec17ba4..be1be56922 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -24,21 +24,44 @@ public final class PixelKit { public typealias CompletionBlock = (Bool, Error?) -> Void /// The frequency with which a pixel is sent to our endpoint. - /// public enum Frequency { /// The default frequency for pixels. This fires pixels with the event names as-is. case standard - /// Sent only once ever. The timestamp for this pixel is stored. Name for pixels of this type must end with `_u`. - case justOnce + /// [Legacy] Used in Pixel.fire(...) as .unique but without the `_u` requirement in the name + case legacyInitial + + /// Sent only once ever. The timestamp for this pixel is stored. + /// Note: This is the only pixel that MUST end with `_u`, Name for pixels of this type must end with if it doesn't an assertion is fired. + case unique + + /// [Legacy] Used in Pixel.fire(...) as .daily but without the `_d` automatically added to the name + case legacyDaily /// Sent once per day. The last timestamp for this pixel is stored and compared to the current date. Pixels of this type will have `_d` appended to their name. - case dailyOnly + case daily /// Sent once per day with a `_d` suffix, in addition to every time it is called with a `_c` suffix. /// This means a pixel will get sent twice the first time it is called per-day, and subsequent calls that day will only send the `_c` variant. /// This is useful in situations where pixels receive spikes in volume, as the daily pixel can be used to determine how many users are actually affected. - case dailyAndContinuous + case dailyAndCount + + fileprivate var description: String { + switch self { + case .standard: + "Standard" + case .legacyInitial: + "Legacy Initial" + case .unique: + "Unique" + case .legacyDaily: + "Legacy Daily" + case .daily: + "Daily" + case .dailyAndCount: + "Daily and Count" + } + } } public enum Header { @@ -50,8 +73,14 @@ public final class PixelKit { public static let client = "X-DuckDuckGo-Client" } + public enum Source: String { + case macStore = "browser-appstore" + case macDMG = "browser-dmg" + case iOS = "phone" + case iPadOS = "tablet" + } + /// A closure typealias to request sending pixels through the network. - /// public typealias FireRequest = ( _ pixelName: String, _ headers: [String: String], @@ -61,11 +90,11 @@ public final class PixelKit { _ onComplete: @escaping CompletionBlock) -> Void public typealias Event = PixelKitEvent - public static let duckDuckGoMorePrivacyInfo = URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/atb/")! - private let defaults: UserDefaults + private let logger = Logger(subsystem: "com.duckduckgo.PixelKit", category: "PixelKit") + private static let defaultDailyPixelCalendar: Calendar = { var calendar = Calendar.current calendar.timeZone = TimeZone(secondsFromGMT: 0)! @@ -77,9 +106,9 @@ public final class PixelKit { private let dateGenerator: () -> Date public private(set) static var shared: PixelKit? + private let appVersion: String private let defaultHeaders: [String: String] - private let log: OSLog private let fireRequest: FireRequest /// Sets up PixelKit for the entire app. @@ -92,7 +121,6 @@ public final class PixelKit { appVersion: String, source: String? = nil, defaultHeaders: [String: String], - log: OSLog, dailyPixelCalendar: Calendar? = nil, dateGenerator: @escaping () -> Date = Date.init, defaults: UserDefaults, @@ -101,14 +129,13 @@ public final class PixelKit { appVersion: appVersion, source: source, defaultHeaders: defaultHeaders, - log: log, dailyPixelCalendar: dailyPixelCalendar, dateGenerator: dateGenerator, defaults: defaults, fireRequest: fireRequest) } - static func tearDown() { + public static func tearDown() { shared = nil } @@ -116,28 +143,27 @@ public final class PixelKit { private let source: String? private let pixelCalendar: Calendar - init(dryRun: Bool, - appVersion: String, - source: String? = nil, - defaultHeaders: [String: String], - log: OSLog, - dailyPixelCalendar: Calendar? = nil, - dateGenerator: @escaping () -> Date = Date.init, - defaults: UserDefaults, - fireRequest: @escaping FireRequest) { + public init(dryRun: Bool, + appVersion: String, + source: String? = nil, + defaultHeaders: [String: String], + dailyPixelCalendar: Calendar? = nil, + dateGenerator: @escaping () -> Date = Date.init, + defaults: UserDefaults, + fireRequest: @escaping FireRequest) { self.dryRun = dryRun self.appVersion = appVersion self.source = source self.defaultHeaders = defaultHeaders - self.log = log self.pixelCalendar = dailyPixelCalendar ?? Self.defaultDailyPixelCalendar self.dateGenerator = dateGenerator self.defaults = defaults self.fireRequest = fireRequest + logger.debug("👾 PixelKit initialised: dryRun: \(self.dryRun, privacy: .public) appVersion: \(self.appVersion, privacy: .public) source: \(self.source ?? "-", privacy: .public) defaultHeaders: \(self.defaultHeaders, privacy: .public) pixelCalendar: \(self.pixelCalendar, privacy: .public)") } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next function_body_length cyclomatic_complexity private func fire(pixelNamed pixelName: String, frequency: Frequency, withHeaders headers: [String: String]?, @@ -148,18 +174,9 @@ public final class PixelKit { onComplete: @escaping CompletionBlock) { var newParams = params ?? [:] - - if includeAppVersionParameter { - newParams[Parameters.appVersion] = appVersion - } - - if let source { - newParams[Parameters.pixelSource] = source - } - - if let error { - newParams.appendErrorPixelParams(error: error) - } + if includeAppVersionParameter { newParams[Parameters.appVersion] = appVersion } + if let source { newParams[Parameters.pixelSource] = source } + if let error { newParams.appendErrorPixelParams(error: error) } #if DEBUG newParams[Parameters.test] = Values.test @@ -171,42 +188,74 @@ public final class PixelKit { switch frequency { case .standard: - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete) - case .justOnce: + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) + case .legacyInitial: + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") + if !pixelHasBeenFiredEver(pixelName) { + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) + updatePixelLastFireDate(pixelName: pixelName) + } else { + printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) + } + case .unique: + reportErrorIf(pixel: pixelName, endsWith: "_d") guard pixelName.hasSuffix("_u") else { assertionFailure("Unique pixel: must end with _u") return } if !pixelHasBeenFiredEver(pixelName) { - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) + updatePixelLastFireDate(pixelName: pixelName) + } else { + printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) + } + case .legacyDaily: + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") + if !pixelHasBeenFiredToday(pixelName) { + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { - printDebugInfo(pixelName: pixelName, parameters: newParams, skipped: true) + printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) } - case .dailyOnly: + case .daily: + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { - fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { - printDebugInfo(pixelName: pixelName + "_d", parameters: newParams, skipped: true) + printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) } - case .dailyAndContinuous: + case .dailyAndCount: + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") // Because is added automatically + reportErrorIf(pixel: pixelName, endsWith: "_c") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { - fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { - printDebugInfo(pixelName: pixelName + "_d", parameters: newParams, skipped: true) + printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) } - fireRequestWrapper(pixelName + "_c", headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName + "_c", headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) } } - private func printDebugInfo(pixelName: String, parameters: [String: String], skipped: Bool = false) { -#if DEBUG + /// If the pixel name ends with the forbiddenString then an error is logged or an assertion failure is fired in debug + func reportErrorIf(pixel: String, endsWith forbiddenString: String) { + if pixel.hasSuffix(forbiddenString) { + logger.error("Pixel \(pixel, privacy: .public) must not end with \(forbiddenString, privacy: .public)") + assertionFailure("Pixel \(pixel) must not end with \(forbiddenString)") + } + } + + private func printDebugInfo(pixelName: String, frequency: Frequency, parameters: [String: String], skipped: Bool = false) { let params = parameters.filter { key, _ in !["test"].contains(key) } - os_log(.debug, log: log, "👾 [%{public}@] %{public}@ %{public}@", skipped ? "SKIPPED" : "FIRED", pixelName.replacingOccurrences(of: "_", with: "."), params) -#endif + logger.debug("👾[\(frequency.description, privacy: .public)-\(skipped ? "Skipped" : "Fired", privacy: .public)] \(pixelName, privacy: .public) \(params, privacy: .public)") } private func fireRequestWrapper( @@ -215,21 +264,19 @@ public final class PixelKit { _ parameters: [String: String], _ allowedQueryReservedCharacters: CharacterSet?, _ callBackOnMainThread: Bool, + _ frequency: Frequency, _ onComplete: @escaping CompletionBlock) { - guard !dryRun else { - printDebugInfo(pixelName: pixelName, parameters: parameters) - - // simulate server response time for Dry Run mode - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - onComplete(true, nil) + printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: parameters, skipped: false) + guard !dryRun else { + // simulate server response time for Dry Run mode + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + onComplete(true, nil) + } + return } - - return + fireRequest(pixelName, headers, parameters, allowedQueryReservedCharacters, callBackOnMainThread, onComplete) } - fireRequest(pixelName, headers, parameters, allowedQueryReservedCharacters, callBackOnMainThread, onComplete) - } - private func prefixedName(for event: Event) -> String { if event.name.hasPrefix("m_mac_") { return event.name @@ -254,10 +301,10 @@ public final class PixelKit { let pixelName = prefixedName(for: event) if !dryRun { - if frequency == .dailyOnly, pixelHasBeenFiredToday(pixelName) { + if frequency == .daily, pixelHasBeenFiredToday(pixelName) { onComplete(false, nil) return - } else if frequency == .justOnce, pixelHasBeenFiredEver(pixelName) { + } else if frequency == .unique, pixelHasBeenFiredEver(pixelName) { onComplete(false, nil) return } @@ -340,7 +387,11 @@ public final class PixelKit { } public func pixelLastFireDate(pixelName: String) -> Date? { - defaults.object(forKey: userDefaultsKeyName(forPixelName: pixelName)) as? Date + var date = defaults.object(forKey: userDefaultsKeyName(forPixelName: pixelName)) as? Date + if date == nil { + date = defaults.object(forKey: legacyUserDefaultsKeyName(forPixelName: pixelName)) as? Date + } + return date } public func pixelLastFireDate(event: Event) -> Date? { @@ -372,12 +423,36 @@ public final class PixelKit { pixelLastFireDate(pixelName: name) != nil } - private func userDefaultsKeyName(forPixelName pixelName: String) -> String { + public func clearFrequencyHistoryFor(pixel: PixelKitEventV2) { + guard let name = Self.shared?.userDefaultsKeyName(forPixelName: pixel.name) else { + return + } + self.defaults.removeObject(forKey: name) + } + + public func clearFrequencyHistoryForAllPixels() { + for (key, _) in self.defaults.dictionaryRepresentation() { + if key.hasPrefix(Self.storageKeyPrefixLegacy) || key.hasPrefix(Self.storageKeyPrefix) { + self.defaults.removeObject(forKey: key) + self.logger.debug("🚮 Removing from storage \(key, privacy: .public)") + } + } + } + + static let storageKeyPrefixLegacy = "com.duckduckgo.network-protection.pixel." + static let storageKeyPrefix = "com.duckduckgo.network-protection.pixel." + + /// Initially PixelKit was configured only for serving netP so these very specific keys were used, now PixelKit serves the entire app so we need to move away from them. + /// NOTE: I would remove this 6 months after release + private func legacyUserDefaultsKeyName(forPixelName pixelName: String) -> String { dryRun - ? "com.duckduckgo.network-protection.pixel.\(pixelName).dry-run" - : "com.duckduckgo.network-protection.pixel.\(pixelName)" + ? "\(Self.storageKeyPrefixLegacy)\(pixelName).dry-run" + : "\(Self.storageKeyPrefixLegacy)\(pixelName)" } + private func userDefaultsKeyName(forPixelName pixelName: String) -> String { + return "\(Self.storageKeyPrefix)\(pixelName)\( dryRun ? ".dry-run" : "" )" + } } extension Dictionary where Key == String, Value == String { @@ -388,3 +463,12 @@ extension Dictionary where Key == String, Value == String { } } } + +internal extension PixelKit { + + /// [USE ONLY FOR TESTS] Sets the shared PixelKit.shared singleton + /// - Parameter pixelkit: A custom instance of PixelKit + static func setSharedForTesting(pixelKit: PixelKit) { + Self.shared = pixelKit + } +} diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift index 19525d0d0b..5568497fb5 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift @@ -22,7 +22,7 @@ import Foundation /// /// This new implementation seeks to unify the handling of standard pixel parameters inside PixelKit. /// The starting example of how this can be useful is error parameter handling - this protocol allows -/// the implementer to speciy an error without having to know about the parametrization of the error. +/// the implementer to specify an error without having to know about its parameterisation. /// /// The reason this wasn't done directly in `PixelKitEvent` is to reduce the risk of breaking existing /// pixels, and to allow us to migrate towards this incrementally. diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift index 9017d8774a..81232cd4bb 100644 --- a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift @@ -25,9 +25,7 @@ public extension XCTestCase { // MARK: - Parameters /// List of standard pixel parameters. - /// /// This is useful to support filtering these parameters out if needed. - /// private static var standardPixelParameters = [ PixelKit.Parameters.appVersion, PixelKit.Parameters.pixelSource, @@ -35,21 +33,18 @@ public extension XCTestCase { ] /// List of errror pixel parameters - /// private static var errorPixelParameters = [ PixelKit.Parameters.errorCode, PixelKit.Parameters.errorDomain ] /// List of underlying error pixel parameters - /// private static var underlyingErrorPixelParameters = [ PixelKit.Parameters.underlyingErrorCode, PixelKit.Parameters.underlyingErrorDomain ] /// Filter out the standard parameters. - /// private static func filterStandardPixelParameters(from parameters: [String: String]) -> [String: String] { parameters.filter { element in !standardPixelParameters.contains(element.key) @@ -59,10 +54,8 @@ public extension XCTestCase { static var pixelPlatformPrefix: String { #if os(macOS) return "m_mac_" -#else - // Intentionally left blank for now because PixelKit currently doesn't support - // other platforms, but if we decide to implement another platform this'll fail - // and indicate that we need a value here. +#elseif os(iOS) + return "m_" #endif } @@ -71,7 +64,6 @@ public extension XCTestCase { /// They're not a complete list of parameters for the event, as the fire call may contain extra information /// that results in additional parameters. Ideally we want most (if not all) that information to eventually /// make part of the pixel definition. - /// func knownExpectedParameters(for event: PixelKitEventV2) -> [String: String] { var expectedParameters = [String: String]() @@ -97,20 +89,26 @@ public extension XCTestCase { // MARK: - Pixel Firing Expectations - func fire(_ event: PixelKitEventV2, and expectations: PixelFireExpectations, file: StaticString, line: UInt) { - verifyThat(event, meets: expectations, file: file, line: line) + func fire(_ event: PixelKitEventV2, frequency: PixelKit.Frequency, and expectations: PixelFireExpectations, file: StaticString, line: UInt) { + verifyThat(event, frequency: frequency, meets: expectations, file: file, line: line) } /// Provides some snapshot of a fired pixel so that external libraries can validate all the expected info is included. /// /// This method also checks that there is internal consistency in the expected fields. - /// - func verifyThat(_ event: PixelKitEventV2, meets expectations: PixelFireExpectations, file: StaticString, line: UInt) { - - let expectedPixelName = event.name.hasPrefix(Self.pixelPlatformPrefix) ? event.name : Self.pixelPlatformPrefix + event.name + func verifyThat(_ event: PixelKitEventV2, + frequency: PixelKit.Frequency, + meets expectations: PixelFireExpectations, + file: StaticString, + line: UInt) { + let expectedPixelNames: [String] = expectedPixelNames(originalName: event.name, frequency: frequency) let knownExpectedParameters = knownExpectedParameters(for: event) let callbackExecutedExpectation = expectation(description: "The PixelKit callback has been executed") + if frequency == .dailyAndCount { + callbackExecutedExpectation.expectedFulfillmentCount = 2 + } + // Ensure PixelKit is torn down before setting it back up, avoiding unit test race conditions: PixelKit.tearDown() @@ -118,28 +116,54 @@ public extension XCTestCase { appVersion: "1.0.5", source: "test-app", defaultHeaders: [:], - log: .disabled, defaults: userDefaults) { firedPixelName, _, firedParameters, _, _, completion in callbackExecutedExpectation.fulfill() let firedParameters = Self.filterStandardPixelParameters(from: firedParameters) // Internal validations - - XCTAssertEqual(firedPixelName, expectedPixelName, file: file, line: line) - + XCTAssertTrue(expectedPixelNames.contains(firedPixelName), file: file, line: line) XCTAssertTrue(knownExpectedParameters.allSatisfy { (key, value) in firedParameters[key] == value }) - // Expectations - XCTAssertEqual(firedPixelName, expectations.pixelName) + if frequency == .dailyAndCount { + XCTAssertTrue(firedPixelName.hasPrefix(expectations.pixelName)) + XCTAssertTrue(firedPixelName.hasSuffix("_c") || firedPixelName.hasSuffix("_d")) + XCTAssertEqual(firedPixelName.count, expectations.pixelName.count + 2) + let exp = self.expectedPixelNames(originalName: expectations.pixelName, frequency: frequency) + XCTAssertTrue(exp.contains(firedPixelName)) + } else { + XCTAssertEqual(expectations.pixelName, firedPixelName) + } XCTAssertEqual(firedParameters, expectations.parameters) completion(true, nil) } - PixelKit.fire(event) + PixelKit.fire(event, frequency: frequency) waitForExpectations(timeout: 0.1) } + + func expectedPixelNames(originalName: String, frequency: PixelKit.Frequency) -> [String] { + let expectedPixelNameWithoutSuffix = originalName.hasPrefix(Self.pixelPlatformPrefix) ? originalName : Self.pixelPlatformPrefix + originalName + var expectedPixelNames: [String] = [] + + switch frequency { + case .standard: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .legacyInitial: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .unique: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .legacyDaily: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .daily: + expectedPixelNames.append(expectedPixelNameWithoutSuffix.appending("_d")) + case .dailyAndCount: + expectedPixelNames.append(expectedPixelNameWithoutSuffix.appending("_d")) + expectedPixelNames.append(expectedPixelNameWithoutSuffix.appending("_c")) + } + return expectedPixelNames + } } diff --git a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitParametersTests.swift b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitParametersTests.swift index 50d5e01d51..4d661d3336 100644 --- a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitParametersTests.swift +++ b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitParametersTests.swift @@ -65,6 +65,7 @@ final class PixelKitParametersTests: XCTestCase { ]) fire(TestEvent.errorEvent(error: topLevelError), + frequency: .standard, and: .expect(pixelName: "m_mac_error_event", error: topLevelError, underlyingErrors: [underlyingError2, underlyingError3]), diff --git a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift index 3ce7447aae..fed5b7789d 100644 --- a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift +++ b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift @@ -63,11 +63,11 @@ final class PixelKitTests: XCTestCase { case .testEvent, .testEventWithoutParameters: return .standard case .uniqueEvent: - return .justOnce + return .unique case .dailyEvent, .dailyEventWithoutParameters: - return .dailyOnly + return .daily case .dailyAndContinuousEvent, .dailyAndContinuousEventWithoutParameters: - return .dailyAndContinuous + return .dailyAndCount } } } @@ -77,9 +77,8 @@ final class PixelKitTests: XCTestCase { func testDryRunWontExecuteCallback() async { let appVersion = "1.0.5" let headers: [String: String] = [:] - let log = OSLog.disabled - let pixelKit = PixelKit(dryRun: true, appVersion: appVersion, defaultHeaders: headers, log: log, dailyPixelCalendar: nil, defaults: userDefaults()) { _, _, _, _, _, _ in + let pixelKit = PixelKit(dryRun: true, appVersion: appVersion, defaultHeaders: headers, dailyPixelCalendar: nil, defaults: userDefaults()) { _, _, _, _, _, _ in XCTFail("This callback should not be executed when doing a dry run") } @@ -93,7 +92,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.testEvent let userDefaults = userDefaults() @@ -105,7 +103,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in @@ -139,7 +136,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.dailyEvent let userDefaults = userDefaults() @@ -152,7 +148,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in @@ -173,7 +168,7 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .dailyOnly) + pixelKit.fire(event, frequency: .daily) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -185,7 +180,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.dailyEvent let userDefaults = userDefaults() @@ -200,7 +194,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in @@ -221,8 +214,8 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .dailyOnly) - pixelKit.fire(event, frequency: .dailyOnly) + pixelKit.fire(event, frequency: .daily) + pixelKit.fire(event, frequency: .daily) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -233,7 +226,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.dailyEvent let userDefaults = userDefaults() @@ -248,7 +240,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, dateGenerator: timeMachine.now, defaults: userDefaults) { _, _, _, _, _, _ in @@ -256,20 +247,19 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .dailyOnly) // Fired - + pixelKit.fire(event, frequency: .daily) // Fired timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped + pixelKit.fire(event, frequency: .legacyDaily) // Skipped timeMachine.travel(by: .day, value: 1) timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .dailyOnly) // Fired + pixelKit.fire(event, frequency: .legacyDaily) // Fired timeMachine.travel(by: .hour, value: 10) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped + pixelKit.fire(event, frequency: .legacyDaily) // Skipped timeMachine.travel(by: .day, value: 1) - pixelKit.fire(event, frequency: .dailyOnly) // Fired + pixelKit.fire(event, frequency: .legacyDaily) // Fired // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -280,7 +270,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.uniqueEvent let userDefaults = userDefaults() @@ -294,7 +283,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, dateGenerator: timeMachine.now, defaults: userDefaults) { _, _, _, _, _, _ in @@ -302,20 +290,19 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .justOnce) // Fired - + pixelKit.fire(event, frequency: .unique) // Fired timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: .day, value: 1) timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: .hour, value: 10) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: .day, value: 1) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -337,7 +324,6 @@ final class PixelKitTests: XCTestCase { PixelKit.setUp(appVersion: "test", defaultHeaders: [:], - log: .disabled, dailyPixelCalendar: calendar, dateGenerator: timeMachine.now, defaults: userDefaults()) { _, _, _, _, _, _ in } diff --git a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift index a2209cd64b..cd60e9adf7 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift +++ b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift @@ -62,164 +62,200 @@ final class NetworkProtectionPixelEventTests: XCTestCase { /// func testVPNPixelFireExpectations() { fire(NetworkProtectionPixelEvent.networkProtectionActiveUser, + frequency: .legacyDaily, and: .expect(pixelName: "m_mac_netp_daily_active"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionNewUser, + frequency: .unique, and: .expect(pixelName: "m_mac_netp_daily_active_u"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionControllerStartAttempt, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_controller_start_attempt"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionControllerStartFailure(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_controller_start_failure", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionControllerStartSuccess, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_controller_start_success"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelStartAttempt, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_start_attempt"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelStartFailure(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_start_failure", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelStartSuccess, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_start_success"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelUpdateAttempt, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_update_attempt"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelUpdateFailure(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_update_failure", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelUpdateSuccess, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_update_success"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionEnableAttemptConnecting, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_ev_enable_attempt"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionEnableAttemptSuccess, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_ev_enable_attempt_success"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionEnableAttemptFailure, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_ev_enable_attempt_failure"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelFailureDetected, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_ev_tunnel_failure"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelFailureRecovered, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_ev_tunnel_failure_recovered"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionLatency(quality: .excellent), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_ev_\(NetworkProtectionLatencyMonitor.ConnectionQuality.excellent.rawValue)_latency"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionLatencyError, + frequency: .legacyDaily, and: .expect(pixelName: "m_mac_netp_ev_latency_error"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelConfigurationNoServerRegistrationInfo, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_config_error_no_server_registration_info"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelConfigurationCouldNotSelectClosestServer, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_config_error_could_not_select_closest_server"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_config_error_could_not_get_peer_public_key"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelConfigurationCouldNotGetPeerHostName, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_config_error_could_not_get_peer_host_name"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_tunnel_config_error_could_not_get_interface_address_range"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToFetchServerList(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_failed_to_fetch_server_list", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToParseServerListResponse, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_parsing_server_list_response_failed"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToEncodeRegisterKeyRequest, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_encoding_register_request_body_failed"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToFetchRegisteredServers(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_failed_to_fetch_registered_servers", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToParseRegisteredServersResponse, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_parsing_device_registration_response_failed"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToEncodeRedeemRequest, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_encoding_redeem_request_body_failed"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientInvalidInviteCode, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_invalid_invite_code"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToRedeemInviteCode(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_failed_to_redeem_invite_code", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToParseRedeemResponse(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_parsing_redeem_response_failed", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToFetchLocations(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_failed_to_fetch_location_list", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientFailedToParseLocationsResponse(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_parsing_location_list_response_failed", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionClientInvalidAuthToken, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_backend_api_error_invalid_auth_token"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionKeychainErrorFailedToCastKeychainValueToData(field: "field"), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_keychain_error_failed_to_cast_keychain_value_to_data", customFields: [ PixelKit.Parameters.keychainFieldName: "field", @@ -227,6 +263,7 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionKeychainReadError(field: "field", status: 1), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_keychain_error_read_failed", customFields: [ PixelKit.Parameters.keychainFieldName: "field", @@ -235,6 +272,7 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionKeychainWriteError(field: "field", status: 1), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_keychain_error_write_failed", customFields: [ PixelKit.Parameters.keychainFieldName: "field", @@ -243,6 +281,7 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionKeychainUpdateError(field: "field", status: 1), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_keychain_error_update_failed", customFields: [ PixelKit.Parameters.keychainFieldName: "field", @@ -251,6 +290,7 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionKeychainDeleteError(status: 1), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_keychain_error_delete_failed", customFields: [ PixelKit.Parameters.errorCode: "1" @@ -258,10 +298,12 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_wireguard_error_cannot_locate_tunnel_file_descriptor"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionWireguardErrorInvalidState(reason: "reason"), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_wireguard_error_invalid_state", customFields: [ PixelKit.Parameters.reason: "reason" @@ -269,16 +311,19 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionWireguardErrorFailedDNSResolution, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_wireguard_error_failed_dns_resolution"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionWireguardErrorCannotSetNetworkSettings(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_wireguard_error_cannot_set_network_settings", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionWireguardErrorCannotStartWireguardBackend(code: 1), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_wireguard_error_cannot_start_wireguard_backend", customFields: [ PixelKit.Parameters.errorCode: "1" @@ -286,30 +331,36 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionNoAuthTokenFoundError, + frequency: .standard, and: .expect(pixelName: "m_mac_netp_no_auth_token_found_error"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionRekeyAttempt, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_rekey_attempt"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionRekeyCompleted, + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_rekey_completed"), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionRekeyFailure(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_rekey_failure", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure(TestError.testError), + frequency: .dailyAndCount, and: .expect(pixelName: "m_mac_netp_system_extension_activation_failure", error: TestError.testError, underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionUnhandledError(function: "function", line: 1, error: TestError.testError), + frequency: .standard, and: .expect(pixelName: "m_mac_netp_unhandled_error", error: TestError.testError, underlyingErrors: [TestError.underlyingError], diff --git a/UnitTests/Permissions/PermissionModelTests.swift b/UnitTests/Permissions/PermissionModelTests.swift index 1357aec53a..1a31091936 100644 --- a/UnitTests/Permissions/PermissionModelTests.swift +++ b/UnitTests/Permissions/PermissionModelTests.swift @@ -21,6 +21,7 @@ import Combine import Foundation import WebKit import XCTest +@testable import PixelKit @testable import DuckDuckGo_Privacy_Browser @@ -31,6 +32,11 @@ final class PermissionModelTests: XCTestCase { var geolocationProviderMock: GeolocationProviderMock! let webView = WebViewMock() var model: PermissionModel! + let pixelKit = PixelKit(dryRun: true, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in }) var securityOrigin: WKSecurityOrigin { WKSecurityOriginMock.new(url: .duckDuckGo) @@ -42,6 +48,8 @@ final class PermissionModelTests: XCTestCase { } override func setUp() { + PixelKit.setSharedForTesting(pixelKit: pixelKit) + webView.uiDelegate = self geolocationProviderMock = GeolocationProviderMock(geolocationService: geolocationServiceMock) diff --git a/UnitTests/Permissions/PermissionStoreTests.swift b/UnitTests/Permissions/PermissionStoreTests.swift index 4566288bf5..f95ff7f2ed 100644 --- a/UnitTests/Permissions/PermissionStoreTests.swift +++ b/UnitTests/Permissions/PermissionStoreTests.swift @@ -19,11 +19,21 @@ import Foundation import XCTest @testable import DuckDuckGo_Privacy_Browser +@testable import PixelKit final class PermissionStoreTests: XCTestCase { let container = CoreData.permissionContainer() lazy var store = LocalPermissionStore(context: container.viewContext) + let pixelKit = PixelKit(dryRun: true, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in }) + + override func setUp() { + PixelKit.setSharedForTesting(pixelKit: pixelKit) + } func testWhenPermissionIsAddedThenItMustBeLoadedFromStore() throws { let stored1 = try store.add(domain: "duckduckgo.com", permissionType: .camera, decision: .allow) diff --git a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift index bc52a33041..1d38801f44 100644 --- a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift +++ b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift @@ -51,7 +51,7 @@ final class BrokenSiteReportingReferenceTests: XCTestCase { APIRequest.Headers.setUserAgent("") var params = parameters params["test"] = "1" - let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), + let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: GeneralPixel.brokenSiteReport.name), queryParameters: params, allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) return configuration.request diff --git a/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift b/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift index f94f1f402f..0d217cf769 100644 --- a/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift +++ b/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift @@ -17,6 +17,7 @@ // import XCTest +import PixelKit @testable import DuckDuckGo_Privacy_Browser final class InstallationAttributionPixelHandlerTests: XCTestCase { @@ -27,10 +28,12 @@ final class InstallationAttributionPixelHandlerTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() capturedParams = CapturedParameters() - fireRequest = { event, repetition, parameters, reservedCharacters, includeAppVersion, onComplete in + fireRequest = { event, frequency, headers, parameters, error, reservedCharacters, includeAppVersion, onComplete in self.capturedParams.event = event - self.capturedParams.repetition = repetition + self.capturedParams.frequency = frequency + self.capturedParams.headers = headers self.capturedParams.parameters = parameters + self.capturedParams.error = error self.capturedParams.reservedCharacters = reservedCharacters self.capturedParams.includeAppVersion = includeAppVersion self.capturedParams.onComplete = onComplete @@ -97,7 +100,7 @@ final class InstallationAttributionPixelHandlerTests: XCTestCase { XCTAssertEqual(capturedParams?.parameters?[InstallationAttributionPixelHandler.Parameters.locale], "en-US") } - func testWhenPixelFiresThenAddAppVersionIsTrueAndRepetitionIsInitial() { + func testWhenPixelFiresThenAddAppVersionIsTrueAndFrequencyIsLegacyInitial() { // GIVEN sut = .init(fireRequest: fireRequest, originProvider: MockAttributionOriginProvider(), locale: .current) @@ -106,19 +109,21 @@ final class InstallationAttributionPixelHandlerTests: XCTestCase { // THEN XCTAssertEqual(capturedParams.includeAppVersion, true) - XCTAssertEqual(capturedParams.repetition, .initial) + XCTAssertEqual(capturedParams.frequency, .legacyInitial) } } extension InstallationAttributionPixelHandlerTests { struct CapturedParameters { - var event: Pixel.Event? - var repetition: Pixel.Event.Repetition = .repetitive + var event: PixelKit.Event? + var frequency: PixelKit.Frequency = .standard + var headers: [String: String] = [:] var parameters: [String: String]? + var error: Error? var reservedCharacters: CharacterSet? var includeAppVersion: Bool? - var onComplete: (Error?) -> Void = { _ in } + var onComplete: (Bool, Error?) -> Void = { _, _ in } } } diff --git a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift index 573284cd17..eb6bbfaa2c 100644 --- a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift +++ b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift @@ -19,6 +19,7 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift +@testable import PixelKit @testable import DuckDuckGo_Privacy_Browser class StatisticsLoaderTests: XCTestCase { @@ -26,14 +27,22 @@ class StatisticsLoaderTests: XCTestCase { private var mockAttributionsPixelHandler: MockAttributionsPixelHandler! private var mockStatisticsStore: StatisticsStore! private var testee: StatisticsLoader! + let pixelKit = PixelKit(dryRun: true, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in }) override func setUp() { + PixelKit.setSharedForTesting(pixelKit: pixelKit) + mockAttributionsPixelHandler = MockAttributionsPixelHandler() mockStatisticsStore = MockStatisticsStore() testee = StatisticsLoader(statisticsStore: mockStatisticsStore, attributionPixelHandler: mockAttributionsPixelHandler) } override func tearDown() { + PixelKit.tearDown() HTTPStubs.removeAllStubs() mockStatisticsStore = nil mockAttributionsPixelHandler = nil diff --git a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift index 95187b5346..f6e7aef1d2 100644 --- a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift +++ b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift @@ -19,6 +19,7 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift +@testable import PixelKit @testable import DuckDuckGo_Privacy_Browser class CBRCompileTimeReporterTests: XCTestCase { @@ -27,15 +28,20 @@ class CBRCompileTimeReporterTests: XCTestCase { let host = "improving.duckduckgo.com" var tab: NSObject! = NSObject() var time = CACurrentMediaTime() + let pixelKit = PixelKit(dryRun: true, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in }) override func setUp() { - Pixel.setUp() + PixelKit.setSharedForTesting(pixelKit: pixelKit) UserDefaultsWrapper.clearAll() } override func tearDown() { HTTPStubs.removeAllStubs() - Pixel.tearDown() + PixelKit.tearDown() super.tearDown() } @@ -51,8 +57,8 @@ class CBRCompileTimeReporterTests: XCTestCase { @discardableResult func performTest(withOnboardingFinished onboardingFinished: Bool, waitTime: TimeInterval, - expectedWaitTime: Pixel.Event.CompileRulesWaitTime, - result: Pixel.Event.WaitResult, + expectedWaitTime: GeneralPixel.CompileRulesWaitTime, + result: GeneralPixel.WaitResult, runBeforeFinishing: ((Reporter) throws -> Void)? = nil) rethrows -> Reporter { HTTPStubs.removeAllStubs() @@ -60,7 +66,7 @@ class CBRCompileTimeReporterTests: XCTestCase { HTTPStubs.removeAllStubs() } let reporter = initReporter(onboardingFinished: onboardingFinished) - let pixel = Pixel.Event.compileRulesWait(onboardingShown: onboardingFinished ? .regularNavigation : .onboardingShown, + let pixel = GeneralPixel.compileRulesWait(onboardingShown: onboardingFinished ? .regularNavigation : .onboardingShown, waitTime: expectedWaitTime, result: result) @@ -98,7 +104,7 @@ class CBRCompileTimeReporterTests: XCTestCase { return reporter } - typealias Pair = (TimeInterval, Pixel.Event.CompileRulesWaitTime) + typealias Pair = (TimeInterval, GeneralPixel.CompileRulesWaitTime) let waitExpectationSeq: [Pair] = [(0, .noWait), (0.5, .lessThan1s), (1, .lessThan1s), diff --git a/UnitTests/Statistics/PixelArgumentsTests.swift b/UnitTests/Statistics/Legacy/PixelArgumentsTests.swift similarity index 79% rename from UnitTests/Statistics/PixelArgumentsTests.swift rename to UnitTests/Statistics/Legacy/PixelArgumentsTests.swift index 8347a25732..f5e812e079 100644 --- a/UnitTests/Statistics/PixelArgumentsTests.swift +++ b/UnitTests/Statistics/Legacy/PixelArgumentsTests.swift @@ -50,7 +50,7 @@ class PixelArgumentsTests: XCTestCase { // MARK: AccessPoint func testWhenInitWithButtonThenAccessPointIsButton() { - let ap = Pixel.Event.AccessPoint(sender: NSButton(), default: .tabMenu) + let ap = GeneralPixel.AccessPoint(sender: NSButton(), default: .tabMenu) XCTAssertEqual(ap, .button) } @@ -68,7 +68,7 @@ class PixelArgumentsTests: XCTestCase { func testWhenInitWithMainMenuItemThenAccessPointIsMenu() { let mainMenu = makeMenu() - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .mainMenu) } @@ -79,7 +79,7 @@ class PixelArgumentsTests: XCTestCase { context: nil, characters: "x", charactersIgnoringModifiers: "", isARepeat: false, keyCode: UInt16(kVK_ANSI_X))! NSApp.setValue(event, forKey: "currentEvent") - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .hotKey) } @@ -90,7 +90,7 @@ class PixelArgumentsTests: XCTestCase { context: nil, characters: "\n", charactersIgnoringModifiers: "", isARepeat: false, keyCode: UInt16(kVK_Return))! NSApp.setValue(event, forKey: "currentEvent") - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .mainMenu) } @@ -101,30 +101,30 @@ class PixelArgumentsTests: XCTestCase { context: nil, characters: " ", charactersIgnoringModifiers: "", isARepeat: false, keyCode: UInt16(kVK_Space))! NSApp.setValue(event, forKey: "currentEvent") - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .mainMenu) } func testWhenInitWithTabMenuItemThenAccessPointIsMenu() { let mainMenu = makeMenu() let tabMenu = makeMenu() - let ap = Pixel.Event.AccessPoint(sender: tabMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: tabMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .tabMenu) } // MARK: Repetition func testWhenInitFirstTimeThenRepetitionIsInitial() { - let rep1 = Pixel.Event.Repetition(key: "test", store: pixelDataStore) - let rep2 = Pixel.Event.Repetition(key: "test2", store: pixelDataStore) + let rep1 = GeneralPixel.Repetition(key: "test", store: pixelDataStore) + let rep2 = GeneralPixel.Repetition(key: "test2", store: pixelDataStore) XCTAssertEqual(rep1, .initial) XCTAssertEqual(rep2, .initial) } func testWhenInitSecondTimeThenRepetitionIsRepetitive() { - _=Pixel.Event.Repetition(key: "test", store: pixelDataStore) - let rep1 = Pixel.Event.Repetition(key: "test", store: pixelDataStore) - let rep2 = Pixel.Event.Repetition(key: "test2", store: pixelDataStore) + _=GeneralPixel.Repetition(key: "test", store: pixelDataStore) + let rep1 = GeneralPixel.Repetition(key: "test", store: pixelDataStore) + let rep2 = GeneralPixel.Repetition(key: "test2", store: pixelDataStore) XCTAssertEqual(rep1, .repetitive) XCTAssertEqual(rep2, .initial) } @@ -135,10 +135,10 @@ class PixelArgumentsTests: XCTestCase { let tomorrow2 = now.addingTimeInterval(3600 * 24 + 1) let afterTomorrow = tomorrow.addingTimeInterval(3600 * 24) - _=Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: now) - let rep1 = Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: tomorrow) - let rep2 = Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: tomorrow2) - let rep3 = Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: afterTomorrow) + _=GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: now) + let rep1 = GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: tomorrow) + let rep2 = GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: tomorrow2) + let rep3 = GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: afterTomorrow) XCTAssertEqual(rep1, .dailyFirst) XCTAssertEqual(rep2, .repetitive) diff --git a/UnitTests/Statistics/PixelEventTests.swift b/UnitTests/Statistics/Legacy/PixelEventTests.swift similarity index 100% rename from UnitTests/Statistics/PixelEventTests.swift rename to UnitTests/Statistics/Legacy/PixelEventTests.swift diff --git a/UnitTests/Statistics/PixelStoreTests.swift b/UnitTests/Statistics/Legacy/PixelStoreTests.swift similarity index 100% rename from UnitTests/Statistics/PixelStoreTests.swift rename to UnitTests/Statistics/Legacy/PixelStoreTests.swift diff --git a/UnitTests/Statistics/PixelTests.swift b/UnitTests/Statistics/Legacy/PixelTests.swift similarity index 94% rename from UnitTests/Statistics/PixelTests.swift rename to UnitTests/Statistics/Legacy/PixelTests.swift index 1e580a0652..25b9de1126 100644 --- a/UnitTests/Statistics/PixelTests.swift +++ b/UnitTests/Statistics/Legacy/PixelTests.swift @@ -66,7 +66,7 @@ class PixelTests: XCTestCase { return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) } - Pixel.fire(.crash, withAdditionalParameters: params) + PixelKit.fire(GeneralPixel.crash, withAdditionalParameters: params) waitForExpectations(timeout: 1.0) } @@ -82,7 +82,7 @@ class PixelTests: XCTestCase { return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) } - Pixel.fire(.debug(event: Pixel.Event.Debug.appOpenURLFailed, error: error)) + PixelKit.fire(DebugEvent( GeneralPixel.appOpenURLFailed, error: error)) waitForExpectations(timeout: 1.0) } @@ -107,7 +107,7 @@ class PixelTests: XCTestCase { return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) } - Pixel.fire(.debug(event: Pixel.Event.Debug.appOpenURLFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent( GeneralPixel.appOpenURLFailed, error: error), withAdditionalParameters: params) waitForExpectations(timeout: 1.0) } diff --git a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift index cb3953e760..6215677a64 100644 --- a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift +++ b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift @@ -17,8 +17,15 @@ // import XCTest +import PixelKit +import os.log @testable import DuckDuckGo_Privacy_Browser +/* + WARNING: This component doesn't work anymore, re-implementation needed + https://app.asana.com/0/0/1207002879349166/f + */ + class PixelExperimentTests: XCTestCase { var now = Date() @@ -55,13 +62,16 @@ class PixelExperimentTests: XCTestCase { override func tearDown() { logic.cleanup() - Pixel.tearDown() + PixelKit.tearDown() _store = nil } func testWhenNotInstalledThenCohortIsNill() { XCTAssertNil(logic.cohort) - Pixel.setUp { _ in + + PixelKit.setUp(appVersion: "", + defaultHeaders: [:], + defaults: UserDefaults()) { _, _, _, _, _, _ in XCTFail("shouldn‘t fire pixels") } @@ -71,14 +81,18 @@ class PixelExperimentTests: XCTestCase { } func testWhenNoCohort_NoEnrollmentPixelFired() { - Pixel.firstLaunchDate = now - PixelExperiment.install() + DispatchQueue.main.async { + AppDelegate.firstLaunchDate = self.now + PixelExperiment.install() - Pixel.setUp(store: self.store) { _ in - XCTFail("shouldn‘t fire pixels") - } + PixelKit.setUp(appVersion: "", + defaultHeaders: [:], + defaults: UserDefaults()) { _, _, _, _, _, _ in + XCTFail("shouldn‘t fire pixels") + } - PixelExperiment.fireEnrollmentPixel() + PixelExperiment.fireEnrollmentPixel() + } } // See git history for fireFirstSerpPixel and fireDay21To27SerpPixel tests diff --git a/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift b/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift index c5c389feac..1103a8f2eb 100644 --- a/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift +++ b/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift @@ -129,7 +129,7 @@ class WebsiteBreakageReportTests: XCTestCase { APIRequest.Headers.setUserAgent("") var params = parameters params["test"] = "1" - let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), + let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: GeneralPixel.brokenSiteReport.name), queryParameters: params, allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) return configuration.request