From 9f11a01f28ab7fe2ebc0e23b7a7968a38ce7543a Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 22 May 2024 15:22:46 -0700 Subject: [PATCH 01/24] Replace old survey classes. --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++---- .../HomePageRemoteMessagingRequest.swift | 32 +--- .../HomePageRemoteMessagingStorage.swift | 24 +-- .../DataBrokerProtectionRemoteMessage.swift | 71 ------- .../DataBrokerProtectionRemoteMessaging.swift | 180 ------------------ .../Model/HomePageContinueSetUpModel.swift | 112 ++--------- .../View/HomePageViewController.swift | 2 +- .../MainWindow/MainViewController.swift | 22 +-- .../NetworkProtectionDebugMenu.swift | 4 +- .../NetworkProtectionDebugUtilities.swift | 2 - ...essage.swift => SurveyRemoteMessage.swift} | 8 +- ...ging.swift => SurveyRemoteMessaging.swift} | 27 ++- .../HomePage/ContinueSetUpModelTests.swift | 86 ++------- .../SurveyRemoteMessagingTests.swift} | 66 ++----- .../NetworkProtectionRemoteMessageTests.swift | 6 +- .../network-protection-messages.json | 4 +- 16 files changed, 109 insertions(+), 593 deletions(-) delete mode 100644 DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessage.swift delete mode 100644 DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessaging.swift rename DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/{NetworkProtectionRemoteMessage.swift => SurveyRemoteMessage.swift} (90%) rename DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/{NetworkProtectionRemoteMessaging.swift => SurveyRemoteMessaging.swift} (84%) rename UnitTests/{NetworkProtection/NetworkProtectionRemoteMessagingTests.swift => HomePage/SurveyRemoteMessagingTests.swift} (81%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6caadc88f1..6ebc3ae8ef 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1136,10 +1136,6 @@ 4B37EE722B4CFEE400A89A61 /* SurveyURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5D2B4CFC3C00A89A61 /* SurveyURLBuilder.swift */; }; 4B37EE732B4CFF0800A89A61 /* HomePageRemoteMessagingStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5C2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift */; }; 4B37EE742B4CFF0A00A89A61 /* HomePageRemoteMessagingRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5E2B4CFC3C00A89A61 /* HomePageRemoteMessagingRequest.swift */; }; - 4B37EE752B4CFF3300A89A61 /* DataBrokerProtectionRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE672B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessaging.swift */; }; - 4B37EE762B4CFF3300A89A61 /* DataBrokerProtectionRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE672B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessaging.swift */; }; - 4B37EE772B4CFF3900A89A61 /* DataBrokerProtectionRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE662B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessage.swift */; }; - 4B37EE782B4CFF3900A89A61 /* DataBrokerProtectionRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE662B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessage.swift */; }; 4B39AAF627D9B2C700A73FD5 /* NSStackViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B39AAF527D9B2C700A73FD5 /* NSStackViewExtension.swift */; }; 4B3B8490297A0E1000A384BD /* EmailManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3B848F297A0E1000A384BD /* EmailManagerExtension.swift */; }; 4B3F641E27A8D3BD00E0C118 /* BrowserProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F641D27A8D3BD00E0C118 /* BrowserProfileTests.swift */; }; @@ -1365,16 +1361,16 @@ 4BCBE4582BA7E17800FC75A1 /* SubscriptionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE4572BA7E17800FC75A1 /* SubscriptionUI */; }; 4BCBE45A2BA7E17800FC75A1 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE4592BA7E17800FC75A1 /* Subscription */; }; 4BCBE45C2BA7E18500FC75A1 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE45B2BA7E18500FC75A1 /* Subscription */; }; - 4BCF15D72ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */; }; - 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift */; }; + 4BCF15D72ABB8A110083F6DF /* SurveyRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */; }; + 4BCF15D92ABB8A7F0083F6DF /* SurveyRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */; }; 4BCF15EC2ABB9AF80083F6DF /* NetworkProtectionRemoteMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */; }; 4BCF15ED2ABB9B180083F6DF /* network-protection-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */; }; - 4BCF15EE2ABBDBFD0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift */; }; - 4BCF15EF2ABBDBFF0083F6DF /* NetworkProtectionRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */; }; + 4BCF15EE2ABBDBFD0083F6DF /* SurveyRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */; }; + 4BCF15EF2ABBDBFF0083F6DF /* SurveyRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */; }; 4BD18F01283F0BC500058124 /* BookmarksBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */; }; 4BD18F05283F151F00058124 /* BookmarksBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */; }; - 4BD57C042AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD57C032AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift */; }; - 4BD57C052AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD57C032AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift */; }; + 4BD57C042AC112DF00B580EE /* SurveyRemoteMessagingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD57C032AC112DF00B580EE /* SurveyRemoteMessagingTests.swift */; }; + 4BD57C052AC112DF00B580EE /* SurveyRemoteMessagingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD57C032AC112DF00B580EE /* SurveyRemoteMessagingTests.swift */; }; 4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */; }; 4BE344EE2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */; }; 4BE344EF2B23786F003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */; }; @@ -3078,8 +3074,6 @@ 4B37EE5C2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageRemoteMessagingStorage.swift; sourceTree = ""; }; 4B37EE5D2B4CFC3C00A89A61 /* SurveyURLBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurveyURLBuilder.swift; sourceTree = ""; }; 4B37EE5E2B4CFC3C00A89A61 /* HomePageRemoteMessagingRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageRemoteMessagingRequest.swift; sourceTree = ""; }; - 4B37EE662B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionRemoteMessage.swift; sourceTree = ""; }; - 4B37EE672B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessaging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionRemoteMessaging.swift; sourceTree = ""; }; 4B37EE6E2B4CFE8500A89A61 /* dbp-messages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "dbp-messages.json"; sourceTree = ""; }; 4B39AAF527D9B2C700A73FD5 /* NSStackViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSStackViewExtension.swift; sourceTree = ""; }; 4B3B848F297A0E1000A384BD /* EmailManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailManagerExtension.swift; sourceTree = ""; }; @@ -3258,13 +3252,13 @@ 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 = ""; }; - 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessaging.swift; sourceTree = ""; }; - 4BCF15D82ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessage.swift; sourceTree = ""; }; + 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessaging.swift; sourceTree = ""; }; + 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessage.swift; sourceTree = ""; }; 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessageTests.swift; sourceTree = ""; }; 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "network-protection-messages.json"; sourceTree = ""; }; 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewController.swift; sourceTree = ""; }; 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BookmarksBar.storyboard; sourceTree = ""; }; - 4BD57C032AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessagingTests.swift; sourceTree = ""; }; + 4BD57C032AC112DF00B580EE /* SurveyRemoteMessagingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessagingTests.swift; sourceTree = ""; }; 4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableScrollView.swift; sourceTree = ""; }; 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModelTests.swift; sourceTree = ""; }; 4BE4005227CF3DC3007D3161 /* SavePaymentMethodPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePaymentMethodPopover.swift; sourceTree = ""; }; @@ -4594,7 +4588,6 @@ 31AA6B962B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift */, BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */, BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */, - 4B37EE652B4CFC9500A89A61 /* RemoteMessaging */, 316913252BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift */, ); path = DBP; @@ -5030,15 +5023,6 @@ path = Surveys; sourceTree = ""; }; - 4B37EE652B4CFC9500A89A61 /* RemoteMessaging */ = { - isa = PBXGroup; - children = ( - 4B37EE662B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessage.swift */, - 4B37EE672B4CFC9500A89A61 /* DataBrokerProtectionRemoteMessaging.swift */, - ); - path = RemoteMessaging; - sourceTree = ""; - }; 4B41EDAC2B168A66001EEDF4 /* VPNFeedbackForm */ = { isa = PBXGroup; children = ( @@ -5694,8 +5678,8 @@ 4BCF15D52ABB83D70083F6DF /* NetworkProtectionRemoteMessaging */ = { isa = PBXGroup; children = ( - 4BCF15D82ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift */, - 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */, + 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */, + 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */, ); path = NetworkProtectionRemoteMessaging; sourceTree = ""; @@ -5706,7 +5690,6 @@ BDA7648F2BC4E56200D0400C /* Mocks */, 4BCF15E62ABB98A20083F6DF /* Resources */, 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */, - 4BD57C032AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift */, 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */, BDA7648C2BC4E4EF00D0400C /* DefaultVPNLocationFormatterTests.swift */, 7B4C5CF42BE51D640007A164 /* VPNUninstallerTests.swift */, @@ -5754,6 +5737,7 @@ 569277C329DEE09D00B633EF /* ContinueSetUpModelTests.swift */, 56D145ED29E6DAD900E3488A /* DataImportProviderTests.swift */, 560C3FFB2BC9911000F589CE /* PermanentSurveyManagerTests.swift */, + 4BD57C032AC112DF00B580EE /* SurveyRemoteMessagingTests.swift */, ); path = HomePage; sourceTree = ""; @@ -9845,7 +9829,6 @@ 3706FB7E293F65D500E42796 /* FireCoordinator.swift in Sources */, 3706FB7F293F65D500E42796 /* GeolocationProvider.swift in Sources */, B603FD9F2A02712E00F3FCA9 /* CIImageExtension.swift in Sources */, - 4B37EE762B4CFF3300A89A61 /* DataBrokerProtectionRemoteMessaging.swift in Sources */, 3706FB80293F65D500E42796 /* NSAlert+ActiveDownloadsTermination.swift in Sources */, B677FC552B064A9C0099EB04 /* DataImportViewModel.swift in Sources */, D64A5FF92AEA5C2B00B6D6E7 /* HomeButtonMenuFactory.swift in Sources */, @@ -9942,7 +9925,7 @@ 3706FBB6293F65D500E42796 /* ChromiumPreferences.swift in Sources */, 3706FBB7293F65D500E42796 /* FirePopoverViewController.swift in Sources */, 3706FBB8293F65D500E42796 /* SavePaymentMethodPopover.swift in Sources */, - 4BCF15EF2ABBDBFF0083F6DF /* NetworkProtectionRemoteMessaging.swift in Sources */, + 4BCF15EF2ABBDBFF0083F6DF /* SurveyRemoteMessaging.swift in Sources */, B6CC266D2BAD9CD800F53F8D /* FileProgressPresenter.swift in Sources */, 3706FBB9293F65D500E42796 /* FindInPageViewController.swift in Sources */, 3706FBBA293F65D500E42796 /* Cryptography.swift in Sources */, @@ -10092,7 +10075,6 @@ 1DC669712B6CF0D700AA0645 /* TabSnapshotStore.swift in Sources */, 3706FC19293F65D500E42796 /* NSNotificationName+Favicons.swift in Sources */, 3706FC1A293F65D500E42796 /* PinningManager.swift in Sources */, - 4B37EE782B4CFF3900A89A61 /* DataBrokerProtectionRemoteMessage.swift in Sources */, 7BBA7CE72BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, 3706FC1B293F65D500E42796 /* TabCollectionViewModel+NSSecureCoding.swift in Sources */, 3706FC1D293F65D500E42796 /* EmailManagerRequestDelegate.swift in Sources */, @@ -10255,7 +10237,7 @@ B6BCC53C2AFD15DF002C5499 /* DataImportProfilePicker.swift in Sources */, 3706FC8A293F65D500E42796 /* AutoconsentUserScript.swift in Sources */, 3706FC8B293F65D500E42796 /* BookmarksExporter.swift in Sources */, - 4BCF15EE2ABBDBFD0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, + 4BCF15EE2ABBDBFD0083F6DF /* SurveyRemoteMessage.swift in Sources */, 3706FC8C293F65D500E42796 /* FirefoxDataImporter.swift in Sources */, 3706FC8D293F65D500E42796 /* PreferencesGeneralView.swift in Sources */, 37197EA92942443D00394917 /* WebView.swift in Sources */, @@ -10535,7 +10517,7 @@ 3706FE71293F661700E42796 /* SavedStateMock.swift in Sources */, 3706FE72293F661700E42796 /* ClickToLoadTDSTests.swift in Sources */, 3706FE73293F661700E42796 /* PermissionManagerMock.swift in Sources */, - 4BD57C052AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift in Sources */, + 4BD57C052AC112DF00B580EE /* SurveyRemoteMessagingTests.swift in Sources */, 3706FE74293F661700E42796 /* WebsiteDataStoreMock.swift in Sources */, 3706FE75293F661700E42796 /* WebsiteBreakageReportTests.swift in Sources */, 56D145EF29E6DAD900E3488A /* DataImportProviderTests.swift in Sources */, @@ -11127,7 +11109,6 @@ 4BBC16A227C485BC00E00A38 /* DeviceIdleStateDetector.swift in Sources */, 4B379C2427BDE1B0008A968E /* FlatButton.swift in Sources */, 37054FC92873301700033B6F /* PinnedTabView.swift in Sources */, - 4B37EE772B4CFF3900A89A61 /* DataBrokerProtectionRemoteMessage.swift in Sources */, 4BA1A6A0258B079600F6F690 /* DataEncryption.swift in Sources */, B6FA8941269C425400588ECD /* PrivacyDashboardPopover.swift in Sources */, B626A76D29928B1600053070 /* TestsClosureNavigationResponder.swift in Sources */, @@ -11222,7 +11203,6 @@ B66260E029AC6EBD00E9E3EE /* HistoryTabExtension.swift in Sources */, B6BCC54F2AFE4F7D002C5499 /* DataImportTypePicker.swift in Sources */, AAEEC6A927088ADB008445F7 /* FireCoordinator.swift in Sources */, - 4B37EE752B4CFF3300A89A61 /* DataBrokerProtectionRemoteMessaging.swift in Sources */, 9F6434682BEC9A5F00D2D8A0 /* SubscriptionRedirectManager.swift in Sources */, B655369B268442EE00085A79 /* GeolocationProvider.swift in Sources */, B6C0B23C26E87D900031CB7F /* NSAlert+ActiveDownloadsTermination.swift in Sources */, @@ -11403,7 +11383,7 @@ 9F9C49FD2BC7E9830099738D /* BookmarkAllTabsDialogViewModel.swift in Sources */, 4B9292A226670D2A00AD2C21 /* PseudoFolder.swift in Sources */, 1DDD3EC42B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */, - 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, + 4BCF15D92ABB8A7F0083F6DF /* SurveyRemoteMessage.swift in Sources */, 4B05265E2B1AE5C70054955A /* VPNMetadataCollector.swift in Sources */, 4B9DB0292A983B24000927DB /* WaitlistStorage.swift in Sources */, AA2CB1352587C29500AA6FBE /* TabBarFooter.swift in Sources */, @@ -11427,7 +11407,7 @@ 85707F31276A7DCA00DC0649 /* OnboardingViewModel.swift in Sources */, 85AC3B0525D6B1D800C7D2AA /* ScriptSourceProviding.swift in Sources */, 4BB99D0026FE191E001E4761 /* CoreDataBookmarkImporter.swift in Sources */, - 4BCF15D72ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift in Sources */, + 4BCF15D72ABB8A110083F6DF /* SurveyRemoteMessaging.swift in Sources */, C168B9AC2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */, 9FA173E72B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */, D64A5FF82AEA5C2B00B6D6E7 /* HomeButtonMenuFactory.swift in Sources */, @@ -11668,7 +11648,7 @@ 9833913327AAAEEE00DAF119 /* EmbeddedTrackerDataTests.swift in Sources */, 3776583127F8325B009A6B35 /* AutofillPreferencesTests.swift in Sources */, B67C6C472654C643006C872E /* FileManagerExtensionTests.swift in Sources */, - 4BD57C042AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift in Sources */, + 4BD57C042AC112DF00B580EE /* SurveyRemoteMessagingTests.swift in Sources */, B69B50482726C5C200758A2B /* StatisticsLoaderTests.swift in Sources */, 9F6434702BECBA2800D2D8A0 /* SubscriptionRedirectManagerTests.swift in Sources */, 56BA1E872BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift in Sources */, diff --git a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift index 823ee28e4b..4433e395e2 100644 --- a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift +++ b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift @@ -27,26 +27,14 @@ protocol HomePageRemoteMessagingRequest { final class DefaultHomePageRemoteMessagingRequest: HomePageRemoteMessagingRequest { - enum NetworkProtectionEndpoint { + enum SurveysEndpoint { case debug case production var url: URL { switch self { - case .debug: return URL(string: "https://staticcdn.duckduckgo.com/macos-desktop-browser/network-protection/messages-v2-debug.json")! - case .production: return URL(string: "https://staticcdn.duckduckgo.com/macos-desktop-browser/network-protection/messages-v2.json")! - } - } - } - - enum DataBrokerProtectionEndpoint { - case debug - case production - - var url: URL { - switch self { - case .debug: return URL(string: "https://staticcdn.duckduckgo.com/macos-desktop-browser/dbp/messages-debug.json")! - case .production: return URL(string: "https://staticcdn.duckduckgo.com/macos-desktop-browser/dbp/messages.json")! + case .debug: return URL(string: "https://staticcdn.duckduckgo.com/macos-desktop-browser/surveys/surveys-debug.json")! + case .production: return URL(string: "https://staticcdn.duckduckgo.com/macos-desktop-browser/surveys/surveys.json")! } } } @@ -56,19 +44,11 @@ final class DefaultHomePageRemoteMessagingRequest: HomePageRemoteMessagingReques case requestCompletedWithoutErrorOrResponse } - static func networkProtectionMessagesRequest() -> HomePageRemoteMessagingRequest { -#if DEBUG || REVIEW - return DefaultHomePageRemoteMessagingRequest(endpointURL: NetworkProtectionEndpoint.debug.url) -#else - return DefaultHomePageRemoteMessagingRequest(endpointURL: NetworkProtectionEndpoint.production.url) -#endif - } - - static func dataBrokerProtectionMessagesRequest() -> HomePageRemoteMessagingRequest { + static func surveysRequest() -> HomePageRemoteMessagingRequest { #if DEBUG || REVIEW - return DefaultHomePageRemoteMessagingRequest(endpointURL: DataBrokerProtectionEndpoint.debug.url) + return DefaultHomePageRemoteMessagingRequest(endpointURL: SurveysEndpoint.debug.url) #else - return DefaultHomePageRemoteMessagingRequest(endpointURL: DataBrokerProtectionEndpoint.production.url) + return DefaultHomePageRemoteMessagingRequest(endpointURL: SurveysEndpoint.production.url) #endif } diff --git a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift index d9b1fd01ab..a2f3a7262b 100644 --- a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift +++ b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift @@ -30,14 +30,9 @@ protocol HomePageRemoteMessagingStorage { final class DefaultHomePageRemoteMessagingStorage: HomePageRemoteMessagingStorage { - enum NetworkProtectionConstants { - static let dismissedMessageIdentifiersKey = "home.page.network-protection.dismissed-message-identifiers" - static let networkProtectionMessagesFileName = "network-protection-messages.json" - } - - enum DataBrokerProtectionConstants { - static let dismissedMessageIdentifiersKey = "home.page.dbp.dismissed-message-identifiers" - static let networkProtectionMessagesFileName = "dbp-messages.json" + enum SurveyConstants { + static let dismissedMessageIdentifiersKey = "home.page.survey.dismissed-message-identifiers" + static let networkProtectionMessagesFileName = "survey-messages.json" } private let userDefaults: UserDefaults @@ -48,17 +43,10 @@ final class DefaultHomePageRemoteMessagingStorage: HomePageRemoteMessagingStorag URL.sandboxApplicationSupportURL } - static func networkProtection() -> DefaultHomePageRemoteMessagingStorage { - return DefaultHomePageRemoteMessagingStorage( - messagesFileName: NetworkProtectionConstants.networkProtectionMessagesFileName, - dismissedMessageIdentifiersKey: NetworkProtectionConstants.dismissedMessageIdentifiersKey - ) - } - - static func dataBrokerProtection() -> DefaultHomePageRemoteMessagingStorage { + static func surveys() -> DefaultHomePageRemoteMessagingStorage { return DefaultHomePageRemoteMessagingStorage( - messagesFileName: DataBrokerProtectionConstants.networkProtectionMessagesFileName, - dismissedMessageIdentifiersKey: DataBrokerProtectionConstants.dismissedMessageIdentifiersKey + messagesFileName: SurveyConstants.networkProtectionMessagesFileName, + dismissedMessageIdentifiersKey: SurveyConstants.dismissedMessageIdentifiersKey ) } diff --git a/DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessage.swift b/DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessage.swift deleted file mode 100644 index a49c997ca0..0000000000 --- a/DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessage.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// DataBrokerProtectionRemoteMessage.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 - -struct DataBrokerRemoteMessageAction: Codable, Equatable, Hashable { - enum Action: String, Codable { - case openDataBrokerProtection - case openSurveyURL - case openURL - } - - let actionTitle: String - let actionType: Action? - let actionURL: String? -} - -struct DataBrokerProtectionRemoteMessage: Codable, Equatable, Hashable { - - let id: String - let cardTitle: String - let cardDescription: String - /// If this is set, the message won't be displayed if DBP hasn't been used, even if the usage and access booleans are false - let daysSinceDataBrokerProtectionEnabled: Int? - let requiresDataBrokerProtectionUsage: Bool - let requiresDataBrokerProtectionAccess: Bool - let action: DataBrokerRemoteMessageAction - - func presentableSurveyURL( - statisticsStore: StatisticsStore = LocalStatisticsStore(), - activationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp), - operatingSystemVersion: String = ProcessInfo.processInfo.operatingSystemVersion.description, - appVersion: String = AppVersion.shared.versionNumber, - hardwareModel: String? = HardwareModel.model - ) -> URL? { - if let actionType = action.actionType, actionType == .openURL, let urlString = action.actionURL, let url = URL(string: urlString) { - return url - } - - guard let actionType = action.actionType, actionType == .openSurveyURL, let surveyURL = action.actionURL else { - return nil - } - - let surveyURLBuilder = SurveyURLBuilder( - statisticsStore: statisticsStore, - operatingSystemVersion: operatingSystemVersion, - appVersion: appVersion, - hardwareModel: hardwareModel, - daysSinceActivation: activationDateStore.daysSinceActivation(), - daysSinceLastActive: activationDateStore.daysSinceLastActive() - ) - - return surveyURLBuilder.buildSurveyURL(from: surveyURL) - } -} diff --git a/DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessaging.swift b/DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessaging.swift deleted file mode 100644 index b43994834b..0000000000 --- a/DuckDuckGo/DBP/RemoteMessaging/DataBrokerProtectionRemoteMessaging.swift +++ /dev/null @@ -1,180 +0,0 @@ -// -// DataBrokerProtectionRemoteMessaging.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 Networking -import PixelKit - -#if DBP - -protocol DataBrokerProtectionRemoteMessaging { - - func fetchRemoteMessages(completion: (() -> Void)?) - func presentableRemoteMessages() -> [DataBrokerProtectionRemoteMessage] - func dismiss(message: DataBrokerProtectionRemoteMessage) - -} - -final class DefaultDataBrokerProtectionRemoteMessaging: DataBrokerProtectionRemoteMessaging { - - enum Constants { - static let lastRefreshDateKey = "data-broker-protection.remote-messaging.last-refresh-date" - } - - private let messageRequest: HomePageRemoteMessagingRequest - private let messageStorage: HomePageRemoteMessagingStorage - private let waitlistStorage: WaitlistStorage - private let waitlistActivationDateStore: WaitlistActivationDateStore - private let dataBrokerProtectionVisibility: DataBrokerProtectionFeatureVisibility - private let minimumRefreshInterval: TimeInterval - private let userDefaults: UserDefaults - - convenience init() { - #if DEBUG || REVIEW - self.init(minimumRefreshInterval: .seconds(30)) - #else - self.init(minimumRefreshInterval: .hours(1)) - #endif - } - - init( - messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.dataBrokerProtectionMessagesRequest(), - messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.dataBrokerProtection(), - waitlistStorage: WaitlistStorage = WaitlistKeychainStore(waitlistIdentifier: "dbp", keychainAppGroup: Bundle.main.appGroup(bundle: .dbp)), - waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp), - dataBrokerProtectionVisibility: DataBrokerProtectionFeatureVisibility = DefaultDataBrokerProtectionFeatureVisibility(), - minimumRefreshInterval: TimeInterval, - userDefaults: UserDefaults = .standard - ) { - self.messageRequest = messageRequest - self.messageStorage = messageStorage - self.waitlistStorage = waitlistStorage - self.waitlistActivationDateStore = waitlistActivationDateStore - self.dataBrokerProtectionVisibility = dataBrokerProtectionVisibility - self.minimumRefreshInterval = minimumRefreshInterval - self.userDefaults = userDefaults - } - - func fetchRemoteMessages(completion fetchCompletion: (() -> Void)? = nil) { - if let lastRefreshDate = lastRefreshDate(), lastRefreshDate.addingTimeInterval(minimumRefreshInterval) > Date() { - fetchCompletion?() - return - } - - self.messageRequest.fetchHomePageRemoteMessages { [weak self] result in - defer { - fetchCompletion?() - } - - guard let self else { return } - - // Cast the generic parameter to a concrete type: - let result: Result<[DataBrokerProtectionRemoteMessage], Error> = result - - switch result { - case .success(let messages): - do { - try self.messageStorage.store(messages: messages) - self.updateLastRefreshDate() // Update last refresh date on success, otherwise let the app try again next time - } catch { - PixelKit.fire(DebugEvent(GeneralPixel.dataBrokerProtectionRemoteMessageStorageFailed, error: error)) - } - case .failure(let error): - // Ignore 403 errors, those happen when a file can't be found on S3 - if case APIRequest.Error.invalidStatusCode(403) = error { - self.updateLastRefreshDate() // Avoid refreshing constantly when the file isn't available - return - } - - PixelKit.fire(DebugEvent(GeneralPixel.dataBrokerProtectionRemoteMessageFetchingFailed, error: error)) - } - } - } - - /// Uses the "days since DBP activated" count combined with the set of dismissed messages to determine which messages should be displayed to the user. - func presentableRemoteMessages() -> [DataBrokerProtectionRemoteMessage] { - let dismissedMessageIDs = messageStorage.dismissedMessageIDs() - let possibleMessages: [DataBrokerProtectionRemoteMessage] = messageStorage.storedMessages() - - // Only show messages that haven't been dismissed, and check whether they have a requirement on how long the user has used DBP for. - let filteredMessages = possibleMessages.filter { message in - - // Don't show messages that have already been dismissed. If you need to show the same message to a user again, - // it should get a new message ID. - if dismissedMessageIDs.contains(message.id) { - return false - } - - // First, check messages that require a number of days of DBP usage - if let requiredDaysSinceActivation = message.daysSinceDataBrokerProtectionEnabled, - let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation() { - if requiredDaysSinceActivation <= daysSinceActivation { - return true - } else { - return false - } - } - - // Next, check if the message requires access to DBP but it's not visible: - if message.requiresDataBrokerProtectionAccess, !dataBrokerProtectionVisibility.isFeatureVisible() { - return false - } - - // Finally, check if the message requires DBP usage, and check if the user has used it at all: - if message.requiresDataBrokerProtectionUsage, waitlistActivationDateStore.daysSinceActivation() == nil { - return false - } - - return true - - } - - return filteredMessages - } - - func dismiss(message: DataBrokerProtectionRemoteMessage) { - messageStorage.dismissRemoteMessage(with: message.id) - } - - func resetLastRefreshTimestamp() { - userDefaults.removeObject(forKey: Constants.lastRefreshDateKey) - } - - // MARK: - Private - - private func lastRefreshDate() -> Date? { - guard let object = userDefaults.object(forKey: Constants.lastRefreshDateKey) else { - return nil - } - - guard let date = object as? Date else { - assertionFailure("Got rate limited date, but couldn't convert it to Date") - userDefaults.removeObject(forKey: Constants.lastRefreshDateKey) - return nil - } - - return date - } - - private func updateLastRefreshDate() { - userDefaults.setValue(Date(), forKey: Constants.lastRefreshDateKey) - } - -} - -#endif diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index 22ddf43e7e..e102a8534f 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -39,7 +39,7 @@ extension HomePage.Models { let gridWidth = FeaturesGridDimensions.width let deleteActionTitle = UserText.newTabSetUpRemoveItemAction let privacyConfigurationManager: PrivacyConfigurationManaging - let homePageRemoteMessaging: HomePageRemoteMessaging + let surveyRemoteMessaging: SurveyRemoteMessaging let permanentSurveyManager: SurveyManager var duckPlayerURL: String { @@ -109,7 +109,7 @@ extension HomePage.Models { tabCollectionViewModel: TabCollectionViewModel, emailManager: EmailManager = EmailManager(), duckPlayerPreferences: DuckPlayerPreferencesPersistor, - homePageRemoteMessaging: HomePageRemoteMessaging, + surveyRemoteMessaging: SurveyRemoteMessaging, privacyConfigurationManager: PrivacyConfigurationManaging = AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager, permanentSurveyManager: SurveyManager = PermanentSurveyManager()) { self.defaultBrowserProvider = defaultBrowserProvider @@ -118,7 +118,7 @@ extension HomePage.Models { self.tabCollectionViewModel = tabCollectionViewModel self.emailManager = emailManager self.duckPlayerPreferences = duckPlayerPreferences - self.homePageRemoteMessaging = homePageRemoteMessaging + self.surveyRemoteMessaging = surveyRemoteMessaging self.privacyConfigurationManager = privacyConfigurationManager self.permanentSurveyManager = permanentSurveyManager @@ -142,9 +142,7 @@ extension HomePage.Models { performEmailProtectionAction() case .permanentSurvey: visitSurvey() - case .networkProtectionRemoteMessage(let message): - handle(remoteMessage: message) - case .dataBrokerProtectionRemoteMessage(let message): + case .surveyRemoteMessage(let message): handle(remoteMessage: message) case .dataBrokerProtectionWaitlistInvited: performDataBrokerProtectionWaitlistInvitedAction() @@ -205,14 +203,9 @@ extension HomePage.Models { shouldShowEmailProtectionSetting = false case .permanentSurvey: shouldShowPermanentSurvey = false - case .networkProtectionRemoteMessage(let message): - homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: message) + case .surveyRemoteMessage(let message): + surveyRemoteMessaging.dismiss(message: message) PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: message.id)) - case .dataBrokerProtectionRemoteMessage(let message): -#if DBP - homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: message) - PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDismissed(messageID: message.id)) -#endif case .dataBrokerProtectionWaitlistInvited: shouldShowDBPWaitlistInvitedCardUI = false } @@ -221,20 +214,16 @@ extension HomePage.Models { func refreshFeaturesMatrix() { var features: [FeatureType] = [] -#if DBP + if shouldDBPWaitlistCardBeVisible { features.append(.dataBrokerProtectionWaitlistInvited) } - for message in homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.presentableRemoteMessages() { - features.append(.dataBrokerProtectionRemoteMessage(message)) + for message in surveyRemoteMessaging.presentableRemoteMessages() { + features.append(.surveyRemoteMessage(message)) PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .daily) } -#endif - for message in homePageRemoteMessaging.networkProtectionRemoteMessaging.presentableRemoteMessages() { - PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .daily) - } appendFeatureCards(&features) featuresMatrix = features.chunked(into: itemsPerRow) @@ -260,8 +249,7 @@ extension HomePage.Models { return shouldEmailProtectionCardBeVisible case .permanentSurvey: return shouldPermanentSurveyBeVisible - case .networkProtectionRemoteMessage, - .dataBrokerProtectionRemoteMessage, + case .surveyRemoteMessage, .dataBrokerProtectionWaitlistInvited: return false // These are handled separately } @@ -360,10 +348,10 @@ extension HomePage.Models { shouldShowPermanentSurvey = false } - @MainActor private func handle(remoteMessage: NetworkProtectionRemoteMessage) { + @MainActor private func handle(remoteMessage: SurveyRemoteMessage) { guard let actionType = remoteMessage.action.actionType else { PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) - homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: remoteMessage) + surveyRemoteMessaging.dismiss(message: remoteMessage) refreshFeaturesMatrix() return } @@ -378,37 +366,12 @@ extension HomePage.Models { 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) + surveyRemoteMessaging.dismiss(message: remoteMessage) refreshFeaturesMatrix() } } } - @MainActor private func handle(remoteMessage: DataBrokerProtectionRemoteMessage) { -#if DBP - guard let actionType = remoteMessage.action.actionType else { - PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) - homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: remoteMessage) - refreshFeaturesMatrix() - return - } - - switch actionType { - case .openDataBrokerProtection: - break // Not used currently - case .openSurveyURL, .openURL: - if let surveyURL = remoteMessage.presentableSurveyURL() { - let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) - tabCollectionViewModel.append(tab: tab) - 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) - refreshFeaturesMatrix() - } - } -#endif - } } // MARK: Feature Type @@ -431,8 +394,7 @@ extension HomePage.Models { case dock case importBookmarksAndPasswords case permanentSurvey - case networkProtectionRemoteMessage(NetworkProtectionRemoteMessage) - case dataBrokerProtectionRemoteMessage(DataBrokerProtectionRemoteMessage) + case surveyRemoteMessage(SurveyRemoteMessage) case dataBrokerProtectionWaitlistInvited var title: String { @@ -449,9 +411,7 @@ extension HomePage.Models { return UserText.newTabSetUpEmailProtectionCardTitle case .permanentSurvey: return PermanentSurveyManager.title - case .networkProtectionRemoteMessage(let message): - return message.cardTitle - case .dataBrokerProtectionRemoteMessage(let message): + case .surveyRemoteMessage(let message): return message.cardTitle case .dataBrokerProtectionWaitlistInvited: return "Personal Information Removal" @@ -472,9 +432,7 @@ extension HomePage.Models { return UserText.newTabSetUpEmailProtectionSummary case .permanentSurvey: return PermanentSurveyManager.body - case .networkProtectionRemoteMessage(let message): - return message.cardDescription - case .dataBrokerProtectionRemoteMessage(let message): + case .surveyRemoteMessage(let message): return message.cardDescription case .dataBrokerProtectionWaitlistInvited: return "You're invited to try Personal Information Removal beta!" @@ -495,9 +453,7 @@ extension HomePage.Models { return UserText.newTabSetUpEmailProtectionAction case .permanentSurvey: return PermanentSurveyManager.actionTitle - case .networkProtectionRemoteMessage(let message): - return message.action.actionTitle - case .dataBrokerProtectionRemoteMessage(let message): + case .surveyRemoteMessage(let message): return message.action.actionTitle case .dataBrokerProtectionWaitlistInvited: return "Get Started" @@ -529,10 +485,8 @@ extension HomePage.Models { return .inbox128.resized(to: iconSize)! case .permanentSurvey: return .survey128.resized(to: iconSize)! - case .networkProtectionRemoteMessage: - return .vpnEnded.resized(to: iconSize)! - case .dataBrokerProtectionRemoteMessage: - return .dbpInformationRemover.resized(to: iconSize)! + case .surveyRemoteMessage: + return .survey128.resized(to: iconSize)! case .dataBrokerProtectionWaitlistInvited: return .dbpInformationRemover.resized(to: iconSize)! } @@ -555,34 +509,6 @@ extension HomePage.Models { // MARK: - Remote Messaging -struct HomePageRemoteMessaging { - - static func defaultMessaging() -> HomePageRemoteMessaging { -#if DBP - return HomePageRemoteMessaging( - networkProtectionRemoteMessaging: DefaultNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: .netP, - dataBrokerProtectionRemoteMessaging: DefaultDataBrokerProtectionRemoteMessaging(), - dataBrokerProtectionUserDefaults: .dbp - ) -#else - return HomePageRemoteMessaging( - networkProtectionRemoteMessaging: DefaultNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: .netP - ) -#endif - } - - let networkProtectionRemoteMessaging: NetworkProtectionRemoteMessaging - let networkProtectionUserDefaults: UserDefaults - -#if DBP - let dataBrokerProtectionRemoteMessaging: DataBrokerProtectionRemoteMessaging - let dataBrokerProtectionUserDefaults: UserDefaults -#endif - -} - extension AppVersion { public var majorAndMinorOSVersion: String { let components = osVersion.split(separator: ".") diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index 092a33384c..ebcc1d66de 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -154,7 +154,7 @@ final class HomePageViewController: NSViewController { dataImportProvider: BookmarksAndPasswordsImportStatusProvider(), tabCollectionViewModel: tabCollectionViewModel, duckPlayerPreferences: DuckPlayerPreferencesUserDefaultsPersistor(), - homePageRemoteMessaging: .defaultMessaging() + surveyRemoteMessaging: DefaultSurveyRemoteMessaging() ) } diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 113e525dca..736f5387fe 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -192,13 +192,7 @@ final class MainViewController: NSViewController { updateReloadMenuItem() updateStopMenuItem() browserTabViewController.windowDidBecomeKey() - - refreshNetworkProtectionMessages() - -#if DBP - DataBrokerProtectionAppEvents().windowDidBecomeMain() - refreshDataBrokerProtectionMessages() -#endif + refreshSurveyMessages() } func windowDidResignKey() { @@ -220,20 +214,12 @@ final class MainViewController: NSViewController { } } - private let networkProtectionMessaging = DefaultNetworkProtectionRemoteMessaging() + private let surveyMessaging = DefaultSurveyRemoteMessaging() - func refreshNetworkProtectionMessages() { - networkProtectionMessaging.fetchRemoteMessages() + func refreshSurveyMessages() { + surveyMessaging.fetchRemoteMessages() } -#if DBP - private let dataBrokerProtectionMessaging = DefaultDataBrokerProtectionRemoteMessaging() - - func refreshDataBrokerProtectionMessages() { - dataBrokerProtectionMessaging.fetchRemoteMessages() - } -#endif - override func encodeRestorableState(with coder: NSCoder) { fatalError("Default AppKit State Restoration should not be used") } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 0323060f84..0af05155f5 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -486,8 +486,8 @@ final class NetworkProtectionDebugMenu: NSMenu { } @objc func resetNetworkProtectionRemoteMessages(_ sender: Any?) { - DefaultHomePageRemoteMessagingStorage.networkProtection().removeStoredAndDismissedMessages() - DefaultNetworkProtectionRemoteMessaging(minimumRefreshInterval: 0).resetLastRefreshTimestamp() + DefaultHomePageRemoteMessagingStorage.surveys().removeStoredAndDismissedMessages() + DefaultSurveyRemoteMessaging(minimumRefreshInterval: 0).resetLastRefreshTimestamp() } @objc func overrideNetworkProtectionActivationDateToNow(_ sender: Any?) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift index cf810d9225..aa2c99a004 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift @@ -59,8 +59,6 @@ final class NetworkProtectionDebugUtilities { settings.resetToDefaults() - DefaultHomePageRemoteMessagingStorage.networkProtection().removeStoredAndDismissedMessages() - UserDefaults().removeObject(forKey: UserDefaultsWrapper.Key.networkProtectionTermsAndConditionsAccepted.rawValue) NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) UserDefaults.netP.networkProtectionEntitlementsExpired = false diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessage.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift similarity index 90% rename from DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessage.swift rename to DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift index 2f5f6ce6fc..effc6049be 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessage.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionRemoteMessage.swift +// SurveyRemoteMessage.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -19,7 +19,7 @@ import Foundation import Common -struct NetworkProtectionRemoteMessageAction: Codable, Equatable, Hashable { +struct SurveyRemoteMessageAction: Codable, Equatable, Hashable { enum Action: String, Codable { case openNetworkProtection case openSurveyURL @@ -31,7 +31,7 @@ struct NetworkProtectionRemoteMessageAction: Codable, Equatable, Hashable { let actionURL: String? } -struct NetworkProtectionRemoteMessage: Codable, Equatable, Identifiable, Hashable { +struct SurveyRemoteMessage: Codable, Equatable, Identifiable, Hashable { let id: String let cardTitle: String @@ -40,7 +40,7 @@ struct NetworkProtectionRemoteMessage: Codable, Equatable, Identifiable, Hashabl let daysSinceNetworkProtectionEnabled: Int? let requiresNetworkProtectionUsage: Bool let requiresNetworkProtectionAccess: Bool - let action: NetworkProtectionRemoteMessageAction + let action: SurveyRemoteMessageAction func presentableSurveyURL( statisticsStore: StatisticsStore = LocalStatisticsStore(), diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift similarity index 84% rename from DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift rename to DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift index 6daeb90569..035b25d266 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionRemoteMessaging.swift +// SurveyRemoteMessaging.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,23 +20,22 @@ import Foundation import Networking import PixelKit -protocol NetworkProtectionRemoteMessaging { +protocol SurveyRemoteMessaging { func fetchRemoteMessages(completion: (() -> Void)?) - func presentableRemoteMessages() -> [NetworkProtectionRemoteMessage] - func dismiss(message: NetworkProtectionRemoteMessage) + func presentableRemoteMessages() -> [SurveyRemoteMessage] + func dismiss(message: SurveyRemoteMessage) } -final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMessaging { +final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { enum Constants { - static let lastRefreshDateKey = "network-protection.remote-messaging.last-refresh-date" + static let lastRefreshDateKey = "surveys.remote-messaging.last-refresh-date" } private let messageRequest: HomePageRemoteMessagingRequest private let messageStorage: HomePageRemoteMessagingStorage - private let waitlistStorage: WaitlistStorage private let waitlistActivationDateStore: WaitlistActivationDateStore private let networkProtectionVisibility: NetworkProtectionFeatureVisibility private let minimumRefreshInterval: TimeInterval @@ -51,9 +50,8 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess } init( - messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.networkProtectionMessagesRequest(), - messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.networkProtection(), - waitlistStorage: WaitlistStorage = WaitlistKeychainStore(waitlistIdentifier: "networkprotection", keychainAppGroup: Bundle.main.appGroup(bundle: .netP)), + messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.surveysRequest(), + messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.surveys(), waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), minimumRefreshInterval: TimeInterval, @@ -61,7 +59,6 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess ) { self.messageRequest = messageRequest self.messageStorage = messageStorage - self.waitlistStorage = waitlistStorage self.waitlistActivationDateStore = waitlistActivationDateStore self.networkProtectionVisibility = networkProtectionVisibility self.minimumRefreshInterval = minimumRefreshInterval @@ -83,7 +80,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess guard let self else { return } // Cast the generic parameter to a concrete type: - let result: Result<[NetworkProtectionRemoteMessage], Error> = result + let result: Result<[SurveyRemoteMessage], Error> = result switch result { case .success(let messages): @@ -107,9 +104,9 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess } /// Uses the "days since VPN activated" count combined with the set of dismissed messages to determine which messages should be displayed to the user. - func presentableRemoteMessages() -> [NetworkProtectionRemoteMessage] { + func presentableRemoteMessages() -> [SurveyRemoteMessage] { let dismissedMessageIDs = messageStorage.dismissedMessageIDs() - let possibleMessages: [NetworkProtectionRemoteMessage] = messageStorage.storedMessages() + let possibleMessages: [SurveyRemoteMessage] = messageStorage.storedMessages() // Only show messages that haven't been dismissed, and check whether they have a // requirement on how long the user has used the VPN for. @@ -148,7 +145,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess return filteredMessages } - func dismiss(message: NetworkProtectionRemoteMessage) { + func dismiss(message: SurveyRemoteMessage) { messageStorage.dismissRemoteMessage(with: message.id) } diff --git a/UnitTests/HomePage/ContinueSetUpModelTests.swift b/UnitTests/HomePage/ContinueSetUpModelTests.swift index 9d49ab4073..8f0370d6ed 100644 --- a/UnitTests/HomePage/ContinueSetUpModelTests.swift +++ b/UnitTests/HomePage/ContinueSetUpModelTests.swift @@ -21,42 +21,22 @@ import BrowserServicesKit import Common @testable import DuckDuckGo_Privacy_Browser -final class MockNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMessaging { +final class MockSurveyRemoteMessaging: SurveyRemoteMessaging { - var messages: [NetworkProtectionRemoteMessage] = [] + var messages: [SurveyRemoteMessage] = [] func fetchRemoteMessages(completion fetchCompletion: (() -> Void)? = nil) { fetchCompletion?() } - func presentableRemoteMessages() -> [NetworkProtectionRemoteMessage] { + func presentableRemoteMessages() -> [SurveyRemoteMessage] { messages } - func dismiss(message: NetworkProtectionRemoteMessage) {} + func dismiss(message: SurveyRemoteMessage) {} } -#if DBP - -final class MockDataBrokerProtectionRemoteMessaging: DataBrokerProtectionRemoteMessaging { - - var messages: [DataBrokerProtectionRemoteMessage] = [] - - func fetchRemoteMessages(completion fetchCompletion: (() -> Void)? = nil) { - fetchCompletion?() - } - - func presentableRemoteMessages() -> [DataBrokerProtectionRemoteMessage] { - messages - } - - func dismiss(message: DataBrokerProtectionRemoteMessage) {} - -} - -#endif - final class ContinueSetUpModelTests: XCTestCase { var vm: HomePage.Models.ContinueSetUpModel! @@ -70,6 +50,7 @@ final class ContinueSetUpModelTests: XCTestCase { var privacyConfigManager: MockPrivacyConfigurationManager! var randomNumberGenerator: MockRandomNumberGenerator! var dockCustomizer: DockCustomization! + var mockSurveyMessaging: SurveyRemoteMessaging! let userDefaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).\(NSApplication.runType)")! @MainActor override func setUp() { @@ -87,22 +68,9 @@ final class ContinueSetUpModelTests: XCTestCase { let config = MockPrivacyConfiguration() privacyConfigManager.privacyConfig = config randomNumberGenerator = MockRandomNumberGenerator() + mockSurveyMessaging = MockSurveyRemoteMessaging() dockCustomizer = DockCustomizerMock() -#if DBP - let messaging = HomePageRemoteMessaging( - networkProtectionRemoteMessaging: MockNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: userDefaults, - dataBrokerProtectionRemoteMessaging: MockDataBrokerProtectionRemoteMessaging(), - dataBrokerProtectionUserDefaults: userDefaults - ) -#else - let messaging = HomePageRemoteMessaging( - networkProtectionRemoteMessaging: MockNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: userDefaults - ) -#endif - vm = HomePage.Models.ContinueSetUpModel( defaultBrowserProvider: capturingDefaultBrowserProvider, dockCustomizer: dockCustomizer, @@ -110,7 +78,7 @@ final class ContinueSetUpModelTests: XCTestCase { tabCollectionViewModel: tabCollectionVM, emailManager: emailManager, duckPlayerPreferences: duckPlayerPreferences, - homePageRemoteMessaging: messaging, + surveyRemoteMessaging: mockSurveyMessaging, privacyConfigurationManager: privacyConfigManager, permanentSurveyManager: MockPermanentSurveyManager() ) @@ -154,7 +122,7 @@ final class ContinueSetUpModelTests: XCTestCase { tabCollectionViewModel: tabCollectionVM, emailManager: emailManager, duckPlayerPreferences: duckPlayerPreferences, - homePageRemoteMessaging: createMessaging(), + surveyRemoteMessaging: createMessaging(), permanentSurveyManager: MockPermanentSurveyManager() ) @@ -368,7 +336,7 @@ final class ContinueSetUpModelTests: XCTestCase { tabCollectionViewModel: tabCollectionVM, emailManager: emailManager, duckPlayerPreferences: duckPlayerPreferences, - homePageRemoteMessaging: createMessaging(), + surveyRemoteMessaging: createMessaging(), permanentSurveyManager: MockPermanentSurveyManager() ) @@ -467,7 +435,7 @@ final class ContinueSetUpModelTests: XCTestCase { XCTAssertFalse(vm2.visibleFeaturesMatrix.reduce([], +).contains(HomePage.Models.FeatureType.permanentSurvey)) } - @MainActor func testWhenAskedToPerformActionForPermanetShowsTheSurveySite() async { + @MainActor func testWhenAskedToPerformActionForPermanentShowsTheSurveySite() async { let expectedURL = URL(string: "someurl.com") let surveyManager = MockPermanentSurveyManager(isSurveyAvailable: true, url: expectedURL) userDefaults.set(true, forKey: UserDefaultsWrapper.Key.homePageShowPermanentSurvey.rawValue) @@ -478,7 +446,7 @@ final class ContinueSetUpModelTests: XCTestCase { tabCollectionViewModel: tabCollectionVM, emailManager: emailManager, duckPlayerPreferences: duckPlayerPreferences, - homePageRemoteMessaging: createMessaging(), + surveyRemoteMessaging: createMessaging(), privacyConfigurationManager: privacyConfigManager, permanentSurveyManager: surveyManager ) @@ -509,20 +477,8 @@ final class ContinueSetUpModelTests: XCTestCase { return features.chunked(into: HomePage.featuresPerRow) } - private func createMessaging() -> HomePageRemoteMessaging { -#if DBP - return HomePageRemoteMessaging( - networkProtectionRemoteMessaging: MockNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: userDefaults, - dataBrokerProtectionRemoteMessaging: MockDataBrokerProtectionRemoteMessaging(), - dataBrokerProtectionUserDefaults: userDefaults - ) -#else - return HomePageRemoteMessaging( - networkProtectionRemoteMessaging: MockNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: userDefaults - ) -#endif + private func createMessaging() -> SurveyRemoteMessaging { + MockSurveyRemoteMessaging() } @MainActor func test_WhenUserDoesntHaveApplicationInTheDock_ThenAddToDockCardIsDisplayed() { @@ -566,20 +522,6 @@ extension HomePage.Models.ContinueSetUpModel { let manager = MockPrivacyConfigurationManager() manager.privacyConfig = privacyConfig -#if DBP - let messaging = HomePageRemoteMessaging( - networkProtectionRemoteMessaging: MockNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: appGroupUserDefaults, - dataBrokerProtectionRemoteMessaging: MockDataBrokerProtectionRemoteMessaging(), - dataBrokerProtectionUserDefaults: appGroupUserDefaults - ) -#else - let messaging = HomePageRemoteMessaging( - networkProtectionRemoteMessaging: MockNetworkProtectionRemoteMessaging(), - networkProtectionUserDefaults: appGroupUserDefaults - ) -#endif - return HomePage.Models.ContinueSetUpModel( defaultBrowserProvider: defaultBrowserProvider, dockCustomizer: dockCustomizer, @@ -587,7 +529,7 @@ extension HomePage.Models.ContinueSetUpModel { tabCollectionViewModel: TabCollectionViewModel(), emailManager: emailManager, duckPlayerPreferences: duckPlayerPreferences, - homePageRemoteMessaging: messaging, + surveyRemoteMessaging: MockSurveyRemoteMessaging(), privacyConfigurationManager: manager, permanentSurveyManager: permanentSurveyManager) } diff --git a/UnitTests/NetworkProtection/NetworkProtectionRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift similarity index 81% rename from UnitTests/NetworkProtection/NetworkProtectionRemoteMessagingTests.swift rename to UnitTests/HomePage/SurveyRemoteMessagingTests.swift index 3f96a7d0ea..3d9b22f122 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionRemoteMessagingTests.swift +// SurveyRemoteMessagingTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -19,7 +19,7 @@ import XCTest @testable import DuckDuckGo_Privacy_Browser -final class NetworkProtectionRemoteMessagingTests: XCTestCase { +final class SurveyRemoteMessagingTests: XCTestCase { private var defaults: UserDefaults! private let testGroupName = "remote-messaging" @@ -33,22 +33,18 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() request.result = .success([]) let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) - XCTAssertTrue(!waitlistStorage.isWaitlistUser) - let expectation = expectation(description: "Remote Message Fetch") messaging.fetchRemoteMessages { @@ -63,26 +59,20 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { func testWhenFetchingRemoteMessages_AndTheUserDidSignUpViaWaitlist_ButUserHasNotActivatedNetP_ThenMessagesAreFetched() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) request.result = .success([]) - waitlistStorage.store(waitlistToken: "token") - waitlistStorage.store(waitlistTimestamp: 123) - waitlistStorage.store(inviteCode: "ABCD1234") - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) - XCTAssertTrue(waitlistStorage.isWaitlistUser) XCTAssertNil(activationDateStorage.daysSinceActivation()) let expectation = expectation(description: "Remote Message Fetch") @@ -99,29 +89,23 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ThenMessagesAreFetched_AndMessagesAreStored() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) let messages = [mockMessage(id: "123")] request.result = .success(messages) - waitlistStorage.store(waitlistToken: "token") - waitlistStorage.store(waitlistTimestamp: 123) - waitlistStorage.store(inviteCode: "ABCD1234") activationDateStorage._daysSinceActivation = 10 - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) - XCTAssertTrue(waitlistStorage.isWaitlistUser) XCTAssertEqual(storage.storedMessages(), []) XCTAssertNotNil(activationDateStorage.daysSinceActivation()) @@ -140,28 +124,22 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ButRateLimitedOperationCannotRunAgain_ThenMessagesAreNotFetched() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) - waitlistStorage.store(waitlistToken: "token") - waitlistStorage.store(waitlistTimestamp: 123) - waitlistStorage.store(inviteCode: "ABCD1234") activationDateStorage._daysSinceActivation = 10 - defaults.setValue(Date(), forKey: DefaultNetworkProtectionRemoteMessaging.Constants.lastRefreshDateKey) + defaults.setValue(Date(), forKey: DefaultSurveyRemoteMessaging.Constants.lastRefreshDateKey) - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: .days(7), // Use a large number to hit the refresh check userDefaults: defaults ) - XCTAssertTrue(waitlistStorage.isWaitlistUser) XCTAssertNotNil(activationDateStorage.daysSinceActivation()) let expectation = expectation(description: "Remote Message Fetch") @@ -179,7 +157,6 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesHaveBeenDismissed_ThenPresentableMessagesDoNotIncludeDismissedMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) @@ -188,10 +165,9 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { try? storage.store(messages: [dismissedMessage, activeMessage]) activationDateStorage._daysSinceActivation = 10 - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: 0, @@ -208,7 +184,6 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesRequireDaysActive_ThenPresentableMessagesDoNotIncludeInvalidMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) @@ -217,10 +192,9 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { try? storage.store(messages: [hiddenMessage, activeMessage]) activationDateStorage._daysSinceActivation = 5 - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: 0, @@ -234,17 +208,15 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesNetPVisibility_ThenPresentableMessagesDoNotIncludeInvalidMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: false) let hiddenMessage = mockMessage(id: "123", requiresNetPAccess: true) try? storage.store(messages: [hiddenMessage]) - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: 0, @@ -258,17 +230,15 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesRequireNetPUsage_ThenPresentableMessagesDoNotIncludeInvalidMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() - let waitlistStorage = MockWaitlistStorage() let activationDateStorage = MockWaitlistActivationDateStore() let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) let message = mockMessage(id: "123", requiresNetPUsage: false, requiresNetPAccess: true) try? storage.store(messages: [message]) - let messaging = DefaultNetworkProtectionRemoteMessaging( + let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - waitlistStorage: waitlistStorage, waitlistActivationDateStore: activationDateStorage, networkProtectionVisibility: visibility, minimumRefreshInterval: 0, @@ -282,7 +252,7 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { private func mockMessage(id: String, daysSinceNetworkProtectionEnabled: Int = 0, requiresNetPUsage: Bool = true, - requiresNetPAccess: Bool = true) -> NetworkProtectionRemoteMessage { + requiresNetPAccess: Bool = true) -> SurveyRemoteMessage { let remoteMessageJSON = """ { "id": "\(id)", @@ -299,7 +269,7 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { """ let decoder = JSONDecoder() - return try! decoder.decode(NetworkProtectionRemoteMessage.self, from: remoteMessageJSON.data(using: .utf8)!) + return try! decoder.decode(SurveyRemoteMessage.self, from: remoteMessageJSON.data(using: .utf8)!) } } @@ -308,7 +278,7 @@ final class NetworkProtectionRemoteMessagingTests: XCTestCase { private final class MockNetworkProtectionRemoteMessagingRequest: HomePageRemoteMessagingRequest { - var result: Result<[NetworkProtectionRemoteMessage], Error>! + var result: Result<[SurveyRemoteMessage], Error>! var didFetchMessages: Bool = false func fetchHomePageRemoteMessages(completion: @escaping (Result<[T], Error>) -> Void) where T: Decodable { @@ -325,19 +295,19 @@ private final class MockNetworkProtectionRemoteMessagingRequest: HomePageRemoteM private final class MockNetworkProtectionRemoteMessagingStorage: HomePageRemoteMessagingStorage { - var _storedMessages: [NetworkProtectionRemoteMessage] = [] + var _storedMessages: [SurveyRemoteMessage] = [] var _storedDismissedMessageIDs: [String] = [] - func store(messages: [NetworkProtectionRemoteMessage]) throws { + func store(messages: [SurveyRemoteMessage]) throws { self._storedMessages = messages } - func storedMessages() -> [NetworkProtectionRemoteMessage] { + func storedMessages() -> [SurveyRemoteMessage] { _storedMessages } func store(messages: [Message]) throws { - if let messages = messages as? [NetworkProtectionRemoteMessage] { + if let messages = messages as? [SurveyRemoteMessage] { self._storedMessages = messages } else { fatalError("Failed to cast messages") diff --git a/UnitTests/NetworkProtection/NetworkProtectionRemoteMessageTests.swift b/UnitTests/NetworkProtection/NetworkProtectionRemoteMessageTests.swift index 1a791b92e9..39e2afe8a2 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionRemoteMessageTests.swift +++ b/UnitTests/NetworkProtection/NetworkProtectionRemoteMessageTests.swift @@ -34,7 +34,7 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { let data = try Data(contentsOf: fileURL) let decoder = JSONDecoder() - let decodedMessages = try decoder.decode([NetworkProtectionRemoteMessage].self, from: data) + let decodedMessages = try decoder.decode([SurveyRemoteMessage].self, from: data) XCTAssertEqual(decodedMessages.count, 3) @@ -114,9 +114,9 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { """ let decoder = JSONDecoder() - let message: NetworkProtectionRemoteMessage + let message: SurveyRemoteMessage do { - message = try decoder.decode(NetworkProtectionRemoteMessage.self, from: remoteMessageJSON.data(using: .utf8)!) + message = try decoder.decode(SurveyRemoteMessage.self, from: remoteMessageJSON.data(using: .utf8)!) } catch { XCTFail("Failed to decode with error: \(error.localizedDescription)") return diff --git a/UnitTests/NetworkProtection/Resources/network-protection-messages.json b/UnitTests/NetworkProtection/Resources/network-protection-messages.json index d69450766f..adc9a34c3c 100644 --- a/UnitTests/NetworkProtection/Resources/network-protection-messages.json +++ b/UnitTests/NetworkProtection/Resources/network-protection-messages.json @@ -11,22 +11,22 @@ }, { "id": "456", - "daysSinceNetworkProtectionEnabled": 1, "cardTitle": "Title 2", "cardDescription": "Description 2", "requiresNetworkProtectionAccess": true, "requiresNetworkProtectionUsage": true, + "daysSinceNetworkProtectionEnabled": 1, "action": { "actionTitle": "Action 2" } }, { "id": "789", - "daysSinceNetworkProtectionEnabled": 5, "cardTitle": "Title 3", "cardDescription": "Description 3", "requiresNetworkProtectionAccess": true, "requiresNetworkProtectionUsage": true, + "daysSinceNetworkProtectionEnabled": 5, "action": { "actionTitle": "Action 3", "actionType": "openSurveyURL", From 26992a62c8bbcbd55335ef945e1dc70dc938a944 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 22 May 2024 19:35:20 -0700 Subject: [PATCH 02/24] Add new parameters. --- DuckDuckGo.xcodeproj/project.pbxproj | 14 +++++++---- .../Model/HomePageContinueSetUpModel.swift | 2 -- .../SurveyRemoteMessage.swift | 14 +++++++---- .../SurveyRemoteMessaging.swift | 23 +++---------------- .../InputFilesChecker/InputFilesChecker.swift | 5 +--- .../SurveyRemoteMessageTests.swift} | 15 ++++++++---- .../HomePage/SurveyRemoteMessagingTests.swift | 16 ------------- 7 files changed, 33 insertions(+), 56 deletions(-) rename UnitTests/{NetworkProtection/NetworkProtectionRemoteMessageTests.swift => HomePage/SurveyRemoteMessageTests.swift} (91%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6ebc3ae8ef..38a11fb43b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1352,6 +1352,9 @@ 4BBDEE9328FC14760092FAA6 /* ConnectBitwardenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBDEE8F28FC14760092FAA6 /* ConnectBitwardenViewModel.swift */; }; 4BBDEE9428FC14760092FAA6 /* ConnectBitwardenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBDEE9028FC14760092FAA6 /* ConnectBitwardenViewController.swift */; }; 4BBE0AA727B9B027003B37A8 /* PopUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE0AA627B9B027003B37A8 /* PopUpButton.swift */; }; + 4BBEE8DE2BFEDE3E00E5E111 /* SurveyRemoteMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15E42ABB98990083F6DF /* SurveyRemoteMessageTests.swift */; }; + 4BBEE8DF2BFEE07D00E5E111 /* network-protection-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */; }; + 4BBEE8E02BFEE08100E5E111 /* dbp-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B37EE6E2B4CFE8500A89A61 /* dbp-messages.json */; }; 4BBF0915282DD40100EE1418 /* TemporaryFileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0914282DD40100EE1418 /* TemporaryFileHandler.swift */; }; 4BBF0917282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0916282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift */; }; 4BBF09232830812900EE1418 /* FileSystemDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF09222830812900EE1418 /* FileSystemDSL.swift */; }; @@ -1363,7 +1366,7 @@ 4BCBE45C2BA7E18500FC75A1 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE45B2BA7E18500FC75A1 /* Subscription */; }; 4BCF15D72ABB8A110083F6DF /* SurveyRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */; }; 4BCF15D92ABB8A7F0083F6DF /* SurveyRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */; }; - 4BCF15EC2ABB9AF80083F6DF /* NetworkProtectionRemoteMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */; }; + 4BCF15EC2ABB9AF80083F6DF /* SurveyRemoteMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15E42ABB98990083F6DF /* SurveyRemoteMessageTests.swift */; }; 4BCF15ED2ABB9B180083F6DF /* network-protection-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */; }; 4BCF15EE2ABBDBFD0083F6DF /* SurveyRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */; }; 4BCF15EF2ABBDBFF0083F6DF /* SurveyRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */; }; @@ -3254,7 +3257,7 @@ 4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemDSLTests.swift; sourceTree = ""; }; 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessaging.swift; sourceTree = ""; }; 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessage.swift; sourceTree = ""; }; - 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessageTests.swift; sourceTree = ""; }; + 4BCF15E42ABB98990083F6DF /* SurveyRemoteMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessageTests.swift; sourceTree = ""; }; 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "network-protection-messages.json"; sourceTree = ""; }; 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewController.swift; sourceTree = ""; }; 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BookmarksBar.storyboard; sourceTree = ""; }; @@ -5689,7 +5692,6 @@ children = ( BDA7648F2BC4E56200D0400C /* Mocks */, 4BCF15E62ABB98A20083F6DF /* Resources */, - 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */, 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */, BDA7648C2BC4E4EF00D0400C /* DefaultVPNLocationFormatterTests.swift */, 7B4C5CF42BE51D640007A164 /* VPNUninstallerTests.swift */, @@ -5738,6 +5740,7 @@ 56D145ED29E6DAD900E3488A /* DataImportProviderTests.swift */, 560C3FFB2BC9911000F589CE /* PermanentSurveyManagerTests.swift */, 4BD57C032AC112DF00B580EE /* SurveyRemoteMessagingTests.swift */, + 4BCF15E42ABB98990083F6DF /* SurveyRemoteMessageTests.swift */, ); path = HomePage; sourceTree = ""; @@ -8955,8 +8958,10 @@ 3706FE8B293F661700E42796 /* empty in Resources */, 376E2D282942843D001CD31B /* privacy-reference-tests in Resources */, 3706FE8C293F661700E42796 /* atb-with-update.json in Resources */, + 4BBEE8E02BFEE08100E5E111 /* dbp-messages.json in Resources */, 9FBD84622BB3BC6400220859 /* Origin-empty.txt in Resources */, 3706FE8D293F661700E42796 /* DataImportResources in Resources */, + 4BBEE8DF2BFEE07D00E5E111 /* network-protection-messages.json in Resources */, 3706FE8E293F661700E42796 /* atb.json in Resources */, 9FBD845E2BB3B80300220859 /* Origin.txt in Resources */, 3706FE8F293F661700E42796 /* DuckDuckGo-ExampleCrash.ips in Resources */, @@ -10397,6 +10402,7 @@ B626A7652992506A00053070 /* SerpHeadersNavigationResponderTests.swift in Sources */, 9F6434712BECBA2800D2D8A0 /* SubscriptionRedirectManagerTests.swift in Sources */, 9F26060C2B85C20B00819292 /* AddEditBookmarkDialogViewModelTests.swift in Sources */, + 4BBEE8DE2BFEDE3E00E5E111 /* SurveyRemoteMessageTests.swift in Sources */, 562532A12BC069190034D316 /* ZoomPopoverViewModelTests.swift in Sources */, 3706FE28293F661700E42796 /* BookmarkTests.swift in Sources */, 3706FE29293F661700E42796 /* SuggestionContainerViewModelTests.swift in Sources */, @@ -11739,7 +11745,7 @@ AA91F83927076F1900771A0D /* PrivacyIconViewModelTests.swift in Sources */, 9F9C49F62BC786790099738D /* MoreOptionsMenu+BookmarksTests.swift in Sources */, 4B723E0726B0003E00E14D75 /* CSVImporterTests.swift in Sources */, - 4BCF15EC2ABB9AF80083F6DF /* NetworkProtectionRemoteMessageTests.swift in Sources */, + 4BCF15EC2ABB9AF80083F6DF /* SurveyRemoteMessageTests.swift in Sources */, B62EB47C25BAD3BB005745C6 /* WKWebViewPrivateMethodsAvailabilityTests.swift in Sources */, 9F0FFFBB2BCCAEC2007C87DD /* AddEditBookmarkFolderDialogViewModelMock.swift in Sources */, 4BBC16A527C488C900E00A38 /* DeviceAuthenticatorTests.swift in Sources */, diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index e102a8534f..d642774f89 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -357,8 +357,6 @@ extension HomePage.Models { } switch actionType { - case .openNetworkProtection: - NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: nil) case .openSurveyURL, .openURL: if let surveyURL = remoteMessage.presentableSurveyURL() { let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift index effc6049be..ad95075070 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift @@ -21,7 +21,6 @@ import Common struct SurveyRemoteMessageAction: Codable, Equatable, Hashable { enum Action: String, Codable { - case openNetworkProtection case openSurveyURL case openURL } @@ -33,13 +32,18 @@ struct SurveyRemoteMessageAction: Codable, Equatable, Hashable { struct SurveyRemoteMessage: Codable, Equatable, Identifiable, Hashable { + struct Attributes: Codable, Equatable, Hashable { + let subscriptionStatus: String? + let minimumDaysSinceSubscriptionStarted: Int? + let maximumDaysUntilSubscriptionExpirationOrRenewal: Int? + let daysSinceVPNEnabled: Int? + let daysSincePIREnabled: Int? + } + let id: String let cardTitle: String let cardDescription: String - /// If this is set, the message won't be displayed if NetP hasn't been used, even if the usage and access booleans are false - let daysSinceNetworkProtectionEnabled: Int? - let requiresNetworkProtectionUsage: Bool - let requiresNetworkProtectionAccess: Bool + let attributes: Attributes let action: SurveyRemoteMessageAction func presentableSurveyURL( diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift index 035b25d266..0205b2da26 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift @@ -37,7 +37,6 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { private let messageRequest: HomePageRemoteMessagingRequest private let messageStorage: HomePageRemoteMessagingStorage private let waitlistActivationDateStore: WaitlistActivationDateStore - private let networkProtectionVisibility: NetworkProtectionFeatureVisibility private let minimumRefreshInterval: TimeInterval private let userDefaults: UserDefaults @@ -53,14 +52,12 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.surveysRequest(), messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.surveys(), waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), - networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), minimumRefreshInterval: TimeInterval, userDefaults: UserDefaults = .standard ) { self.messageRequest = messageRequest self.messageStorage = messageStorage self.waitlistActivationDateStore = waitlistActivationDateStore - self.networkProtectionVisibility = networkProtectionVisibility self.minimumRefreshInterval = minimumRefreshInterval self.userDefaults = userDefaults } @@ -108,18 +105,14 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { let dismissedMessageIDs = messageStorage.dismissedMessageIDs() let possibleMessages: [SurveyRemoteMessage] = messageStorage.storedMessages() - // Only show messages that haven't been dismissed, and check whether they have a - // requirement on how long the user has used the VPN for. let filteredMessages = possibleMessages.filter { message in - - // Don't show messages that have already been dismissed. If you need to show the same message to a user again, - // it should get a new message ID. if dismissedMessageIDs.contains(message.id) { return false } - // First, check messages that require a number of days of NetP usage - if let requiredDaysSinceActivation = message.daysSinceNetworkProtectionEnabled, + // Check VPN usage: + + if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled, let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation() { if requiredDaysSinceActivation <= daysSinceActivation { return true @@ -128,16 +121,6 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } } - // Next, check if the message requires access to NetP but it's not visible: - if message.requiresNetworkProtectionAccess, !networkProtectionVisibility.isVPNVisible() { - return false - } - - // Finally, check if the message requires NetP usage, and check if the user has used it at all: - if message.requiresNetworkProtectionUsage, waitlistActivationDateStore.daysSinceActivation() == nil { - return false - } - return true } diff --git a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift index cd6186444d..eebcb0f3d9 100644 --- a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift +++ b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift @@ -50,10 +50,7 @@ let extraInputFiles: [TargetName: Set] = [ "Unit Tests": [ .init("BWEncryptionTests.swift", .source), - .init("WKWebViewPrivateMethodsAvailabilityTests.swift", .source), - .init("NetworkProtectionRemoteMessageTests.swift", .source), - .init("network-protection-messages.json", .resource), - .init("dbp-messages.json", .resource), + .init("WKWebViewPrivateMethodsAvailabilityTests.swift", .source) ], "Integration Tests": [] diff --git a/UnitTests/NetworkProtection/NetworkProtectionRemoteMessageTests.swift b/UnitTests/HomePage/SurveyRemoteMessageTests.swift similarity index 91% rename from UnitTests/NetworkProtection/NetworkProtectionRemoteMessageTests.swift rename to UnitTests/HomePage/SurveyRemoteMessageTests.swift index 39e2afe8a2..724fc06fbc 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionRemoteMessageTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessageTests.swift @@ -55,7 +55,7 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { XCTAssertEqual(firstMessage.cardDescription, "Description 1") XCTAssertEqual(firstMessage.action.actionTitle, "Action 1") XCTAssertNil(firstMessagePresentableSurveyURL) - XCTAssertNil(firstMessage.daysSinceNetworkProtectionEnabled) + XCTAssertNil(firstMessage.attributes.daysSinceVPNEnabled) guard let secondMessage = decodedMessages.first(where: { $0.id == "456"}) else { XCTFail("Failed to find expected message") @@ -70,7 +70,7 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { hardwareModel: "MacBookPro,123" ) - XCTAssertEqual(secondMessage.daysSinceNetworkProtectionEnabled, 1) + XCTAssertEqual(secondMessage.attributes.daysSinceVPNEnabled, 1) XCTAssertEqual(secondMessage.cardTitle, "Title 2") XCTAssertEqual(secondMessage.cardDescription, "Description 2") XCTAssertEqual(secondMessage.action.actionTitle, "Action 2") @@ -89,7 +89,7 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { hardwareModel: "MacBookPro,123" ) - XCTAssertEqual(thirdMessage.daysSinceNetworkProtectionEnabled, 5) + XCTAssertEqual(thirdMessage.attributes.daysSinceVPNEnabled, 5) XCTAssertEqual(thirdMessage.cardTitle, "Title 3") XCTAssertEqual(thirdMessage.cardDescription, "Description 3") XCTAssertEqual(thirdMessage.action.actionTitle, "Action 3") @@ -103,8 +103,13 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { "daysSinceNetworkProtectionEnabled": 0, "cardTitle": "Title", "cardDescription": "Description", - "requiresNetworkProtectionAccess": true, - "requiresNetworkProtectionUsage": true, + "attributes": { + "subscriptionStatus": "" + "minimumDaysSinceSubscriptionStarted: 1 + "maximumDaysUntilSubscriptionExpirationOrRenewal": 30 + "daysSinceNetworkProtectionEnabled": 1 + "daysSincePersonalInformationRemovalEnabled": 1 + } "action": { "actionTitle": "Action", "actionType": "openSurveyURL", diff --git a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift index 3d9b22f122..2da849df95 100644 --- a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -34,13 +34,11 @@ final class SurveyRemoteMessagingTests: XCTestCase { request.result = .success([]) let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -60,7 +58,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) request.result = .success([]) @@ -68,7 +65,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -90,7 +86,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) let messages = [mockMessage(id: "123")] @@ -101,7 +96,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -125,7 +119,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) activationDateStorage._daysSinceActivation = 10 @@ -135,7 +128,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: .days(7), // Use a large number to hit the refresh check userDefaults: defaults ) @@ -158,7 +150,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) let dismissedMessage = mockMessage(id: "123") let activeMessage = mockMessage(id: "456") @@ -169,7 +160,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -185,7 +175,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) let hiddenMessage = mockMessage(id: "123", daysSinceNetworkProtectionEnabled: 10) let activeMessage = mockMessage(id: "456") @@ -196,7 +185,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -209,7 +197,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: false) let hiddenMessage = mockMessage(id: "123", requiresNetPAccess: true) try? storage.store(messages: [hiddenMessage]) @@ -218,7 +205,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -231,7 +217,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockNetworkProtectionRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let visibility = NetworkProtectionVisibilityMock(isInstalled: false, visible: true) let message = mockMessage(id: "123", requiresNetPUsage: false, requiresNetPAccess: true) try? storage.store(messages: [message]) @@ -240,7 +225,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageRequest: request, messageStorage: storage, waitlistActivationDateStore: activationDateStorage, - networkProtectionVisibility: visibility, minimumRefreshInterval: 0, userDefaults: defaults ) From b37b60e141c7697d91ddd4292a84e37535b22c75 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 22 May 2024 20:20:46 -0700 Subject: [PATCH 03/24] Clean up pixels and tests. --- DuckDuckGo.xcodeproj/project.pbxproj | 45 ++++-------- .../Model/HomePageContinueSetUpModel.swift | 6 +- .../SurveyRemoteMessaging.swift | 2 + DuckDuckGo/Statistics/GeneralPixel.swift | 21 +++--- .../HomePage/Resources/survey-messages.json | 19 +++++ .../HomePage/SurveyRemoteMessageTests.swift | 69 +++++-------------- .../Resources/dbp-messages.json | 37 ---------- .../network-protection-messages.json | 37 ---------- 8 files changed, 67 insertions(+), 169 deletions(-) create mode 100644 UnitTests/HomePage/Resources/survey-messages.json delete mode 100644 UnitTests/NetworkProtection/Resources/dbp-messages.json delete mode 100644 UnitTests/NetworkProtection/Resources/network-protection-messages.json diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 742bc2ba97..694b5e90ef 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1132,7 +1132,6 @@ 4B37EE5F2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5C2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift */; }; 4B37EE612B4CFC3C00A89A61 /* SurveyURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5D2B4CFC3C00A89A61 /* SurveyURLBuilder.swift */; }; 4B37EE632B4CFC3C00A89A61 /* HomePageRemoteMessagingRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5E2B4CFC3C00A89A61 /* HomePageRemoteMessagingRequest.swift */; }; - 4B37EE6F2B4CFE8500A89A61 /* dbp-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B37EE6E2B4CFE8500A89A61 /* dbp-messages.json */; }; 4B37EE722B4CFEE400A89A61 /* SurveyURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5D2B4CFC3C00A89A61 /* SurveyURLBuilder.swift */; }; 4B37EE732B4CFF0800A89A61 /* HomePageRemoteMessagingStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5C2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift */; }; 4B37EE742B4CFF0A00A89A61 /* HomePageRemoteMessagingRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE5E2B4CFC3C00A89A61 /* HomePageRemoteMessagingRequest.swift */; }; @@ -1353,8 +1352,7 @@ 4BBDEE9428FC14760092FAA6 /* ConnectBitwardenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBDEE9028FC14760092FAA6 /* ConnectBitwardenViewController.swift */; }; 4BBE0AA727B9B027003B37A8 /* PopUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE0AA627B9B027003B37A8 /* PopUpButton.swift */; }; 4BBEE8DE2BFEDE3E00E5E111 /* SurveyRemoteMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15E42ABB98990083F6DF /* SurveyRemoteMessageTests.swift */; }; - 4BBEE8DF2BFEE07D00E5E111 /* network-protection-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */; }; - 4BBEE8E02BFEE08100E5E111 /* dbp-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B37EE6E2B4CFE8500A89A61 /* dbp-messages.json */; }; + 4BBEE8DF2BFEE07D00E5E111 /* survey-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BCF15E92ABB99470083F6DF /* survey-messages.json */; }; 4BBF0915282DD40100EE1418 /* TemporaryFileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0914282DD40100EE1418 /* TemporaryFileHandler.swift */; }; 4BBF0917282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0916282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift */; }; 4BBF09232830812900EE1418 /* FileSystemDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF09222830812900EE1418 /* FileSystemDSL.swift */; }; @@ -1367,7 +1365,7 @@ 4BCF15D72ABB8A110083F6DF /* SurveyRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */; }; 4BCF15D92ABB8A7F0083F6DF /* SurveyRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */; }; 4BCF15EC2ABB9AF80083F6DF /* SurveyRemoteMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15E42ABB98990083F6DF /* SurveyRemoteMessageTests.swift */; }; - 4BCF15ED2ABB9B180083F6DF /* network-protection-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */; }; + 4BCF15ED2ABB9B180083F6DF /* survey-messages.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BCF15E92ABB99470083F6DF /* survey-messages.json */; }; 4BCF15EE2ABBDBFD0083F6DF /* SurveyRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */; }; 4BCF15EF2ABBDBFF0083F6DF /* SurveyRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */; }; 4BD18F01283F0BC500058124 /* BookmarksBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */; }; @@ -3121,7 +3119,6 @@ 4B37EE5C2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageRemoteMessagingStorage.swift; sourceTree = ""; }; 4B37EE5D2B4CFC3C00A89A61 /* SurveyURLBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurveyURLBuilder.swift; sourceTree = ""; }; 4B37EE5E2B4CFC3C00A89A61 /* HomePageRemoteMessagingRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageRemoteMessagingRequest.swift; sourceTree = ""; }; - 4B37EE6E2B4CFE8500A89A61 /* dbp-messages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "dbp-messages.json"; sourceTree = ""; }; 4B39AAF527D9B2C700A73FD5 /* NSStackViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSStackViewExtension.swift; sourceTree = ""; }; 4B3B848F297A0E1000A384BD /* EmailManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailManagerExtension.swift; sourceTree = ""; }; 4B3F641D27A8D3BD00E0C118 /* BrowserProfileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserProfileTests.swift; sourceTree = ""; }; @@ -3302,7 +3299,7 @@ 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessaging.swift; sourceTree = ""; }; 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessage.swift; sourceTree = ""; }; 4BCF15E42ABB98990083F6DF /* SurveyRemoteMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessageTests.swift; sourceTree = ""; }; - 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "network-protection-messages.json"; sourceTree = ""; }; + 4BCF15E92ABB99470083F6DF /* survey-messages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "survey-messages.json"; sourceTree = ""; }; 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewController.swift; sourceTree = ""; }; 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BookmarksBar.storyboard; sourceTree = ""; }; 4BD57C032AC112DF00B580EE /* SurveyRemoteMessagingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessagingTests.swift; sourceTree = ""; }; @@ -5722,6 +5719,14 @@ path = View; sourceTree = ""; }; + 4BBEE8E12BFEE54100E5E111 /* Resources */ = { + isa = PBXGroup; + children = ( + 4BCF15E92ABB99470083F6DF /* survey-messages.json */, + ); + path = Resources; + sourceTree = ""; + }; 4BCF15D52ABB83D70083F6DF /* NetworkProtectionRemoteMessaging */ = { isa = PBXGroup; children = ( @@ -5735,7 +5740,6 @@ isa = PBXGroup; children = ( BDA7648F2BC4E56200D0400C /* Mocks */, - 4BCF15E62ABB98A20083F6DF /* Resources */, 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */, BDA7648C2BC4E4EF00D0400C /* DefaultVPNLocationFormatterTests.swift */, 7B4C5CF42BE51D640007A164 /* VPNUninstallerTests.swift */, @@ -5743,15 +5747,6 @@ path = NetworkProtection; sourceTree = ""; }; - 4BCF15E62ABB98A20083F6DF /* Resources */ = { - isa = PBXGroup; - children = ( - 4B37EE6E2B4CFE8500A89A61 /* dbp-messages.json */, - 4BCF15E92ABB99470083F6DF /* network-protection-messages.json */, - ); - path = Resources; - sourceTree = ""; - }; 4BD18F02283F0F1000058124 /* View */ = { isa = PBXGroup; children = ( @@ -5778,6 +5773,7 @@ 4BF6961B28BE90E800D402D4 /* HomePage */ = { isa = PBXGroup; children = ( + 4BBEE8E12BFEE54100E5E111 /* Resources */, 56534DEB29DF251C00121467 /* Mocks */, 4BF6961C28BE911100D402D4 /* RecentlyVisitedSiteModelTests.swift */, 569277C329DEE09D00B633EF /* ContinueSetUpModelTests.swift */, @@ -9022,10 +9018,9 @@ 3706FE8B293F661700E42796 /* empty in Resources */, 376E2D282942843D001CD31B /* privacy-reference-tests in Resources */, 3706FE8C293F661700E42796 /* atb-with-update.json in Resources */, - 4BBEE8E02BFEE08100E5E111 /* dbp-messages.json in Resources */, 9FBD84622BB3BC6400220859 /* Origin-empty.txt in Resources */, 3706FE8D293F661700E42796 /* DataImportResources in Resources */, - 4BBEE8DF2BFEE07D00E5E111 /* network-protection-messages.json in Resources */, + 4BBEE8DF2BFEE07D00E5E111 /* survey-messages.json in Resources */, 3706FE8E293F661700E42796 /* atb.json in Resources */, 9FBD845E2BB3B80300220859 /* Origin.txt in Resources */, 3706FE8F293F661700E42796 /* DuckDuckGo-ExampleCrash.ips in Resources */, @@ -9219,10 +9214,9 @@ B69B50542726CD8100758A2B /* atb-with-update.json in Resources */, 37A803DB27FD69D300052F4C /* DataImportResources in Resources */, B65CD8D52B316FCA00A595BB /* __Snapshots__ in Resources */, - 4B37EE6F2B4CFE8500A89A61 /* dbp-messages.json in Resources */, B69B50522726CD8100758A2B /* atb.json in Resources */, 4B70C00127B0793D000386ED /* DuckDuckGo-ExampleCrash.ips in Resources */, - 4BCF15ED2ABB9B180083F6DF /* network-protection-messages.json in Resources */, + 4BCF15ED2ABB9B180083F6DF /* survey-messages.json in Resources */, B67C6C422654BF49006C872E /* DuckDuckGo-Symbol.jpg in Resources */, B69B50552726CD8100758A2B /* invalid.json in Resources */, 9FBD84612BB3BC6400220859 /* Origin-empty.txt in Resources */, @@ -10148,7 +10142,6 @@ 1DC669712B6CF0D700AA0645 /* TabSnapshotStore.swift in Sources */, 3706FC19293F65D500E42796 /* NSNotificationName+Favicons.swift in Sources */, 3706FC1A293F65D500E42796 /* PinningManager.swift in Sources */, - 4B37EE782B4CFF3900A89A61 /* DataBrokerProtectionRemoteMessage.swift in Sources */, 3706FC1B293F65D500E42796 /* TabCollectionViewModel+NSSecureCoding.swift in Sources */, 3706FC1D293F65D500E42796 /* EmailManagerRequestDelegate.swift in Sources */, 3706FC1E293F65D500E42796 /* ApplicationVersionReader.swift in Sources */, @@ -10928,10 +10921,7 @@ files = ( 31A83FB72BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, - 9D9AE92C2AAB84FF0026E7DC /* DBPMocks.swift in Sources */, F1D042912BFB9FD700A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 7B0099792B65013800FE7C31 /* BrowserWindowManager.swift in Sources */, - 9D9AE9292AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */, 9D9AE91D2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -10946,10 +10936,7 @@ files = ( 31A83FB82BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, - 7B1459542B7D437200047F2C /* BrowserWindowManager.swift in Sources */, F1D042922BFB9FD800A31506 /* SubscriptionEnvironment+Default.swift in Sources */, - 9D9AE92D2AAB84FF0026E7DC /* DBPMocks.swift in Sources */, - 9D9AE92A2AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */, 9D9AE91E2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -11151,8 +11138,7 @@ AA5FA6A0275F948900DCE9C9 /* Favicons.xcdatamodeld in Sources */, 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 9F6434612BEC82B700D2D8A0 /* AttributionPixelHandler.swift in Sources */, - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemScheduler.swift in Sources */, - 7BBA7CE62BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, + 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, B684592225C93BE000DC17B6 /* Publisher.asVoid.swift in Sources */, 4B9DB01D2A983B24000927DB /* Waitlist.swift in Sources */, BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */, @@ -11318,7 +11304,6 @@ B66260E029AC6EBD00E9E3EE /* HistoryTabExtension.swift in Sources */, B6BCC54F2AFE4F7D002C5499 /* DataImportTypePicker.swift in Sources */, AAEEC6A927088ADB008445F7 /* FireCoordinator.swift in Sources */, - 4B37EE752B4CFF3300A89A61 /* DataBrokerProtectionRemoteMessaging.swift in Sources */, B655369B268442EE00085A79 /* GeolocationProvider.swift in Sources */, B6C0B23C26E87D900031CB7F /* NSAlert+ActiveDownloadsTermination.swift in Sources */, AAECA42024EEA4AC00EFA63A /* IndexPathExtension.swift in Sources */, diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index d642774f89..a0b1d55096 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -205,7 +205,7 @@ extension HomePage.Models { shouldShowPermanentSurvey = false case .surveyRemoteMessage(let message): surveyRemoteMessaging.dismiss(message: message) - PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: message.id)) + PixelKit.fire(GeneralPixel.surveyRemoteMessageDismissed(messageID: message.id)) case .dataBrokerProtectionWaitlistInvited: shouldShowDBPWaitlistInvitedCardUI = false } @@ -350,7 +350,7 @@ extension HomePage.Models { @MainActor private func handle(remoteMessage: SurveyRemoteMessage) { guard let actionType = remoteMessage.action.actionType else { - PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.surveyRemoteMessageDismissed(messageID: remoteMessage.id)) surveyRemoteMessaging.dismiss(message: remoteMessage) refreshFeaturesMatrix() return @@ -361,7 +361,7 @@ extension HomePage.Models { if let surveyURL = remoteMessage.presentableSurveyURL() { let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) tabCollectionViewModel.append(tab: tab) - PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageOpened(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.surveyRemoteMessageOpened(messageID: remoteMessage.id)) // Dismiss the message after the user opens the URL, even if they just close the tab immediately afterwards. surveyRemoteMessaging.dismiss(message: remoteMessage) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift index 430052fcb7..2a7c15fc83 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift @@ -106,6 +106,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { let dismissedMessageIDs = messageStorage.dismissedMessageIDs() let possibleMessages: [SurveyRemoteMessage] = messageStorage.storedMessages() + // TODO: Check all attributes + let filteredMessages = possibleMessages.filter { message in if dismissedMessageIDs.contains(message.id) { return false diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index d5846e38a7..df26e79285 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -95,13 +95,14 @@ enum GeneralPixel: PixelKitEventV2 { case dashboardProtectionAllowlistAdd(triggerOrigin: String?) case dashboardProtectionAllowlistRemove(triggerOrigin: String?) + // Survey + case surveyRemoteMessageDisplayed(messageID: String) + case surveyRemoteMessageDismissed(messageID: String) + case surveyRemoteMessageOpened(messageID: 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 @@ -469,12 +470,12 @@ enum GeneralPixel: PixelKitEventV2 { case .vpnBreakageReport: return "m_mac_vpn_breakage_report" - case .networkProtectionRemoteMessageDisplayed(let messageID): - return "m_mac_netp_remote_message_displayed_\(messageID)" - case .networkProtectionRemoteMessageDismissed(let messageID): - return "m_mac_netp_remote_message_dismissed_\(messageID)" - case .networkProtectionRemoteMessageOpened(let messageID): - return "m_mac_netp_remote_message_opened_\(messageID)" + case .surveyRemoteMessageDisplayed(let messageID): + return "m_mac_survey_remote_message_displayed_\(messageID)" + case .surveyRemoteMessageDismissed(let messageID): + return "m_mac_survey_remote_message_dismissed_\(messageID)" + case .surveyRemoteMessageOpened(let messageID): + return "m_mac_survey_remote_message_opened_\(messageID)" case .networkProtectionEnabledOnSearch: return "m_mac_netp_ev_enabled_on_search" diff --git a/UnitTests/HomePage/Resources/survey-messages.json b/UnitTests/HomePage/Resources/survey-messages.json new file mode 100644 index 0000000000..832d06ef47 --- /dev/null +++ b/UnitTests/HomePage/Resources/survey-messages.json @@ -0,0 +1,19 @@ +[ + { + "id": "message-1", + "cardTitle": "Title 1", + "cardDescription": "Description 1", + "attributes": { + "subscriptionStatus": "", + "minimumDaysSinceSubscriptionStarted": 1, + "maximumDaysUntilSubscriptionExpirationOrRenewal": 30, + "daysSinceVPNEnabled": 2, + "daysSincePIREnabled": 3 + }, + "action": { + "actionTitle": "Action 1", + "actionType": "openSurveyURL", + "actionURL": "https://duckduckgo.com/" + } + } +] diff --git a/UnitTests/HomePage/SurveyRemoteMessageTests.swift b/UnitTests/HomePage/SurveyRemoteMessageTests.swift index 724fc06fbc..ecdc62b4a4 100644 --- a/UnitTests/HomePage/SurveyRemoteMessageTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessageTests.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionRemoteMessageTests.swift +// SurveyRemoteMessageTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -19,7 +19,7 @@ import XCTest @testable import DuckDuckGo_Privacy_Browser -final class NetworkProtectionRemoteMessageTests: XCTestCase { +final class SurveyRemoteMessageTests: XCTestCase { func testWhenDecodingMessages_ThenMessagesDecodeSuccessfully() throws { let mockStatisticsStore = MockStatisticsStore() @@ -36,9 +36,9 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { let decoder = JSONDecoder() let decodedMessages = try decoder.decode([SurveyRemoteMessage].self, from: data) - XCTAssertEqual(decodedMessages.count, 3) + XCTAssertEqual(decodedMessages.count, 1) - guard let firstMessage = decodedMessages.first(where: { $0.id == "123"}) else { + guard let firstMessage = decodedMessages.first(where: { $0.id == "message-1"}) else { XCTFail("Failed to find expected message") return } @@ -54,46 +54,11 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { XCTAssertEqual(firstMessage.cardTitle, "Title 1") XCTAssertEqual(firstMessage.cardDescription, "Description 1") XCTAssertEqual(firstMessage.action.actionTitle, "Action 1") - XCTAssertNil(firstMessagePresentableSurveyURL) - XCTAssertNil(firstMessage.attributes.daysSinceVPNEnabled) - - guard let secondMessage = decodedMessages.first(where: { $0.id == "456"}) else { - XCTFail("Failed to find expected message") - return - } - - let secondMessagePresentableSurveyURL = secondMessage.presentableSurveyURL( - statisticsStore: mockStatisticsStore, - activationDateStore: mockActivationDateStore, - operatingSystemVersion: "1.2.3", - appVersion: "4.5.6", - hardwareModel: "MacBookPro,123" - ) - - XCTAssertEqual(secondMessage.attributes.daysSinceVPNEnabled, 1) - XCTAssertEqual(secondMessage.cardTitle, "Title 2") - XCTAssertEqual(secondMessage.cardDescription, "Description 2") - XCTAssertEqual(secondMessage.action.actionTitle, "Action 2") - XCTAssertNil(secondMessagePresentableSurveyURL) - - guard let thirdMessage = decodedMessages.first(where: { $0.id == "789"}) else { - XCTFail("Failed to find expected message") - return - } - - let thirdMessagePresentableSurveyURL = thirdMessage.presentableSurveyURL( - statisticsStore: mockStatisticsStore, - activationDateStore: mockActivationDateStore, - operatingSystemVersion: "1.2.3", - appVersion: "4.5.6", - hardwareModel: "MacBookPro,123" - ) - - XCTAssertEqual(thirdMessage.attributes.daysSinceVPNEnabled, 5) - XCTAssertEqual(thirdMessage.cardTitle, "Title 3") - XCTAssertEqual(thirdMessage.cardDescription, "Description 3") - XCTAssertEqual(thirdMessage.action.actionTitle, "Action 3") - XCTAssertTrue(thirdMessagePresentableSurveyURL!.absoluteString.hasPrefix("https://duckduckgo.com/")) + XCTAssertEqual(firstMessage.attributes.minimumDaysSinceSubscriptionStarted, 1) + XCTAssertEqual(firstMessage.attributes.daysSinceVPNEnabled, 2) + XCTAssertEqual(firstMessage.attributes.daysSincePIREnabled, 3) + XCTAssertEqual(firstMessage.attributes.maximumDaysUntilSubscriptionExpirationOrRenewal, 30) + XCTAssertNotNil(firstMessagePresentableSurveyURL) } func testWhenGettingSurveyURL_AndSurveyURLHasParameters_ThenParametersAreReplaced() { @@ -104,12 +69,12 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { "cardTitle": "Title", "cardDescription": "Description", "attributes": { - "subscriptionStatus": "" - "minimumDaysSinceSubscriptionStarted: 1 - "maximumDaysUntilSubscriptionExpirationOrRenewal": 30 - "daysSinceNetworkProtectionEnabled": 1 - "daysSincePersonalInformationRemovalEnabled": 1 - } + "subscriptionStatus": "", + "minimumDaysSinceSubscriptionStarted": 1, + "maximumDaysUntilSubscriptionExpirationOrRenewal": 30, + "daysSinceVPNEnabled": 1, + "daysSincePIREnabled": 1 + }, "action": { "actionTitle": "Action", "actionType": "openSurveyURL", @@ -148,8 +113,8 @@ final class NetworkProtectionRemoteMessageTests: XCTestCase { } private func mockMessagesURL() -> URL { - let bundle = Bundle(for: NetworkProtectionRemoteMessageTests.self) - return bundle.resourceURL!.appendingPathComponent("network-protection-messages.json") + let bundle = Bundle(for: SurveyRemoteMessageTests.self) + return bundle.resourceURL!.appendingPathComponent("survey-messages.json") } } diff --git a/UnitTests/NetworkProtection/Resources/dbp-messages.json b/UnitTests/NetworkProtection/Resources/dbp-messages.json deleted file mode 100644 index 8d7a6dc7dc..0000000000 --- a/UnitTests/NetworkProtection/Resources/dbp-messages.json +++ /dev/null @@ -1,37 +0,0 @@ -[ - { - "id": "123", - "cardTitle": "Title 1", - "cardDescription": "Description 1", - "requiresDataBrokerProtectionAccess": true, - "requiresDataBrokerProtectionUsage": true, - "action": { - "actionTitle": "Action 1" - } - }, - { - "id": "456", - "daysSinceDataBrokerProtectionEnabled": 1, - "cardTitle": "Title 2", - "cardDescription": "Description 2", - "requiresDataBrokerProtectionAccess": true, - "requiresDataBrokerProtectionUsage": true, - "action": { - "actionTitle": "Action 2" - } - }, - { - "id": "789", - "daysSinceDataBrokerProtectionEnabled": 5, - "cardTitle": "Title 3", - "cardDescription": "Description 3", - "requiresDataBrokerProtectionAccess": true, - "requiresDataBrokerProtectionUsage": true, - "action": { - "actionTitle": "Action 3", - "actionType": "openSurveyURL", - "actionURL": "https://duckduckgo.com/" - } - - } -] diff --git a/UnitTests/NetworkProtection/Resources/network-protection-messages.json b/UnitTests/NetworkProtection/Resources/network-protection-messages.json deleted file mode 100644 index adc9a34c3c..0000000000 --- a/UnitTests/NetworkProtection/Resources/network-protection-messages.json +++ /dev/null @@ -1,37 +0,0 @@ -[ - { - "id": "123", - "cardTitle": "Title 1", - "cardDescription": "Description 1", - "requiresNetworkProtectionAccess": true, - "requiresNetworkProtectionUsage": true, - "action": { - "actionTitle": "Action 1" - } - }, - { - "id": "456", - "cardTitle": "Title 2", - "cardDescription": "Description 2", - "requiresNetworkProtectionAccess": true, - "requiresNetworkProtectionUsage": true, - "daysSinceNetworkProtectionEnabled": 1, - "action": { - "actionTitle": "Action 2" - } - }, - { - "id": "789", - "cardTitle": "Title 3", - "cardDescription": "Description 3", - "requiresNetworkProtectionAccess": true, - "requiresNetworkProtectionUsage": true, - "daysSinceNetworkProtectionEnabled": 5, - "action": { - "actionTitle": "Action 3", - "actionType": "openSurveyURL", - "actionURL": "https://duckduckgo.com/" - } - - } -] From a5beece9edd9e582c3cbc4875099acf439b2017c Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 22 May 2024 20:55:42 -0700 Subject: [PATCH 04/24] Pass the subscription manager through to the remote message fetcher. --- .../View/HomePageViewController.swift | 4 +++- .../MainWindow/MainViewController.swift | 4 +++- .../NetworkProtectionDebugMenu.swift | 5 ++++- .../SurveyRemoteMessaging.swift | 20 ++++++++++++++----- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index ebcc1d66de..224f0f93f4 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -154,7 +154,9 @@ final class HomePageViewController: NSViewController { dataImportProvider: BookmarksAndPasswordsImportStatusProvider(), tabCollectionViewModel: tabCollectionViewModel, duckPlayerPreferences: DuckPlayerPreferencesUserDefaultsPersistor(), - surveyRemoteMessaging: DefaultSurveyRemoteMessaging() + surveyRemoteMessaging: DefaultSurveyRemoteMessaging( + subscriptionManager: Application.appDelegate.subscriptionManager + ) ) } diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 736f5387fe..258fc1b03b 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -214,7 +214,9 @@ final class MainViewController: NSViewController { } } - private let surveyMessaging = DefaultSurveyRemoteMessaging() + private lazy var surveyMessaging: DefaultSurveyRemoteMessaging = { + return DefaultSurveyRemoteMessaging(subscriptionManager: Application.appDelegate.subscriptionManager) + }() func refreshSurveyMessages() { surveyMessaging.fetchRemoteMessages() diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 6b8d16029f..bb771e4805 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -485,7 +485,10 @@ final class NetworkProtectionDebugMenu: NSMenu { @objc func resetNetworkProtectionRemoteMessages(_ sender: Any?) { DefaultHomePageRemoteMessagingStorage.surveys().removeStoredAndDismissedMessages() - DefaultSurveyRemoteMessaging(minimumRefreshInterval: 0).resetLastRefreshTimestamp() + DefaultSurveyRemoteMessaging( + subscriptionManager: Application.appDelegate.subscriptionManager, + minimumRefreshInterval: 0 + ).resetLastRefreshTimestamp() } @objc func overrideNetworkProtectionActivationDateToNow(_ sender: Any?) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift index 2a7c15fc83..1f87b6376f 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift @@ -19,6 +19,7 @@ import Foundation import Networking import PixelKit +import Subscription protocol SurveyRemoteMessaging { @@ -36,21 +37,23 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { private let messageRequest: HomePageRemoteMessagingRequest private let messageStorage: HomePageRemoteMessagingStorage + private let subscriptionManager: SubscriptionManaging private let waitlistActivationDateStore: WaitlistActivationDateStore private let minimumRefreshInterval: TimeInterval private let userDefaults: UserDefaults - convenience init() { + convenience init(subscriptionManager: SubscriptionManaging) { #if DEBUG || REVIEW - self.init(minimumRefreshInterval: .seconds(30)) + self.init(subscriptionManager: subscriptionManager, minimumRefreshInterval: .seconds(30)) #else - self.init(minimumRefreshInterval: .hours(1)) + self.init(subscriptionManager: subscriptionManager, minimumRefreshInterval: .hours(1)) #endif } init( messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.surveysRequest(), messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.surveys(), + subscriptionManager: SubscriptionManaging, waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), minimumRefreshInterval: TimeInterval, @@ -58,13 +61,13 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { ) { self.messageRequest = messageRequest self.messageStorage = messageStorage + self.subscriptionManager = subscriptionManager self.waitlistActivationDateStore = waitlistActivationDateStore self.minimumRefreshInterval = minimumRefreshInterval self.userDefaults = userDefaults } func fetchRemoteMessages(completion fetchCompletion: (() -> Void)? = nil) { - if let lastRefreshDate = lastRefreshDate(), lastRefreshDate.addingTimeInterval(minimumRefreshInterval) > Date() { fetchCompletion?() return @@ -113,6 +116,12 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { return false } + // Check subscription status: + + if let subscriptionStatus = message.attributes.subscriptionStatus { + + } + // Check VPN usage: if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled, @@ -124,7 +133,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } } - return true + // Don't show messages unless at least one attribute matches: + return false } From 205b0b5897a4e9418d483a393a37323dd714d579 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 22 May 2024 21:19:41 -0700 Subject: [PATCH 05/24] Update class names. --- .../HomePageRemoteMessagingStorage.swift | 24 ++++++------ .../NetworkProtectionDebugMenu.swift | 2 +- .../SurveyRemoteMessaging.swift | 37 +++++++++++-------- .../HomePage/SurveyRemoteMessagingTests.swift | 30 +++++---------- 4 files changed, 44 insertions(+), 49 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift index a2f3a7262b..7057913dad 100644 --- a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift +++ b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingStorage.swift @@ -18,21 +18,21 @@ import Foundation -protocol HomePageRemoteMessagingStorage { +protocol SurveyRemoteMessagingStorage { - func store(messages: [Message]) throws - func storedMessages() -> [Message] + func store(messages: [SurveyRemoteMessage]) throws + func storedMessages() -> [SurveyRemoteMessage] func dismissRemoteMessage(with id: String) func dismissedMessageIDs() -> [String] } -final class DefaultHomePageRemoteMessagingStorage: HomePageRemoteMessagingStorage { +final class DefaultSurveyRemoteMessagingStorage: SurveyRemoteMessagingStorage { enum SurveyConstants { static let dismissedMessageIdentifiersKey = "home.page.survey.dismissed-message-identifiers" - static let networkProtectionMessagesFileName = "survey-messages.json" + static let surveyMessagesFileName = "survey-messages.json" } private let userDefaults: UserDefaults @@ -43,16 +43,16 @@ final class DefaultHomePageRemoteMessagingStorage: HomePageRemoteMessagingStorag URL.sandboxApplicationSupportURL } - static func surveys() -> DefaultHomePageRemoteMessagingStorage { - return DefaultHomePageRemoteMessagingStorage( - messagesFileName: SurveyConstants.networkProtectionMessagesFileName, + static func surveys() -> DefaultSurveyRemoteMessagingStorage { + return DefaultSurveyRemoteMessagingStorage( + messagesFileName: SurveyConstants.surveyMessagesFileName, dismissedMessageIdentifiersKey: SurveyConstants.dismissedMessageIdentifiersKey ) } init( userDefaults: UserDefaults = .standard, - messagesDirectoryURL: URL = DefaultHomePageRemoteMessagingStorage.applicationSupportURL, + messagesDirectoryURL: URL = DefaultSurveyRemoteMessagingStorage.applicationSupportURL, messagesFileName: String, dismissedMessageIdentifiersKey: String ) { @@ -61,15 +61,15 @@ final class DefaultHomePageRemoteMessagingStorage: HomePageRemoteMessagingStorag self.dismissedMessageIdentifiersKey = dismissedMessageIdentifiersKey } - func store(messages: [Message]) throws { + func store(messages: [SurveyRemoteMessage]) throws { let encoded = try JSONEncoder().encode(messages) try encoded.write(to: messagesURL) } - func storedMessages() -> [Message] { + func storedMessages() -> [SurveyRemoteMessage] { do { let messagesData = try Data(contentsOf: messagesURL) - let messages = try JSONDecoder().decode([Message].self, from: messagesData) + let messages = try JSONDecoder().decode([SurveyRemoteMessage].self, from: messagesData) return messages } catch { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index bb771e4805..e26c61ae15 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -484,7 +484,7 @@ final class NetworkProtectionDebugMenu: NSMenu { } @objc func resetNetworkProtectionRemoteMessages(_ sender: Any?) { - DefaultHomePageRemoteMessagingStorage.surveys().removeStoredAndDismissedMessages() + DefaultSurveyRemoteMessagingStorage.surveys().removeStoredAndDismissedMessages() DefaultSurveyRemoteMessaging( subscriptionManager: Application.appDelegate.subscriptionManager, minimumRefreshInterval: 0 diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift index 1f87b6376f..89c41fa56c 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift @@ -36,7 +36,7 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } private let messageRequest: HomePageRemoteMessagingRequest - private let messageStorage: HomePageRemoteMessagingStorage + private let messageStorage: SurveyRemoteMessagingStorage private let subscriptionManager: SubscriptionManaging private let waitlistActivationDateStore: WaitlistActivationDateStore private let minimumRefreshInterval: TimeInterval @@ -52,7 +52,7 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { init( messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.surveysRequest(), - messageStorage: HomePageRemoteMessagingStorage = DefaultHomePageRemoteMessagingStorage.surveys(), + messageStorage: SurveyRemoteMessagingStorage = DefaultSurveyRemoteMessagingStorage.surveys(), subscriptionManager: SubscriptionManaging, waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), @@ -104,18 +104,11 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } - /// Uses the "days since VPN activated" count combined with the set of dismissed messages to determine which messages should be displayed to the user. - func presentableRemoteMessages() -> [SurveyRemoteMessage] { - let dismissedMessageIDs = messageStorage.dismissedMessageIDs() - let possibleMessages: [SurveyRemoteMessage] = messageStorage.storedMessages() - - // TODO: Check all attributes - - let filteredMessages = possibleMessages.filter { message in - if dismissedMessageIDs.contains(message.id) { - return false - } - + /// Processes the messages received from S3 and returns those which the user is eligible for. This is done by checking each of the attributes against the user's local state. + /// Because the result of the message fetch is cached, it means that they won't be immediately updated if the user suddenly qualifies, but the refresh interval for remote messages is only 1 hour so it + /// won't take long for the message to appear to the user. + private func process(messages: [SurveyRemoteMessage]) async -> [SurveyRemoteMessage] { + let filteredMessages = messages.filter { message in // Check subscription status: if let subscriptionStatus = message.attributes.subscriptionStatus { @@ -123,7 +116,7 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } // Check VPN usage: - + if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled, let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation() { if requiredDaysSinceActivation <= daysSinceActivation { @@ -137,6 +130,20 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { return false } + } + + func presentableRemoteMessages() -> [SurveyRemoteMessage] { + let dismissedMessageIDs = messageStorage.dismissedMessageIDs() + let possibleMessages: [SurveyRemoteMessage] = messageStorage.storedMessages() + + let filteredMessages = possibleMessages.filter { message in + if dismissedMessageIDs.contains(message.id) { + return false + } + + return true + + } return filteredMessages } diff --git a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift index 2da849df95..a04c64bb7f 100644 --- a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -32,7 +32,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenFetchingRemoteMessages_AndTheUserDidNotSignUpViaWaitlist_ThenMessagesAreFetched() { let request = MockNetworkProtectionRemoteMessagingRequest() request.result = .success([]) - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() let messaging = DefaultSurveyRemoteMessaging( @@ -56,7 +56,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenFetchingRemoteMessages_AndTheUserDidSignUpViaWaitlist_ButUserHasNotActivatedNetP_ThenMessagesAreFetched() { let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() request.result = .success([]) @@ -84,7 +84,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ThenMessagesAreFetched_AndMessagesAreStored() { let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() let messages = [mockMessage(id: "123")] @@ -117,7 +117,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ButRateLimitedOperationCannotRunAgain_ThenMessagesAreNotFetched() { let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() activationDateStorage._daysSinceActivation = 10 @@ -148,7 +148,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesHaveBeenDismissed_ThenPresentableMessagesDoNotIncludeDismissedMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() let dismissedMessage = mockMessage(id: "123") @@ -173,7 +173,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesRequireDaysActive_ThenPresentableMessagesDoNotIncludeInvalidMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() let hiddenMessage = mockMessage(id: "123", daysSinceNetworkProtectionEnabled: 10) @@ -195,7 +195,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesNetPVisibility_ThenPresentableMessagesDoNotIncludeInvalidMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() let hiddenMessage = mockMessage(id: "123", requiresNetPAccess: true) @@ -215,7 +215,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { func testWhenStoredMessagesExist_AndSomeMessagesRequireNetPUsage_ThenPresentableMessagesDoNotIncludeInvalidMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockNetworkProtectionRemoteMessagingStorage() + let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() let message = mockMessage(id: "123", requiresNetPUsage: false, requiresNetPAccess: true) @@ -277,7 +277,7 @@ private final class MockNetworkProtectionRemoteMessagingRequest: HomePageRemoteM } -private final class MockNetworkProtectionRemoteMessagingStorage: HomePageRemoteMessagingStorage { +private final class MockSurveyRemoteMessagingStorage: SurveyRemoteMessagingStorage { var _storedMessages: [SurveyRemoteMessage] = [] var _storedDismissedMessageIDs: [String] = [] @@ -290,18 +290,6 @@ private final class MockNetworkProtectionRemoteMessagingStorage: HomePageRemoteM _storedMessages } - func store(messages: [Message]) throws { - if let messages = messages as? [SurveyRemoteMessage] { - self._storedMessages = messages - } else { - fatalError("Failed to cast messages") - } - } - - func storedMessages() -> [Message] { - return _storedMessages as! [Message] - } - func dismissRemoteMessage(with id: String) { if !_storedDismissedMessageIDs.contains(id) { _storedDismissedMessageIDs.append(id) From 8349e8b3b70b7a3240d9ef26f2e647e9d913fb9c Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 22 May 2024 22:28:40 -0700 Subject: [PATCH 06/24] Convert to Swift Concurrency. --- .../HomePageRemoteMessagingRequest.swift | 32 +++---- .../MainWindow/MainViewController.swift | 4 +- .../SurveyRemoteMessaging.swift | 88 ++++++++++++------- .../HomePage/ContinueSetUpModelTests.swift | 4 +- .../HomePage/SurveyRemoteMessagingTests.swift | 65 ++++++++------ 5 files changed, 114 insertions(+), 79 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift index 4433e395e2..0e32253ec0 100644 --- a/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift +++ b/DuckDuckGo/Common/Surveys/HomePageRemoteMessagingRequest.swift @@ -21,7 +21,7 @@ import Networking protocol HomePageRemoteMessagingRequest { - func fetchHomePageRemoteMessages(completion: @escaping (Result<[T], Error>) -> Void) + func fetchHomePageRemoteMessages() async -> Result<[SurveyRemoteMessage], Error> } @@ -58,25 +58,27 @@ final class DefaultHomePageRemoteMessagingRequest: HomePageRemoteMessagingReques self.endpointURL = endpointURL } - func fetchHomePageRemoteMessages(completion: @escaping (Result<[T], Error>) -> Void) { + func fetchHomePageRemoteMessages() async -> Result<[SurveyRemoteMessage], Error> { let httpMethod = APIRequest.HTTPMethod.get let configuration = APIRequest.Configuration(url: endpointURL, method: httpMethod, body: nil) let request = APIRequest(configuration: configuration) - request.fetch { response, error in - if let error { - completion(Result.failure(error)) - } else if let responseData = response?.data { - do { - let decoder = JSONDecoder() - let decoded = try decoder.decode([T].self, from: responseData) - completion(Result.success(decoded)) - } catch { - completion(.failure(HomePageRemoteMessagingRequestError.failedToDecodeMessages)) - } - } else { - completion(.failure(HomePageRemoteMessagingRequestError.requestCompletedWithoutErrorOrResponse)) + do { + let response = try await request.fetch() + + guard let data = response.data else { + return .failure(HomePageRemoteMessagingRequestError.requestCompletedWithoutErrorOrResponse) + } + + do { + let decoder = JSONDecoder() + let decoded = try decoder.decode([SurveyRemoteMessage].self, from: data) + return .success(decoded) + } catch { + return .failure(HomePageRemoteMessagingRequestError.failedToDecodeMessages) } + } catch { + return .failure(error) } } diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 258fc1b03b..fc05d497c5 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -219,7 +219,9 @@ final class MainViewController: NSViewController { }() func refreshSurveyMessages() { - surveyMessaging.fetchRemoteMessages() + Task { + await surveyMessaging.fetchRemoteMessages() + } } override func encodeRestorableState(with coder: NSCoder) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift index 89c41fa56c..b07c5e9496 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift @@ -23,7 +23,7 @@ import Subscription protocol SurveyRemoteMessaging { - func fetchRemoteMessages(completion: (() -> Void)?) + func fetchRemoteMessages() async func presentableRemoteMessages() -> [SurveyRemoteMessage] func dismiss(message: SurveyRemoteMessage) @@ -67,56 +67,80 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { self.userDefaults = userDefaults } - func fetchRemoteMessages(completion fetchCompletion: (() -> Void)? = nil) { + func fetchRemoteMessages() async { if let lastRefreshDate = lastRefreshDate(), lastRefreshDate.addingTimeInterval(minimumRefreshInterval) > Date() { - fetchCompletion?() return } - self.messageRequest.fetchHomePageRemoteMessages { [weak self] result in - defer { - fetchCompletion?() - } - - guard let self else { return } + let messageFetchResult = await self.messageRequest.fetchHomePageRemoteMessages() - // Cast the generic parameter to a concrete type: - let result: Result<[SurveyRemoteMessage], Error> = result - - switch result { - case .success(let messages): - do { - try self.messageStorage.store(messages: messages) - self.updateLastRefreshDate() // Update last refresh date on success, otherwise let the app try again next time - } catch { - 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 - if case APIRequest.Error.invalidStatusCode(403) = error { - self.updateLastRefreshDate() // Avoid refreshing constantly when the file isn't available - return - } - - PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageFetchingFailed, error: error)) + switch messageFetchResult { + case .success(let messages): + do { + let processedMessages = await self.process(messages: messages) + try self.messageStorage.store(messages: processedMessages) + self.updateLastRefreshDate() + } catch { + 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 + if case APIRequest.Error.invalidStatusCode(403) = error { + self.updateLastRefreshDate() + return } - } + PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageFetchingFailed, error: error)) + } } /// Processes the messages received from S3 and returns those which the user is eligible for. This is done by checking each of the attributes against the user's local state. /// Because the result of the message fetch is cached, it means that they won't be immediately updated if the user suddenly qualifies, but the refresh interval for remote messages is only 1 hour so it /// won't take long for the message to appear to the user. private func process(messages: [SurveyRemoteMessage]) async -> [SurveyRemoteMessage] { - let filteredMessages = messages.filter { message in + guard let token = subscriptionManager.accountManager.accessToken else { + return [] + } + + guard case let .success(subscription) = await subscriptionManager.subscriptionService.getSubscription(accessToken: token) else { + return [] + } + + return messages.filter { message in + // Check subscription status: + if let messageSubscriptionStatus = message.attributes.subscriptionStatus { + if let subscriptionStatus = Subscription.Status(rawValue: messageSubscriptionStatus) { + return subscription.status == subscriptionStatus + } else { + // If we received a subscription status but can't map it to a valid type, don't show the message. + return false + } + } - if let subscriptionStatus = message.attributes.subscriptionStatus { + // Check subscription start date: + if let messageDaysSinceSubscriptionStarted = message.attributes.minimumDaysSinceSubscriptionStarted { + guard let daysSinceSubscriptionStartDate = Calendar.current.dateComponents( + [.day], from: subscription.startedAt, to: Date() + ).day else { + return false + } + return daysSinceSubscriptionStartDate >= messageDaysSinceSubscriptionStarted } - // Check VPN usage: + // Check subscription end/expiration date: + if let messageDaysUntilSubscriptionExpiration = message.attributes.maximumDaysUntilSubscriptionExpirationOrRenewal { + guard let daysUntilSubscriptionExpiration = Calendar.current.dateComponents( + [.day], from: subscription.expiresOrRenewsAt, to: Date() + ).day else { + return false + } + return daysUntilSubscriptionExpiration <= messageDaysUntilSubscriptionExpiration + } + + // Check VPN usage: if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled, let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation() { if requiredDaysSinceActivation <= daysSinceActivation { diff --git a/UnitTests/HomePage/ContinueSetUpModelTests.swift b/UnitTests/HomePage/ContinueSetUpModelTests.swift index 8f0370d6ed..0986fc1150 100644 --- a/UnitTests/HomePage/ContinueSetUpModelTests.swift +++ b/UnitTests/HomePage/ContinueSetUpModelTests.swift @@ -25,8 +25,8 @@ final class MockSurveyRemoteMessaging: SurveyRemoteMessaging { var messages: [SurveyRemoteMessage] = [] - func fetchRemoteMessages(completion fetchCompletion: (() -> Void)? = nil) { - fetchCompletion?() + func fetchRemoteMessages() async { + return } func presentableRemoteMessages() -> [SurveyRemoteMessage] { diff --git a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift index a04c64bb7f..a57f15cf9f 100644 --- a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -17,19 +17,33 @@ // import XCTest +import Subscription +import SubscriptionTestingUtilities @testable import DuckDuckGo_Privacy_Browser +@available(macOS 12.0, *) final class SurveyRemoteMessagingTests: XCTestCase { private var defaults: UserDefaults! private let testGroupName = "remote-messaging" + private var subscriptionManager: SubscriptionManaging! + override func setUp() { defaults = UserDefaults(suiteName: testGroupName)! defaults.removePersistentDomain(forName: testGroupName) + + subscriptionManager = SubscriptionManagerMock( + accountManager: AccountManagerMock(isUserAuthenticated: true), + subscriptionService: SubscriptionService(currentServiceEnvironment: .staging), + authService: AuthService(currentServiceEnvironment: .staging), + storePurchaseManager: StorePurchaseManager(), + currentEnvironment: .default, + canPurchase: true + ) } - func testWhenFetchingRemoteMessages_AndTheUserDidNotSignUpViaWaitlist_ThenMessagesAreFetched() { + func testWhenFetchingRemoteMessages_AndTheUserDidNotSignUpViaWaitlist_ThenMessagesAreFetched() async { let request = MockNetworkProtectionRemoteMessagingRequest() request.result = .success([]) let storage = MockSurveyRemoteMessagingStorage() @@ -38,6 +52,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -45,16 +60,15 @@ final class SurveyRemoteMessagingTests: XCTestCase { let expectation = expectation(description: "Remote Message Fetch") - messaging.fetchRemoteMessages { - expectation.fulfill() - } + await messaging.fetchRemoteMessages() + expectation.fulfill() wait(for: [expectation], timeout: 1.0) XCTAssertTrue(request.didFetchMessages) } - func testWhenFetchingRemoteMessages_AndTheUserDidSignUpViaWaitlist_ButUserHasNotActivatedNetP_ThenMessagesAreFetched() { + func testWhenFetchingRemoteMessages_AndTheUserDidSignUpViaWaitlist_ButUserHasNotActivatedNetP_ThenMessagesAreFetched() async { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() @@ -64,6 +78,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -73,16 +88,13 @@ final class SurveyRemoteMessagingTests: XCTestCase { let expectation = expectation(description: "Remote Message Fetch") - messaging.fetchRemoteMessages { - expectation.fulfill() - } - - wait(for: [expectation], timeout: 1.0) + await messaging.fetchRemoteMessages() + await fulfillment(of: [expectation], timeout: 1.0) XCTAssertTrue(request.didFetchMessages) } - func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ThenMessagesAreFetched_AndMessagesAreStored() { + func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ThenMessagesAreFetched_AndMessagesAreStored() async { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() @@ -95,6 +107,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -105,17 +118,14 @@ final class SurveyRemoteMessagingTests: XCTestCase { let expectation = expectation(description: "Remote Message Fetch") - messaging.fetchRemoteMessages { - expectation.fulfill() - } - - wait(for: [expectation], timeout: 1.0) + await messaging.fetchRemoteMessages() + await fulfillment(of: [expectation], timeout: 1.0) XCTAssertTrue(request.didFetchMessages) XCTAssertEqual(storage.storedMessages(), messages) } - func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ButRateLimitedOperationCannotRunAgain_ThenMessagesAreNotFetched() { + func testWhenFetchingRemoteMessages_AndWaitlistUserHasActivatedNetP_ButRateLimitedOperationCannotRunAgain_ThenMessagesAreNotFetched() async { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() @@ -127,6 +137,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: .days(7), // Use a large number to hit the refresh check userDefaults: defaults @@ -136,11 +147,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { let expectation = expectation(description: "Remote Message Fetch") - messaging.fetchRemoteMessages { - expectation.fulfill() - } - - wait(for: [expectation], timeout: 1.0) + await messaging.fetchRemoteMessages() + await fulfillment(of: [expectation], timeout: 1.0) XCTAssertFalse(request.didFetchMessages) XCTAssertEqual(storage.storedMessages(), []) @@ -159,6 +167,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -184,6 +193,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -204,6 +214,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -224,6 +235,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, + subscriptionManager: subscriptionManager, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -265,14 +277,9 @@ private final class MockNetworkProtectionRemoteMessagingRequest: HomePageRemoteM var result: Result<[SurveyRemoteMessage], Error>! var didFetchMessages: Bool = false - func fetchHomePageRemoteMessages(completion: @escaping (Result<[T], Error>) -> Void) where T: Decodable { + func fetchHomePageRemoteMessages() async -> Result<[SurveyRemoteMessage], any Error> { didFetchMessages = true - - if let castResult = self.result as? Result<[T], Error> { - completion(castResult) - } else { - fatalError("Could not cast result to expected type") - } + return result } } From 67a083b840e6026be7b6b8cff09df484ae5e208b Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 10:08:07 -0700 Subject: [PATCH 07/24] Fix test suite compilation. --- .../HomePage/SurveyRemoteMessagingTests.swift | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift index a57f15cf9f..9397754279 100644 --- a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -58,12 +58,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { userDefaults: defaults ) - let expectation = expectation(description: "Remote Message Fetch") - await messaging.fetchRemoteMessages() - expectation.fulfill() - - wait(for: [expectation], timeout: 1.0) XCTAssertTrue(request.didFetchMessages) } @@ -86,10 +81,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { XCTAssertNil(activationDateStorage.daysSinceActivation()) - let expectation = expectation(description: "Remote Message Fetch") - await messaging.fetchRemoteMessages() - await fulfillment(of: [expectation], timeout: 1.0) XCTAssertTrue(request.didFetchMessages) } @@ -116,10 +108,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { XCTAssertEqual(storage.storedMessages(), []) XCTAssertNotNil(activationDateStorage.daysSinceActivation()) - let expectation = expectation(description: "Remote Message Fetch") - await messaging.fetchRemoteMessages() - await fulfillment(of: [expectation], timeout: 1.0) XCTAssertTrue(request.didFetchMessages) XCTAssertEqual(storage.storedMessages(), messages) @@ -145,10 +134,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { XCTAssertNotNil(activationDateStorage.daysSinceActivation()) - let expectation = expectation(description: "Remote Message Fetch") - await messaging.fetchRemoteMessages() - await fulfillment(of: [expectation], timeout: 1.0) XCTAssertFalse(request.didFetchMessages) XCTAssertEqual(storage.storedMessages(), []) @@ -185,7 +171,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let hiddenMessage = mockMessage(id: "123", daysSinceNetworkProtectionEnabled: 10) + let hiddenMessage = mockMessage(id: "123", daysSinceVPNEnabled: 10) let activeMessage = mockMessage(id: "456") try? storage.store(messages: [hiddenMessage, activeMessage]) activationDateStorage._daysSinceActivation = 5 @@ -208,7 +194,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let hiddenMessage = mockMessage(id: "123", requiresNetPAccess: true) + let hiddenMessage = mockMessage(id: "123") try? storage.store(messages: [hiddenMessage]) let messaging = DefaultSurveyRemoteMessaging( @@ -229,7 +215,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { let storage = MockSurveyRemoteMessagingStorage() let activationDateStorage = MockWaitlistActivationDateStore() - let message = mockMessage(id: "123", requiresNetPUsage: false, requiresNetPAccess: true) + let message = mockMessage(id: "123") try? storage.store(messages: [message]) let messaging = DefaultSurveyRemoteMessaging( @@ -246,20 +232,25 @@ final class SurveyRemoteMessagingTests: XCTestCase { } private func mockMessage(id: String, - daysSinceNetworkProtectionEnabled: Int = 0, - requiresNetPUsage: Bool = true, - requiresNetPAccess: Bool = true) -> SurveyRemoteMessage { + subscriptionStatus: String = "", + minimumDaysSinceSubscriptionStarted: Int = 0, + maximumDaysUntilSubscriptionExpirationOrRenewal: Int = 0, + daysSinceVPNEnabled: Int = 0, + daysSincePIREnabled: Int = 0) -> SurveyRemoteMessage { let remoteMessageJSON = """ { "id": "\(id)", - "daysSinceNetworkProtectionEnabled": \(daysSinceNetworkProtectionEnabled), "cardTitle": "Title", - "cardDescription": "Description", - "surveyURL": "https://duckduckgo.com/", - "requiresNetworkProtectionUsage": \(String(describing: requiresNetPUsage)), - "requiresNetworkProtectionAccess": \(String(describing: requiresNetPAccess)), + "cardDescription": "Description 1", + "attributes": { + "subscriptionStatus": "\(subscriptionStatus)", + "minimumDaysSinceSubscriptionStarted": \(minimumDaysSinceSubscriptionStarted), + "maximumDaysUntilSubscriptionExpirationOrRenewal": \(maximumDaysUntilSubscriptionExpirationOrRenewal), + "daysSinceVPNEnabled": \(daysSinceVPNEnabled), + "daysSincePIREnabled": \(daysSincePIREnabled) + }, "action": { - "actionTitle": "Action" + "actionTitle": "Action 1" } } """ From f7564af232a143876e4a03a15533e123bf44cb19 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 11:07:41 -0700 Subject: [PATCH 08/24] Fix up unit tests. --- .../NetworkProtectionDebugMenu.swift | 5 +- .../SurveyRemoteMessaging.swift | 39 ++++++-- .../HomePage/SurveyRemoteMessagingTests.swift | 96 +++++++------------ 3 files changed, 68 insertions(+), 72 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index e26c61ae15..749e95c02b 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -485,10 +485,7 @@ final class NetworkProtectionDebugMenu: NSMenu { @objc func resetNetworkProtectionRemoteMessages(_ sender: Any?) { DefaultSurveyRemoteMessagingStorage.surveys().removeStoredAndDismissedMessages() - DefaultSurveyRemoteMessaging( - subscriptionManager: Application.appDelegate.subscriptionManager, - minimumRefreshInterval: 0 - ).resetLastRefreshTimestamp() + DefaultSurveyRemoteMessaging(subscriptionManager: Application.appDelegate.subscriptionManager).resetLastRefreshTimestamp() } @objc func overrideNetworkProtectionActivationDateToNow(_ sender: Any?) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift index b07c5e9496..d25f3e5064 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift @@ -29,6 +29,12 @@ protocol SurveyRemoteMessaging { } +protocol SurveyRemoteMessageSubscriptionFetching { + + func getSubscription(accessToken: String) async -> Result + +} + final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { enum Constants { @@ -37,23 +43,33 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { private let messageRequest: HomePageRemoteMessagingRequest private let messageStorage: SurveyRemoteMessagingStorage - private let subscriptionManager: SubscriptionManaging + private let accountManager: AccountManaging + private let subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching private let waitlistActivationDateStore: WaitlistActivationDateStore private let minimumRefreshInterval: TimeInterval private let userDefaults: UserDefaults convenience init(subscriptionManager: SubscriptionManaging) { #if DEBUG || REVIEW - self.init(subscriptionManager: subscriptionManager, minimumRefreshInterval: .seconds(30)) + self.init( + accountManager: subscriptionManager.accountManager, + subscriptionFetcher: subscriptionManager.subscriptionService, + minimumRefreshInterval: .seconds(30) + ) #else - self.init(subscriptionManager: subscriptionManager, minimumRefreshInterval: .hours(1)) + self.init( + accountManager: subscriptionManager.accountManager, + subscriptionFetcher: subscriptionManager.subscriptionService, + minimumRefreshInterval: .hours(1) + ) #endif } init( messageRequest: HomePageRemoteMessagingRequest = DefaultHomePageRemoteMessagingRequest.surveysRequest(), messageStorage: SurveyRemoteMessagingStorage = DefaultSurveyRemoteMessagingStorage.surveys(), - subscriptionManager: SubscriptionManaging, + accountManager: AccountManaging, + subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching, waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), minimumRefreshInterval: TimeInterval, @@ -61,7 +77,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { ) { self.messageRequest = messageRequest self.messageStorage = messageStorage - self.subscriptionManager = subscriptionManager + self.accountManager = accountManager + self.subscriptionFetcher = subscriptionFetcher self.waitlistActivationDateStore = waitlistActivationDateStore self.minimumRefreshInterval = minimumRefreshInterval self.userDefaults = userDefaults @@ -98,11 +115,11 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { /// Because the result of the message fetch is cached, it means that they won't be immediately updated if the user suddenly qualifies, but the refresh interval for remote messages is only 1 hour so it /// won't take long for the message to appear to the user. private func process(messages: [SurveyRemoteMessage]) async -> [SurveyRemoteMessage] { - guard let token = subscriptionManager.accountManager.accessToken else { + guard let token = accountManager.accessToken else { return [] } - guard case let .success(subscription) = await subscriptionManager.subscriptionService.getSubscription(accessToken: token) else { + guard case let .success(subscription) = await subscriptionFetcher.getSubscription(accessToken: token) else { return [] } @@ -201,3 +218,11 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } } + +extension SubscriptionService: SurveyRemoteMessageSubscriptionFetching { + + func getSubscription(accessToken: String) async -> Result { + return await self.getSubscription(accessToken: accessToken, cachePolicy: .returnCacheDataElseLoad) + } + +} diff --git a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift index 9397754279..e4591c707d 100644 --- a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -17,8 +17,8 @@ // import XCTest -import Subscription import SubscriptionTestingUtilities +@testable import Subscription @testable import DuckDuckGo_Privacy_Browser @available(macOS 12.0, *) @@ -27,20 +27,15 @@ final class SurveyRemoteMessagingTests: XCTestCase { private var defaults: UserDefaults! private let testGroupName = "remote-messaging" - private var subscriptionManager: SubscriptionManaging! + private var accountManager: AccountManaging! + private var subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching! override func setUp() { defaults = UserDefaults(suiteName: testGroupName)! defaults.removePersistentDomain(forName: testGroupName) - subscriptionManager = SubscriptionManagerMock( - accountManager: AccountManagerMock(isUserAuthenticated: true), - subscriptionService: SubscriptionService(currentServiceEnvironment: .staging), - authService: AuthService(currentServiceEnvironment: .staging), - storePurchaseManager: StorePurchaseManager(), - currentEnvironment: .default, - canPurchase: true - ) + accountManager = AccountManagerMock(isUserAuthenticated: true, accessToken: "mock-token") + subscriptionFetcher = MockSubscriptionFetcher() } func testWhenFetchingRemoteMessages_AndTheUserDidNotSignUpViaWaitlist_ThenMessagesAreFetched() async { @@ -52,7 +47,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - subscriptionManager: subscriptionManager, + accountManager: accountManager, + subscriptionFetcher: subscriptionFetcher, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -73,7 +69,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - subscriptionManager: subscriptionManager, + accountManager: accountManager, + subscriptionFetcher: subscriptionFetcher, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -99,7 +96,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - subscriptionManager: subscriptionManager, + accountManager: accountManager, + subscriptionFetcher: subscriptionFetcher, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -126,7 +124,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - subscriptionManager: subscriptionManager, + accountManager: accountManager, + subscriptionFetcher: subscriptionFetcher, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: .days(7), // Use a large number to hit the refresh check userDefaults: defaults @@ -153,7 +152,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - subscriptionManager: subscriptionManager, + accountManager: accountManager, + subscriptionFetcher: subscriptionFetcher, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -166,50 +166,6 @@ final class SurveyRemoteMessagingTests: XCTestCase { XCTAssertEqual(presentableMessagesAfter, [activeMessage]) } - func testWhenStoredMessagesExist_AndSomeMessagesRequireDaysActive_ThenPresentableMessagesDoNotIncludeInvalidMessages() { - let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockSurveyRemoteMessagingStorage() - let activationDateStorage = MockWaitlistActivationDateStore() - - let hiddenMessage = mockMessage(id: "123", daysSinceVPNEnabled: 10) - let activeMessage = mockMessage(id: "456") - try? storage.store(messages: [hiddenMessage, activeMessage]) - activationDateStorage._daysSinceActivation = 5 - - let messaging = DefaultSurveyRemoteMessaging( - messageRequest: request, - messageStorage: storage, - subscriptionManager: subscriptionManager, - waitlistActivationDateStore: activationDateStorage, - minimumRefreshInterval: 0, - userDefaults: defaults - ) - - let presentableMessagesAfter = messaging.presentableRemoteMessages() - XCTAssertEqual(presentableMessagesAfter, [activeMessage]) - } - - func testWhenStoredMessagesExist_AndSomeMessagesNetPVisibility_ThenPresentableMessagesDoNotIncludeInvalidMessages() { - let request = MockNetworkProtectionRemoteMessagingRequest() - let storage = MockSurveyRemoteMessagingStorage() - let activationDateStorage = MockWaitlistActivationDateStore() - - let hiddenMessage = mockMessage(id: "123") - try? storage.store(messages: [hiddenMessage]) - - let messaging = DefaultSurveyRemoteMessaging( - messageRequest: request, - messageStorage: storage, - subscriptionManager: subscriptionManager, - waitlistActivationDateStore: activationDateStorage, - minimumRefreshInterval: 0, - userDefaults: defaults - ) - - let presentableMessages = messaging.presentableRemoteMessages() - XCTAssertEqual(presentableMessages, []) - } - func testWhenStoredMessagesExist_AndSomeMessagesRequireNetPUsage_ThenPresentableMessagesDoNotIncludeInvalidMessages() { let request = MockNetworkProtectionRemoteMessagingRequest() let storage = MockSurveyRemoteMessagingStorage() @@ -221,7 +177,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { let messaging = DefaultSurveyRemoteMessaging( messageRequest: request, messageStorage: storage, - subscriptionManager: subscriptionManager, + accountManager: accountManager, + subscriptionFetcher: subscriptionFetcher, waitlistActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults @@ -232,7 +189,7 @@ final class SurveyRemoteMessagingTests: XCTestCase { } private func mockMessage(id: String, - subscriptionStatus: String = "", + subscriptionStatus: String = Subscription.Status.autoRenewable.rawValue, minimumDaysSinceSubscriptionStarted: Int = 0, maximumDaysUntilSubscriptionExpirationOrRenewal: Int = 0, daysSinceVPNEnabled: Int = 0, @@ -314,3 +271,20 @@ final class MockWaitlistActivationDateStore: WaitlistActivationDateStore { } } + +final class MockSubscriptionFetcher: SurveyRemoteMessageSubscriptionFetching { + + var subscription: Subscription = Subscription( + productId: "product", + name: "name", + billingPeriod: .monthly, + startedAt: Date(), + expiresOrRenewsAt: Date(), + platform: .apple, + status: .autoRenewable) + + func getSubscription(accessToken: String) async -> Result { + return .success(subscription) + } + +} From c47e4ab55e5d0cb7903f688266796f1e804b80d6 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 16:16:12 -0700 Subject: [PATCH 09/24] Hide other surveys when the Privacy Pro survey is visible. --- .../PrivacyProSurvey.imageset/Contents.json | 12 ++++++++++++ .../Privacy-Pro-128.pdf | Bin 0 -> 14556 bytes .../Model/HomePageContinueSetUpModel.swift | 5 +++-- .../HomePage/Resources/survey-messages.json | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Images/PrivacyProSurvey.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/PrivacyProSurvey.imageset/Privacy-Pro-128.pdf diff --git a/DuckDuckGo/Assets.xcassets/Images/PrivacyProSurvey.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/PrivacyProSurvey.imageset/Contents.json new file mode 100644 index 0000000000..85ed0dc199 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/PrivacyProSurvey.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Privacy-Pro-128.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/PrivacyProSurvey.imageset/Privacy-Pro-128.pdf b/DuckDuckGo/Assets.xcassets/Images/PrivacyProSurvey.imageset/Privacy-Pro-128.pdf new file mode 100644 index 0000000000000000000000000000000000000000..18085b7a093a947728b9d5bf2e5100cac4e649b1 GIT binary patch literal 14556 zcmeI3U5_P4k%sTjuc#YKfaEioRayBVAxmHzv;wr=@!p}mXxfb#)HBUaH^}hodEUr8 zQFZzpGd&0{*rhGm<*2I6_>MOs>wNOn7eD(n9?Rhnr!YPI=J$tb`tgsaS6_bj^z*Nu zZl3P?|7H8n?adU9bI{-P*=Ia?@%QS>^x`My7eBea_{rtPPsW!#`PpZZ^X}a@Qp{mK z)6g&P-hKP@yJ>;=T2DPby}!G8dwBK5-Jjmv-o5_$Pp8kn-v8h2u=8LReayQa!eQb^G=Zj_aa7tnvKf@%G(eJ+5pU&TtZm}-_K=QC ziD^mDYSZ6z4=t_`E3}q)ql}AGrg2M4*y90;7MSaFd+5u?F!hbf%v#HF1ODwZ-mXHo5a>3vAG0;27@PdT(EKB zLde#O5%=P+UCUjtx(>Br3*S%g4Zm@|l|+hhXcTVlo6_4AA0Butdhs|)i+ALh84D6mFlf@%_=0_=sUrN7*3IWTDOnW*5iU}m_j6&s|{QD ze)5swH_n%QL~e#BM|e|CM{LzA0XDVAU?>JBsn%X^C3XZO?)th-s2 z^(7cu_Wj%+&O8R}Ly{3gB5vs=Ev4&~6wriC2B0^{-d z#F0Pl6v6KCxYPXN@|G_kbTq{DZTxX(*dKSwd5X+0S8&99U9Btoyj;QdXoKZUZ$Elx_;gbh!kCAwnY(qFY+iu+FSzBq@>p zBBWA4PxfkV+BE`ji|jtO0A+%aT3q0`^U;LvTUaa4ocI;^jWptd>_f1M!PGeNSjI5` zHGw%#)%Ibj;6!c>qQ_>u~ki#4oID)3Z+|Mg5kf6y;RDwvtV5ga%&aS1#o72q}N z3>EF7KW0uSYbG!&@yL^+VevN2cuOv`p2I{aF3HfiYfe$ENLn#TYABdso=d3>r87v0 zB~`*P;>{tWRRuT&53LdsxP6QTM@7RVfTjW1ir(VTdZyiLbV_&toYAK6Hcpq595gYp z!F&tJX(9Q{go|`dux1hscf+!#m9?QYYBj-3lES%?r2_b^SuTWRk)Z0{yB{ zgW&RDM4)Yy?U$wG*@Bq_v4)i~Vj-)REXL^#GmZrlB$<@(YocT>ZM8wtik1l`R7T^o zhJwi$hs0t7H)1U{e7%#%qmwf9OuRH^P`sKL%bA~L@)zw*-vSkr-V+I}bv}wRH3KJF zx_rhj8)US>Y9?6AeV|)&L!7Pv+`0#IIcbUuMI;f9Bg^HbQp=ubqLzSHnC3}ckzihv z0L?IwT1iFZLdMJ%etFJ|QliStoK-y3Lt%f@EWwHj^zss_I525Tz^F#0+EJyZs7x4e zIkW0g6aI)u7k>RuiYf%K&KMpF`RMP9DtNz$DwKOAs-XT0MU^~M{GLY@@rT2oM-|a| z5mkz4O2}DMY2rVkia`9hsER6)e^*r1c8aQciYgrVSybT$c<8gJ!fmZY?xIR5!b;>n zF{)tr9}`vNS90Y?N0sE$-|mdaOA)1}k{v-nu*lf2k_?`eDvCF#L?ihMgtVYi1p(&R zR4%TK6rt({&|1jXV|;DrEi*`yLnodGWO4SvpeZF4HUMSRv`}6}!dd#zk9-lyo|K)y z8IYE5nZ&5cn_vyR!8ovntfEO(Da5%3b%hMfd(`*s_5=Em zHbIdAT%=@3fgD(vN&=+BDJh0Xr8-2P&|;&oq5DKt3|1CNsnl<95CAgMfNDV?K0{5( z%uufZY6x1mp$kxAr(9DL@r{@(NQ__r+RPlyVBH9-9hqxeVN^01X_?}MGAa5X1@#Fb zt}4}pGpAZ8X(Oq^P_Y8hz7Y|e6PQ5Q>s63J4hD`OqW zg-Q!ZA%Pz7Q?d_`f$9lfGh&ejMHFP>&(KII>7>2?89- z(koB~EzDp^LWLNRrvlBaEul5U<`b|O*HF2|KLJr-tBDq0Sc`=TE%lMBx-9D`7exeK zXR!$U!9O`gP-jK(5@(w5;v94Ces0uALnSI0f-X%SPh*M=4D@MpWP*#8!$88DZVbSP zoRk{UPR?0J3JsVW8~bpTR=O6RH0V)vR?2}i*aH{Qb8SXt;+#PRux_lG>Cw1P_5kPs zIP?xjnj{-_rt~O^Pz6ksgBk2y=EOhZEzc29X()=VXA3A?9rqEC4)neF5y68VSM*~D zaHcy_SAs4{Q#iNvbI3!Oi8g1rgvN%n>Gn3UiYMc?(g8^hP1EgWYK_67OzTH)-*u&q zon$rB1nJllE2Szx%G>mfbS0t7xB`%8HA&TQwA2eS$IqC{>1g((GkJv_Zzez_MwD_} zMa?P~IB0(^kYx!(Rb4gXW-R3~UPTW9ylA_!rILuUY88ew?$QJfdn($6T_RZs9!xmcVsye_jR5|#W8=Mv6u8r?!>Yd z7kDi1LgZDYR6#J(H7yXHy^`yd9u#Fb1tfISC^(8nv@OpxB91Rz5Tfc^2vNF8B3s=G zF-2aYi6+mNRxOl8BBXkBpM|{g&6RF|Oh8s6^}(1m66zXw-Fgd^Q|m28vAO6$)V^DK zrH3l&!Bmu(6KJ5I$pV!D??qLGiiP@+m*Q)k4*{x3C8;wFAxJDY(1?-pQ7MtjDV^N# zO4oFW1o5B?#E9C^@0E@X>P2tYdqDLA-UH%ySGvWkedV?O^LnSMI`FKgJ@vluD;Kfe z_j|fz;RgC?xZIHcmA?CWCv&yuN%W`Bdm~Erl`iT^zjdW8yV}0>HcvQ2%C$D{N;7yB z-d^HhtHNhZC~1QLxolP|7-42u(A^2Jqg+O4DdNrPKv@d?z}GGSB6CiG)9~ zPuSRw3`ZU`WE%-3zS>fjlbfg=RzIhDJh@LHH&|hZuGQTZmmDXmCLxpgPzEoeJzOK&*9uT>(yk z-AG~Ez%JG?uv=Dr*hFe$GvPOZ-3lb9oEg}yVoI|ya)=7Cs0&z_Vkq4-%`Y(!U>CHZ z5(jco$~CYHALJHI=s@qPD+YEcwYaD(PQkMY1GR9`gaOxJc!Ia6c2lQ2dSKAt6c!H{dd4%SuDbxs*%{hevS4)F0?Mdn(utvtEpNg2mk~e@; zC79lcD4?$s6&Bc9F4pKha5iW*jl*3?_zcppsX+%MA(XA8#o39r6^$15yR1 zYz!@$-W81)g;m8oc%KoUPF1+4yinNPkOKZLD?s5MYLsiBN17;Q9gJa38pGBIGGs|~ zfcY-d)_EJ`nQNP1>s|BU(XW%%wlhHxwBf49GAuSfYq)8f7~f&7IE$STALaCp77Y%T!2u2FDPNYGXQiP@w!0(wN7-# zbj0w?S_;H&RI|x5k<22UHUuX}cZ=^bqx0b+x-G1{c6t1!ujCY8U}uGmv7H$<0eq27 zOkZUqompawh|L1qmD^$^FLaKTaQc`e0N_MH1RN^R9{8-WjC(&2fww5D{zq*Nai0QzB5g(@+7RMJN9<7LX2D;3VmX@d3-B>5YoHK-QFdUk-(*#=cs3 z?7ERanb&t)C>W;Q7<)~kj4tQ$0CjP*(NlCb5vP0 z#R$1_uGZ@(5E_&~Rs?CmvY}lgX*gyqD>8t9Eb~f4-(}-VILUY z4H6PTqFIQ1G@^*6g%gf}$V7rq70e;90bmsDL{|-Jk&kqezGmT|Sg>Uy(rYlhL8bSw zf-CcZu^N;s#Qr+)Z~r9jrXvV($Rv-^x|jhYCnH_RD`jH~zP(_bu??~-WC#;AIwIn} z2BavEIvi%~rcw#CL~nxx?!tFW7AK+)I3aUeQwwlZ4@|Rz3+WhxQR})16>*gSPzAM4R~cbW(KH_WN;hsK3o8NX=RC9&TDwx zqLr^YaM5ut!#LAkRQq7*gX4dOlsg;hV;v1<-GIg@^P{~{O(4;F^}*?E96*;~w4RIU zG5k4?3R9uJ0&xlnLq3KkcBRKGLZ70rRw2`rtNV-`nSy6qZX|2BpBw#NKey;(1O51F z{IcrwGb~WZ_@NpdWh(yjEPmUv`vKce-o1NxdYpd!KmBKFum1bbf4G}oeRcEg-5~tx z?wdC^KYjRa`t>G~%hvo0%I*8G(@L-J9v}Yr{`T&1!d%nqvE1hm@1E}7v7>$^b|QXt z^B&s7z`;nn-;#;6{o3vqZ@z)mNfNxCPWycR@JHT_7yLg$*{y^{b}LE1N8ga&=MBL> zzj?a3fB5$N)$iWl{q_*1a_EIh|DM#UtP|w%$N1B(CWb8iPoKJ+k)nR1c0ppG{Ol<^ zi4pCzmcDdH=0SRTfAi-4?)^bOKm3m`P{$7*AKu>maQeZUZ+_U7^6K@&!_y>zO~J3e de)E?*xVInQ-MoJ?ap)eG Date: Thu, 23 May 2024 16:17:34 -0700 Subject: [PATCH 10/24] Remove duplicate file entry. --- DuckDuckGo.xcodeproj/project.pbxproj | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 694b5e90ef..e06fa97b22 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -169,7 +169,6 @@ 3158B1492B0BF73000AF130C /* DBPHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */; }; 3158B14A2B0BF74300AF130C /* DataBrokerProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316850712AF3AD58009A2828 /* DataBrokerProtectionDebugMenu.swift */; }; 3158B14D2B0BF74D00AF130C /* DataBrokerProtectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */; }; - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; 3158B1532B0BF75700AF130C /* LoginItem+DataBrokerProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */; }; 3158B1562B0BF75D00AF130C /* DataBrokerProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C5FFB82AF64D120008A79F /* DataBrokerProtectionFeatureVisibility.swift */; }; 3158B1592B0BF76400AF130C /* DataBrokerProtectionFeatureDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3199C6F82AF94F5B002A7BA1 /* DataBrokerProtectionFeatureDisabler.swift */; }; @@ -221,7 +220,6 @@ 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */; }; 31EF1E802B63FFA800E6DB17 /* DBPHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */; }; 31EF1E812B63FFB800E6DB17 /* DataBrokerProtectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */; }; - 31EF1E822B63FFC200E6DB17 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; 31EF1E832B63FFCA00E6DB17 /* LoginItem+DataBrokerProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */; }; 31EF1E842B63FFD100E6DB17 /* DataBrokerProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316850712AF3AD58009A2828 /* DataBrokerProtectionDebugMenu.swift */; }; 31F28C4F28C8EEC500119F70 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; @@ -1200,6 +1198,7 @@ 4B5A4F4C27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A4F4B27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift */; }; 4B5FF67826B602B100D42879 /* FirefoxDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FF67726B602B100D42879 /* FirefoxDataImporter.swift */; }; 4B65143E263924B5005B46EB /* EmailUrlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B65143D263924B5005B46EB /* EmailUrlExtensions.swift */; }; + 4B66E3D42C00060500934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; 4B677432255DBEB800025BD8 /* httpsMobileV2BloomSpec.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B677427255DBEB800025BD8 /* httpsMobileV2BloomSpec.json */; }; 4B677433255DBEB800025BD8 /* httpsMobileV2Bloom.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B677428255DBEB800025BD8 /* httpsMobileV2Bloom.bin */; }; 4B677435255DBEB800025BD8 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; @@ -9813,6 +9812,7 @@ F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */, 3706FB40293F65D500E42796 /* ContextualMenu.swift in Sources */, + 4B66E3D42C00060500934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 3706FB41293F65D500E42796 /* NavigationBarViewController.swift in Sources */, 3707C71C294B5D1900682A9F /* TabExtensionsBuilder.swift in Sources */, 3706FB42293F65D500E42796 /* MainViewController.swift in Sources */, @@ -10182,7 +10182,6 @@ 3706FC34293F65D500E42796 /* PermissionAuthorizationViewController.swift in Sources */, 3706FC35293F65D500E42796 /* BookmarkNode.swift in Sources */, B6ABD0CB2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */, - 31EF1E822B63FFC200E6DB17 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, B6B140892ABDBCC1004F8E85 /* HoverTrackingArea.swift in Sources */, 3706FC36293F65D500E42796 /* LongPressButton.swift in Sources */, 3706FC37293F65D500E42796 /* CoreDataStore.swift in Sources */, @@ -11136,9 +11135,7 @@ 85707F26276A335700DC0649 /* Onboarding.swift in Sources */, B68C92C1274E3EF4002AC6B0 /* PopUpWindow.swift in Sources */, AA5FA6A0275F948900DCE9C9 /* Favicons.xcdatamodeld in Sources */, - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 9F6434612BEC82B700D2D8A0 /* AttributionPixelHandler.swift in Sources */, - 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemInterface.swift in Sources */, B684592225C93BE000DC17B6 /* Publisher.asVoid.swift in Sources */, 4B9DB01D2A983B24000927DB /* Waitlist.swift in Sources */, BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */, From 2fdcf49d9026f804cdad27746eb765017acdb13f Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 16:32:51 -0700 Subject: [PATCH 11/24] Debug menu for survey remote message. --- DuckDuckGo/Menus/MainMenu.swift | 4 ++++ DuckDuckGo/Menus/MainMenuActions.swift | 5 +++++ .../BothAppTargets/NetworkProtectionDebugMenu.swift | 8 -------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 3a41e65525..c7e3b6d6ae 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -642,6 +642,10 @@ import SubscriptionUI currentViewController: { WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController }, subscriptionManager: Application.appDelegate.subscriptionManager) + NSMenuItem(title: "Privacy Pro Survey") { + NSMenuItem(title: "Reset Remote Message Cache", action: #selector(MainViewController.resetSurveyRemoteMessages)) + } + NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) } debugMenu.addItem(internalUserItem) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 103b90bc81..dab061f410 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -924,6 +924,11 @@ extension MainViewController { setConfigurationUrl(nil) } + @objc func resetSurveyRemoteMessages(_ sender: Any?) { + DefaultSurveyRemoteMessagingStorage.surveys().removeStoredAndDismissedMessages() + DefaultSurveyRemoteMessaging(subscriptionManager: Application.appDelegate.subscriptionManager).resetLastRefreshTimestamp() + } + // MARK: - Developer Tools @objc func toggleDeveloperTools(_ sender: Any?) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 749e95c02b..23fb71b7ab 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -76,9 +76,6 @@ final class NetworkProtectionDebugMenu: NSMenu { NSMenuItem(title: "Remove Network Extension and Login Items", action: #selector(NetworkProtectionDebugMenu.removeSystemExtensionAndAgents)) .targetting(self) - - NSMenuItem(title: "Reset Remote Messages", action: #selector(NetworkProtectionDebugMenu.resetNetworkProtectionRemoteMessages)) - .targetting(self) } NSMenuItem.separator() @@ -483,11 +480,6 @@ final class NetworkProtectionDebugMenu: NSMenu { overrideNetworkProtectionActivationDate(to: nil) } - @objc func resetNetworkProtectionRemoteMessages(_ sender: Any?) { - DefaultSurveyRemoteMessagingStorage.surveys().removeStoredAndDismissedMessages() - DefaultSurveyRemoteMessaging(subscriptionManager: Application.appDelegate.subscriptionManager).resetLastRefreshTimestamp() - } - @objc func overrideNetworkProtectionActivationDateToNow(_ sender: Any?) { overrideNetworkProtectionActivationDate(to: Date()) } From e598a9fbfe03f75c42393efff6887086aa468f1d Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 16:36:04 -0700 Subject: [PATCH 12/24] Really fix duplicate file issue. --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e06fa97b22..cb6f4dcf59 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1198,7 +1198,8 @@ 4B5A4F4C27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A4F4B27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift */; }; 4B5FF67826B602B100D42879 /* FirefoxDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FF67726B602B100D42879 /* FirefoxDataImporter.swift */; }; 4B65143E263924B5005B46EB /* EmailUrlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B65143D263924B5005B46EB /* EmailUrlExtensions.swift */; }; - 4B66E3D42C00060500934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; + 4B66E3D92C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; + 4B66E3DA2C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; 4B677432255DBEB800025BD8 /* httpsMobileV2BloomSpec.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B677427255DBEB800025BD8 /* httpsMobileV2BloomSpec.json */; }; 4B677433255DBEB800025BD8 /* httpsMobileV2Bloom.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B677428255DBEB800025BD8 /* httpsMobileV2Bloom.bin */; }; 4B677435255DBEB800025BD8 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; @@ -9812,7 +9813,7 @@ F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */, 3706FB40293F65D500E42796 /* ContextualMenu.swift in Sources */, - 4B66E3D42C00060500934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, + 4B66E3D92C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 3706FB41293F65D500E42796 /* NavigationBarViewController.swift in Sources */, 3707C71C294B5D1900682A9F /* TabExtensionsBuilder.swift in Sources */, 3706FB42293F65D500E42796 /* MainViewController.swift in Sources */, @@ -11636,6 +11637,7 @@ BDA7647C2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */, 3158B1592B0BF76400AF130C /* DataBrokerProtectionFeatureDisabler.swift in Sources */, B655124829A79465009BFE1C /* NavigationActionExtension.swift in Sources */, + 4B66E3DA2C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, 9FA173EB2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 85308E25267FC9F2001ABD76 /* NSAlertExtension.swift in Sources */, B69A14F62B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, From 30a0819c7e09547480e03ea4074a555afb5e0b66 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 17:58:50 -0700 Subject: [PATCH 13/24] Fix subtle VPN presentation survey bug. --- DuckDuckGo.xcodeproj/project.pbxproj | 25 ++++++------------- .../Surveys}/SurveyRemoteMessage.swift | 0 .../Surveys}/SurveyRemoteMessaging.swift | 5 ++-- 3 files changed, 10 insertions(+), 20 deletions(-) rename DuckDuckGo/{NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging => Common/Surveys}/SurveyRemoteMessage.swift (100%) rename DuckDuckGo/{NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging => Common/Surveys}/SurveyRemoteMessaging.swift (97%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9dca5ff20f..d1b5dc0131 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1197,8 +1197,8 @@ 4B5A4F4C27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A4F4B27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift */; }; 4B5FF67826B602B100D42879 /* FirefoxDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FF67726B602B100D42879 /* FirefoxDataImporter.swift */; }; 4B65143E263924B5005B46EB /* EmailUrlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B65143D263924B5005B46EB /* EmailUrlExtensions.swift */; }; - 4B66E3D92C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; - 4B66E3DA2C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D98662ADEB4B000CD35FE /* DataBrokerProtectionLoginItemInterface.swift */; }; + 4B66E3D92C0009DF00934A78 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 4B66E3DA2C0009DF00934A78 /* (null) in Sources */ = {isa = PBXBuildFile; }; 4B677432255DBEB800025BD8 /* httpsMobileV2BloomSpec.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B677427255DBEB800025BD8 /* httpsMobileV2BloomSpec.json */; }; 4B677433255DBEB800025BD8 /* httpsMobileV2Bloom.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B677428255DBEB800025BD8 /* httpsMobileV2Bloom.bin */; }; 4B677435255DBEB800025BD8 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; @@ -4632,7 +4632,6 @@ 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */, 3169132B2BD2C7960051B46D /* ErrorView */, 9D8FA00B2AC5BDCE005DD0D0 /* LoginItem+DataBrokerProtection.swift */, - 4B37EE652B4CFC9500A89A61 /* RemoteMessaging */, ); path = DBP; sourceTree = ""; @@ -5060,9 +5059,11 @@ 4B37EE5B2B4CFC3C00A89A61 /* Surveys */ = { isa = PBXGroup; children = ( - 4B37EE5D2B4CFC3C00A89A61 /* SurveyURLBuilder.swift */, - 4B37EE5C2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift */, 4B37EE5E2B4CFC3C00A89A61 /* HomePageRemoteMessagingRequest.swift */, + 4B37EE5C2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift */, + 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */, + 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */, + 4B37EE5D2B4CFC3C00A89A61 /* SurveyURLBuilder.swift */, ); path = Surveys; sourceTree = ""; @@ -5159,7 +5160,6 @@ 4B4D60612A0B29FA00BCD287 /* DeveloperIDTarget */ = { isa = PBXGroup; children = ( - 4BCF15D52ABB83D70083F6DF /* NetworkProtectionRemoteMessaging */, 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */, 4B2F565B2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift */, ); @@ -5727,15 +5727,6 @@ path = Resources; sourceTree = ""; }; - 4BCF15D52ABB83D70083F6DF /* NetworkProtectionRemoteMessaging */ = { - isa = PBXGroup; - children = ( - 4BCF15D82ABB8A7F0083F6DF /* SurveyRemoteMessage.swift */, - 4BCF15D62ABB8A110083F6DF /* SurveyRemoteMessaging.swift */, - ); - path = NetworkProtectionRemoteMessaging; - sourceTree = ""; - }; 4BCF15E32ABB987F0083F6DF /* NetworkProtection */ = { isa = PBXGroup; children = ( @@ -9812,7 +9803,7 @@ F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */, 3706FB40293F65D500E42796 /* ContextualMenu.swift in Sources */, - 4B66E3D92C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, + 4B66E3D92C0009DF00934A78 /* (null) in Sources */, 3706FB41293F65D500E42796 /* NavigationBarViewController.swift in Sources */, 3707C71C294B5D1900682A9F /* TabExtensionsBuilder.swift in Sources */, 3706FB42293F65D500E42796 /* MainViewController.swift in Sources */, @@ -11637,7 +11628,7 @@ BDA7647C2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */, 3158B1592B0BF76400AF130C /* DataBrokerProtectionFeatureDisabler.swift in Sources */, B655124829A79465009BFE1C /* NavigationActionExtension.swift in Sources */, - 4B66E3DA2C0009DF00934A78 /* DataBrokerProtectionLoginItemInterface.swift in Sources */, + 4B66E3DA2C0009DF00934A78 /* (null) in Sources */, 9FA173EB2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 85308E25267FC9F2001ABD76 /* NSAlertExtension.swift in Sources */, B69A14F62B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift similarity index 100% rename from DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessage.swift rename to DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift similarity index 97% rename from DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift rename to DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift index d25f3e5064..d24d0927b2 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift @@ -158,9 +158,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } // Check VPN usage: - if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled, - let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation() { - if requiredDaysSinceActivation <= daysSinceActivation { + if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled { + if let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation(), requiredDaysSinceActivation <= daysSinceActivation { return true } else { return false From ca90535f47dd65aadf81b6140038d6f38e279c23 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 20:52:00 -0700 Subject: [PATCH 14/24] Clean up pixels. --- .../Common/Surveys/SurveyRemoteMessage.swift | 1 + .../Surveys/SurveyRemoteMessaging.swift | 19 +++++++++++++-- .../Model/HomePageContinueSetUpModel.swift | 2 +- DuckDuckGo/Statistics/GeneralPixel.swift | 24 ++++--------------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift index ad95075070..d33a84a2f8 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift @@ -34,6 +34,7 @@ struct SurveyRemoteMessage: Codable, Equatable, Identifiable, Hashable { struct Attributes: Codable, Equatable, Hashable { let subscriptionStatus: String? + let subscriptionBillingPeriod: String? let minimumDaysSinceSubscriptionStarted: Int? let maximumDaysUntilSubscriptionExpirationOrRenewal: Int? let daysSinceVPNEnabled: Int? diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift index d24d0927b2..a28eb46a0e 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift @@ -98,7 +98,7 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { try self.messageStorage.store(messages: processedMessages) self.updateLastRefreshDate() } catch { - PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageStorageFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.surveyRemoteMessageStorageFailed, error: error)) } case .failure(let error): // Ignore 403 errors, those happen when a file can't be found on S3 @@ -107,10 +107,11 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { return } - PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageFetchingFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.surveyRemoteMessageFetchingFailed, error: error)) } } + // swiftlint:disable cyclomatic_complexity /// Processes the messages received from S3 and returns those which the user is eligible for. This is done by checking each of the attributes against the user's local state. /// Because the result of the message fetch is cached, it means that they won't be immediately updated if the user suddenly qualifies, but the refresh interval for remote messages is only 1 hour so it /// won't take long for the message to appear to the user. @@ -125,6 +126,9 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { return messages.filter { message in + // TODO: Make it so that we check all attributes, not just the first one + var didMatch = false + // Check subscription status: if let messageSubscriptionStatus = message.attributes.subscriptionStatus { if let subscriptionStatus = Subscription.Status(rawValue: messageSubscriptionStatus) { @@ -135,6 +139,16 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } } + // Check subscription billing period: + if let messageSubscriptionBillingPeriod = message.attributes.subscriptionBillingPeriod { + if let subscriptionBillingPeriod = Subscription.BillingPeriod(rawValue: messageSubscriptionBillingPeriod) { + return subscription.billingPeriod == subscriptionBillingPeriod + } else { + // If we received a subscription billing period but can't map it to a valid type, don't show the message. + return false + } + } + // Check subscription start date: if let messageDaysSinceSubscriptionStarted = message.attributes.minimumDaysSinceSubscriptionStarted { guard let daysSinceSubscriptionStartDate = Calendar.current.dateComponents( @@ -171,6 +185,7 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } } + // swiftlint:enable cyclomatic_complexity func presentableRemoteMessages() -> [SurveyRemoteMessage] { let dismissedMessageIDs = messageStorage.dismissedMessageIDs() diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index 4a3bb90938..cf4299bce7 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -221,7 +221,7 @@ extension HomePage.Models { for message in surveyRemoteMessaging.presentableRemoteMessages() { features.append(.surveyRemoteMessage(message)) - PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .daily) + PixelKit.fire(GeneralPixel.surveyRemoteMessageDisplayed(messageID: message.id), frequency: .daily) } appendFeatureCards(&features) diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 92e27883fa..42fb1feab6 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -153,9 +153,6 @@ enum GeneralPixel: PixelKitEventV2 { case dataBrokerProtectionWaitlistCardUITapped case dataBrokerProtectionWaitlistTermsAndConditionsDisplayed case dataBrokerProtectionWaitlistTermsAndConditionsAccepted - case dataBrokerProtectionRemoteMessageDisplayed(messageID: String) - case dataBrokerProtectionRemoteMessageDismissed(messageID: String) - case dataBrokerProtectionRemoteMessageOpened(messageID: String) // Login Item events case dataBrokerEnableLoginItemDaily @@ -344,11 +341,8 @@ enum GeneralPixel: PixelKitEventV2 { case burnerTabMisplaced - case networkProtectionRemoteMessageFetchingFailed - case networkProtectionRemoteMessageStorageFailed - case dataBrokerProtectionRemoteMessageFetchingFailed - case dataBrokerProtectionRemoteMessageStorageFailed - + case surveyRemoteMessageFetchingFailed + case surveyRemoteMessageStorageFailed case loginItemUpdateError(loginItemBundleID: String, action: String, buildType: String, osVersion: String) // Tracks installation without tracking retention. @@ -576,12 +570,6 @@ enum GeneralPixel: PixelKitEventV2 { return "m_mac_dbp_imp_terms" case .dataBrokerProtectionWaitlistTermsAndConditionsAccepted: return "m_mac_dbp_ev_terms_accepted" - case .dataBrokerProtectionRemoteMessageDisplayed(let messageID): - return "m_mac_dbp_remote_message_displayed_\(messageID)" - case .dataBrokerProtectionRemoteMessageDismissed(let messageID): - return "m_mac_dbp_remote_message_dismissed_\(messageID)" - case .dataBrokerProtectionRemoteMessageOpened(let messageID): - return "m_mac_dbp_remote_message_opened_\(messageID)" case .dataBrokerEnableLoginItemDaily: return "m_mac_dbp_daily_login-item_enable" case .dataBrokerDisableLoginItemDaily: return "m_mac_dbp_daily_login-item_disable" @@ -864,12 +852,8 @@ enum GeneralPixel: PixelKitEventV2 { case .burnerTabMisplaced: return "burner_tab_misplaced" - case .networkProtectionRemoteMessageFetchingFailed: return "netp_remote_message_fetching_failed" - case .networkProtectionRemoteMessageStorageFailed: return "netp_remote_message_storage_failed" - - case .dataBrokerProtectionRemoteMessageFetchingFailed: return "dbp_remote_message_fetching_failed" - case .dataBrokerProtectionRemoteMessageStorageFailed: return "dbp_remote_message_storage_failed" - + case .surveyRemoteMessageFetchingFailed: return "survey_remote_message_fetching_failed" + case .surveyRemoteMessageStorageFailed: return "survey_remote_message_storage_failed" case .loginItemUpdateError: return "login-item_update-error" // Installation Attribution From 03a8c1336bc693afd3b961dc94f24d7d2b41286f Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 20:59:55 -0700 Subject: [PATCH 15/24] Check all attributes when matching. --- .../Surveys/SurveyRemoteMessaging.swift | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift index a28eb46a0e..4781d73605 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift @@ -126,13 +126,16 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { return messages.filter { message in - // TODO: Make it so that we check all attributes, not just the first one - var didMatch = false + var attributeMatchStatus = false // Check subscription status: if let messageSubscriptionStatus = message.attributes.subscriptionStatus { if let subscriptionStatus = Subscription.Status(rawValue: messageSubscriptionStatus) { - return subscription.status == subscriptionStatus + if subscription.status == subscriptionStatus { + attributeMatchStatus = true + } else { + return false + } } else { // If we received a subscription status but can't map it to a valid type, don't show the message. return false @@ -142,7 +145,11 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { // Check subscription billing period: if let messageSubscriptionBillingPeriod = message.attributes.subscriptionBillingPeriod { if let subscriptionBillingPeriod = Subscription.BillingPeriod(rawValue: messageSubscriptionBillingPeriod) { - return subscription.billingPeriod == subscriptionBillingPeriod + if subscription.billingPeriod == subscriptionBillingPeriod { + attributeMatchStatus = true + } else { + return false + } } else { // If we received a subscription billing period but can't map it to a valid type, don't show the message. return false @@ -157,7 +164,11 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { return false } - return daysSinceSubscriptionStartDate >= messageDaysSinceSubscriptionStarted + if daysSinceSubscriptionStartDate >= messageDaysSinceSubscriptionStarted { + attributeMatchStatus = true + } else { + return false + } } // Check subscription end/expiration date: @@ -168,20 +179,23 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { return false } - return daysUntilSubscriptionExpiration <= messageDaysUntilSubscriptionExpiration + if daysUntilSubscriptionExpiration <= messageDaysUntilSubscriptionExpiration { + attributeMatchStatus = true + } else { + return false + } } // Check VPN usage: if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled { if let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation(), requiredDaysSinceActivation <= daysSinceActivation { - return true + attributeMatchStatus = true } else { return false } } - // Don't show messages unless at least one attribute matches: - return false + return attributeMatchStatus } } From efa97ccc4d2fadbd6cadb78627e41ffd2ab15cbe Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 23 May 2024 21:34:27 -0700 Subject: [PATCH 16/24] Add new survey parameters. --- .../Common/Surveys/SurveyRemoteMessage.swift | 9 ++-- .../Common/Surveys/SurveyURLBuilder.swift | 49 +++++++++++++------ .../Model/AutofillPreferencesModel.swift | 6 ++- .../HomePage/SurveyRemoteMessageTests.swift | 9 ++-- 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift index d33a84a2f8..ff9f781916 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift @@ -49,7 +49,8 @@ struct SurveyRemoteMessage: Codable, Equatable, Identifiable, Hashable { func presentableSurveyURL( statisticsStore: StatisticsStore = LocalStatisticsStore(), - activationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), + vpnActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), + pirActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp), operatingSystemVersion: String = ProcessInfo.processInfo.operatingSystemVersion.description, appVersion: String = AppVersion.shared.versionNumber, hardwareModel: String? = HardwareModel.model @@ -67,8 +68,10 @@ struct SurveyRemoteMessage: Codable, Equatable, Identifiable, Hashable { operatingSystemVersion: operatingSystemVersion, appVersion: appVersion, hardwareModel: hardwareModel, - daysSinceActivation: activationDateStore.daysSinceActivation(), - daysSinceLastActive: activationDateStore.daysSinceLastActive() + daysSinceVPNActivated: vpnActivationDateStore.daysSinceActivation(), + daysSinceVPNLastActive: vpnActivationDateStore.daysSinceLastActive(), + daysSincePIRActivated: pirActivationDateStore.daysSinceActivation(), + daysSincePIRLastActive: pirActivationDateStore.daysSinceLastActive() ) return surveyURLBuilder.buildSurveyURL(from: surveyURL) diff --git a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift index a431276800..c803f17153 100644 --- a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift +++ b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift @@ -25,32 +25,41 @@ final class SurveyURLBuilder { enum SurveyURLParameters: String, CaseIterable { case atb = "atb" case atbVariant = "var" - case daysSinceActivated = "delta" - case macOSVersion = "mv" + case macOSVersion = "osv" case appVersion = "ddgv" case hardwareModel = "mo" - case lastDayActive = "da" + + case vpnFirstUsed = "vpn_first_used" + case vpnLastUsed = "vpn_last_used" + case pirFirstUsed = "pir_first_used" + case pirLastUsed = "pir_last_used" } private let statisticsStore: StatisticsStore private let operatingSystemVersion: String private let appVersion: String private let hardwareModel: String? - private let daysSinceActivation: Int? - private let daysSinceLastActive: Int? + private let daysSinceVPNActivated: Int? + private let daysSinceVPNLastActive: Int? + private let daysSincePIRActivated: Int? + private let daysSincePIRLastActive: Int? init(statisticsStore: StatisticsStore, operatingSystemVersion: String, appVersion: String, hardwareModel: String?, - daysSinceActivation: Int?, - daysSinceLastActive: Int?) { + daysSinceVPNActivated: Int?, + daysSinceVPNLastActive: Int?, + daysSincePIRActivated: Int?, + daysSincePIRLastActive: Int?) { self.statisticsStore = statisticsStore self.operatingSystemVersion = operatingSystemVersion self.appVersion = appVersion self.hardwareModel = hardwareModel - self.daysSinceActivation = daysSinceActivation - self.daysSinceLastActive = daysSinceLastActive + self.daysSinceVPNActivated = daysSinceVPNActivated + self.daysSinceVPNLastActive = daysSinceVPNLastActive + self.daysSincePIRActivated = daysSincePIRActivated + self.daysSincePIRLastActive = daysSincePIRLastActive } // swiftlint:disable:next cyclomatic_complexity @@ -72,10 +81,6 @@ final class SurveyURLBuilder { if let variant = statisticsStore.variant { queryItems.append(queryItem(parameter: parameter, value: variant)) } - case .daysSinceActivated: - if let daysSinceActivation { - queryItems.append(queryItem(parameter: parameter, value: daysSinceActivation)) - } case .macOSVersion: queryItems.append(queryItem(parameter: parameter, value: operatingSystemVersion)) case .appVersion: @@ -84,9 +89,21 @@ final class SurveyURLBuilder { if let hardwareModel = hardwareModel { queryItems.append(queryItem(parameter: parameter, value: hardwareModel)) } - case .lastDayActive: - if let daysSinceLastActive { - queryItems.append(queryItem(parameter: parameter, value: daysSinceLastActive)) + case .vpnFirstUsed: + if let daysSinceVPNActivated { + queryItems.append(queryItem(parameter: parameter, value: daysSinceVPNActivated)) + } + case .vpnLastUsed: + if let daysSinceVPNLastActive { + queryItems.append(queryItem(parameter: parameter, value: daysSinceVPNLastActive)) + } + case .pirFirstUsed: + if let daysSincePIRActivated { + queryItems.append(queryItem(parameter: parameter, value: daysSincePIRActivated)) + } + case .pirLastUsed: + if let daysSincePIRLastActive { + queryItems.append(queryItem(parameter: parameter, value: daysSincePIRLastActive)) } } } diff --git a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift index e7e56043b8..a91f97252c 100644 --- a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift @@ -212,8 +212,10 @@ final class AutofillPreferencesModel: ObservableObject { operatingSystemVersion: operatingSystemVersion, appVersion: appVersion, hardwareModel: hardwareModel, - daysSinceActivation: activationDateStore.daysSinceActivation(), - daysSinceLastActive: activationDateStore.daysSinceLastActive() + daysSinceVPNActivated: nil, + daysSinceVPNLastActive: nil, + daysSincePIRActivated: nil, + daysSincePIRLastActive: nil ) guard let surveyUrl = surveyURLBuilder.buildSurveyURLWithPasswordsCountSurveyParameter(from: "https://selfserve.decipherinc.com/survey/selfserve/32ab/240307") else { diff --git a/UnitTests/HomePage/SurveyRemoteMessageTests.swift b/UnitTests/HomePage/SurveyRemoteMessageTests.swift index ecdc62b4a4..00d7aa322e 100644 --- a/UnitTests/HomePage/SurveyRemoteMessageTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessageTests.swift @@ -45,7 +45,7 @@ final class SurveyRemoteMessageTests: XCTestCase { let firstMessagePresentableSurveyURL = firstMessage.presentableSurveyURL( statisticsStore: mockStatisticsStore, - activationDateStore: mockActivationDateStore, + vpnActivationDateStore: mockActivationDateStore, operatingSystemVersion: "1.2.3", appVersion: "4.5.6", hardwareModel: "MacBookPro,123" @@ -102,13 +102,16 @@ final class SurveyRemoteMessageTests: XCTestCase { let presentableSurveyURL = message.presentableSurveyURL( statisticsStore: mockStatisticsStore, - activationDateStore: mockActivationDateStore, + vpnActivationDateStore: mockActivationDateStore, operatingSystemVersion: "1.2.3", appVersion: "4.5.6", hardwareModel: "MacBookPro,123" ) - let expectedURL = "https://duckduckgo.com/?atb=atb-123&var=variant&delta=2&mv=1.2.3&ddgv=4.5.6&mo=MacBookPro%252C123&da=1" + let expectedURL = """ + https://duckduckgo.com/?atb=atb-123&var=variant&osv=1.2.3&ddgv=4.5.6&mo=MacBookPro%252C123&vpn_first_used=2&vpn_last_used=1 + """ + XCTAssertEqual(presentableSurveyURL!.absoluteString, expectedURL) } From 7798f7b65d25caa3884da52b4585ed77b2838f4a Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 24 May 2024 09:29:53 -0700 Subject: [PATCH 17/24] Fix SwiftLint. --- DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift index 4781d73605..33c7cfb1d4 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift @@ -111,7 +111,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } } - // swiftlint:disable cyclomatic_complexity + // swiftlint:disable cyclomatic_complexity function_body_length + /// Processes the messages received from S3 and returns those which the user is eligible for. This is done by checking each of the attributes against the user's local state. /// Because the result of the message fetch is cached, it means that they won't be immediately updated if the user suddenly qualifies, but the refresh interval for remote messages is only 1 hour so it /// won't take long for the message to appear to the user. @@ -199,7 +200,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } } - // swiftlint:enable cyclomatic_complexity + + // swiftlint:enable cyclomatic_complexity function_body_length func presentableRemoteMessages() -> [SurveyRemoteMessage] { let dismissedMessageIDs = messageStorage.dismissedMessageIDs() From e723c0515c5b71d6430e568864f19de185dc4bb2 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 24 May 2024 09:40:05 -0700 Subject: [PATCH 18/24] Update labels for clarity and add PIR checks. --- .../Surveys/SurveyRemoteMessaging.swift | 22 ++++++++++++++----- .../HomePage/SurveyRemoteMessagingTests.swift | 18 ++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift index 33c7cfb1d4..db0e5a651a 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift @@ -45,7 +45,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { private let messageStorage: SurveyRemoteMessagingStorage private let accountManager: AccountManaging private let subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching - private let waitlistActivationDateStore: WaitlistActivationDateStore + private let vpnActivationDateStore: WaitlistActivationDateStore + private let pirActivationDateStore: WaitlistActivationDateStore private let minimumRefreshInterval: TimeInterval private let userDefaults: UserDefaults @@ -70,7 +71,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { messageStorage: SurveyRemoteMessagingStorage = DefaultSurveyRemoteMessagingStorage.surveys(), accountManager: AccountManaging, subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching, - waitlistActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), + vpnActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), + pirActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp), networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager), minimumRefreshInterval: TimeInterval, userDefaults: UserDefaults = .standard @@ -79,7 +81,8 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { self.messageStorage = messageStorage self.accountManager = accountManager self.subscriptionFetcher = subscriptionFetcher - self.waitlistActivationDateStore = waitlistActivationDateStore + self.vpnActivationDateStore = vpnActivationDateStore + self.pirActivationDateStore = pirActivationDateStore self.minimumRefreshInterval = minimumRefreshInterval self.userDefaults = userDefaults } @@ -188,8 +191,17 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { } // Check VPN usage: - if let requiredDaysSinceActivation = message.attributes.daysSinceVPNEnabled { - if let daysSinceActivation = waitlistActivationDateStore.daysSinceActivation(), requiredDaysSinceActivation <= daysSinceActivation { + if let requiredDaysSinceVPNActivation = message.attributes.daysSinceVPNEnabled { + if let daysSinceActivation = vpnActivationDateStore.daysSinceActivation(), requiredDaysSinceVPNActivation <= daysSinceActivation { + attributeMatchStatus = true + } else { + return false + } + } + + // Check PIR usage: + if let requiredDaysSincePIRActivation = message.attributes.daysSincePIREnabled { + if let daysSinceActivation = pirActivationDateStore.daysSinceActivation(), requiredDaysSincePIRActivation <= daysSinceActivation { attributeMatchStatus = true } else { return false diff --git a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift index e4591c707d..09a6f43fbb 100644 --- a/UnitTests/HomePage/SurveyRemoteMessagingTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessagingTests.swift @@ -49,7 +49,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageStorage: storage, accountManager: accountManager, subscriptionFetcher: subscriptionFetcher, - waitlistActivationDateStore: activationDateStorage, + vpnActivationDateStore: activationDateStorage, + pirActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -71,7 +72,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageStorage: storage, accountManager: accountManager, subscriptionFetcher: subscriptionFetcher, - waitlistActivationDateStore: activationDateStorage, + vpnActivationDateStore: activationDateStorage, + pirActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -98,7 +100,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageStorage: storage, accountManager: accountManager, subscriptionFetcher: subscriptionFetcher, - waitlistActivationDateStore: activationDateStorage, + vpnActivationDateStore: activationDateStorage, + pirActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -126,7 +129,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageStorage: storage, accountManager: accountManager, subscriptionFetcher: subscriptionFetcher, - waitlistActivationDateStore: activationDateStorage, + vpnActivationDateStore: activationDateStorage, + pirActivationDateStore: activationDateStorage, minimumRefreshInterval: .days(7), // Use a large number to hit the refresh check userDefaults: defaults ) @@ -154,7 +158,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageStorage: storage, accountManager: accountManager, subscriptionFetcher: subscriptionFetcher, - waitlistActivationDateStore: activationDateStorage, + vpnActivationDateStore: activationDateStorage, + pirActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults ) @@ -179,7 +184,8 @@ final class SurveyRemoteMessagingTests: XCTestCase { messageStorage: storage, accountManager: accountManager, subscriptionFetcher: subscriptionFetcher, - waitlistActivationDateStore: activationDateStorage, + vpnActivationDateStore: activationDateStorage, + pirActivationDateStore: activationDateStorage, minimumRefreshInterval: 0, userDefaults: defaults ) From fe6ec896ac6bf091be4eec24fa465101310f5d82 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 24 May 2024 09:42:59 -0700 Subject: [PATCH 19/24] Restore DBP `windowDidBecomeMain` call. --- DuckDuckGo/MainWindow/MainViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index fc05d497c5..8d4c5852a9 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -193,6 +193,7 @@ final class MainViewController: NSViewController { updateStopMenuItem() browserTabViewController.windowDidBecomeKey() refreshSurveyMessages() + DataBrokerProtectionAppEvents().windowDidBecomeMain() } func windowDidResignKey() { From 69737016f4a90fd54e8cc39d0a1e6dc9748c22a7 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 24 May 2024 09:51:28 -0700 Subject: [PATCH 20/24] Clean up duplicate logic. --- DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift index db0e5a651a..51c1496884 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift @@ -248,7 +248,7 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging { guard let date = object as? Date else { assertionFailure("Got rate limited date, but couldn't convert it to Date") - userDefaults.removeObject(forKey: Constants.lastRefreshDateKey) + resetLastRefreshTimestamp() return nil } From 43dbeb470bc9ab944a944c42241ca8fc829700f8 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 24 May 2024 10:20:01 -0700 Subject: [PATCH 21/24] Remove null file references --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5d44e59db7..c18322e0a6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1197,8 +1197,6 @@ 4B5A4F4C27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A4F4B27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift */; }; 4B5FF67826B602B100D42879 /* FirefoxDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FF67726B602B100D42879 /* FirefoxDataImporter.swift */; }; 4B65143E263924B5005B46EB /* EmailUrlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B65143D263924B5005B46EB /* EmailUrlExtensions.swift */; }; - 4B66E3D92C0009DF00934A78 /* (null) in Sources */ = {isa = PBXBuildFile; }; - 4B66E3DA2C0009DF00934A78 /* (null) in Sources */ = {isa = PBXBuildFile; }; 4B677432255DBEB800025BD8 /* httpsMobileV2BloomSpec.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B677427255DBEB800025BD8 /* httpsMobileV2BloomSpec.json */; }; 4B677433255DBEB800025BD8 /* httpsMobileV2Bloom.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B677428255DBEB800025BD8 /* httpsMobileV2Bloom.bin */; }; 4B677435255DBEB800025BD8 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; @@ -9803,7 +9801,6 @@ F1FDC9392BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */, 3706FB40293F65D500E42796 /* ContextualMenu.swift in Sources */, - 4B66E3D92C0009DF00934A78 /* (null) in Sources */, 3706FB41293F65D500E42796 /* NavigationBarViewController.swift in Sources */, 3707C71C294B5D1900682A9F /* TabExtensionsBuilder.swift in Sources */, 3706FB42293F65D500E42796 /* MainViewController.swift in Sources */, @@ -11628,7 +11625,6 @@ BDA7647C2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */, 3158B1592B0BF76400AF130C /* DataBrokerProtectionFeatureDisabler.swift in Sources */, B655124829A79465009BFE1C /* NavigationActionExtension.swift in Sources */, - 4B66E3DA2C0009DF00934A78 /* (null) in Sources */, 9FA173EB2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 85308E25267FC9F2001ABD76 /* NSAlertExtension.swift in Sources */, B69A14F62B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, From a49ea72f92b6123a8d4b5705a2a6bcaed1fc9260 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 24 May 2024 13:27:21 -0700 Subject: [PATCH 22/24] Add Privacy Pro parameters. --- .../Common/Surveys/SurveyRemoteMessage.swift | 5 +- .../Common/Surveys/SurveyURLBuilder.swift | 50 +++++++++++++++++++ .../Model/HomePageContinueSetUpModel.swift | 38 ++++++++++---- .../Model/AutofillPreferencesModel.swift | 1 + .../HomePage/Resources/survey-messages.json | 1 + .../HomePage/SurveyRemoteMessageTests.swift | 6 ++- 6 files changed, 89 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift b/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift index ff9f781916..b9fb348f24 100644 --- a/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift +++ b/DuckDuckGo/Common/Surveys/SurveyRemoteMessage.swift @@ -18,6 +18,7 @@ import Foundation import Common +import Subscription struct SurveyRemoteMessageAction: Codable, Equatable, Hashable { enum Action: String, Codable { @@ -53,7 +54,8 @@ struct SurveyRemoteMessage: Codable, Equatable, Identifiable, Hashable { pirActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp), operatingSystemVersion: String = ProcessInfo.processInfo.operatingSystemVersion.description, appVersion: String = AppVersion.shared.versionNumber, - hardwareModel: String? = HardwareModel.model + hardwareModel: String? = HardwareModel.model, + subscription: Subscription? ) -> URL? { if let actionType = action.actionType, actionType == .openURL, let urlString = action.actionURL, let url = URL(string: urlString) { return url @@ -68,6 +70,7 @@ struct SurveyRemoteMessage: Codable, Equatable, Identifiable, Hashable { operatingSystemVersion: operatingSystemVersion, appVersion: appVersion, hardwareModel: hardwareModel, + subscription: subscription, daysSinceVPNActivated: vpnActivationDateStore.daysSinceActivation(), daysSinceVPNLastActive: vpnActivationDateStore.daysSinceLastActive(), daysSincePIRActivated: pirActivationDateStore.daysSinceActivation(), diff --git a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift index c803f17153..37b20fcdb3 100644 --- a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift +++ b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift @@ -19,6 +19,7 @@ import Foundation import Common import BrowserServicesKit +import Subscription final class SurveyURLBuilder { @@ -29,6 +30,12 @@ final class SurveyURLBuilder { case appVersion = "ddgv" case hardwareModel = "mo" + case privacyProStatus = "ppro_status" + case privacyProPurchasePlatform = "ppro_platform" + case privacyProBillingPeriod = "ppro_billing" + case privacyProDaysSincePurchase = "ppro_days_since_purchase" + case privacyProDaysUntilExpiration = "ppro_days_until_exp" + case vpnFirstUsed = "vpn_first_used" case vpnLastUsed = "vpn_last_used" case pirFirstUsed = "pir_first_used" @@ -39,6 +46,7 @@ final class SurveyURLBuilder { private let operatingSystemVersion: String private let appVersion: String private let hardwareModel: String? + private let subscription: Subscription? private let daysSinceVPNActivated: Int? private let daysSinceVPNLastActive: Int? private let daysSincePIRActivated: Int? @@ -48,6 +56,7 @@ final class SurveyURLBuilder { operatingSystemVersion: String, appVersion: String, hardwareModel: String?, + subscription: Subscription?, daysSinceVPNActivated: Int?, daysSinceVPNLastActive: Int?, daysSincePIRActivated: Int?, @@ -56,6 +65,7 @@ final class SurveyURLBuilder { self.operatingSystemVersion = operatingSystemVersion self.appVersion = appVersion self.hardwareModel = hardwareModel + self.subscription = subscription self.daysSinceVPNActivated = daysSinceVPNActivated self.daysSinceVPNLastActive = daysSinceVPNLastActive self.daysSincePIRActivated = daysSincePIRActivated @@ -105,6 +115,42 @@ final class SurveyURLBuilder { if let daysSincePIRLastActive { queryItems.append(queryItem(parameter: parameter, value: daysSincePIRLastActive)) } + case .privacyProStatus: + if let privacyProStatus = subscription?.status { + switch privacyProStatus { + case .autoRenewable: queryItems.append(queryItem(parameter: parameter, value: "auto_renewable")) + case .notAutoRenewable: queryItems.append(queryItem(parameter: parameter, value: "not_auto_renewable")) + case .gracePeriod: queryItems.append(queryItem(parameter: parameter, value: "grace_period")) + case .inactive: queryItems.append(queryItem(parameter: parameter, value: "inactive")) + case .expired: queryItems.append(queryItem(parameter: parameter, value: "expired")) + case .unknown: queryItems.append(queryItem(parameter: parameter, value: "unknown")) + } + } + case .privacyProPurchasePlatform: + if let privacyProPurchasePlatform = subscription?.platform { + switch privacyProPurchasePlatform { + case .apple: queryItems.append(queryItem(parameter: parameter, value: "apple")) + case .google: queryItems.append(queryItem(parameter: parameter, value: "google")) + case .stripe: queryItems.append(queryItem(parameter: parameter, value: "stripe")) + case .unknown: queryItems.append(queryItem(parameter: parameter, value: "unknown")) + } + } + case .privacyProBillingPeriod: + if let privacyProBillingPeriod = subscription?.billingPeriod { + switch privacyProBillingPeriod { + case .monthly: queryItems.append(queryItem(parameter: parameter, value: "monthly")) + case .yearly: queryItems.append(queryItem(parameter: parameter, value: "yearly")) + case .unknown: queryItems.append(queryItem(parameter: parameter, value: "unknown")) + } + } + case .privacyProDaysSincePurchase: + if let startedAt = subscription?.startedAt, let daysSincePurchase = daysSince(date: startedAt) { + queryItems.append(queryItem(parameter: parameter, value: daysSincePurchase)) + } + case .privacyProDaysUntilExpiration: + if let expiresOrRenewsAt = subscription?.expiresOrRenewsAt, let daysUntilExpiry = daysSince(date: expiresOrRenewsAt) { + queryItems.append(queryItem(parameter: parameter, value: daysUntilExpiry)) + } } } @@ -149,4 +195,8 @@ final class SurveyURLBuilder { return bucket } + private func daysSince(date storedDate: Date) -> Int? { + return Calendar.current.dateComponents([.day], from: storedDate, to: Date()).day + } + } diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index cf4299bce7..9522183014 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Common import Foundation import PixelKit +import Subscription import NetworkProtection import NetworkProtectionUI @@ -53,6 +54,7 @@ extension HomePage.Models { private let tabCollectionViewModel: TabCollectionViewModel private let emailManager: EmailManager private let duckPlayerPreferences: DuckPlayerPreferencesPersistor + private let subscriptionManager: SubscriptionManaging @UserDefaultsWrapper(key: .homePageShowAllFeatures, defaultValue: false) var shouldShowAllFeatures: Bool { @@ -111,7 +113,8 @@ extension HomePage.Models { duckPlayerPreferences: DuckPlayerPreferencesPersistor, surveyRemoteMessaging: SurveyRemoteMessaging, privacyConfigurationManager: PrivacyConfigurationManaging = AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager, - permanentSurveyManager: SurveyManager = PermanentSurveyManager()) { + permanentSurveyManager: SurveyManager = PermanentSurveyManager(), + subscriptionManager: SubscriptionManaging = Application.appDelegate.subscriptionManager) { self.defaultBrowserProvider = defaultBrowserProvider self.dockCustomizer = dockCustomizer self.dataImportProvider = dataImportProvider @@ -121,6 +124,7 @@ extension HomePage.Models { self.surveyRemoteMessaging = surveyRemoteMessaging self.privacyConfigurationManager = privacyConfigurationManager self.permanentSurveyManager = permanentSurveyManager + self.subscriptionManager = subscriptionManager refreshFeaturesMatrix() @@ -357,14 +361,30 @@ extension HomePage.Models { switch actionType { case .openSurveyURL, .openURL: - if let surveyURL = remoteMessage.presentableSurveyURL() { - let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) - tabCollectionViewModel.append(tab: tab) - PixelKit.fire(GeneralPixel.surveyRemoteMessageOpened(messageID: remoteMessage.id)) - - // Dismiss the message after the user opens the URL, even if they just close the tab immediately afterwards. - surveyRemoteMessaging.dismiss(message: remoteMessage) - refreshFeaturesMatrix() + Task { @MainActor in + var subscription: Subscription? + + if let token = subscriptionManager.accountManager.accessToken { + switch await subscriptionManager.subscriptionService.getSubscription( + accessToken: token, + cachePolicy: .returnCacheDataElseLoad + ) { + case .success(let fetchedSubscription): + subscription = fetchedSubscription + case .failure: + break + } + } + + if let surveyURL = remoteMessage.presentableSurveyURL(subscription: subscription) { + let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) + tabCollectionViewModel.append(tab: tab) + PixelKit.fire(GeneralPixel.surveyRemoteMessageOpened(messageID: remoteMessage.id)) + + // Dismiss the message after the user opens the URL, even if they just close the tab immediately afterwards. + surveyRemoteMessaging.dismiss(message: remoteMessage) + refreshFeaturesMatrix() + } } } } diff --git a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift index a91f97252c..39914a5dad 100644 --- a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift @@ -212,6 +212,7 @@ final class AutofillPreferencesModel: ObservableObject { operatingSystemVersion: operatingSystemVersion, appVersion: appVersion, hardwareModel: hardwareModel, + subscription: nil, daysSinceVPNActivated: nil, daysSinceVPNLastActive: nil, daysSincePIRActivated: nil, diff --git a/UnitTests/HomePage/Resources/survey-messages.json b/UnitTests/HomePage/Resources/survey-messages.json index 34a7bc6a22..1259271b49 100644 --- a/UnitTests/HomePage/Resources/survey-messages.json +++ b/UnitTests/HomePage/Resources/survey-messages.json @@ -5,6 +5,7 @@ "cardDescription": "Description 1", "attributes": { "subscriptionStatus": "Auto-Renewable", + "subscriptionBillingPeriod": "Monthly", "minimumDaysSinceSubscriptionStarted": 1, "maximumDaysUntilSubscriptionExpirationOrRenewal": 30, "daysSinceVPNEnabled": 2, diff --git a/UnitTests/HomePage/SurveyRemoteMessageTests.swift b/UnitTests/HomePage/SurveyRemoteMessageTests.swift index 00d7aa322e..1c7939b36f 100644 --- a/UnitTests/HomePage/SurveyRemoteMessageTests.swift +++ b/UnitTests/HomePage/SurveyRemoteMessageTests.swift @@ -48,7 +48,8 @@ final class SurveyRemoteMessageTests: XCTestCase { vpnActivationDateStore: mockActivationDateStore, operatingSystemVersion: "1.2.3", appVersion: "4.5.6", - hardwareModel: "MacBookPro,123" + hardwareModel: "MacBookPro,123", + subscription: nil ) XCTAssertEqual(firstMessage.cardTitle, "Title 1") @@ -105,7 +106,8 @@ final class SurveyRemoteMessageTests: XCTestCase { vpnActivationDateStore: mockActivationDateStore, operatingSystemVersion: "1.2.3", appVersion: "4.5.6", - hardwareModel: "MacBookPro,123" + hardwareModel: "MacBookPro,123", + subscription: nil ) let expectedURL = """ From 3d9aefeba75a33c7bb3e3c143e698581716aa25e Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 24 May 2024 21:47:29 -0700 Subject: [PATCH 23/24] Never use a negative amount of days for survey params. --- DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift index 37b20fcdb3..88f25de5b0 100644 --- a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift +++ b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift @@ -196,7 +196,11 @@ final class SurveyURLBuilder { } private func daysSince(date storedDate: Date) -> Int? { - return Calendar.current.dateComponents([.day], from: storedDate, to: Date()).day + if let days = Calendar.current.dateComponents([.day], from: storedDate, to: Date()).day { + return abs(days) + } + + return nil } } From 76b2738d9c82fe3a9f23a14f105342f0f397a602 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Sun, 26 May 2024 09:15:24 -0700 Subject: [PATCH 24/24] SwiftLint fixes. --- DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift index 88f25de5b0..7392cb7f75 100644 --- a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift +++ b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift @@ -72,7 +72,7 @@ final class SurveyURLBuilder { self.daysSincePIRLastActive = daysSincePIRLastActive } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length func buildSurveyURL(from originalURLString: String) -> URL? { guard var components = URLComponents(string: originalURLString) else { assertionFailure("Could not build components from survey URL")