From cd760855e235fd18829222652413ab87c1f6a4fb Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 4 Jul 2024 21:00:17 +1200 Subject: [PATCH 01/15] Update the Privacy Pro status attribute matcher (#3033) Task/Issue URL: https://app.asana.com/0/1199333091098016/1207731341550989/f Tech Design URL: CC: Description: This PR updates the Privacy Pro status attribute matcher to accept an array. On the client side, it also makes it so that the active case no longer also targets expiring users. If we want to target both active and expiring users, we can do so by specifying multiple cases in the array. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/RemoteMessagingClient.swift | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5c1fc64349..4d7c34df6a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9962,7 +9962,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 164.2.1; + version = 164.2.2; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3159dff62e..cc5fac8cac 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "a9a41a03ab6a230b424c3f3f5f689c9021c61c5e", - "version" : "164.2.1" + "revision" : "683424378092d5ca3c57df50d5676cf8c7d2d83c", + "version" : "164.2.2" } }, { diff --git a/DuckDuckGo/RemoteMessagingClient.swift b/DuckDuckGo/RemoteMessagingClient.swift index e914e671c1..0925657a2d 100644 --- a/DuckDuckGo/RemoteMessagingClient.swift +++ b/DuckDuckGo/RemoteMessagingClient.swift @@ -187,7 +187,6 @@ struct RemoteMessagingClient { case .autoRenewable, .gracePeriod: privacyProIsActive = true case .notAutoRenewable: - privacyProIsActive = true privacyProIsExpiring = true case .expired, .inactive: privacyProIsExpired = true From 4d90ef7af064c48d71a472fda96f8d26742b5765 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 4 Jul 2024 15:17:05 +0600 Subject: [PATCH 02/15] Fixes for Xcode 16 (BSK -> 164.3.0) (#3035) Task/Issue URL: https://app.asana.com/0/1199230911884351/1207655348648728/f macOS PR: duckduckgo/macos-browser#2907 BSK PR: duckduckgo/BrowserServicesKit#864 --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4d7c34df6a..a0bb87b389 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9962,7 +9962,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 164.2.2; + version = 164.3.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cc5fac8cac..c6f6300232 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "683424378092d5ca3c57df50d5676cf8c7d2d83c", - "version" : "164.2.2" + "revision" : "28dd48c5aca37c46402e2a14f7c47aad3877b3aa", + "version" : "164.3.0" } }, { From 2a5a11fbb48127330a747b3c67edb8e50d2238bb Mon Sep 17 00:00:00 2001 From: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Date: Thu, 4 Jul 2024 12:49:49 +0200 Subject: [PATCH 03/15] update sync error copy (#2870) Task/Issue URL: https://app.asana.com/0/414235014887631/1207333613649639/f Description: Update sync error copy and move sync settings error management into the error manager --- Core/SyncDataProviders.swift | 3 +- Core/SyncErrorHandler.swift | 59 +++++++++++++++---- Core/SyncErrorHandling.swift | 1 + Core/SyncSettingsAdapter.swift | 26 ++------ .../MainViewController+SyncAlerts.swift | 4 +- DuckDuckGo/UserText.swift | 3 +- DuckDuckGo/bg.lproj/Localizable.strings | 7 ++- DuckDuckGo/cs.lproj/Localizable.strings | 7 ++- DuckDuckGo/da.lproj/Localizable.strings | 7 ++- DuckDuckGo/de.lproj/Localizable.strings | 7 ++- DuckDuckGo/el.lproj/Localizable.strings | 7 ++- DuckDuckGo/en.lproj/Localizable.strings | 7 ++- DuckDuckGo/es.lproj/Localizable.strings | 7 ++- DuckDuckGo/et.lproj/Localizable.strings | 7 ++- DuckDuckGo/fi.lproj/Localizable.strings | 7 ++- DuckDuckGo/fr.lproj/Localizable.strings | 7 ++- DuckDuckGo/hr.lproj/Localizable.strings | 7 ++- DuckDuckGo/hu.lproj/Localizable.strings | 7 ++- DuckDuckGo/it.lproj/Localizable.strings | 7 ++- DuckDuckGo/lt.lproj/Localizable.strings | 7 ++- DuckDuckGo/lv.lproj/Localizable.strings | 7 ++- DuckDuckGo/nb.lproj/Localizable.strings | 7 ++- DuckDuckGo/nl.lproj/Localizable.strings | 7 ++- DuckDuckGo/pl.lproj/Localizable.strings | 7 ++- DuckDuckGo/pt.lproj/Localizable.strings | 7 ++- DuckDuckGo/ro.lproj/Localizable.strings | 7 ++- DuckDuckGo/ru.lproj/Localizable.strings | 7 ++- DuckDuckGo/sk.lproj/Localizable.strings | 7 ++- DuckDuckGo/sl.lproj/Localizable.strings | 7 ++- DuckDuckGo/sv.lproj/Localizable.strings | 7 ++- DuckDuckGo/tr.lproj/Localizable.strings | 7 ++- .../CapturingAdapterErrorHandler.swift | 6 ++ .../SyncBookmarksAdapterTests.swift | 2 +- 33 files changed, 190 insertions(+), 89 deletions(-) diff --git a/Core/SyncDataProviders.swift b/Core/SyncDataProviders.swift index eca348657d..fd7c595947 100644 --- a/Core/SyncDataProviders.swift +++ b/Core/SyncDataProviders.swift @@ -111,7 +111,8 @@ public class SyncDataProviders: DataProvidersSource { credentialsAdapter = SyncCredentialsAdapter(secureVaultFactory: secureVaultFactory, secureVaultErrorReporter: secureVaultErrorReporter, syncErrorHandler: syncErrorHandler) - settingsAdapter = SyncSettingsAdapter(settingHandlers: settingHandlers) + settingsAdapter = SyncSettingsAdapter(settingHandlers: settingHandlers, + syncErrorHandler: syncErrorHandler) } private func initializeMetadataDatabaseIfNeeded() { diff --git a/Core/SyncErrorHandler.swift b/Core/SyncErrorHandler.swift index 87a4c6bdee..025a3abfcb 100644 --- a/Core/SyncErrorHandler.swift +++ b/Core/SyncErrorHandler.swift @@ -23,6 +23,7 @@ import Combine import Persistence import Foundation import SyncUI +import SyncDataProviders public enum AsyncErrorType: String { case bookmarksCountLimitExceeded @@ -157,28 +158,27 @@ extension SyncErrorHandler { private func handleError(_ error: Error, modelType: ModelType) { switch error { case SyncError.patchPayloadCompressionFailed(let errorCode): - let pixel: Pixel.Event = { - switch modelType { - case .bookmarks: - return .syncBookmarksPatchCompressionFailed - case .credentials: - return .syncCredentialsPatchCompressionFailed - } - }() - Pixel.fire(pixel: pixel, withAdditionalParameters: ["error": "\(errorCode)"]) + Pixel.fire(pixel: modelType.patchPayloadCompressionFailedPixel, withAdditionalParameters: ["error": "\(errorCode)"]) case let syncError as SyncError: handleSyncError(syncError, modelType: modelType) + Pixel.fire(pixel: modelType.syncFailedPixel, error: syncError) + case let settingsMetadataError as SettingsSyncMetadataSaveError: + let underlyingError = settingsMetadataError.underlyingError + let processedErrors = CoreDataErrorsParser.parse(error: underlyingError as NSError) + let params = processedErrors.errorPixelParameters + Pixel.fire(pixel: .syncSettingsMetadataUpdateFailed, error: underlyingError, withAdditionalParameters: params) default: let nsError = error as NSError if nsError.domain != NSURLErrorDomain { let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(pixel: .syncCredentialsFailed, error: error, withAdditionalParameters: params) + Pixel.fire(pixel: modelType.syncFailedPixel, error: error, withAdditionalParameters: params) } } - os_log(.error, log: OSLog.syncLog, "Credentials Sync error: %{public}s", String(reflecting: error)) + let modelTypeString = modelType.rawValue.capitalized + os_log(.error, log: OSLog.syncLog, "%{public}@ Sync error: %{public}s", modelTypeString, String(reflecting: error)) } - + private func handleSyncError(_ syncError: SyncError, modelType: ModelType) { switch syncError { case .unexpectedStatusCode(409): @@ -187,6 +187,8 @@ extension SyncErrorHandler { syncIsPaused(errorType: .bookmarksCountLimitExceeded) case .credentials: syncIsPaused(errorType: .credentialsCountLimitExceeded) + case .settings: + break } case .unexpectedStatusCode(413): switch modelType { @@ -194,6 +196,8 @@ extension SyncErrorHandler { syncIsPaused(errorType: .bookmarksRequestSizeLimitExceeded) case .credentials: syncIsPaused(errorType: .credentialsRequestSizeLimitExceeded) + case .settings: + break } case .unexpectedStatusCode(400): switch modelType { @@ -201,6 +205,8 @@ extension SyncErrorHandler { syncIsPaused(errorType: .badRequestBookmarks) case .credentials: syncIsPaused(errorType: .badRequestCredentials) + case .settings: + break } case .unexpectedStatusCode(401): syncIsPaused(errorType: .invalidLoginCredentials) @@ -273,14 +279,41 @@ extension SyncErrorHandler { } } - private enum ModelType { + private enum ModelType: String { case bookmarks case credentials + case settings + + var syncFailedPixel: Pixel.Event { + switch self { + case .bookmarks: + .syncBookmarksFailed + case .credentials: + .syncCredentialsFailed + case .settings: + .syncSettingsFailed + } + } + + var patchPayloadCompressionFailedPixel: Pixel.Event { + switch self { + case .bookmarks: + .syncBookmarksPatchCompressionFailed + case .credentials: + .syncCredentialsPatchCompressionFailed + case .settings: + .syncSettingsPatchCompressionFailed + } + } } } // MARK: - SyncErrorHandler extension SyncErrorHandler: SyncErrorHandling { + public func handleSettingsError(_ error: Error) { + handleError(error, modelType: .settings) + } + public func handleBookmarkError(_ error: Error) { handleError(error, modelType: .bookmarks) } diff --git a/Core/SyncErrorHandling.swift b/Core/SyncErrorHandling.swift index f5abe7df74..724ebf29ef 100644 --- a/Core/SyncErrorHandling.swift +++ b/Core/SyncErrorHandling.swift @@ -23,6 +23,7 @@ import Foundation public protocol SyncErrorHandling { func handleBookmarkError(_ error: Error) func handleCredentialError(_ error: Error) + func handleSettingsError(_ error: Error) func syncBookmarksSucceded() func syncCredentialsSucceded() } diff --git a/Core/SyncSettingsAdapter.swift b/Core/SyncSettingsAdapter.swift index 5f99a3610c..f2ed077e4b 100644 --- a/Core/SyncSettingsAdapter.swift +++ b/Core/SyncSettingsAdapter.swift @@ -29,10 +29,12 @@ public final class SyncSettingsAdapter { public private(set) var provider: SettingsProvider? public private(set) var emailManager: EmailManager? public let syncDidCompletePublisher: AnyPublisher + private let syncErrorHandler: SyncErrorHandling - public init(settingHandlers: [SettingSyncHandler]) { + public init(settingHandlers: [SettingSyncHandler], syncErrorHandler: SyncErrorHandling) { self.settingHandlers = settingHandlers syncDidCompletePublisher = syncDidCompleteSubject.eraseToAnyPublisher() + self.syncErrorHandler = syncErrorHandler } public func updateDatabaseCleanupSchedule(shouldEnable: Bool) { @@ -61,26 +63,8 @@ public final class SyncSettingsAdapter { ) syncErrorCancellable = provider.syncErrorPublisher - .sink { error in - switch error { - case SyncError.patchPayloadCompressionFailed(let errorCode): - Pixel.fire(pixel: .syncSettingsPatchCompressionFailed, withAdditionalParameters: ["error": "\(errorCode)"]) - case let syncError as SyncError: - Pixel.fire(pixel: .syncSettingsFailed, error: syncError) - case let settingsMetadataError as SettingsSyncMetadataSaveError: - let underlyingError = settingsMetadataError.underlyingError - let processedErrors = CoreDataErrorsParser.parse(error: underlyingError as NSError) - let params = processedErrors.errorPixelParameters - Pixel.fire(pixel: .syncSettingsMetadataUpdateFailed, error: underlyingError, withAdditionalParameters: params) - default: - let nsError = error as NSError - if nsError.domain != NSURLErrorDomain { - let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) - let params = processedErrors.errorPixelParameters - Pixel.fire(pixel: .syncSettingsFailed, error: error, withAdditionalParameters: params) - } - } - os_log(.error, log: OSLog.syncLog, "Settings Sync error: %{public}s", String(reflecting: error)) + .sink { [weak self] error in + self?.syncErrorHandler.handleSettingsError(error) } self.provider = provider diff --git a/DuckDuckGo/MainViewController+SyncAlerts.swift b/DuckDuckGo/MainViewController+SyncAlerts.swift index 3cf2e3946b..e041db31c1 100644 --- a/DuckDuckGo/MainViewController+SyncAlerts.swift +++ b/DuckDuckGo/MainViewController+SyncAlerts.swift @@ -42,11 +42,11 @@ extension MainViewController: SyncAlertsPresenting { case .badRequestBookmarks: showSyncPausedAlert( title: UserText.syncBookmarkPausedAlertTitle, - informative: UserText.syncBadRequestAlertDescription) + informative: UserText.syncBadBookmarksRequestAlertDescription) case .badRequestCredentials: showSyncPausedAlert( title: UserText.syncBookmarkPausedAlertTitle, - informative: UserText.syncBadRequestAlertDescription) + informative: UserText.syncBadCredentialsRequestAlertDescription) } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index c9f1a16264..335e175fdb 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -832,7 +832,8 @@ But if you *do* want a peek under the hood, you can find more information about static let syncPausedAlertTitle = NSLocalizedString("alert.sync-paused-title", value: "Sync is Paused", comment: "Title for alert shown when sync paused for an error") static let syncInvalidLoginAlertDescription = NSLocalizedString("alert.sync-invalid-login-error-description", value: "Sync has been paused. If you want to continue syncing this device, reconnect using another device or your recovery code.", comment: "Description for alert shown when user logged off from sync") static let syncTooManyRequestsAlertDescription = NSLocalizedString("alert.sync-too-many-requests-error-description", value: "Sync & Backup is temporarily unavailable.", comment: "Description for alert shown when sync error occurs because of too many requests") - static let syncBadRequestAlertDescription = NSLocalizedString("alert.sync-bad-data-error-description", value: "Some bookmarks or passwords are formatted incorrectly or too long and were not synced.", comment: "Description for alert shown when sync error occurs because of bad data") + static let syncBadBookmarksRequestAlertDescription = NSLocalizedString("alert.sync-bookmarks-bad-data-error-description", value: "Some bookmarks are formatted incorrectly or too long and were not synced.", comment: "Description for alert shown when sync error occurs because of bad bookmarks data") + static let syncBadCredentialsRequestAlertDescription = NSLocalizedString("alert.sync-credentials-bad-data-error-description", value: "Some passwords are formatted incorrectly or too long and were not synced.", comment: "Description for alert shown when sync error occurs because of bad credentials data") static let syncErrorAlertAction = NSLocalizedString("alert.sync-error-action", value: "Sync Settings", comment: "Sync error alert action button title, takes the user to the sync settings page.") static let syncBookmarkPausedAlertTitle = NSLocalizedString("alert.sync-bookmarks-paused-title", value: "Bookmark Sync is Paused", comment: "Title for alert shown when sync bookmarks paused for too many items") static let syncBookmarkPausedAlertDescription = NSLocalizedString("alert.sync-bookmarks-paused-description", value: "You've reached the maximum number of bookmarks. Please delete some bookmarks to resume sync.", comment: "Description for alert shown when sync bookmarks paused for too many items") diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index 0b7ec2ea41..c997501945 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Съществуващите отметки няма да бъдат дублирани."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Някои отметки или пароли са форматирани неправилно или са твърде дълги и не бяха синхронизирани."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Някои отметки са форматирани неправилно или са твърде дълги и не бяха синхронизирани."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Достигнахте максималния брой отметки. Изтрийте някои отметки, за да възобновите синхронизирането."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Синхронизирането на отметки е на пауза"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Някои пароли са форматирани неправилно или са твърде дълги и не бяха синхронизирани."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Достигнахте максималния брой пароли. Изтрийте някои пароли, за да възобновите синхронизирането."; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 32d6be1434..d916ba23c1 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Existující záložky nebudou duplikovány."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Některé záložky nebo hesla jsou nesprávně naformátované nebo příliš dlouhé, takže jsme je nemohli synchronizovat."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Některé záložky jsou nesprávně naformátované nebo příliš dlouhé, takže jsme je nemohli synchronizovat."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Už máš maximální počet záložek. Jestli chceš synchronizaci obnovit, některé záložky smaž."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Synchronizace záložek je pozastavená"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Některá hesla jsou nesprávně naformátovaná nebo příliš dlouhá, takže jsme je nemohli synchronizovat."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Už máš maximální počet hesel. Jestli chceš synchronizaci obnovit, některá hesla smaž."; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 0f939020df..c0f12afeb1 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Eksisterende bogmærker duplikeres ikke."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Nogle bogmærker eller adgangskoder er formateret forkert eller er for lange og blev ikke synkroniseret."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Nogle bogmærker er formateret forkert eller er for lange og blev ikke synkroniseret."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Du har nået det maksimale antal bogmærker. Slet nogle bogmærker for at genoptage synkroniseringen."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Bogmærkesynkronisering er sat på pause"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Nogle adgangskoder er formateret forkert eller er for lange og blev ikke synkroniseret."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Du har nået det maksimale antal adgangskoder. Slet nogle adgangskoder for at genoptage synkroniseringen."; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 9a47be7145..1ffb4a9fbc 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Bestehende Lesezeichen werden nicht kopiert."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Einige Lesezeichen oder Passwörter sind falsch formatiert oder zu lang und wurden nicht synchronisiert."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Einige Lesezeichen sind falsch formatiert oder zu lang und wurden nicht synchronisiert."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Du hast die maximale Anzahl von Lesezeichen erreicht. Bitte lösche einige Lesezeichen, um die Synchronisierung fortzusetzen."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Lesezeichen-Synchronisierung ist angehalten"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Einige Passwörter sind falsch formatiert oder zu lang und wurden nicht synchronisiert."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Du hast die maximale Anzahl von Passwörtern erreicht. Lösche Passwörter, um die Synchronisierung fortzusetzen."; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 21b2604eaf..8180e33a74 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Οι υπάρχοντες σελιδοδείκτες δεν θα αναπαραχθούν."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Ορισμένοι σελιδοδείκτες ή κωδικοί πρόσβασης δεν έχουν μορφοποιηθεί σωστά ή είναι πολύ μεγάλοι και δεν συγχρονίστηκαν."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Ορισμένοι σελιδοδείκτες δεν έχουν μορφοποιηθεί σωστά ή είναι πολύ μεγάλοι και δεν συγχρονίστηκαν."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Έχετε φτάσει στον μέγιστο αριθμό σελιδοδεικτών. Διαγράψτε ορισμένους σελιδοδείκτες για να συνεχίσετε τον συγχρονισμό."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Ο συγχρονισμός σελιδοδεικτών έχει τεθεί σε παύση"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Ορισμένοι κωδικοί πρόσβασης δεν έχουν μορφοποιηθεί σωστά ή είναι πολύ μεγάλοι και δεν συγχρονίστηκαν."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Έχετε φτάσει στον μέγιστο αριθμό κωδικών πρόσβασης. Διαγράψτε ορισμένους κωδικούς πρόσβασης για να συνεχίσετε τον συγχρονισμό."; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index dde3b956c3..980c425db8 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -130,8 +130,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Existing bookmarks will not be duplicated."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Some bookmarks or passwords are formatted incorrectly or too long and were not synced."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Some bookmarks are formatted incorrectly or too long and were not synced."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "You've reached the maximum number of bookmarks. Please delete some bookmarks to resume sync."; @@ -139,6 +139,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Bookmark Sync is Paused"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Some passwords are formatted incorrectly or too long and were not synced."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "You've reached the maximum number of passwords. Please delete some passwords to resume sync."; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index fc4bd5136b..0d187667e7 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Los marcadores existentes no se duplicarán."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Algunos marcadores o contraseñas tienen un formato incorrecto o demasiado largo y no se han sincronizado."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Algunos marcadores tienen un formato incorrecto o demasiado largo y no se han sincronizado."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Has alcanzado el número máximo de marcadores. Elimina algunos marcadores para reanudar la sincronización."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "La sincronización de marcadores está en pausa"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Algunas contraseñas tienen un formato incorrecto o demasiado largo y no se han sincronizado."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Has alcanzado el número máximo de contraseñas. Elimina algunas contraseñas para reanudar la sincronización."; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index 1331829971..d969f50d56 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Olemasolevaid järjehoidjaid ei dubleerita."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Mõned järjehoidjad või paroolid on valesti vormindatud või liiga pikad ja neid ei sünkroonitud."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Mõned järjehoidjad on valesti vormindatud või liiga pikad ja neid ei sünkroonitud."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Oled saavutanud maksimaalse järjehoidjate arvu. Sünkroonimise jätkamiseks kustuta mõned järjehoidjad."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Järjehoidja sünkroonimine on peatatud"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Mõned paroolid on valesti vormindatud või liiga pikad ja neid ei sünkroonitud."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Oled saavutanud maksimaalse paroolide arvu. Sünkroonimise jätkamiseks kustuta mõned paroolid."; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index c20a7cfe29..89b9181452 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Olemassa olevia kirjanmerkkejä ei kopioida."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Jotkin kirjanmerkit tai salasanat on väärin muotoiltuja tai liian pitkiä, eikä niitä ole synkronoitu."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Jotkin kirjanmerkit on muotoiltu väärin tai ovat liian pitkiä, eikä niitä ole synkronoitu."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Kirjanmerkkikiintiösi on täynnä. Poista joitakin kirjanmerkkejä jatkaaksesi synkronointia."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Kirjanmerkkien synkronointi on keskeytetty"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Jotkin salasanat on muotoiltu väärin tai ovat liian pitkiä, eikä niitä ole synkronoitu."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Salasanakiintiösi on täynnä. Poista joitakin salasanoja jatkaaksesi synkronointia."; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 9de1356f04..4a7880583f 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Les signets existants ne seront pas dupliqués."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Certains signets ou mots de passe sont mal formatés ou trop longs et n'ont pas été synchronisés."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Certains signets sont mal formatés ou trop longs et n'ont pas été synchronisés."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Vous avez atteint le nombre maximal de signets. Veuillez en supprimer quelques-uns pour reprendre la synchronisation."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "La synchronisation des signets est suspendue"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Certains mots de passe sont mal formatés ou trop longs et n'ont pas été synchronisés."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Vous avez atteint le nombre maximal de mots de passe. Veuillez en supprimer quelques-uns pour reprendre la synchronisation."; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 47008863c2..83e98d3806 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Postojeće knjižne oznake neće se duplicirati."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Neke oznake ili lozinke formatirane su pogrešno, ili su preduge i nisu sinkronizirane."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Neke oznake formatirane su pogrešno, ili su preduge i nisu sinkronizirane."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Dosegnut je maksimalni broj knjižnih oznaka. Izbriši neke oznake da bi nastavio sinkronizaciju."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Sinkronizacija knjižnih oznaka je pauzirana"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Neke lozinke formatirane su pogrešno, ili su preduge i nisu sinkronizirane."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Dosegnut je maksimalni broj lozinki. Izbriši neke lozinke za nastavak sinkronizacije."; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index f511622ecd..eb4d09ba8a 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "A meglévő könyvjelzők nem kettőződnek."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Egyes könyvjelzők vagy jelszavak helytelen formátumúak vagy túl hosszúak, és nem lettek szinkronizálva."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Egyes könyvjelzők helytelen formátumúak vagy túl hosszúak, és nem lettek szinkronizálva."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Elérted a könyvjelzők maximális számát. A szinkronizálás folytatásához törölj néhány könyvjelzőt."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "A könyvjelző-szinkronizálás szünetel"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Egyes jelszavak helytelen formátumúak vagy túl hosszúak, és nem lettek szinkronizálva."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Elérted a jelszavak maximális számát. A szinkronizálás folytatásához törölj néhány jelszót."; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index cdc7afcd16..21b65d3c4d 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "I segnalibri già presenti non saranno duplicati."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Alcuni segnalibri o password sono formattati in modo errato o sono troppo lunghi e non sono stati sincronizzati."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Alcuni segnalibri sono formattati in modo errato o sono troppo lunghi e non sono stati sincronizzati."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Hai raggiunto il numero massimo di segnalibri. Elimina alcuni segnalibri per riprendere la sincronizzazione."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Sync dei segnalibri è in pausa"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Alcune password sono formattate in modo errato o sono troppo lunghe e non sono state sincronizzate."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Hai raggiunto il numero massimo di password. Elimina alcune password per riprendere la sincronizzazione."; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 1c6fcda60e..42feaed1c0 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Esamos žymės nebus kopijuojamos."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Kai kurios žymės ar slaptažodžiai suformatuoti neteisingai arba yra per ilgi ir nebuvo sinchronizuoti."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Kai kurios žymės suformatuotos neteisingai arba yra per ilgos ir nebuvo sinchronizuotos."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Pasiekėte didžiausią žymių skaičių. Ištrinkite kai kurias žymes, kad tęstumėte sinchronizavimą."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Žymių sinchronizavimas pristabdytas"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Kai kurie slaptažodžiai suformatuoti neteisingai arba yra per ilgi ir nebuvo sinchronizuoti."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Pasiekėte maksimalų slaptažodžių skaičių. Ištrinkite kai kuriuos slaptažodžius, kad tęstumėte sinchronizavimą."; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index f6ebf8a58b..fa318dbbba 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Esošās grāmatzīmes netiks dublētas."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Dažas grāmatzīmes vai paroles ir formatētas nepareizi vai pārāk garas un netika sinhronizētas."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Dažas grāmatzīmes ir formatētas nepareizi vai pārāk garas un netika sinhronizētas."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Tu esi sasniedzis maksimālo grāmatzīmju skaitu. Lūdzu, izdzēs dažas grāmatzīmes, lai atsāktu sinhronizāciju."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Grāmatzīmju sinhronizācija ir apturēta"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Dažas paroles ir formatētas nepareizi vai pārāk garas un netika sinhronizētas."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Tu esi sasniedzis maksimālo paroļu skaitu. Lūdzu, izdzēs dažas paroles, lai atsāktu sinhronizāciju."; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 9f949c9f4e..c12d524984 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Eksisterende bokmerker blir ikke duplisert."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Noen bokmerker eller passord er formatert feil eller for lange og ble ikke synkronisert."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Noen bokmerker er formatert feil eller for lange og ble ikke synkronisert."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Du har nådd det maksimale antallet bokmerker. Slett noen bokmerker for å gjenoppta synkronisering."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Synkronisering av bokmerker er satt på pause"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Noen passord er formatert feil eller for lange og ble ikke synkronisert."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Du har nådd det maksimale antallet passord. Slett noen passord for å gjenoppta synkronisering."; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 45f7f3ddfe..616ae54984 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Bestaande bladwijzers worden niet gedupliceerd."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Sommige bladwijzers of wachtwoorden hebben de verkeerd indeling of te lang en werden niet gesynchroniseerd."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Sommige bladwijzers hebben de verkeerde indeling of zijn te lang en werden niet gesynchroniseerd."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Je hebt het maximumaantal bladwijzers bereikt. Verwijder enkele bladwijzers om de synchronisatie te hervatten."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Synchronisatie van bladwijzers is gepauzeerd"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Sommige wachtwoorden hebben de verkeerde indeling of zijn te lang en werden niet gesynchroniseerd."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Je hebt het maximumaantal wachtwoorden bereikt. Verwijder enkele wachtwoorden om de synchronisatie te hervatten."; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 6e109279ef..02689aa9e2 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Istniejące zakładki nie zostaną zduplikowane."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Niektóre zakładki lub hasła mają niepoprawny format lub są zbyt długie i nie zostały zsynchronizowane."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Niektóre zakładki mają niepoprawny format lub są zbyt długie i nie zostały zsynchronizowane."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Osiągnięto maksymalną liczbę zakładek. Usuń część zakładek, aby wznowić synchronizację."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Synchronizacja zakładek jest wstrzymana"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Niektóre hasła mają niepoprawny format lub są zbyt długie i nie zostały zsynchronizowane."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Osiągnięto maksymalną liczbę haseł. Usuń część haseł, aby wznowić synchronizację."; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 8ccaa869a1..e4db35ec48 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Os marcadores existentes não serão duplicados."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Alguns marcadores ou palavras-passe estão formatados incorretamente ou são demasiado longos e não foram sincronizados."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Alguns marcadores estão formatados incorretamente ou são demasiado longos e não foram sincronizados."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Atingiste o número máximo de marcadores. Elimina alguns marcadores para retomar a sincronização."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "A sincronização de marcadores está em pausa"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Algumas palavras-passe estão formatadas incorretamente ou são demasiado longas e não foram sincronizadas."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Atingiste o número máximo de palavras-passe. Elimina algumas palavras-passe para retomar a sincronização."; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index d2c478099c..d7e7c6f3dc 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Marcajele existente nu vor fi duplicate."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Unele semne de carte sau parole sunt formatate incorect sau sunt prea lungi și nu au fost sincronizate."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Unele semne de carte sunt formatate incorect sau sunt prea lungi și nu au fost sincronizate."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Ai atins numărul maxim de marcaje. Șterge câteva marcaje pentru a relua sincronizarea."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Sincronizarea marcajelor este întreruptă"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Unele parole sunt formatate incorect sau sunt prea lungi și nu au fost sincronizate."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Ai atins numărul maxim de parole. Șterge câteva parole pentru a relua sincronizarea."; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index 69f9e3de7f..19dc60242f 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Существующие закладки дублироваться не будут."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Некоторые закладки или пароли не синхронизируются из-за неверного формата или превышения ограничений по длине."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Некоторые закладки не синхронизируются из-за неверного формата или превышения ограничений по длине."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Достигнуто максимальное число закладок. Чтобы возобновить синхронизацию, удалите некоторые из них."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Синхронизация закладок приостановлена"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Некоторые пароли не синхронизируются из-за неверного формата или превышения ограничений по длине."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Достигнуто максимальное число паролей. Чтобы возобновить синхронизацию, удалите некоторые из них."; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index f613e9be41..277ba1b449 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Existujúce záložky sa nebudú duplikovať."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Niektoré záložky alebo heslá sú nesprávne naformátované alebo príliš dlhé a neboli synchronizované."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Niektoré záložky sú nesprávne naformátované alebo sú príliš dlhé, a neboli synchronizované."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Dosiahli ste maximálny počet záložiek. Ak chcete obnoviť synchronizáciu, odstráňte niektoré záložky."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Synchronizácia záložiek je pozastavená."; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Niektoré heslá sú nesprávne naformátované alebo sú príliš dlhé, a neboli synchronizované."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Dosiahli ste maximálny počet hesiel. Ak chcete obnoviť synchronizáciu, odstráňte niektoré heslá."; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 05d4f72a9d..76f00ef372 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Obstoječi zaznamki ne bodo podvojeni."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Nekateri zaznamki ali gesla so napačno oblikovani ali predolgi in niso bili sinhronizirani."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Nekateri zaznamki so napačno oblikovani ali predolgi in niso bili sinhronizirani."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Dosegli ste največje število zaznamkov. Izbrišite nekaj zaznamkov, da nadaljujete sinhronizacijo."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Sinhronizacija zaznamkov je zaustavljena"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Nekatera gesla so napačno oblikovana ali predolga in niso bila sinhronizirana."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Dosegli ste največje število gesel. Izbrišite nekatera gesla, da nadaljujete sinhronizacijo."; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 799c79dc36..4eef979147 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Befintliga bokmärken kommer inte att kopieras."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Vissa bokmärken eller lösenord är felaktigt formaterade eller för långa och synkroniserades inte."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Vissa bokmärken är felaktigt formaterade eller för långa och synkroniserades inte."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Du har nått det maximala antalet bokmärken. Ta bort några bokmärken för att återuppta synkroniseringen."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Bokmärkessynkronisering är pausad"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Vissa lösenord är felaktigt formaterade eller för långa och synkroniserades inte."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Du har nått det maximala antalet lösenord. Ta bort några lösenord för att återuppta synkroniseringen."; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index f9c3674851..90c99493a2 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -133,8 +133,8 @@ /* No comment provided by engineer. */ "alert.message.bookmarkAll" = "Mevcut yer imleri çoğaltılmayacaktır."; -/* Description for alert shown when sync error occurs because of bad data */ -"alert.sync-bad-data-error-description" = "Bazı yer işaretleri veya parolalar yanlış veya çok uzun biçimlendirilmiş ve senkronize edilmemiş."; +/* Description for alert shown when sync error occurs because of bad bookmarks data */ +"alert.sync-bookmarks-bad-data-error-description" = "Bazı yer işaretleri yanlış biçimlendirilmiş veya çok uzun olduğu için senkronize edilmedi."; /* Description for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-description" = "Maksimum yer işareti sayısına ulaştınız. Senkronizasyona devam etmek için lütfen bazı yer işaretlerini silin."; @@ -142,6 +142,9 @@ /* Title for alert shown when sync bookmarks paused for too many items */ "alert.sync-bookmarks-paused-title" = "Yer İşaretleri Senkronizasyonu Duraklatıldı"; +/* Description for alert shown when sync error occurs because of bad credentials data */ +"alert.sync-credentials-bad-data-error-description" = "Bazı şifreler yanlış biçimlendirilmiş veya çok uzun olduğu için senkronize edilmedi."; + /* Description for alert shown when sync credentials paused for too many items */ "alert.sync-credentials-paused-description" = "Maksimum şifre sayısına ulaştınız. Lütfen senkronizasyona devam etmek için bazı şifreleri silin."; diff --git a/DuckDuckGoTests/CapturingAdapterErrorHandler.swift b/DuckDuckGoTests/CapturingAdapterErrorHandler.swift index 09847724fc..e7b06c7725 100644 --- a/DuckDuckGoTests/CapturingAdapterErrorHandler.swift +++ b/DuckDuckGoTests/CapturingAdapterErrorHandler.swift @@ -25,8 +25,14 @@ class CapturingAdapterErrorHandler: SyncErrorHandling { var syncCredentialsSuccededCalled = false var handleCredentialErrorCalled = false var syncBookmarksSuccededCalled = false + var handleSettingsErrorCalled = false var capturedError: Error? + func handleSettingsError(_ error: Error) { + handleSettingsErrorCalled = true + capturedError = error + } + func handleBookmarkError(_ error: Error) { handleBookmarkErrorCalled = true capturedError = error diff --git a/DuckDuckGoTests/SyncBookmarksAdapterTests.swift b/DuckDuckGoTests/SyncBookmarksAdapterTests.swift index a3686e2e23..acf9a6da12 100644 --- a/DuckDuckGoTests/SyncBookmarksAdapterTests.swift +++ b/DuckDuckGoTests/SyncBookmarksAdapterTests.swift @@ -58,7 +58,7 @@ final class SyncBookmarksAdapterTests: XCTestCase { cancellables = nil } - func testWhenSyncErrorPublished_ThenErrorHandlerHandleCredentialErrorCalled() async { + func testWhenSyncErrorPublished_ThenErrorHandlerHandleBookmarksErrorCalled() async { let expectation = XCTestExpectation(description: "Sync did fail") let expectedError = NSError(domain: "some error", code: 400) adapter.setUpProviderIfNeeded(database: database, metadataStore: metadataStore) From dc1d84e1f7e17f32737f9b5ac1e7366cb20efcb1 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 5 Jul 2024 01:30:28 +0200 Subject: [PATCH 04/15] Fixes App Data Clearing State Status In Settings (#3041) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1204099484721401/1207731330276486/f Description: Fixes an issue that caused the "Automatically Clear Data" state in settings to be out of sync. Fixed by observing Data Clearing updates. – (Eventually, this and all other parts of AppSettings should adopt Combine publishers instead of Notifications, but this does the trick today) --- DuckDuckGo/AppUserDefaults.swift | 2 ++ DuckDuckGo/SettingsViewModel.swift | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 76501da7c3..388c4bd0a2 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -40,6 +40,7 @@ public class AppUserDefaults: AppSettings { public static let showsFullURLAddressSettingChanged = Notification.Name("com.duckduckgo.app.ShowsFullURLAddressSettingChanged") public static let autofillDebugScriptToggled = Notification.Name("com.duckduckgo.app.DidToggleAutofillDebugScript") public static let duckPlayerSettingsUpdated = Notification.Name("com.duckduckgo.app.DuckPlayerSettingsUpdated") + public static let appDataClearingUpdated = Notification.Name("com.duckduckgo.app.dataClearingUpdates") } private let groupName: String @@ -154,6 +155,7 @@ public class AppUserDefaults: AppSettings { set { userDefaults?.setValue(newValue.rawValue, forKey: Keys.autoClearActionKey) + NotificationCenter.default.post(name: Notifications.appDataClearingUpdated, object: nil) } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index d6c9a39cee..e547ac8500 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -60,6 +60,9 @@ final class SettingsViewModel: ObservableObject { private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad private var cancellables = Set() + // App Data State Notification Observer + private var appDataClearingObserver: Any? + // Closures to interact with legacy view controllers through the container var onRequestPushLegacyView: ((UIViewController) -> Void)? var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? @@ -345,6 +348,7 @@ final class SettingsViewModel: ObservableObject { deinit { subscriptionSignOutObserver = nil + appDataClearingObserver = nil } } // swiftlint:enable type_body_length @@ -732,6 +736,15 @@ extension SettingsViewModel { } } } + + // Observe App Data clearing state + appDataClearingObserver = NotificationCenter.default.addObserver(forName: AppUserDefaults.Notifications.appDataClearingUpdated, + object: nil, + queue: .main) { [weak self] _ in + guard let settings = self?.appSettings else { return } + self?.state.autoclearDataEnabled = (AutoClearSettingsModel(settings: settings) != nil) + } + } @available(iOS 15.0, *) From 950d8ba0881ab7f7532e846cf362b087f7f5e4b2 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Fri, 5 Jul 2024 11:36:14 +0200 Subject: [PATCH 05/15] Update Sync error pixels (#3046) Task/Issue URL: https://app.asana.com/0/1201621708115095/1207559244852258/f Description: Rename count limit exceeded as object limit exceeded and add "Too Many Requests" pixels. --- Core/PixelEvent.swift | 28 ++++++++++++++++++++-------- Core/SyncErrorHandler.swift | 28 ++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 4e48109b4f..83926da830 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -552,11 +552,17 @@ extension Pixel { case syncFailedToMigrate case syncFailedToLoadAccount case syncFailedToSetupEngine - case syncBookmarksCountLimitExceededDaily - case syncCredentialsCountLimitExceededDaily + case syncBookmarksObjectLimitExceededDaily + case syncCredentialsObjectLimitExceededDaily case syncBookmarksRequestSizeLimitExceededDaily case syncCredentialsRequestSizeLimitExceededDaily - + case syncBookmarksTooManyRequestsDaily + case syncCredentialsTooManyRequestsDaily + case syncSettingsTooManyRequestsDaily + case syncBookmarksValidationErrorDaily + case syncCredentialsValidationErrorDaily + case syncSettingsValidationErrorDaily + case syncSentUnauthenticatedRequest case syncMetadataCouldNotLoadDatabase case syncBookmarksFailed @@ -1250,11 +1256,17 @@ extension Pixel.Event { case .syncFailedToMigrate: return "m_d_sync_failed_to_migrate" case .syncFailedToLoadAccount: return "m_d_sync_failed_to_load_account" case .syncFailedToSetupEngine: return "m_d_sync_failed_to_setup_engine" - case .syncBookmarksCountLimitExceededDaily: return "m_d_sync_bookmarks_count_limit_exceeded_daily" - case .syncCredentialsCountLimitExceededDaily: return "m_d_sync_credentials_count_limit_exceeded_daily" - case .syncBookmarksRequestSizeLimitExceededDaily: return "m_d_sync_bookmarks_request_size_limit_exceeded_daily" - case .syncCredentialsRequestSizeLimitExceededDaily: return "m_d_sync_credentials_request_size_limit_exceeded_daily" - + case .syncBookmarksObjectLimitExceededDaily: return "m_sync_bookmarks_object_limit_exceeded_daily" + case .syncCredentialsObjectLimitExceededDaily: return "m_sync_credentials_object_limit_exceeded_daily" + case .syncBookmarksRequestSizeLimitExceededDaily: return "m_sync_bookmarks_request_size_limit_exceeded_daily" + case .syncCredentialsRequestSizeLimitExceededDaily: return "m_sync_credentials_request_size_limit_exceeded_daily" + case .syncBookmarksTooManyRequestsDaily: return "m_sync_bookmarks_too_many_requests_daily" + case .syncCredentialsTooManyRequestsDaily: return "m_sync_credentials_too_many_requests_daily" + case .syncSettingsTooManyRequestsDaily: return "m_sync_settings_too_many_requests_daily" + case .syncBookmarksValidationErrorDaily: return "m_sync_bookmarks_validation_error_daily" + case .syncCredentialsValidationErrorDaily: return "m_sync_credentials_validation_error_daily" + case .syncSettingsValidationErrorDaily: return "m_sync_settings_validation_error_daily" + case .syncSentUnauthenticatedRequest: return "m_d_sync_sent_unauthenticated_request" case .syncMetadataCouldNotLoadDatabase: return "m_d_sync_metadata_could_not_load_database" case .syncBookmarksFailed: return "m_d_sync_bookmarks_failed" diff --git a/Core/SyncErrorHandler.swift b/Core/SyncErrorHandler.swift index 025a3abfcb..ec929c8fbd 100644 --- a/Core/SyncErrorHandler.swift +++ b/Core/SyncErrorHandler.swift @@ -208,10 +208,12 @@ extension SyncErrorHandler { case .settings: break } + DailyPixel.fire(pixel: modelType.badRequestPixel) case .unexpectedStatusCode(401): syncIsPaused(errorType: .invalidLoginCredentials) case .unexpectedStatusCode(418), .unexpectedStatusCode(429): syncIsPaused(errorType: .tooManyRequests) + DailyPixel.fire(pixel: modelType.tooManyRequestsPixel) default: break } @@ -223,11 +225,11 @@ extension SyncErrorHandler { case .bookmarksCountLimitExceeded: currentSyncBookmarksPausedError = errorType.rawValue self.isSyncBookmarksPaused = true - DailyPixel.fire(pixel: .syncBookmarksCountLimitExceededDaily) + DailyPixel.fire(pixel: .syncBookmarksObjectLimitExceededDaily) case .credentialsCountLimitExceeded: currentSyncCredentialsPausedError = errorType.rawValue self.isSyncCredentialsPaused = true - DailyPixel.fire(pixel: .syncCredentialsCountLimitExceededDaily) + DailyPixel.fire(pixel: .syncCredentialsObjectLimitExceededDaily) case .bookmarksRequestSizeLimitExceeded: currentSyncBookmarksPausedError = errorType.rawValue self.isSyncBookmarksPaused = true @@ -305,6 +307,28 @@ extension SyncErrorHandler { .syncSettingsPatchCompressionFailed } } + + var tooManyRequestsPixel: Pixel.Event { + switch self { + case .bookmarks: + .syncBookmarksTooManyRequestsDaily + case .credentials: + .syncCredentialsTooManyRequestsDaily + case .settings: + .syncSettingsTooManyRequestsDaily + } + } + + var badRequestPixel: Pixel.Event { + switch self { + case .bookmarks: + .syncBookmarksValidationErrorDaily + case .credentials: + .syncCredentialsValidationErrorDaily + case .settings: + .syncSettingsValidationErrorDaily + } + } } } From f083e076a68363a6c4ad8a4ee574a20938843d09 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 5 Jul 2024 11:14:00 +0100 Subject: [PATCH 06/15] Subscription refactoring #5 (#3023) Task/Issue URL: https://app.asana.com/0/1205842942115003/1206805455884775/f Tech Design URL: https://app.asana.com/0/1205842942115003/1207147511614062/f This PR updates BSK from https://github.com/duckduckgo/BrowserServicesKit/pull/874 and contains all the needed refactoring for the Subscription classes inits. It also contains a new unit test class: - SubscriptionPagesUseSubscriptionFeatureTests Complete tests will be implemented in follow-up tasks --- DuckDuckGo.xcodeproj/project.pbxproj | 66 +++++++------- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/SettingsViewModel.swift | 5 +- .../SubscriptionManageriOS14.swift | 7 ++ ...scriptionPagesUseSubscriptionFeature.swift | 2 - .../SubscriptionContainerViewFactory.swift | 49 +++++++--- .../SubscriptionContainerViewModelTests.swift | 89 +++++++++++++++++++ .../SubscriptionFlowViewModelTests.swift | 44 ++++++--- ...tionPagesUseSubscriptionFeatureTests.swift | 47 ++++++++++ .../SubscriptionContainerViewModelTests.swift | 74 --------------- .../SyncManagementViewModelTests.swift | 0 11 files changed, 250 insertions(+), 137 deletions(-) create mode 100644 DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift rename DuckDuckGoTests/{ => Subscription}/SubscriptionFlowViewModelTests.swift (50%) create mode 100644 DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift delete mode 100644 DuckDuckGoTests/SubscriptionContainerViewModelTests.swift rename DuckDuckGoTests/{ => SyncUI}/SyncManagementViewModelTests.swift (100%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a0bb87b389..c7a42731e3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -447,7 +447,6 @@ 85BDC310243359040053DB07 /* FindInPageUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BDC30F243359040053DB07 /* FindInPageUserScript.swift */; }; 85BDC3142434D8F80053DB07 /* DebugUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BDC3132434D8F80053DB07 /* DebugUserScript.swift */; }; 85BDC3192436161C0053DB07 /* LoginFormDetectionUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BDC3182436161C0053DB07 /* LoginFormDetectionUserScript.swift */; }; - 85C011FD299285A6001E0A99 /* SyncManagementViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C011FB29928524001E0A99 /* SyncManagementViewModelTests.swift */; }; 85C11E4120904BBE00BFFEB4 /* VariantManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C11E4020904BBE00BFFEB4 /* VariantManagerTests.swift */; }; 85C11E4C2090888C00BFFEB4 /* HomeRowReminder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C11E4B2090888C00BFFEB4 /* HomeRowReminder.swift */; }; 85C11E532090B23A00BFFEB4 /* UserDefaultsHomeRowReminderStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C11E522090B23A00BFFEB4 /* UserDefaultsHomeRowReminderStorageTests.swift */; }; @@ -609,10 +608,8 @@ 98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1D7217B37010011A0D4 /* Theme.swift */; }; 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; }; 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; }; - 9F2510142BF5809E0096DB16 /* SubscriptionFlowViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2510132BF5809E0096DB16 /* SubscriptionFlowViewModelTests.swift */; }; 9F8FE9492BAE50E50071E372 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 9F8FE9482BAE50E50071E372 /* Lottie */; }; 9FA5E44B2BF1AF3400BDEF02 /* SubscriptionContainerViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA5E44A2BF1AF3400BDEF02 /* SubscriptionContainerViewFactory.swift */; }; - 9FA5E44E2BF1B16400BDEF02 /* SubscriptionContainerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA5E44D2BF1B16400BDEF02 /* SubscriptionContainerViewModelTests.swift */; }; AA3D854523D9942200788410 /* AppIconSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854423D9942200788410 /* AppIconSettingsViewController.swift */; }; AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */; }; AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854823DA1DFB00788410 /* AppIcon.swift */; }; @@ -938,6 +935,10 @@ F198D7981E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */; }; F1A886781F29394E0096251E /* WebCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A886771F29394E0096251E /* WebCacheManager.swift */; }; F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */; }; + F1BDDBFD2C340D9C00459306 /* SubscriptionContainerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBF92C340D9C00459306 /* SubscriptionContainerViewModelTests.swift */; }; + F1BDDBFE2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBFA2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift */; }; + F1BDDBFF2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDBFB2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */; }; + F1BDDC022C340DDF00459306 /* SyncManagementViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BDDC002C340DDF00459306 /* SyncManagementViewModelTests.swift */; }; F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */; }; F1C4A70E1E57725800A6CA1B /* OmniBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4A70D1E57725800A6CA1B /* OmniBar.swift */; }; F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CA3C361F045878005FADB3 /* PrivacyStore.swift */; }; @@ -1567,7 +1568,6 @@ 85BDC30F243359040053DB07 /* FindInPageUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindInPageUserScript.swift; sourceTree = ""; }; 85BDC3132434D8F80053DB07 /* DebugUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugUserScript.swift; sourceTree = ""; }; 85BDC3182436161C0053DB07 /* LoginFormDetectionUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginFormDetectionUserScript.swift; sourceTree = ""; }; - 85C011FB29928524001E0A99 /* SyncManagementViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncManagementViewModelTests.swift; sourceTree = ""; }; 85C11E4020904BBE00BFFEB4 /* VariantManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantManagerTests.swift; sourceTree = ""; }; 85C11E4B2090888C00BFFEB4 /* HomeRowReminder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRowReminder.swift; sourceTree = ""; }; 85C11E522090B23A00BFFEB4 /* UserDefaultsHomeRowReminderStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsHomeRowReminderStorageTests.swift; sourceTree = ""; }; @@ -2252,9 +2252,7 @@ 98F3A1D7217B37010011A0D4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesLists.swift; sourceTree = ""; }; 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemableNavigationController.swift; sourceTree = ""; }; - 9F2510132BF5809E0096DB16 /* SubscriptionFlowViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModelTests.swift; sourceTree = ""; }; 9FA5E44A2BF1AF3400BDEF02 /* SubscriptionContainerViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewFactory.swift; sourceTree = ""; }; - 9FA5E44D2BF1B16400BDEF02 /* SubscriptionContainerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewModelTests.swift; sourceTree = ""; }; AA3D854423D9942200788410 /* AppIconSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconSettingsViewController.swift; sourceTree = ""; }; AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconSettingsCell.swift; sourceTree = ""; }; AA3D854823DA1DFB00788410 /* AppIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIcon.swift; sourceTree = ""; }; @@ -2620,6 +2618,10 @@ F1AA54601E48D90700223211 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; F1B745211E549D550072547E /* UIColorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIColorExtension.swift; path = ../Core/UIColorExtension.swift; sourceTree = ""; }; + F1BDDBF92C340D9C00459306 /* SubscriptionContainerViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewModelTests.swift; sourceTree = ""; }; + F1BDDBFA2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModelTests.swift; sourceTree = ""; }; + F1BDDBFB2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeatureTests.swift; sourceTree = ""; }; + F1BDDC002C340DDF00459306 /* SyncManagementViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncManagementViewModelTests.swift; sourceTree = ""; }; F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialSettings.swift; sourceTree = ""; }; F1C4A70D1E57725800A6CA1B /* OmniBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmniBar.swift; sourceTree = ""; }; F1CA3C361F045878005FADB3 /* PrivacyStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyStore.swift; sourceTree = ""; }; @@ -3735,7 +3737,7 @@ 84E341A91E2F7EFB00BDBA6F /* UnitTests */ = { isa = PBXGroup; children = ( - 85C011FA2992850A001E0A99 /* SyncUI */, + F1BDDC012C340DDF00459306 /* SyncUI */, F12D98401F266B30003C2EE3 /* DuckDuckGo */, F1E092B31E92A6B900732CCC /* Core */, F1134ED11F40EDB600B73467 /* TestUtils */, @@ -3961,14 +3963,6 @@ name = Web; sourceTree = ""; }; - 85C011FA2992850A001E0A99 /* SyncUI */ = { - isa = PBXGroup; - children = ( - 85C011FB29928524001E0A99 /* SyncManagementViewModelTests.swift */, - ); - name = SyncUI; - sourceTree = ""; - }; 85C11E4A209084DE00BFFEB4 /* HomeRow */ = { isa = PBXGroup; children = ( @@ -4217,15 +4211,6 @@ name = Themes; sourceTree = ""; }; - 9FA5E44C2BF1B14100BDEF02 /* Subscription */ = { - isa = PBXGroup; - children = ( - 9FA5E44D2BF1B16400BDEF02 /* SubscriptionContainerViewModelTests.swift */, - 9F2510132BF5809E0096DB16 /* SubscriptionFlowViewModelTests.swift */, - ); - name = Subscription; - sourceTree = ""; - }; AA4D6A8023DE4973007E8790 /* AppIcon */ = { isa = PBXGroup; children = ( @@ -4953,9 +4938,7 @@ isa = PBXGroup; children = ( 6F03CAFF2C32ED22004179A8 /* NewTabPage */, - 569437222BDD402600C0881B /* Sync */, 6FF9157F2B88E04F0042AC87 /* AdAttribution */, - CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */, F17669A21E411D63003D3222 /* Application */, 981FED7222045FFA008488D7 /* AutoClear */, 1E1D8B5B2994FF7800C96994 /* Autoconsent */, @@ -4969,10 +4952,12 @@ EE56DE3A2A6038F500375C41 /* NetworkProtection */, F1D477C71F2139210031ED49 /* OmniBar */, 98EA2C3F218BB5140023E1DC /* Settings */, - 9FA5E44C2BF1B14100BDEF02 /* Subscription */, + F1BDDBFC2C340D9C00459306 /* Subscription */, + 569437222BDD402600C0881B /* Sync */, F13B4BF71F18C9E800814661 /* Tabs */, 98EA2C3A218B9A880023E1DC /* Themes */, F12790DD1EBBDDF3001D3AEC /* Tutorials */, + CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */, F194FAF91F14E605009B4DF8 /* UserInterface */, 317045BE2858C69A0016ED1F /* Utils */, 4B6484F927FFCF520050A7A1 /* Waitlist */, @@ -5329,6 +5314,24 @@ name = Privacy; sourceTree = ""; }; + F1BDDBFC2C340D9C00459306 /* Subscription */ = { + isa = PBXGroup; + children = ( + F1BDDBF92C340D9C00459306 /* SubscriptionContainerViewModelTests.swift */, + F1BDDBFA2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift */, + F1BDDBFB2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift */, + ); + path = Subscription; + sourceTree = ""; + }; + F1BDDC012C340DDF00459306 /* SyncUI */ = { + isa = PBXGroup; + children = ( + F1BDDC002C340DDF00459306 /* SyncManagementViewModelTests.swift */, + ); + path = SyncUI; + sourceTree = ""; + }; F1BE54481E69DD5F00FCF649 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -7001,13 +7004,13 @@ 83EDCC411F86B89C005CDFCD /* StatisticsLoaderTests.swift in Sources */, C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */, 85C29708247BDD060063A335 /* DaxDialogsBrowsingSpecTests.swift in Sources */, - 9FA5E44E2BF1B16400BDEF02 /* SubscriptionContainerViewModelTests.swift in Sources */, 85BA58581F34F72F00C6E8CA /* AppUserDefaultsTests.swift in Sources */, F1134EBC1F40D45700B73467 /* MockStatisticsStore.swift in Sources */, 983C52E72C2C0ACB007B5747 /* BookmarkStateRepairTests.swift in Sources */, 31C138AC27A403CB00FFD4B2 /* DownloadManagerTests.swift in Sources */, EEFE9C732A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift in Sources */, F13B4BF91F18CA0600814661 /* TabsModelTests.swift in Sources */, + F1BDDBFD2C340D9C00459306 /* SubscriptionContainerViewModelTests.swift in Sources */, 98B31290218CCB2200E54DE1 /* MockDependencyProvider.swift in Sources */, CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */, 986B45D0299E30A50089D2D7 /* BookmarkEntityTests.swift in Sources */, @@ -7024,6 +7027,8 @@ F1134ECE1F40EA9C00B73467 /* AtbParserTests.swift in Sources */, D62EC3BE2C24710F00FC9D04 /* DuckPlayerURLExtensionTests.swift in Sources */, F189AEE41F18FDAF001EBAE1 /* LinkTests.swift in Sources */, + F1BDDBFE2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift in Sources */, + F1BDDC022C340DDF00459306 /* SyncManagementViewModelTests.swift in Sources */, D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */, 5694372B2BE3F2D900C0881B /* SyncErrorHandlerTests.swift in Sources */, 987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */, @@ -7060,7 +7065,6 @@ 1E8146AD28C8ABF000D1AF63 /* TrackerAnimationLogicTests.swift in Sources */, C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */, B6AD9E3A28D456820019CDE9 /* PrivacyConfigurationManagerMock.swift in Sources */, - 9F2510142BF5809E0096DB16 /* SubscriptionFlowViewModelTests.swift in Sources */, F189AED71F18F6DE001EBAE1 /* TabTests.swift in Sources */, F13B4BFB1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift in Sources */, CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */, @@ -7111,11 +7115,11 @@ F1134ED21F40EF3A00B73467 /* JsonTestDataLoader.swift in Sources */, 850250B520D80419002199C7 /* AtbAndVariantCleanupTests.swift in Sources */, 834DF992248FDE1A0075EA48 /* UserAgentTests.swift in Sources */, - 85C011FD299285A6001E0A99 /* SyncManagementViewModelTests.swift in Sources */, C14882E727F20DAB00D59F0C /* HtmlTestDataLoader.swift in Sources */, F17D72391E8B35C6003E8B0E /* AppURLsTests.swift in Sources */, F1134ED61F40F29F00B73467 /* StatisticsUserDefaultsTests.swift in Sources */, 98629D342C21BE37001E6031 /* BookmarksStateValidationTests.swift in Sources */, + F1BDDBFF2C340D9C00459306 /* SubscriptionPagesUseSubscriptionFeatureTests.swift in Sources */, 98EA2C3C218B9AAD0023E1DC /* ThemeManagerTests.swift in Sources */, 569437292BDD487600C0881B /* SyncCredentialsAdapterTests.swift in Sources */, 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */, @@ -9962,7 +9966,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 164.3.0; + version = 165.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c6f6300232..15eb24cbec 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "28dd48c5aca37c46402e2a14f7c47aad3877b3aa", - "version" : "164.3.0" + "revision" : "777e5ae1ab890d9ec22e069bc5dc0f0ada4b35af", + "version" : "165.0.0" } }, { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index e547ac8500..74a4202ed2 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -750,7 +750,10 @@ extension SettingsViewModel { @available(iOS 15.0, *) func restoreAccountPurchase() async { DispatchQueue.main.async { self.state.subscription.isRestoring = true } - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { case .success: diff --git a/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift index 684ea20a41..6172f1bb66 100644 --- a/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift +++ b/DuckDuckGo/Subscription/SubscriptionManageriOS14.swift @@ -29,6 +29,13 @@ class SubscriptionManageriOS14: SubscriptionManager { func storePurchaseManager() -> StorePurchaseManager { DefaultStorePurchaseManager() } + + static func loadEnvironmentFrom(userDefaults: UserDefaults) -> SubscriptionEnvironment? { + return nil + } + + static func save(subscriptionEnvironment: SubscriptionEnvironment, userDefaults: UserDefaults) {} + var currentEnvironment: SubscriptionEnvironment = SubscriptionEnvironment.default var canPurchase: Bool = false func loadInitialData() {} diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index e257a026be..d28469bc02 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -95,7 +95,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec private let subscriptionManager: SubscriptionManager private var accountManager: AccountManager { subscriptionManager.accountManager } private let appStorePurchaseFlow: AppStorePurchaseFlow - private let appStoreRestoreFlow: AppStoreRestoreFlow private let appStoreAccountManagementFlow: AppStoreAccountManagementFlow @@ -177,7 +176,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec private func resetSubscriptionFlow() { setTransactionError(nil) - } private func setTransactionError(_ error: UseSubscriptionError?) { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift index 32c1423204..8b49dd0a8d 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift @@ -24,10 +24,18 @@ import Subscription enum SubscriptionContainerViewFactory { static func makeSubscribeFlow(origin: String?, navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager) -> some View { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager, + appStoreRestoreFlow: appStoreRestoreFlow, + authEndpointService: subscriptionManager.authEndpointService) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager) let viewModel = SubscriptionContainerViewModel( subscriptionManager: subscriptionManager, @@ -44,10 +52,18 @@ enum SubscriptionContainerViewFactory { } static func makeRestoreFlow(navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager) -> some View { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager, + appStoreRestoreFlow: appStoreRestoreFlow, + authEndpointService: subscriptionManager.authEndpointService) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager) let viewModel = SubscriptionContainerViewModel( subscriptionManager: subscriptionManager, @@ -66,11 +82,18 @@ enum SubscriptionContainerViewFactory { static func makeEmailFlow(navigationCoordinator: SubscriptionNavigationCoordinator, subscriptionManager: SubscriptionManager, onDisappear: @escaping () -> Void) -> some View { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: subscriptionManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: subscriptionManager) - + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager, + appStoreRestoreFlow: appStoreRestoreFlow, + authEndpointService: subscriptionManager.authEndpointService) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager) let viewModel = SubscriptionContainerViewModel( subscriptionManager: subscriptionManager, origin: nil, diff --git a/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift new file mode 100644 index 0000000000..77f460da48 --- /dev/null +++ b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift @@ -0,0 +1,89 @@ +// +// SubscriptionContainerViewModelTests.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import DuckDuckGo +@testable import Subscription +import SubscriptionTestingUtilities + +@available(iOS 15.0, *) +final class SubscriptionContainerViewModelTests: XCTestCase { + var sut: SubscriptionContainerViewModel! + let subscriptionManager = MockDependencyProvider().subscriptionManager + + func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { + // GIVEN + let origin = "test_origin" + let queryParameter = URLQueryItem(name: "origin", value: "test_origin") + let expectedURL = SubscriptionURL.purchase.subscriptionURL(environment: .production).appending(percentEncodedQueryItem: queryParameter) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager, + appStoreRestoreFlow: appStoreRestoreFlow, + authEndpointService: subscriptionManager.authEndpointService) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager) + + // WHEN + sut = .init(subscriptionManager: subscriptionManager, + origin: origin, + userScript: .init(), + subFeature: .init(subscriptionManager: subscriptionManager, + subscriptionAttributionOrigin: nil, + appStorePurchaseFlow: appStorePurchaseFlow, + appStoreRestoreFlow: appStoreRestoreFlow, + appStoreAccountManagementFlow: appStoreAccountManagementFlow)) + + // THEN + XCTAssertEqual(sut.flow.purchaseURL, expectedURL) + } + + func testWhenInitWithoutOriginThenSubscriptionFlowPurchaseURLDoesNotHaveOriginSet() { + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager, + appStoreRestoreFlow: appStoreRestoreFlow, + authEndpointService: subscriptionManager.authEndpointService) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager) + + // WHEN + sut = .init(subscriptionManager: subscriptionManager, + origin: nil, + userScript: .init(), + subFeature: .init(subscriptionManager: subscriptionManager, + subscriptionAttributionOrigin: nil, + appStorePurchaseFlow: appStorePurchaseFlow, + appStoreRestoreFlow: appStoreRestoreFlow, + appStoreAccountManagementFlow: appStoreAccountManagementFlow)) + + // THEN + XCTAssertEqual(sut.flow.purchaseURL, SubscriptionURL.purchase.subscriptionURL(environment: .production)) + } +} diff --git a/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift similarity index 50% rename from DuckDuckGoTests/SubscriptionFlowViewModelTests.swift rename to DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift index 6d375a2a87..76646f449e 100644 --- a/DuckDuckGoTests/SubscriptionFlowViewModelTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift @@ -26,43 +26,59 @@ import SubscriptionTestingUtilities final class SubscriptionFlowViewModelTests: XCTestCase { private var sut: SubscriptionFlowViewModel! - let mockDependencyProvider = MockDependencyProvider() - + let subscriptionManager = MockDependencyProvider().subscriptionManager + func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { // GIVEN let origin = "test_origin" let queryParameter = URLQueryItem(name: "origin", value: "test_origin") let expectedURL = SubscriptionURL.purchase.subscriptionURL(environment: .production).appending(percentEncodedQueryItem: queryParameter) - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: mockDependencyProvider.subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager, + appStoreRestoreFlow: appStoreRestoreFlow, + authEndpointService: subscriptionManager.authEndpointService) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager) // WHEN - sut = .init(origin: origin, userScript: .init(), subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + sut = .init(origin: origin, userScript: .init(), subFeature: .init(subscriptionManager: subscriptionManager, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, appStoreRestoreFlow: appStoreRestoreFlow, appStoreAccountManagementFlow: appStoreAccountManagementFlow), - subscriptionManager: mockDependencyProvider.subscriptionManager) + subscriptionManager: subscriptionManager) // THEN XCTAssertEqual(sut.purchaseURL, expectedURL) } func testWhenInitWithoutOriginThenSubscriptionFlowPurchaseURLDoesNotHaveOriginSet() { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: mockDependencyProvider.subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) + let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(accountManager: subscriptionManager.accountManager, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + authEndpointService: subscriptionManager.authEndpointService) + let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionEndpointService: subscriptionManager.subscriptionEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager, + appStoreRestoreFlow: appStoreRestoreFlow, + authEndpointService: subscriptionManager.authEndpointService) + let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(authEndpointService: subscriptionManager.authEndpointService, + storePurchaseManager: subscriptionManager.storePurchaseManager(), + accountManager: subscriptionManager.accountManager) // WHEN - sut = .init(origin: nil, userScript: .init(), subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, + sut = .init(origin: nil, userScript: .init(), subFeature: .init(subscriptionManager: subscriptionManager, subscriptionAttributionOrigin: nil, appStorePurchaseFlow: appStorePurchaseFlow, appStoreRestoreFlow: appStoreRestoreFlow, appStoreAccountManagementFlow: appStoreAccountManagementFlow), - subscriptionManager: mockDependencyProvider.subscriptionManager) + subscriptionManager: subscriptionManager) // THEN XCTAssertEqual(sut.purchaseURL, SubscriptionURL.purchase.subscriptionURL(environment: .production)) diff --git a/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift new file mode 100644 index 0000000000..f6292a6138 --- /dev/null +++ b/DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift @@ -0,0 +1,47 @@ +// +// SubscriptionPagesUseSubscriptionFeatureTests.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import DuckDuckGo +@testable import Subscription +import SubscriptionTestingUtilities + +@available(iOS 15.0, *) +final class SubscriptionPagesUseSubscriptionFeatureTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + let appStorePurchaseFlow = AppStorePurchaseFlowMock(purchaseSubscriptionResult: .success("TransactionJWS"), + completeSubscriptionPurchaseResult: .success(PurchaseUpdate(type: "t", token: "t"))) + let appStoreAccountManagementFlow = AppStoreAccountManagementFlowMock(refreshAuthTokenIfNeededResult: .success("Something")) + let feature = SubscriptionPagesUseSubscriptionFeature(subscriptionManager: SubscriptionMockFactory.subscriptionManager, + subscriptionAttributionOrigin: "???", + appStorePurchaseFlow: appStorePurchaseFlow, + appStoreRestoreFlow: SubscriptionMockFactory.appStoreRestoreFlow, + appStoreAccountManagementFlow: appStoreAccountManagementFlow) + // To be implemented + } +} diff --git a/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift b/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift deleted file mode 100644 index 922c406ba6..0000000000 --- a/DuckDuckGoTests/SubscriptionContainerViewModelTests.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// SubscriptionContainerViewModelTests.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import XCTest -@testable import DuckDuckGo -@testable import Subscription -import SubscriptionTestingUtilities - -@available(iOS 15.0, *) -final class SubscriptionContainerViewModelTests: XCTestCase { - var sut: SubscriptionContainerViewModel! - let mockDependencyProvider = MockDependencyProvider() - - func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { - // GIVEN - let origin = "test_origin" - let queryParameter = URLQueryItem(name: "origin", value: "test_origin") - let expectedURL = SubscriptionURL.purchase.subscriptionURL(environment: .production).appending(percentEncodedQueryItem: queryParameter) - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: mockDependencyProvider.subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) - - // WHEN - sut = .init(subscriptionManager: mockDependencyProvider.subscriptionManager, - origin: origin, - userScript: .init(), - subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, - subscriptionAttributionOrigin: nil, - appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow)) - - // THEN - XCTAssertEqual(sut.flow.purchaseURL, expectedURL) - } - - func testWhenInitWithoutOriginThenSubscriptionFlowPurchaseURLDoesNotHaveOriginSet() { - let appStoreRestoreFlow = DefaultAppStoreRestoreFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) - let appStorePurchaseFlow = DefaultAppStorePurchaseFlow(subscriptionManager: mockDependencyProvider.subscriptionManager, - appStoreRestoreFlow: appStoreRestoreFlow) - let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: mockDependencyProvider.subscriptionManager) - - // WHEN - sut = .init(subscriptionManager: mockDependencyProvider.subscriptionManager, - origin: nil, - userScript: .init(), - subFeature: .init(subscriptionManager: mockDependencyProvider.subscriptionManager, - subscriptionAttributionOrigin: nil, - appStorePurchaseFlow: appStorePurchaseFlow, - appStoreRestoreFlow: appStoreRestoreFlow, - appStoreAccountManagementFlow: appStoreAccountManagementFlow)) - - // THEN - XCTAssertEqual(sut.flow.purchaseURL, SubscriptionURL.purchase.subscriptionURL(environment: .production)) - } - -} diff --git a/DuckDuckGoTests/SyncManagementViewModelTests.swift b/DuckDuckGoTests/SyncUI/SyncManagementViewModelTests.swift similarity index 100% rename from DuckDuckGoTests/SyncManagementViewModelTests.swift rename to DuckDuckGoTests/SyncUI/SyncManagementViewModelTests.swift From 5a31f2553d9d90207ac93d2014752f779c761b6c Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 5 Jul 2024 12:37:43 +0200 Subject: [PATCH 07/15] Improve VPN logging logic (#3032) Task/Issue URL: https://app.asana.com/0/1206580121312550/1207223772590802/f macOS PR: https://github.com/duckduckgo/macos-browser/pull/2939 BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/877 ## Description Improves the logging logic for the VPN. --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- ...etworkProtectionPacketTunnelProvider.swift | 24 +++- .../NetworkProtection/VPNLogger.swift | 108 ++++++++++++++++++ 4 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 PacketTunnelProvider/NetworkProtection/VPNLogger.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c7a42731e3..6067a74e9a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -285,6 +285,7 @@ 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */; }; 6FE1274B2C20943500EB5724 /* ShortcutItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; + 7B0D52352C35FEAD0035A60E /* VPNLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0D52342C35FEAD0035A60E /* VPNLogger.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; @@ -1401,6 +1402,7 @@ 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageViewController.swift; sourceTree = ""; }; 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutItemView.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; + 7B0D52342C35FEAD0035A60E /* VPNLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNLogger.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtension.swift; path = ../Core/UIAlertControllerExtension.swift; sourceTree = ""; }; @@ -4727,6 +4729,7 @@ EE3766DC2AC5940A00AAB575 /* NetworkProtection */ = { isa = PBXGroup; children = ( + 7B0D52342C35FEAD0035A60E /* VPNLogger.swift */, EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */, EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */, ); @@ -6465,6 +6468,7 @@ EEEB80A32A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift in Sources */, EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, F1FDC9362BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */, + 7B0D52352C35FEAD0035A60E /* VPNLogger.swift in Sources */, BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */, EEFC6A602AC0F2F80065027D /* UserText.swift in Sources */, 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */, @@ -9966,7 +9970,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 165.0.0; + version = 165.0.1; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 15eb24cbec..75c78d0626 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "777e5ae1ab890d9ec22e069bc5dc0f0ada4b35af", - "version" : "165.0.0" + "revision" : "2df7f9d9063c9f8f8f07ccb80c95d7e35738d1ea", + "version" : "165.0.1" } }, { diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 7b66f1efa8..8ef890d60c 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -34,6 +34,7 @@ import WidgetKit // Initial implementation for initial Network Protection tests. Will be fleshed out with https://app.asana.com/0/1203137811378537/1204630829332227/f final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { + private static var vpnLogger = VPNLogger() private var cancellables = Set() private let accountManager: AccountManager @@ -48,6 +49,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { withAdditionalParameters: [PixelParameters.vpnCohort: UniquePixel.cohort(from: defaults.vpnFirstEnabled)], includedParameters: [.appVersion, .atb]) case .reportConnectionAttempt(attempt: let attempt): + vpnLogger.log(attempt) + switch attempt { case .connecting: DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptConnecting, @@ -63,6 +66,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { includedParameters: [.appVersion, .atb]) } case .reportTunnelFailure(result: let result): + vpnLogger.log(result) + switch result { case .failureDetected: DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelFailureDetected, includedParameters: [.appVersion, .atb]) @@ -72,6 +77,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { defaults.updateNetworkPath(with: newPath) } case .reportLatency(result: let result): + vpnLogger.log(result) + switch result { case .error: DailyPixel.fire(pixel: .networkProtectionLatencyError, includedParameters: [.appVersion, .atb]) @@ -80,6 +87,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionLatency(quality: quality), includedParameters: [.appVersion, .atb]) } case .rekeyAttempt(let step): + vpnLogger.log(step, named: "Rekey") + switch step { case .begin: DailyPixel.fireDailyAndCount(pixel: .networkProtectionRekeyAttempt) @@ -89,6 +98,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionRekeyCompleted) } case .tunnelStartAttempt(let step): + vpnLogger.log(step, named: "Tunnel Start") + switch step { case .begin: DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelStartAttempt) @@ -98,6 +109,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelStartSuccess) } case .tunnelStopAttempt(let step): + vpnLogger.log(step, named: "Tunnel Stop") + switch step { case .begin: Pixel.fire(pixel: .networkProtectionTunnelStopAttempt) @@ -107,6 +120,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelStopSuccess) } case .tunnelUpdateAttempt(let step): + vpnLogger.log(step, named: "Tunnel Update") + switch step { case .begin: DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelUpdateAttempt) @@ -116,6 +131,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelUpdateSuccess) } case .tunnelWakeAttempt(let step): + vpnLogger.log(step, named: "Tunnel Wake") + switch step { case .begin: Pixel.fire(pixel: .networkProtectionTunnelWakeAttempt) @@ -125,6 +142,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelWakeSuccess) } case .failureRecoveryAttempt(let step): + vpnLogger.log(step) + switch step { case .started: DailyPixel.fireDailyAndCount(pixel: .networkProtectionFailureRecoveryStarted) @@ -136,6 +155,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionFailureRecoveryFailed, error: error) } case .serverMigrationAttempt(let step): + vpnLogger.log(step, named: "Server Migration") + switch step { case .begin: DailyPixel.fireDailyAndCount(pixel: .networkProtectionServerMigrationAttempt) @@ -145,6 +166,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionServerMigrationAttemptSuccess) } case .tunnelStartOnDemandWithoutAccessToken: + vpnLogger.logStartingWithoutAuthToken() DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelStartAttemptOnDemandWithoutAccessToken) } } @@ -391,5 +413,5 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } // swiftlint:enable type_body_length - +// swiftlint:disable:next file_length #endif diff --git a/PacketTunnelProvider/NetworkProtection/VPNLogger.swift b/PacketTunnelProvider/NetworkProtection/VPNLogger.swift new file mode 100644 index 0000000000..e60e048d4e --- /dev/null +++ b/PacketTunnelProvider/NetworkProtection/VPNLogger.swift @@ -0,0 +1,108 @@ +// +// VPNLogger.swift +// DuckDuckGo +// +// 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 NetworkProtection +// swiftlint:disable:next enforce_os_log_wrapper +import OSLog + +/// Logger for the VPN +/// +/// Since we'll want to ensure this adheres to our privacy standards, grouping the logging logic to be mostly +/// handled by a single class sounds like a good approach to be able to review what's being logged.. +/// +public final class VPNLogger { + public typealias AttemptStep = PacketTunnelProvider.AttemptStep + public typealias ConnectionAttempt = PacketTunnelProvider.ConnectionAttempt + public typealias LogCallback = (OSLogType, OSLogMessage) -> Void + + public init() {} + + public func logStartingWithoutAuthToken() { + os_log("🔴 Starting tunnel without an auth token", log: .networkProtection, type: .error) + } + + public func log(_ step: AttemptStep, named name: String) { + let log = OSLog.networkProtection + + switch step { + case .begin: + os_log("🔵 %{public}@ attempt begins", log: log, name) + case .failure(let error): + os_log("🔴 %{public}@ attempt failed with error: %{public}@", log: log, type: .error, name, error.localizedDescription) + case .success: + os_log("🟢 %{public}@ attempt succeeded", log: log, name) + } + } + + public func log(_ step: ConnectionAttempt) { + let log = OSLog.networkProtection + + switch step { + case .connecting: + os_log("🔵 Connection attempt detected", log: log) + case .failure: + os_log("🔴 Connection attempt failed", log: log, type: .error) + case .success: + os_log("🟢 Connection attempt successful", log: log) + } + } + + public func log(_ step: FailureRecoveryStep) { + let log = OSLog.networkProtectionTunnelFailureMonitorLog + + switch step { + case .started: + os_log("🔵 Failure Recovery attempt started", log: log) + case .failed(let error): + os_log("🔴 Failure Recovery attempt failed with error: %{public}@", log: log, type: .error, error.localizedDescription) + case .completed(let health): + switch health { + case .healthy: + os_log("🟢 Failure Recovery attempt completed", log: log) + case .unhealthy: + os_log("🔴 Failure Recovery attempt ended as unhealthy", log: log, type: .error) + } + } + } + + public func log(_ step: NetworkProtectionTunnelFailureMonitor.Result) { + let log = OSLog.networkProtectionTunnelFailureMonitorLog + + switch step { + case .failureDetected: + os_log("🔴 Tunnel failure detected", log: log, type: .error) + case .failureRecovered: + os_log("🟢 Tunnel failure recovered", log: log) + case .networkPathChanged: + os_log("🔵 Tunnel recovery detected path change", log: log) + } + } + + public func log(_ result: NetworkProtectionLatencyMonitor.Result) { + let log = OSLog.networkProtectionLatencyMonitorLog + + switch result { + case .error: + os_log("🔴 There was an error logging the latency", log: log, type: .error) + case .quality(let quality): + os_log("Connection quality is: %{public}@", log: log, quality.rawValue) + } + } +} From 56dce29447c689068ffe2dbf439507d1ff681fb4 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Fri, 5 Jul 2024 16:22:30 +0200 Subject: [PATCH 08/15] Make Maestro tests fail on flow cancellation (#3036) --- .github/workflows/end-to-end.yml | 8 ++++---- .github/workflows/sync-end-to-end.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index 65f771fcb5..a623d45091 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -54,19 +54,19 @@ jobs: - name: Release tests run: | - export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --timeout=150 --ios-version=17 --include-tags=release DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ + export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --fail-on-cancellation=true --timeout=150 --ios-version=17 --include-tags=release DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ - name: Privacy tests run: | - export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --timeout=150 --ios-version=17 --include-tags=privacy DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ + export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --fail-on-cancellation=true --timeout=150 --ios-version=17 --include-tags=privacy DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ - name: Security tests run: | - export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --timeout=150 --ios-version=17 --include-tags=securityTest DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ + export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --fail-on-cancellation=true --timeout=150 --ios-version=17 --include-tags=securityTest DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ - name: Ad Click Detection Flow tests run: | - export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --timeout=150 --ios-version=17 --include-tags=adClick DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ + export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --fail-on-timeout=true --fail-on-cancellation=true --timeout=150 --ios-version=17 --include-tags=adClick DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ - name: Create Asana task when workflow failed if: ${{ failure() }} diff --git a/.github/workflows/sync-end-to-end.yml b/.github/workflows/sync-end-to-end.yml index 0808ae0570..43c6dab4de 100644 --- a/.github/workflows/sync-end-to-end.yml +++ b/.github/workflows/sync-end-to-end.yml @@ -102,7 +102,7 @@ jobs: - name: Sync e2e tests run: | - export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --env=CODE=${{ steps.sync-recovery-code.outputs.recovery-code }} --fail-on-timeout=true --timeout=150 --ios-version=${{ matrix.os-version }} --include-tags=sync DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ + export PATH="$PATH":"$HOME/.maestro/bin"; maestro cloud --apiKey ${{ secrets.MAESTRO_CLOUD_API_KEY }} --env=CODE=${{ steps.sync-recovery-code.outputs.recovery-code }} --fail-on-timeout=true --fail-on-cancellation=true --timeout=150 --ios-version=${{ matrix.os-version }} --include-tags=sync DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app .maestro/ - name: Reset config run: | From effc931b9959ec87738bce1a0b4e8fd3fb051576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Fri, 5 Jul 2024 17:53:24 +0200 Subject: [PATCH 09/15] Privacy Dashboard refactor (#3038) Task/Issue URL: https://app.asana.com/0/1201392122292466/1207677104594315/f Description: - remove old pixels, - remove user behavior toast experiment, - refactor Privacy Dashboard, - add documentation, - test Privacy Dashboard --- Core/ContentBlocking.swift | 11 -- Core/PixelEvent.swift | 22 --- Core/UserDefaultsPropertyWrapper.swift | 2 - DuckDuckGo.xcodeproj/project.pbxproj | 26 +--- .../xcshareddata/swiftpm/Package.resolved | 14 +- DuckDuckGo/AppDelegate.swift | 7 - DuckDuckGo/AppDependencyProvider.swift | 2 - DuckDuckGo/BrokenSitePromptView.swift | 61 -------- DuckDuckGo/BrokenSitePromptViewModel.swift | 32 ----- DuckDuckGo/MainViewController+Segues.swift | 6 +- DuckDuckGo/MainViewController.swift | 85 +---------- .../PixelExperimentForBrokenSites.swift | 134 ------------------ .../PrivacyDashboardViewController.swift | 87 +++++------- DuckDuckGo/TabDelegate.swift | 3 - DuckDuckGo/TabViewController.swift | 19 +-- ...bViewControllerBrowsingMenuExtension.swift | 6 +- DuckDuckGo/UserBehaviorEvent.swift | 61 -------- DuckDuckGo/UserBehaviorMonitor.swift | 53 +++---- DuckDuckGo/UserText.swift | 7 - DuckDuckGo/en.lproj/Localizable.strings | 15 -- .../AppPrivacyConfigurationTests.swift | 3 +- DuckDuckGoTests/AppURLsTests.swift | 3 +- .../BrokenSiteReportingTests.swift | 2 - .../ContentBlockerProtectionStoreTests.swift | 3 +- DuckDuckGoTests/MockDependencyProvider.swift | 2 - .../MockPrivacyConfiguration.swift | 1 - .../PrivacyConfigurationManagerMock.swift | 1 - .../UserBehaviorMonitorTests.swift | 133 ++++------------- DuckDuckGoTests/WebViewTestHelper.swift | 3 +- .../AutoconsentBackgroundTests.swift | 2 - 30 files changed, 109 insertions(+), 697 deletions(-) delete mode 100644 DuckDuckGo/BrokenSitePromptView.swift delete mode 100644 DuckDuckGo/BrokenSitePromptViewModel.swift delete mode 100644 DuckDuckGo/PixelExperimentForBrokenSites.swift delete mode 100644 DuckDuckGo/UserBehaviorEvent.swift diff --git a/Core/ContentBlocking.swift b/Core/ContentBlocking.swift index 1135f0526f..48f172831b 100644 --- a/Core/ContentBlocking.swift +++ b/Core/ContentBlocking.swift @@ -50,7 +50,6 @@ public final class ContentBlocking { embeddedDataProvider: AppPrivacyConfigurationDataProvider(), localProtection: DomainsProtectionUserDefaultsStore(), errorReporting: Self.debugEvents, - toggleProtectionsCounterEventReporting: toggleProtectionsEvents, internalUserDecider: internalUserDecider, installDate: statisticsStore.installDate) self.privacyConfigurationManager = privacyConfigurationManager @@ -197,16 +196,6 @@ public final class ContentBlocking { Pixel.fire(pixel: domainEvent, includedParameters: []) } - private let toggleProtectionsEvents = EventMapping { event, _, parameters, _ in - let domainEvent: Pixel.Event - switch event { - case .toggleProtectionsCounterDaily: - domainEvent = .toggleProtectionsDailyCount - } - - Pixel.fire(pixel: domainEvent, withAdditionalParameters: parameters ?? [:]) - } - } public class DomainsProtectionUserDefaultsStore: DomainsProtectionStore { diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 83926da830..8b6ca67604 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -614,17 +614,7 @@ extension Pixel { case toggleReportDismiss case userBehaviorReloadTwiceWithin12Seconds - case userBehaviorReloadTwiceWithin24Seconds - case userBehaviorReloadAndRestartWithin30Seconds - case userBehaviorReloadAndRestartWithin50Seconds case userBehaviorReloadThreeTimesWithin20Seconds - case userBehaviorReloadThreeTimesWithin40Seconds - - case siteNotWorkingShown - case siteNotWorkingDismiss - case siteNotWorkingDismissByNavigation - case siteNotWorkingDismissByRefresh - case siteNotWorkingWebsiteIsBroken // MARK: History case historyStoreLoadFailed @@ -1327,19 +1317,7 @@ extension Pixel.Event { // MARK: - User behavior case .userBehaviorReloadTwiceWithin12Seconds: return "m_reload-twice-within-12-seconds" - case .userBehaviorReloadTwiceWithin24Seconds: return "m_reload-twice-within-24-seconds" - - case .userBehaviorReloadAndRestartWithin30Seconds: return "m_reload-and-restart-within-30-seconds" - case .userBehaviorReloadAndRestartWithin50Seconds: return "m_reload-and-restart-within-50-seconds" - case .userBehaviorReloadThreeTimesWithin20Seconds: return "m_reload-three-times-within-20-seconds" - case .userBehaviorReloadThreeTimesWithin40Seconds: return "m_reload-three-times-within-40-seconds" - - case .siteNotWorkingShown: return "m_site-not-working_shown" - case .siteNotWorkingDismiss: return "m_site-not-working_dismiss" - case .siteNotWorkingDismissByNavigation: return "m_site-not-working_dismiss-by-navigation" - case .siteNotWorkingDismissByRefresh: return "m_site-not-working_dismiss-by-refresh" - case .siteNotWorkingWebsiteIsBroken: return "m_site-not-working_website-is-broken" // MARK: - History debug case .historyStoreLoadFailed: return "m_debug_history-store-load-failed" diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index f85adb3585..3248521dd1 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -146,8 +146,6 @@ public struct UserDefaultsWrapper { case historyMessageDisplayCount = "com.duckduckgo.ios.historyMessage.displayCount" case historyMessageDismissed = "com.duckduckgo.ios.historyMessage.dismissed" - case pixelExperimentForBrokenSitesInstalled = "com.duckduckgo.ios.pixel.experiment.for.broken.sites.installed" - case pixelExperimentForBrokenSitesCohort = "com.duckduckgo.ios.pixel.experiment.for.broken.sites.cohort" case duckPlayerMode = "com.duckduckgo.ios.duckPlayerMode" case duckPlayerAskModeOverlayHidden = "com.duckduckgo.ios.duckPlayerAskModeOverlayHidden" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6067a74e9a..23f02bb0f2 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -742,7 +742,6 @@ C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */; }; C1F341C92A6926920032057B /* EmailAddressPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */; }; CB1143DE2AF6D4B600C1CCD3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = CB1143DC2AF6D4B600C1CCD3 /* InfoPlist.strings */; }; - CB2283F32BD79FC20057DD0A /* BrokenSitePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2283F22BD79FC20057DD0A /* BrokenSitePromptView.swift */; }; CB258D1229A4F24900DEBA24 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB258D0F29A4D0FD00DEBA24 /* ConfigurationManager.swift */; }; CB258D1329A4F24E00DEBA24 /* ConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB84C7C029A3F0280088A5B8 /* ConfigurationStore.swift */; }; CB258D1D29A52AF900DEBA24 /* EtagStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9896632322C56716007BE4FE /* EtagStorage.swift */; }; @@ -751,10 +750,8 @@ CB2A7EEF283D185100885F67 /* RulesCompilationMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EEE283D185100885F67 /* RulesCompilationMonitor.swift */; }; CB2A7EF128410DF700885F67 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF028410DF700885F67 /* PixelEvent.swift */; }; CB2A7EF4285383B300885F67 /* AppLastCompiledRulesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */; }; - CB48D3322B90CE9F00631D8B /* UserBehaviorEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3302B90CE9F00631D8B /* UserBehaviorEvent.swift */; }; CB48D3332B90CE9F00631D8B /* UserBehaviorMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */; }; CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */; }; - CB5418632BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB5418622BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift */; }; CB5516D0286500290079B175 /* TrackerRadarIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */; }; CB5516D1286500290079B175 /* ContentBlockingRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */; }; CB5516D2286500290079B175 /* AtbServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F21DBD21121147002631A6 /* AtbServerTests.swift */; }; @@ -766,7 +763,6 @@ CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9B873B278C8FEA001F4906 /* WidgetEducationView.swift */; }; CB9B873E278C93C2001F4906 /* HomeMessage.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CB9B873D278C93C2001F4906 /* HomeMessage.xcassets */; }; CBAA195A27BFE15600A4BD49 /* NSManagedObjectContextExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAA195927BFE15600A4BD49 /* NSManagedObjectContextExtension.swift */; }; - CBBB9A192BED441400BEAC71 /* PixelExperimentForBrokenSites.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBB9A182BED441400BEAC71 /* PixelExperimentForBrokenSites.swift */; }; CBC83E3429B631780008E19C /* Configuration in Frameworks */ = {isa = PBXBuildFile; productRef = CBC83E3329B631780008E19C /* Configuration */; }; CBCCF96828885DEE006F4A71 /* AppPrivacyConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C4BC3127C3F9B600C40026 /* AppPrivacyConfigurationTests.swift */; }; CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD4F13B279EBF4A00B20FD7 /* HomeMessage.swift */; }; @@ -2388,7 +2384,6 @@ CB18F2712AF6D4E400A0F8FE /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; CB1AEFB02799AA940031AE3D /* SwiftUICollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUICollectionViewCell.swift; sourceTree = ""; }; CB1FAE472AF6D59B003F452F /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = ""; }; - CB2283F22BD79FC20057DD0A /* BrokenSitePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptView.swift; sourceTree = ""; }; CB24F70E29A3EB15006DCC58 /* AppConfigurationURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfigurationURLProvider.swift; path = ../Core/AppConfigurationURLProvider.swift; sourceTree = ""; }; CB258D0C29A4CD0500DEBA24 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; CB258D0F29A4D0FD00DEBA24 /* ConfigurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; @@ -2398,11 +2393,9 @@ CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLastCompiledRulesStore.swift; sourceTree = ""; }; CB2C47822AF6D55800AEDCD9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; CB4448752AF6D51D001F93F7 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = ""; }; - CB48D3302B90CE9F00631D8B /* UserBehaviorEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserBehaviorEvent.swift; sourceTree = ""; }; CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitor.swift; sourceTree = ""; }; CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitorTests.swift; sourceTree = ""; }; CB5038622AF6D563007FD69F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - CB5418622BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptViewModel.swift; sourceTree = ""; }; CB6ABD002AF6D52B004A8224 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; CB6CE65B2AF6D4EE00119848 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; CB7407BC2AF6D56D0090A41C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2420,7 +2413,6 @@ CBAA195927BFE15600A4BD49 /* NSManagedObjectContextExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContextExtension.swift; sourceTree = ""; }; CBAA195B27C3982A00A4BD49 /* PrivacyFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyFeatures.swift; sourceTree = ""; }; CBB6B2542AF6D543006B777C /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = ""; }; - CBBB9A182BED441400BEAC71 /* PixelExperimentForBrokenSites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelExperimentForBrokenSites.swift; sourceTree = ""; }; CBC7AB542AF6D583008CB798 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = ""; }; CBC8DC252AF6D4CD00BA681A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; CBD4F13B279EBF4A00B20FD7 /* HomeMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessage.swift; sourceTree = ""; }; @@ -4472,16 +4464,6 @@ name = WidgetEducation; sourceTree = ""; }; - CB2283F12BD79D7B0057DD0A /* BrokenSitePrompt */ = { - isa = PBXGroup; - children = ( - CB2283F22BD79FC20057DD0A /* BrokenSitePromptView.swift */, - CB5418622BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift */, - CBBB9A182BED441400BEAC71 /* PixelExperimentForBrokenSites.swift */, - ); - name = BrokenSitePrompt; - sourceTree = ""; - }; CB258D1129A4F1BB00DEBA24 /* Configuration */ = { isa = PBXGroup; children = ( @@ -4494,7 +4476,6 @@ CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */ = { isa = PBXGroup; children = ( - CB48D3302B90CE9F00631D8B /* UserBehaviorEvent.swift */, CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */, ); name = UserBehaviorMonitor; @@ -5395,7 +5376,6 @@ F1C5ECFA1E37B15B00C599A4 /* Main */ = { isa = PBXGroup; children = ( - CB2283F12BD79D7B0057DD0A /* BrokenSitePrompt */, 310742A52848CD780012660B /* BackForwardMenuHistoryItem.swift */, 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */, 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */, @@ -6563,7 +6543,6 @@ 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, 854A01332A558B3A00FCC628 /* UIView+Constraints.swift in Sources */, C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */, - CB2283F32BD79FC20057DD0A /* BrokenSitePromptView.swift in Sources */, 83134D7D20E2D725006CE65D /* FeedbackSender.swift in Sources */, B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */, 314C92BA27C3E7CB0042EC96 /* QuickLookContainerViewController.swift in Sources */, @@ -6642,7 +6621,6 @@ 6FE1273A2C204BD000EB5724 /* NewTabPageView.swift in Sources */, 986DA94A24884B18004A7E39 /* WebViewTransition.swift in Sources */, 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, - CB48D3322B90CE9F00631D8B /* UserBehaviorEvent.swift in Sources */, C1641EB32BC2F53C0012607A /* ImportPasswordsViewModel.swift in Sources */, 8536A1FD2ACF114B003AC5BA /* Theme+DesignSystem.swift in Sources */, F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */, @@ -6815,7 +6793,6 @@ 85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */, CB825C922C071B1400BCC586 /* AlertView.swift in Sources */, 1DDF40292BA04FCD006850D9 /* SettingsPrivacyProtectionsView.swift in Sources */, - CB5418632BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift in Sources */, F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */, 85F2FFCD2211F615006BB258 /* MainViewController+KeyCommands.swift in Sources */, 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */, @@ -6970,7 +6947,6 @@ 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */, D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */, 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, - CBBB9A192BED441400BEAC71 /* PixelExperimentForBrokenSites.swift in Sources */, 6FE018402C25CB3F001F680D /* FavoritesSectionHeader.swift in Sources */, F17669D71E43401C003D3222 /* MainViewController.swift in Sources */, 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */, @@ -9970,7 +9946,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 165.0.1; + version = 166.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 75c78d0626..ecc177b506 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "2df7f9d9063c9f8f8f07ccb80c95d7e35738d1ea", - "version" : "165.0.1" + "revision" : "f23384018ede5aa63777b1c143e81855a16210fd", + "version" : "166.0.0" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "924a80e20e2465dcaf3dca32c9b6e9b9968222b9", - "version" : "4.1.0" + "revision" : "348594efe2cd40ef156e915c272d02ec22f1903f", + "version" : "4.2.0" } }, { @@ -138,10 +138,10 @@ { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser", + "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { - "revision" : "46989693916f56d1186bd59ac15124caef896560", - "version" : "1.3.1" + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index ef4149ff99..92d0396ef9 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -231,7 +231,6 @@ import WebKit historyMessageManager.dismiss() } - PixelExperimentForBrokenSites.install() PixelExperiment.install() // MARK: Sync initialisation @@ -340,10 +339,6 @@ import WebKit widgetRefreshModel.beginObservingVPNStatus() #endif - AppDependencyProvider.shared.toggleProtectionsCounter.sendEventsIfNeeded() - - AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp) - AppDependencyProvider.shared.subscriptionManager.loadInitialData() setUpAutofillPixelReporter() @@ -637,8 +632,6 @@ import WebKit showKeyboardIfSettingOn = true syncService.scheduler.resumeSyncQueue() } - - AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp) } func applicationDidEnterBackground(_ application: UIApplication) { diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index a7ad14b32a..6ccf795c6e 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -41,7 +41,6 @@ protocol DependencyProvider { var autofillLoginSession: AutofillLoginSession { get } var autofillNeverPromptWebsitesManager: AutofillNeverPromptWebsitesManager { get } var configurationManager: ConfigurationManager { get } - var toggleProtectionsCounter: ToggleProtectionsCounter { get } var userBehaviorMonitor: UserBehaviorMonitor { get } var subscriptionFeatureAvailability: SubscriptionFeatureAvailability { get } var subscriptionManager: SubscriptionManager { get } @@ -80,7 +79,6 @@ class AppDependencyProvider: DependencyProvider { let configurationManager = ConfigurationManager() - let toggleProtectionsCounter: ToggleProtectionsCounter = ContentBlocking.shared.privacyConfigurationManager.toggleProtectionsCounter let userBehaviorMonitor = UserBehaviorMonitor() let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( diff --git a/DuckDuckGo/BrokenSitePromptView.swift b/DuckDuckGo/BrokenSitePromptView.swift deleted file mode 100644 index b04b06a778..0000000000 --- a/DuckDuckGo/BrokenSitePromptView.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// BrokenSitePromptView.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import SwiftUI -import DuckUI -import DesignResourcesKit - -struct BrokenSitePromptView: View { - - let viewModel: BrokenSitePromptViewModel - - var body: some View { - VStack { - VStack(alignment: .leading, spacing: 8) { - VStack(alignment: .leading) { - Text(UserText.siteNotWorkingTitle) - .font(Font(uiFont: .daxSubheadSemibold())) - Text(UserText.siteNotWorkingSubtitle) - .font(Font(uiFont: .daxSubheadRegular())) - } - HStack { - Spacer() - Button(UserText.siteNotWorkingDismiss, action: viewModel.onDidDismiss) - .buttonStyle(GhostButtonStyle()) - .fixedSize() - Button(UserText.siteNotWorkingWebsiteIsBroken, action: viewModel.onDidSubmit) - .buttonStyle(PrimaryButtonStyle(compact: true)) - .fixedSize() - } - } - .padding(EdgeInsets(top: 12, leading: 16, bottom: 4, trailing: 16)) - Color(designSystemColor: .lines).frame(height: 1 / UIScreen.main.scale) - } - .background(Color(designSystemColor: .panel)) - } - -} - -#Preview { - - let viewModel = BrokenSitePromptViewModel(onDidDismiss: {}, - onDidSubmit: {}) - return BrokenSitePromptView(viewModel: viewModel) - -} diff --git a/DuckDuckGo/BrokenSitePromptViewModel.swift b/DuckDuckGo/BrokenSitePromptViewModel.swift deleted file mode 100644 index 93d586f12e..0000000000 --- a/DuckDuckGo/BrokenSitePromptViewModel.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// BrokenSitePromptViewModel.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -final class BrokenSitePromptViewModel { - - let onDidDismiss: () -> Void - let onDidSubmit: () -> Void - - init(onDidDismiss: @escaping () -> Void, onDidSubmit: @escaping () -> Void) { - self.onDidDismiss = onDidDismiss - self.onDidSubmit = onDidSubmit - } - -} diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 350d30cdb3..75335b8c45 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -121,7 +121,7 @@ extension MainViewController { present(controller, animated: true) } - func segueToReportBrokenSite(mode: PrivacyDashboardMode = .report) { + func segueToReportBrokenSite(entryPoint: PrivacyDashboardEntryPoint = .report) { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() @@ -131,7 +131,7 @@ extension MainViewController { return } - if mode == .report { + if entryPoint == .report { fireBrokenSiteReportShown() } @@ -139,7 +139,7 @@ extension MainViewController { let controller = storyboard.instantiateInitialViewController { coder in PrivacyDashboardViewController(coder: coder, privacyInfo: privacyInfo, - dashboardMode: mode, + entryPoint: entryPoint, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, breakageAdditionalInfo: self.currentTab?.makeBreakageAdditionalInfo()) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index d6d10b8449..488db7c02c 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -109,9 +109,6 @@ class MainViewController: UIViewController { @UserDefaultsWrapper(key: .syncDidShowSyncPausedByFeatureFlagAlert, defaultValue: false) private var syncDidShowSyncPausedByFeatureFlagAlert: Bool - @UserDefaultsWrapper(key: .userDidInteractWithBrokenSitePrompt, defaultValue: false) - private var userDidInteractWithBrokenSitePrompt: Bool - private var localUpdatesCancellable: AnyCancellable? private var syncUpdatesCancellable: AnyCancellable? private var syncFeatureFlagsCancellable: AnyCancellable? @@ -274,7 +271,6 @@ class MainViewController: UIViewController { findInPageView.delegate = self findInPageBottomLayoutConstraint.constant = 0 registerForKeyboardNotifications() - registerForUserBehaviorEvents() registerForSyncFeatureFlagsUpdates() decorate() @@ -462,14 +458,6 @@ class MainViewController: UIViewController { keyboardShowing = false } - private func registerForUserBehaviorEvents() { - NotificationCenter.default.addObserver( - self, - selector: #selector(attemptToShowBrokenSitePrompt(_:)), - name: .userBehaviorDidMatchExperimentVariant, - object: nil) - } - private func registerForSyncFeatureFlagsUpdates() { syncFeatureFlagsCancellable = syncService.featureFlagsPublisher .dropFirst() @@ -778,7 +766,6 @@ class MainViewController: UIViewController { @IBAction func onFirePressed() { Pixel.fire(pixel: .forgetAllPressedBrowsing) - hideNotificationBarIfBrokenSitePromptShown() wakeLazyFireButtonAnimator() if let spec = DaxDialogs.shared.fireButtonEducationMessage() { @@ -812,7 +799,6 @@ class MainViewController: UIViewController { Pixel.fire(pixel: .tabBarBackPressed) performCancel() hideSuggestionTray() - hideNotificationBarIfBrokenSitePromptShown() currentTab?.goBack() } @@ -820,7 +806,6 @@ class MainViewController: UIViewController { Pixel.fire(pixel: .tabBarForwardPressed) performCancel() hideSuggestionTray() - hideNotificationBarIfBrokenSitePromptShown() currentTab?.goForward() } @@ -1034,15 +1019,6 @@ class MainViewController: UIViewController { refreshOmniBar() } - private func hideNotificationBarIfBrokenSitePromptShown(afterRefresh: Bool = false) { - guard brokenSitePromptViewHostingController != nil, - let event = brokenSitePromptEvent?.rawValue else { return } - brokenSitePromptViewHostingController = nil - let pixel: Pixel.Event = afterRefresh ? .siteNotWorkingDismissByRefresh: .siteNotWorkingDismissByNavigation - Pixel.fire(pixel: pixel, withAdditionalParameters: [UserBehaviorEvent.Parameter.event: event]) - hideNotification() - } - fileprivate func refreshBackForwardButtons() { viewCoordinator.toolbarBackButton.isEnabled = currentTab?.canGoBack ?? false viewCoordinator.toolbarForwardButton.isEnabled = currentTab?.canGoForward ?? false @@ -1069,8 +1045,6 @@ class MainViewController: UIViewController { } completion: { _ in ViewHighlighter.updatePositions() } - - hideNotificationBarIfBrokenSitePromptShown() } private func deferredFireOrientationPixel() { @@ -1273,49 +1247,6 @@ class MainViewController: UIViewController { } } - private var brokenSitePromptViewHostingController: UIHostingController? - private var brokenSitePromptEvent: UserBehaviorEvent? - - @objc func attemptToShowBrokenSitePrompt(_ notification: Notification) { - guard !userDidInteractWithBrokenSitePrompt, - let event = notification.userInfo?[UserBehaviorEvent.Key.event] as? UserBehaviorEvent, - let url = currentTab?.url, !url.isDuckDuckGo, - notificationView == nil, - !isPad, - DefaultTutorialSettings().hasSeenOnboarding, - !DaxDialogs.shared.isStillOnboarding(), - isPortrait else { return } - // We're using async to ensure the view dismissal happens on the first runloop after a refresh. This prevents the scenario where the view briefly appears and then immediately disappears after a refresh. - DispatchQueue.main.async { - self.showBrokenSitePrompt(after: event) - } - } - - private func showBrokenSitePrompt(after event: UserBehaviorEvent) { - let host = makeBrokenSitePromptViewHostingController(event: event) - brokenSitePromptViewHostingController = host - brokenSitePromptEvent = event - Pixel.fire(pixel: .siteNotWorkingShown, withAdditionalParameters: [UserBehaviorEvent.Parameter.event: event.rawValue]) - showNotification(with: host.view) - } - - private func makeBrokenSitePromptViewHostingController(event: UserBehaviorEvent) -> UIHostingController { - let parameters = [UserBehaviorEvent.Parameter.event: event.rawValue] - let viewModel = BrokenSitePromptViewModel(onDidDismiss: { [weak self] in - self?.hideNotification() - self?.userDidInteractWithBrokenSitePrompt = true - self?.brokenSitePromptViewHostingController = nil - Pixel.fire(pixel: .siteNotWorkingDismiss, withAdditionalParameters: parameters) - }, onDidSubmit: { [weak self] in - self?.segueToReportBrokenSite(mode: .prompt(event.rawValue)) - self?.hideNotification() - self?.userDidInteractWithBrokenSitePrompt = true - self?.brokenSitePromptViewHostingController = nil - Pixel.fire(pixel: .siteNotWorkingWebsiteIsBroken, withAdditionalParameters: parameters) - }) - return UIHostingController(rootView: BrokenSitePromptView(viewModel: viewModel), ignoreSafeArea: true) - } - func animateBackgroundTab() { showBars() tabSwitcherButton.incrementAnimated() @@ -1721,7 +1652,6 @@ extension MainViewController: OmniBarDelegate { omniBar.cancel() loadQuery(query) hideSuggestionTray() - hideNotificationBarIfBrokenSitePromptShown() showHomeRowReminder() } @@ -1885,7 +1815,6 @@ extension MainViewController: OmniBarDelegate { func onRefreshPressed() { hideSuggestionTray() currentTab?.refresh() - hideNotificationBarIfBrokenSitePromptShown(afterRefresh: true) } func onSharePressed() { @@ -2078,7 +2007,6 @@ extension MainViewController: TabDelegate { didRequestNewWebViewWithConfiguration configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, inheritingAttribution: AdClickAttributionLogic.State?) -> WKWebView? { - hideNotificationBarIfBrokenSitePromptShown() showBars() currentTab?.dismiss() @@ -2139,7 +2067,6 @@ extension MainViewController: TabDelegate { openedByPage: Bool, inheritingAttribution attribution: AdClickAttributionLogic.State?) { _ = findInPageView.resignFirstResponder() - hideNotificationBarIfBrokenSitePromptShown() if openedByPage { showBars() newTabAnimation { @@ -2166,7 +2093,7 @@ extension MainViewController: TabDelegate { } func tab(_ tab: TabViewController, didRequestToggleReportWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { - segueToReportBrokenSite(mode: .toggleReport(completionHandler: completionHandler)) + segueToReportBrokenSite(entryPoint: .toggleReport(completionHandler: completionHandler)) } func tabDidRequestBookmarks(tab: TabViewController) { @@ -2283,14 +2210,6 @@ extension MainViewController: TabDelegate { return currentTab === tab } - func tabDidRequestRefresh(tab: TabViewController) { - hideNotificationBarIfBrokenSitePromptShown(afterRefresh: true) - } - - func tabDidRequestNavigationToDifferentSite(tab: TabViewController) { - hideNotificationBarIfBrokenSitePromptShown() - } - } extension MainViewController: TabSwitcherDelegate { @@ -2328,7 +2247,6 @@ extension MainViewController: TabSwitcherDelegate { func closeTab(_ tab: Tab) { guard let index = tabManager.model.indexOf(tab: tab) else { return } hideSuggestionTray() - hideNotificationBarIfBrokenSitePromptShown() tabManager.remove(at: index) updateCurrentTab() tabsBarController?.refresh(tabsModel: tabManager.model) @@ -2372,7 +2290,6 @@ extension MainViewController: TabSwitcherButtonDelegate { guard currentTab ?? tabManager.current(createIfNeeded: true) != nil else { fatalError("Unable to get current tab") } - hideNotificationBarIfBrokenSitePromptShown() updatePreviewForCurrentTab { ViewHighlighter.hideAll() self.segueToTabSwitcher() diff --git a/DuckDuckGo/PixelExperimentForBrokenSites.swift b/DuckDuckGo/PixelExperimentForBrokenSites.swift deleted file mode 100644 index 6ae2aafcd1..0000000000 --- a/DuckDuckGo/PixelExperimentForBrokenSites.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// PixelExperimentForBrokenSites.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Core - -// This class serves the singular purpose of facilitating a specific experiment, necessitated by the limitation of the current API, which precludes running multiple experiments concurrently. It will be removed once this experiment is concluded. -public enum PixelExperimentForBrokenSites: String, CaseIterable { - - fileprivate static var logic: PixelExperimentLogic { defaultLogic } - fileprivate static let defaultLogic = PixelExperimentLogic { - Pixel.fire(pixel: $0, withAdditionalParameters: PixelExperimentForBrokenSites.parameters) - } - - /// When `cohort` is accessed for the first time after the experiment is installed with `install()`, - /// allocate and return a cohort. Subsequently, return the same cohort. - public static var cohort: PixelExperimentForBrokenSites? { - logic.cohort - } - - static var isExperimentInstalled: Bool { - logic.isInstalled - } - - static var allocatedCohortDoesNotMatchCurrentCohorts: Bool { - guard let allocatedCohort = logic.allocatedCohort else { return false } - if PixelExperimentForBrokenSites(rawValue: allocatedCohort) == nil { - return true - } - return false - } - - /// Enables this experiment for new users when called from the new installation path. - public static func install() { - // Disable the experiment until all other experiments are finished - logic.install() - } - - static func cleanup() { - logic.cleanup() - } - - // Internal state for users not included in any variant - case noVariant - - case reloadTwiceWithin12SecondsShowsPrompt - case reloadTwiceWithin24SecondsShowsPrompt - - case reloadAndRestartWithin30SecondsShowsPrompt - case reloadAndRestartWithin50SecondsShowsPrompt - - case reloadThreeTimesWithin20SecondsShowsPrompt - case reloadThreeTimesWithin40SecondsShowsPrompt - -} - -extension PixelExperimentForBrokenSites { - - // Pixel parameter - cohort - public static var parameters: [String: String] { - guard let cohort, cohort != .noVariant else { - return [:] - } - - return [PixelParameters.cohort: cohort.rawValue] - } - -} - -final internal class PixelExperimentLogic { - - private let promptCohorts: [PixelExperimentForBrokenSites] = [ - .reloadTwiceWithin12SecondsShowsPrompt, - .reloadTwiceWithin24SecondsShowsPrompt, - .reloadAndRestartWithin30SecondsShowsPrompt, - .reloadAndRestartWithin50SecondsShowsPrompt, - .reloadThreeTimesWithin20SecondsShowsPrompt, - .reloadThreeTimesWithin40SecondsShowsPrompt - ] - - var cohort: PixelExperimentForBrokenSites? { - guard isInstalled else { return nil } - - // Check if a cohort is already allocated and valid - if let allocatedCohort, - let cohort = PixelExperimentForBrokenSites(rawValue: allocatedCohort) { - return cohort - } - - let bucketIndex = Int.random(in: 0..<6) - let cohort = promptCohorts[bucketIndex] - - // Store and use the selected cohort - allocatedCohort = cohort.rawValue - return cohort - } - - @UserDefaultsWrapper(key: .pixelExperimentForBrokenSitesInstalled, defaultValue: false) - var isInstalled: Bool - - @UserDefaultsWrapper(key: .pixelExperimentForBrokenSitesCohort, defaultValue: nil) - var allocatedCohort: String? - - private let fire: (Pixel.Event) -> Void - init(fire: @escaping (Pixel.Event) -> Void) { - self.fire = fire - } - - func install() { - isInstalled = true - } - - func cleanup() { - isInstalled = false - allocatedCohort = nil - } - -} diff --git a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift index f16ede1bab..ebc1319fcf 100644 --- a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift @@ -57,6 +57,7 @@ final class PrivacyDashboardViewController: UIViewController { private let privacyConfigurationManager: PrivacyConfigurationManaging private let contentBlockingManager: ContentBlockerRulesManager private var privacyDashboardDidTriggerDismiss: Bool = false + private let entryPoint: PrivacyDashboardEntryPoint private let brokenSiteReporter: BrokenSiteReporter = { BrokenSiteReporter(pixelHandler: { parameters in @@ -74,12 +75,9 @@ final class PrivacyDashboardViewController: UIViewController { }, keyValueStoring: UserDefaults.standard) }() - private let toggleReportEvents = EventMapping { event, _, parameters, _ in + private let privacyDashboardEvents = EventMapping { event, _, parameters, _ in let domainEvent: Pixel.Event switch event { - case .toggleReportDismiss: domainEvent = .toggleReportDismiss - case .toggleReportDoNotSend: domainEvent = .toggleReportDoNotSend - case .showReportBrokenSite: domainEvent = .privacyDashboardReportBrokenSite case .breakageCategorySelected: domainEvent = .reportBrokenSiteBreakageCategorySelected @@ -98,7 +96,7 @@ final class PrivacyDashboardViewController: UIViewController { init?(coder: NSCoder, privacyInfo: PrivacyInfo?, - dashboardMode: PrivacyDashboardMode, + entryPoint: PrivacyDashboardEntryPoint, privacyConfigurationManager: PrivacyConfigurationManaging, contentBlockingManager: ContentBlockerRulesManager, breakageAdditionalInfo: BreakageAdditionalInfo?) { @@ -108,20 +106,21 @@ final class PrivacyDashboardViewController: UIViewController { return isExperimentEnabled ? PixelExperiment.privacyDashboardVariant : PrivacyDashboardVariant.control } - self.privacyDashboardController = PrivacyDashboardController(privacyInfo: privacyInfo, - dashboardMode: dashboardMode, - variant: variant, - privacyConfigurationManager: privacyConfigurationManager, - eventMapping: toggleReportEvents) + let toggleReportingConfiguration = ToggleReportingConfiguration(privacyConfigurationManager: privacyConfigurationManager) + let toggleReportingFeature = ToggleReportingFeature(toggleReportingConfiguration: toggleReportingConfiguration) + let toggleReportingManager = ToggleReportingManager(feature: toggleReportingFeature) + privacyDashboardController = PrivacyDashboardController(privacyInfo: privacyInfo, + entryPoint: entryPoint, + variant: variant, + toggleReportingManager: toggleReportingManager, + eventMapping: privacyDashboardEvents) self.privacyConfigurationManager = privacyConfigurationManager self.contentBlockingManager = contentBlockingManager self.breakageAdditionalInfo = breakageAdditionalInfo + self.entryPoint = entryPoint super.init(coder: coder) - self.privacyDashboardController.privacyDashboardDelegate = self - self.privacyDashboardController.privacyDashboardNavigationDelegate = self - self.privacyDashboardController.privacyDashboardReportBrokenSiteDelegate = self - self.privacyDashboardController.privacyDashboardToggleReportDelegate = self + privacyDashboardController.delegate = self } required init?(coder: NSCoder) { @@ -141,7 +140,7 @@ final class PrivacyDashboardViewController: UIViewController { if !privacyDashboardDidTriggerDismiss { privacyDashboardController.handleViewWillDisappear() } - privacyDashboardController.cleanUp() + privacyDashboardController.cleanup() } public func updatePrivacyInfo(_ privacyInfo: PrivacyInfo?) { @@ -153,10 +152,10 @@ final class PrivacyDashboardViewController: UIViewController { privacyDashboardDidTriggerDismiss = true guard let domain = privacyDashboardController.privacyInfo?.url.host else { return } - let source: BrokenSiteReport.Source = privacyDashboardController.initDashboardMode == .report ? .appMenu : .dashboard let privacyConfiguration = privacyConfigurationManager.privacyConfig + let source = entryPoint == .dashboard ? "dashboard" : "menu" let pixelParam = ["trigger_origin": state.eventOrigin.screen.rawValue, - "source": source.rawValue] + "source": source] if state.isProtected { privacyConfiguration.userEnabledProtection(forDomain: domain) ActionMessageView.present(message: UserText.messageProtectionEnabled.format(arguments: domain)) @@ -200,7 +199,8 @@ extension PrivacyDashboardViewController { // MARK: - PrivacyDashboardControllerDelegate extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, didSelectBreakageCategory category: String) { + + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSelectBreakageCategory category: String) { delegate?.privacyDashboardViewController(self, didSelectBreakageCategory: category) } @@ -217,10 +217,9 @@ extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { } } - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, - didRequestOpenSettings target: PrivacyDashboard.PrivacyDashboardOpenSettingsTarget) { + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didRequestOpenSettings target: PrivacyDashboardOpenSettingsTarget) { guard let mainViewController = presentingViewController as? MainViewController else { return } - dismiss(animated: true) { switch target { case .cookiePopupManagement: @@ -230,27 +229,15 @@ extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { } } } - -} - -// MARK: - PrivacyDashboardNavigationDelegate - -extension PrivacyDashboardViewController: PrivacyDashboardNavigationDelegate { - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, didSetHeight height: Int) { + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetHeight height: Int) { // The size received in iPad is wrong, shane will sort this out soon. // preferredContentSize.height = CGFloat(height) } - func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) { + func privacyDashboardControllerDidRequestClose(_ privacyDashboardController: PrivacyDashboardController) { privacyDashboardCloseHandler() } - -} - -// MARK: - PrivacyDashboardReportBrokenSiteDelegate - -extension PrivacyDashboardViewController: PrivacyDashboardReportBrokenSiteDelegate { func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, reportBrokenSiteDidChangeProtectionSwitch protectionState: ProtectionState) { @@ -288,21 +275,11 @@ extension PrivacyDashboardViewController: PrivacyDashboardReportBrokenSiteDelega present(alert, animated: true) } -} - -// MARK: - PrivacyDashboardToggleReportDelegate - -extension PrivacyDashboardViewController: PrivacyDashboardToggleReportDelegate { - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - didRequestSubmitToggleReportWithSource source: BrokenSiteReport.Source, - didOpenReportInfo: Bool, - toggleReportCounter: Int?) { + didRequestSubmitToggleReportWithSource source: BrokenSiteReport.Source) { Task { @MainActor in do { - let report = try await makeBrokenSiteReport(source: source, - didOpenReportInfo: didOpenReportInfo, - toggleReportCounter: toggleReportCounter) + let report = try await makeBrokenSiteReport(source: source) try toggleProtectionsOffReporter.report(report, reportMode: .toggle) } catch { os_log("Failed to generate or send the broken site report: %@", type: .error, error.localizedDescription) @@ -312,6 +289,16 @@ extension PrivacyDashboardViewController: PrivacyDashboardToggleReportDelegate { } } + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didSetPermission permissionName: String, + to state: PermissionAuthorizationState) { + // not supported on iOS + } + + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, setPermission permissionName: String, paused: Bool) { + // not supported on iOS + } + } extension PrivacyDashboardViewController: UIPopoverPresentationControllerDelegate {} @@ -352,9 +339,7 @@ extension PrivacyDashboardViewController { private func makeBrokenSiteReport(category: String = "", description: String = "", - source: BrokenSiteReport.Source, - didOpenReportInfo: Bool = false, - toggleReportCounter: Int? = nil) async throws -> BrokenSiteReport { + source: BrokenSiteReport.Source) async throws -> BrokenSiteReport { guard let privacyInfo = privacyDashboardController.privacyInfo, let breakageAdditionalInfo = breakageAdditionalInfo else { @@ -400,8 +385,6 @@ extension PrivacyDashboardViewController { vpnOn: breakageAdditionalInfo.vpnOn, jsPerformance: webVitalsResult, userRefreshCount: breakageAdditionalInfo.userRefreshCount, - didOpenReportInfo: didOpenReportInfo, - toggleReportCounter: toggleReportCounter, variant: PixelExperiment.cohort?.rawValue ?? "") } diff --git a/DuckDuckGo/TabDelegate.swift b/DuckDuckGo/TabDelegate.swift index b28a7a5263..648fdb1067 100644 --- a/DuckDuckGo/TabDelegate.swift +++ b/DuckDuckGo/TabDelegate.swift @@ -89,7 +89,4 @@ protocol TabDelegate: AnyObject { func showBars() - func tabDidRequestRefresh(tab: TabViewController) - - func tabDidRequestNavigationToDifferentSite(tab: TabViewController) } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index cc2d3b659d..12a6fdd025 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -518,9 +518,8 @@ class TabViewController: UIViewController { refreshControl.addAction(UIAction { [weak self] _ in guard let self else { return } reload() - delegate?.tabDidRequestRefresh(tab: self) Pixel.fire(pixel: .pullToRefresh) - AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.refresh) + AppDependencyProvider.shared.userBehaviorMonitor.handleRefreshAction() }, for: .valueChanged) refreshControl.backgroundColor = .systemBackground @@ -821,7 +820,7 @@ class TabViewController: UIViewController { private func makePrivacyDashboardViewController(coder: NSCoder) -> PrivacyDashboardViewController? { PrivacyDashboardViewController(coder: coder, privacyInfo: privacyInfo, - dashboardMode: .dashboard, + entryPoint: .dashboard, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, breakageAdditionalInfo: makeBreakageAdditionalInfo()) @@ -1062,13 +1061,13 @@ class TabViewController: UIViewController { image: "SiteBreakage", leftButton: (UserText.brokenSiteReportToggleAlertYesButton, { [weak self] in Pixel.fire(pixel: .reportBrokenSiteTogglePromptYes) - (self?.parent as? MainViewController)?.segueToReportBrokenSite(mode: .afterTogglePrompt(category: breakageCategory, - didToggleProtectionsFixIssue: true)) + (self?.parent as? MainViewController)?.segueToReportBrokenSite(entryPoint: .afterTogglePrompt(category: breakageCategory, + didToggleProtectionsFixIssue: true)) }), rightButton: (UserText.brokenSiteReportToggleAlertNoButton, { [weak self] in Pixel.fire(pixel: .reportBrokenSiteTogglePromptNo) - (self?.parent as? MainViewController)?.segueToReportBrokenSite(mode: .afterTogglePrompt(category: breakageCategory, - didToggleProtectionsFixIssue: false)) + (self?.parent as? MainViewController)?.segueToReportBrokenSite(entryPoint: .afterTogglePrompt(category: breakageCategory, + didToggleProtectionsFixIssue: false)) })) self.alertPresenter?.present(in: self, animated: true) } @@ -1527,10 +1526,6 @@ extension TabViewController: WKNavigationDelegate { refreshCountSinceLoad = 0 } - if navigationAction.navigationType != .reload, webView.url != navigationAction.request.mainDocumentURL { - delegate?.tabDidRequestNavigationToDifferentSite(tab: self) - } - // This check needs to happen before GPC checks. Otherwise the navigation type may be rewritten to `.other` // which would skip link rewrites. if navigationAction.navigationType != .backForward && navigationAction.isTargetingMainFrame() { @@ -2294,7 +2289,7 @@ extension TabViewController: UIGestureRecognizerDelegate { } refreshCountSinceLoad += 1 - AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.refresh) + AppDependencyProvider.shared.userBehaviorMonitor.handleRefreshAction() } } diff --git a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift index daeb8fd5de..eb42751226 100644 --- a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift @@ -421,8 +421,10 @@ extension TabViewController { } private func onToggleProtectionAction(forDomain domain: String, isProtected: Bool) { - let manager = ToggleReportsManager(feature: ToggleReportsFeature(manager: ContentBlocking.shared.privacyConfigurationManager)) - if isProtected && manager.shouldShowToggleReport { + let toggleReportingConfig = ToggleReportingConfiguration(privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager) + let toggleReportingFeature = ToggleReportingFeature(toggleReportingConfiguration: toggleReportingConfig) + let toggleReportingManager = ToggleReportingManager(feature: toggleReportingFeature) + if isProtected && toggleReportingManager.shouldShowToggleReport { delegate?.tab(self, didRequestToggleReportWithCompletionHandler: { [weak self] didSendReport in self?.togglePrivacyProtection(domain: domain, didSendReport: didSendReport) }) diff --git a/DuckDuckGo/UserBehaviorEvent.swift b/DuckDuckGo/UserBehaviorEvent.swift deleted file mode 100644 index f8c336d905..0000000000 --- a/DuckDuckGo/UserBehaviorEvent.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// UserBehaviorEvent.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Core - -public enum UserBehaviorEvent: String { - - public enum Key { - - static let event = "com.duckduckgo.com.userBehaviorEvent.key" - - } - - public enum Parameter { - - static let event = "event" - - } - - case reloadTwiceWithin12Seconds = "reload-twice-within-12-seconds" - case reloadTwiceWithin24Seconds = "reload-twice-within-24-seconds" - - case reloadAndRestartWithin30Seconds = "reload-and-restart-within-30-seconds" - case reloadAndRestartWithin50Seconds = "reload-and-restart-within-50-seconds" - - case reloadThreeTimesWithin20Seconds = "reload-three-times-within-20-seconds" - case reloadThreeTimesWithin40Seconds = "reload-three-times-within-40-seconds" - -} - -extension UserBehaviorEvent { - - var matchingPixelExperimentVariant: PixelExperimentForBrokenSites { - switch self { - case .reloadTwiceWithin12Seconds: return .reloadTwiceWithin12SecondsShowsPrompt - case .reloadTwiceWithin24Seconds: return .reloadTwiceWithin24SecondsShowsPrompt - case .reloadAndRestartWithin30Seconds: return .reloadAndRestartWithin30SecondsShowsPrompt - case .reloadAndRestartWithin50Seconds: return .reloadAndRestartWithin50SecondsShowsPrompt - case .reloadThreeTimesWithin20Seconds: return .reloadThreeTimesWithin20SecondsShowsPrompt - case .reloadThreeTimesWithin40Seconds: return .reloadThreeTimesWithin40SecondsShowsPrompt - } - } - -} diff --git a/DuckDuckGo/UserBehaviorMonitor.swift b/DuckDuckGo/UserBehaviorMonitor.swift index c68a332021..a2edd56c7c 100644 --- a/DuckDuckGo/UserBehaviorMonitor.swift +++ b/DuckDuckGo/UserBehaviorMonitor.swift @@ -21,12 +21,6 @@ import Foundation import Common import Core -public extension Notification.Name { - - static let userBehaviorDidMatchExperimentVariant = Notification.Name("com.duckduckgo.app.userBehaviorDidMatchExperimentVariant") - -} - protocol UserBehaviorStoring { var didRefreshTimestamp: Date? { get set } @@ -48,12 +42,18 @@ final class UserBehaviorStore: UserBehaviorStoring { } +public enum UserBehaviorEvent: String { + + case reloadTwiceWithin12Seconds = "reload-twice-within-12-seconds" + case reloadThreeTimesWithin20Seconds = "reload-three-times-within-20-seconds" + +} + final class UserBehaviorMonitor { enum Action: Equatable { case refresh - case reopenApp } @@ -81,35 +81,22 @@ final class UserBehaviorMonitor { set { store.didRefreshCounter = newValue } } - func handleAction(_ action: Action, date: Date = Date()) { - switch action { - case .refresh: - fireEventIfActionOccurredRecently(within: 12.0, since: didRefreshTimestamp, eventToFire: .reloadTwiceWithin12Seconds) - fireEventIfActionOccurredRecently(within: 24.0, since: didRefreshTimestamp, eventToFire: .reloadTwiceWithin24Seconds) - didRefreshTimestamp = date - - if didRefreshCounter == 0 { - didDoubleRefreshTimestamp = date - } - didRefreshCounter += 1 - if didRefreshCounter > 2 { - fireEventIfActionOccurredRecently(within: 20.0, since: didDoubleRefreshTimestamp, eventToFire: .reloadThreeTimesWithin20Seconds) - fireEventIfActionOccurredRecently(within: 40.0, since: didDoubleRefreshTimestamp, eventToFire: .reloadThreeTimesWithin40Seconds) - didRefreshCounter = 0 - } - case .reopenApp: - fireEventIfActionOccurredRecently(within: 30.0, since: didRefreshTimestamp, eventToFire: .reloadAndRestartWithin30Seconds) - fireEventIfActionOccurredRecently(within: 50.0, since: didRefreshTimestamp, eventToFire: .reloadAndRestartWithin50Seconds) + func handleRefreshAction(date: Date = Date()) { + fireEventIfActionOccurredRecently(within: 12.0, since: didRefreshTimestamp, eventToFire: .reloadTwiceWithin12Seconds) + didRefreshTimestamp = date + + if didRefreshCounter == 0 { + didDoubleRefreshTimestamp = date + } + didRefreshCounter += 1 + if didRefreshCounter > 2 { + fireEventIfActionOccurredRecently(within: 20.0, since: didDoubleRefreshTimestamp, eventToFire: .reloadThreeTimesWithin20Seconds) + didRefreshCounter = 0 } func fireEventIfActionOccurredRecently(within interval: Double = 30.0, since timestamp: Date?, eventToFire: UserBehaviorEvent) { if let timestamp = timestamp, date.timeIntervalSince(timestamp) < interval { eventMapping.fire(eventToFire) - if PixelExperimentForBrokenSites.cohort == eventToFire.matchingPixelExperimentVariant { - NotificationCenter.default.post(name: .userBehaviorDidMatchExperimentVariant, - object: self, - userInfo: [UserBehaviorEvent.Key.event: eventToFire]) - } } } } @@ -122,11 +109,7 @@ final class AppUserBehaviorMonitor { let domainEvent: Pixel.Event switch event { case .reloadTwiceWithin12Seconds: domainEvent = .userBehaviorReloadTwiceWithin12Seconds - case .reloadTwiceWithin24Seconds: domainEvent = .userBehaviorReloadTwiceWithin24Seconds - case .reloadAndRestartWithin30Seconds: domainEvent = .userBehaviorReloadAndRestartWithin30Seconds - case .reloadAndRestartWithin50Seconds: domainEvent = .userBehaviorReloadAndRestartWithin50Seconds case .reloadThreeTimesWithin20Seconds: domainEvent = .userBehaviorReloadThreeTimesWithin20Seconds - case .reloadThreeTimesWithin40Seconds: domainEvent = .userBehaviorReloadThreeTimesWithin40Seconds } Pixel.fire(pixel: domainEvent) } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 335e175fdb..612f6f2c10 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1144,13 +1144,6 @@ But if you *do* want a peek under the hood, you can find more information about public static let autocompleteHistoryWarningDescription = NSLocalizedString("autocomplete.history.warning.message", value: "Search suggestions now include your recently visited sites. Turn off in Settings, or clear anytime with the 🔥 Fire Button.", comment: "The message text shown in suggestions") public static let autocompleteSearchDuckDuckGo = NSLocalizedString("autocomplete.history.search.duckduckgo", value: "Search DuckDuckGo", comment: "Subtitle for search history items") - // Site not working - public static let siteNotWorkingTitle = NSLocalizedString("site.not.working.title", value: "Site not working? Let DuckDuckGo know.", comment: "Prompt asking user to send report to us if we suspect site may be broken") - public static let siteNotWorkingSubtitle = NSLocalizedString("site.not.working.subtitle", value: "This helps us improve the browser.", comment: "Prompt asking user to send report to us if we suspect site may be broken") - public static let siteNotWorkingDismiss = NSLocalizedString("site.not.working.dismiss", value: "Dismiss", comment: "Dismiss button") - public static let siteNotWorkingWebsiteIsBroken = NSLocalizedString("site.not.working.website.is.broken", value: "Website Is Broken", comment: "Button that triggers flow to report broken site") - public static let siteNotWorkingDescription = NSLocalizedString("site.not.working.description", value: "Select the option that best describes the problem you experienced.", comment: "Description on a report broken site page.") - // Broken site report experiment public static let brokenSiteReportMenuTitle = NSLocalizedString("broken.site.report.menu.title", value: "Report Problem With This Site", comment: "Button to open report form") public static let brokenSiteReportSuccessToast = NSLocalizedString("broken.site.report.success.toast", value: "Your report helps make DuckDuckGo better for everyone!", comment: "Message that appears after submitting report") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 980c425db8..d9900a1244 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1972,21 +1972,6 @@ But if you *do* want a peek under the hood, you can find more information about /* Explanation in Settings how the web tracking protection feature works */ "settings.web.tracking.protection.explanation" = "DuckDuckGo automatically blocks hidden trackers as you browse the web.\n[Learn More](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/)"; -/* Description on a report broken site page. */ -"site.not.working.description" = "Select the option that best describes the problem you experienced."; - -/* Dismiss button */ -"site.not.working.dismiss" = "Dismiss"; - -/* Prompt asking user to send report to us if we suspect site may be broken */ -"site.not.working.subtitle" = "This helps us improve the browser."; - -/* Prompt asking user to send report to us if we suspect site may be broken */ -"site.not.working.title" = "Site not working? Let DuckDuckGo know."; - -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Website Is Broken"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Submit Report"; diff --git a/DuckDuckGoTests/AppPrivacyConfigurationTests.swift b/DuckDuckGoTests/AppPrivacyConfigurationTests.swift index 402fe493a0..8fb8c681fa 100644 --- a/DuckDuckGoTests/AppPrivacyConfigurationTests.swift +++ b/DuckDuckGoTests/AppPrivacyConfigurationTests.swift @@ -42,8 +42,7 @@ class AppPrivacyConfigurationTests: XCTestCase { let config = AppPrivacyConfiguration(data: configData, identifier: "", localProtection: MockDomainsProtectionStore(), - internalUserDecider: DefaultInternalUserDecider(store: InternalUserStore()), - toggleProtectionsCounter: ToggleProtectionsCounter(eventReporting: nil)) + internalUserDecider: DefaultInternalUserDecider(store: InternalUserStore())) XCTAssert(config.isEnabled(featureKey: .contentBlocking)) diff --git a/DuckDuckGoTests/AppURLsTests.swift b/DuckDuckGoTests/AppURLsTests.swift index 48440b06ad..29a157ef38 100644 --- a/DuckDuckGoTests/AppURLsTests.swift +++ b/DuckDuckGoTests/AppURLsTests.swift @@ -47,8 +47,7 @@ final class AppURLsTests: XCTestCase { appConfig = AppPrivacyConfiguration(data: privacyData, identifier: "", localProtection: localProtection, - internalUserDecider: DefaultInternalUserDecider(), - toggleProtectionsCounter: ToggleProtectionsCounter(eventReporting: nil)) + internalUserDecider: DefaultInternalUserDecider()) } func testWhenRemoveInternalSearchParametersFromSearchUrlThenUrlIsChanged() throws { diff --git a/DuckDuckGoTests/BrokenSiteReportingTests.swift b/DuckDuckGoTests/BrokenSiteReportingTests.swift index e21755acfc..3c515cf373 100644 --- a/DuckDuckGoTests/BrokenSiteReportingTests.swift +++ b/DuckDuckGoTests/BrokenSiteReportingTests.swift @@ -118,8 +118,6 @@ final class BrokenSiteReportingTests: XCTestCase { vpnOn: false, jsPerformance: nil, userRefreshCount: 0, - didOpenReportInfo: false, - toggleReportCounter: nil, variant: "") let reporter = BrokenSiteReporter(pixelHandler: { params in diff --git a/DuckDuckGoTests/ContentBlockerProtectionStoreTests.swift b/DuckDuckGoTests/ContentBlockerProtectionStoreTests.swift index 71e33ec66e..7fb9efde56 100644 --- a/DuckDuckGoTests/ContentBlockerProtectionStoreTests.swift +++ b/DuckDuckGoTests/ContentBlockerProtectionStoreTests.swift @@ -51,8 +51,7 @@ class ContentBlockerProtectionStoreTests: XCTestCase { let config = AppPrivacyConfiguration(data: newConfig.data, identifier: "", localProtection: DomainsProtectionUserDefaultsStore(), - internalUserDecider: DefaultInternalUserDecider(), - toggleProtectionsCounter: ToggleProtectionsCounter(eventReporting: nil)) + internalUserDecider: DefaultInternalUserDecider()) XCTAssertFalse(config.isTempUnprotected(domain: "main1.com")) XCTAssertFalse(config.isTempUnprotected(domain: "notdomain1.com")) diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index 1466f2a2c5..68711cf742 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -41,7 +41,6 @@ class MockDependencyProvider: DependencyProvider { var autofillNeverPromptWebsitesManager: AutofillNeverPromptWebsitesManager var configurationManager: ConfigurationManager var userBehaviorMonitor: UserBehaviorMonitor - var toggleProtectionsCounter: ToggleProtectionsCounter var subscriptionFeatureAvailability: SubscriptionFeatureAvailability var subscriptionManager: SubscriptionManager var accountManager: AccountManager @@ -66,7 +65,6 @@ class MockDependencyProvider: DependencyProvider { autofillNeverPromptWebsitesManager = defaultProvider.autofillNeverPromptWebsitesManager configurationManager = defaultProvider.configurationManager userBehaviorMonitor = defaultProvider.userBehaviorMonitor - toggleProtectionsCounter = defaultProvider.toggleProtectionsCounter subscriptionFeatureAvailability = defaultProvider.subscriptionFeatureAvailability accountManager = AccountManagerMock() diff --git a/DuckDuckGoTests/MockPrivacyConfiguration.swift b/DuckDuckGoTests/MockPrivacyConfiguration.swift index e14a517217..1053217167 100644 --- a/DuckDuckGoTests/MockPrivacyConfiguration.swift +++ b/DuckDuckGoTests/MockPrivacyConfiguration.swift @@ -87,6 +87,5 @@ class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManaging { var updatesPublisher: AnyPublisher = Just(()).eraseToAnyPublisher() var privacyConfig: PrivacyConfiguration = MockPrivacyConfiguration() var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider() - var toggleProtectionsCounter: ToggleProtectionsCounter = ToggleProtectionsCounter(eventReporting: nil) } diff --git a/DuckDuckGoTests/PrivacyConfigurationManagerMock.swift b/DuckDuckGoTests/PrivacyConfigurationManagerMock.swift index dec46c64d8..15c9b403ab 100644 --- a/DuckDuckGoTests/PrivacyConfigurationManagerMock.swift +++ b/DuckDuckGoTests/PrivacyConfigurationManagerMock.swift @@ -121,7 +121,6 @@ class PrivacyConfigurationManagerMock: PrivacyConfigurationManaging { var privacyConfig: PrivacyConfiguration = PrivacyConfigurationMock() var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider() - var toggleProtectionsCounter: ToggleProtectionsCounter = ToggleProtectionsCounter(eventReporting: nil) var reloadFired = [(etag: String?, data: Data?)]() var reloadResult: PrivacyConfigurationManager.ReloadResult = .embedded diff --git a/DuckDuckGoTests/UserBehaviorMonitorTests.swift b/DuckDuckGoTests/UserBehaviorMonitorTests.swift index 5ffcd084bc..265a05484d 100644 --- a/DuckDuckGoTests/UserBehaviorMonitorTests.swift +++ b/DuckDuckGoTests/UserBehaviorMonitorTests.swift @@ -62,138 +62,63 @@ final class UserBehaviorMonitorTests: XCTestCase { // Expecting events func testWhenUserRefreshesTwiceItSendsReloadTwiceEvent() { - monitor.handleAction(.refresh) - monitor.handleAction(.refresh) - XCTAssertEqual(events.count, 2) + monitor.handleRefreshAction() + monitor.handleRefreshAction() + XCTAssertEqual(events.count, 1) XCTAssertEqual(events[0], .reloadTwiceWithin12Seconds) - XCTAssertEqual(events[1], .reloadTwiceWithin24Seconds) - } - - func testWhenUserRefreshesAndThenReopensAppItSendsReloadAndRestartEvent() { - monitor.handleAction(.refresh) - monitor.handleAction(.reopenApp) - XCTAssertEqual(events.count, 2) - XCTAssertEqual(events[0], .reloadAndRestartWithin30Seconds) - XCTAssertEqual(events[1], .reloadAndRestartWithin50Seconds) } func testWhenUserRefreshesThreeTimesItSendsTwoReloadTwiceEvents() { - monitor.handleAction(.refresh) - monitor.handleAction(.refresh) - monitor.handleAction(.refresh) - XCTAssertEqual(events.count, 6) + monitor.handleRefreshAction() + monitor.handleRefreshAction() + monitor.handleRefreshAction() + XCTAssertEqual(events.count, 3) XCTAssertEqual(events[0], .reloadTwiceWithin12Seconds) - XCTAssertEqual(events[1], .reloadTwiceWithin24Seconds) - XCTAssertEqual(events[2], .reloadTwiceWithin12Seconds) - XCTAssertEqual(events[3], .reloadTwiceWithin24Seconds) + XCTAssertEqual(events[1], .reloadTwiceWithin12Seconds) } func testWhenUserRefreshesThreeTimesItSendsReloadThreeTimesEvent() { - monitor.handleAction(.refresh) - monitor.handleAction(.refresh) - monitor.handleAction(.refresh) - XCTAssertEqual(events.count, 6) - XCTAssertEqual(events[4], .reloadThreeTimesWithin20Seconds) - XCTAssertEqual(events[5], .reloadThreeTimesWithin40Seconds) - } - - func testWhenUserRefreshesThenReopensAppThenRefreshesAgainItSendsTwoEvents() { - monitor.handleAction(.refresh) - monitor.handleAction(.reopenApp) - monitor.handleAction(.refresh) - XCTAssertEqual(events.count, 4) - XCTAssertEqual(events[0], .reloadAndRestartWithin30Seconds) - XCTAssertEqual(events[1], .reloadAndRestartWithin50Seconds) - XCTAssertEqual(events[2], .reloadTwiceWithin12Seconds) - XCTAssertEqual(events[3], .reloadTwiceWithin24Seconds) - } - - // Not expecting any events - - func testWhenUserUsesReopensAppAndThenRefreshesItShouldNotSendAnyEvent() { - monitor.handleAction(.reopenApp) - monitor.handleAction(.refresh) - XCTAssertTrue(events.isEmpty) + monitor.handleRefreshAction() + monitor.handleRefreshAction() + monitor.handleRefreshAction() + XCTAssertEqual(events.count, 3) + XCTAssertEqual(events[2], .reloadThreeTimesWithin20Seconds) } // Timed pixels - func testReloadTwiceEventShouldNotSendEventIfSecondRefreshOccuredAfter24Seconds() { + func testReloadTwiceEventShouldNotSendEventIfSecondRefreshOccuredAfter12Seconds() { let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date + 24) // 24 seconds after the first event + monitor.handleRefreshAction(date: date) + monitor.handleRefreshAction(date: date + 13) // 13 seconds after the first event XCTAssertTrue(events.isEmpty) } - func testReloadTwiceEventShouldSendEventIfSecondRefreshOccurredBelow24Seconds() { + func testReloadTwiceEventShouldSendEventIfSecondRefreshOccurredBelow12Seconds() { let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date + 20) // 20 seconds after the first event + monitor.handleRefreshAction(date: date) + monitor.handleRefreshAction(date: date + 11) // 20 seconds after the first event XCTAssertEqual(events.count, 1) - XCTAssertEqual(events[0], .reloadTwiceWithin24Seconds) - } - - func testReloadTwiceEventShouldSendTwoEventsIfSecondRefreshOccurredBelow12Seconds() { - let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date + 10) // 10 seconds after the first event - XCTAssertEqual(events.count, 2) XCTAssertEqual(events[0], .reloadTwiceWithin12Seconds) - XCTAssertEqual(events[1], .reloadTwiceWithin24Seconds) - } - - func testReloadAndRestartEventShouldNotSendEventIfRestartOccurredAfter50Seconds() { - let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.reopenApp, date: date + 50) // 50 seconds after the first event - XCTAssertTrue(events.isEmpty) - } - - func testReloadAndRestartEventShouldSendEventIfRestartOccurredBelow50Seconds() { - let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.reopenApp, date: date + 30) // 30 seconds after the first event - XCTAssertEqual(events.count, 1) - XCTAssertEqual(events[0], .reloadAndRestartWithin50Seconds) } - func testReloadAndRestartEventShouldSendTwoEventsIfRestartOccurredBelow30Seconds() { + func testReloadThreeTimesEventShouldNotSendEventIfThreeRefreshesOccurredAfter20Seconds() { let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.reopenApp, date: date + 20) // 20 seconds after the first event - XCTAssertEqual(events.count, 2) - XCTAssertEqual(events[0], .reloadAndRestartWithin30Seconds) - XCTAssertEqual(events[1], .reloadAndRestartWithin50Seconds) - } - - func testReloadThreeTimesEventShouldNotSendEventIfSecondRefreshOccuredAfter40Seconds() { - let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date + 40) // 40 seconds after the first event - events.removeAll { $0 == .reloadTwiceWithin12Seconds || $0 == .reloadTwiceWithin24Seconds } // remove events that are not being tested + monitor.handleRefreshAction(date: date) + monitor.handleRefreshAction(date: date) + monitor.handleRefreshAction(date: date + 21) // 21 seconds after the first event + events.removeAll { $0 == .reloadTwiceWithin12Seconds } // remove events that are not being tested XCTAssertTrue(events.isEmpty) } - func testReloadThreeTimesEventShouldSendEventIfSecondRefreshOccurredBelow40Seconds() { + func testReloadThreeTimesEventShouldSendEventIfThreeRefreshesOccurredBelow20Seconds() { let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date + 30) // 30 seconds after the first event - events.removeAll { $0 == .reloadTwiceWithin12Seconds || $0 == .reloadTwiceWithin24Seconds } // remove events that are not being tested + monitor.handleRefreshAction(date: date) + monitor.handleRefreshAction(date: date) + monitor.handleRefreshAction(date: date + 19) // 10 seconds after the first event + events.removeAll { $0 == .reloadTwiceWithin12Seconds } // remove events that are not being tested XCTAssertEqual(events.count, 1) - XCTAssertEqual(events[0], .reloadThreeTimesWithin40Seconds) - } - - func testReloadThreeTimesEventShouldSendTwoEventsIfSecondRefreshOccurredBelow20Seconds() { - let date = Date() - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date) - monitor.handleAction(.refresh, date: date + 10) // 10 seconds after the first event - events.removeAll { $0 == .reloadTwiceWithin12Seconds || $0 == .reloadTwiceWithin24Seconds } // remove events that are not being tested - XCTAssertEqual(events.count, 2) XCTAssertEqual(events[0], .reloadThreeTimesWithin20Seconds) - XCTAssertEqual(events[1], .reloadThreeTimesWithin40Seconds) } } diff --git a/DuckDuckGoTests/WebViewTestHelper.swift b/DuckDuckGoTests/WebViewTestHelper.swift index e5806c3b93..d25d09c368 100644 --- a/DuckDuckGoTests/WebViewTestHelper.swift +++ b/DuckDuckGoTests/WebViewTestHelper.swift @@ -112,8 +112,7 @@ class WebKitTestHelper { return AppPrivacyConfiguration(data: privacyData, identifier: "", localProtection: localProtection, - internalUserDecider: DefaultInternalUserDecider(), - toggleProtectionsCounter: ToggleProtectionsCounter(eventReporting: nil)) + internalUserDecider: DefaultInternalUserDecider()) } static func prepareContentBlockingRules(trackerData: TrackerData, diff --git a/IntegrationTests/AutoconsentBackgroundTests.swift b/IntegrationTests/AutoconsentBackgroundTests.swift index 121b7afe18..5ca834fe4e 100644 --- a/IntegrationTests/AutoconsentBackgroundTests.swift +++ b/IntegrationTests/AutoconsentBackgroundTests.swift @@ -47,12 +47,10 @@ final class AutoconsentBackgroundTests: XCTestCase { """.data(using: .utf8)! let mockEmbeddedData = MockEmbeddedDataProvider(data: embeddedConfig, etag: "embedded") - let eventMapping = EventMapping { _, _, _, _ in } let manager = PrivacyConfigurationManager(fetchedETag: nil, fetchedData: nil, embeddedDataProvider: mockEmbeddedData, localProtection: MockDomainsProtectionStore(), - toggleProtectionsCounterEventReporting: eventMapping, internalUserDecider: DefaultInternalUserDecider()) return AutoconsentUserScript(config: manager.privacyConfig, preferences: MockAutoconsentPreferences(), From 761b139b574c2e765c0cd12a27c0746260dbbcf0 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 5 Jul 2024 18:28:42 +0200 Subject: [PATCH 10/15] macOS BSK change: De-duplicate passwords on import (#3048) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGoTests/MockSecureVault.swift | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 23f02bb0f2..79d5668d3f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9946,7 +9946,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 166.0.0; + version = 167.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ecc177b506..d69794d5b7 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "f23384018ede5aa63777b1c143e81855a16210fd", - "version" : "166.0.0" + "revision" : "5954412504b0cf294f5c0d90d7a0c8dfcd009558", + "version" : "167.0.0" } }, { diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index eb4acc3a78..2bdc097a81 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -39,7 +39,6 @@ let MockSecureVaultFactory = SecureVaultFactory( ) final class MockSecureVault: AutofillSecureVault { - public typealias MockSecureVaultDatabaseProviders = SecureStorageProviders var storedAccounts: [SecureVaultModels.WebsiteAccount] = [] @@ -63,6 +62,11 @@ final class MockSecureVault: AutofillSecureVault { data } + func encryptPassword(for credentials: BrowserServicesKit.SecureVaultModels.WebsiteCredentials, key l2Key: Data?, salt: Data?) throws -> BrowserServicesKit.SecureVaultModels.WebsiteCredentials { + .init(account: .init(username: nil, domain: nil), password: nil) + } + + func decrypt(_ data: Data, using key: Data) throws -> Data { data } From 9679563635a44724d86eeeeef38593b97bd68b30 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 5 Jul 2024 17:37:41 +0100 Subject: [PATCH 11/15] fix autofill widget failure (#3040) --- .../password-authentication.yaml | 39 +++++++++---------- .maestro/setup_ui_tests.sh | 2 +- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.maestro/release_tests/password-authentication.yaml b/.maestro/release_tests/password-authentication.yaml index f9bf9be52a..8722ecd798 100644 --- a/.maestro/release_tests/password-authentication.yaml +++ b/.maestro/release_tests/password-authentication.yaml @@ -84,23 +84,22 @@ tags: - tapOn: "Done" # Validate launch from widget - -- pressKey: HOME -- longPressOn: - point: 50%,50% -- tapOn: "Add Widget" -- tapOn: Search Widgets -- inputText: DuckDuck -- tapOn: DuckDuckGo -- tapOn: "Page 1 of 5" -- tapOn: " Add Widget" -- tapOn: "Done" -- tapOn: - id: "DuckDuckGo" - index: 0 - -- assertVisible: "Unlock device to access passwords" -- inputText: "Anything" -- pressKey: Enter -- assertVisible: "Search passwords" - +# Disabled until we can get iOS 17.2 runner on mobile.dev (or our own CI) +# - pressKey: HOME +# - longPressOn: +# point: 50%,50% +# - tapOn: "Add Widget" +# - tapOn: "Search Widgets" +# - inputText: "DuckDuck" +# - tapOn: "DuckDuckGo" +# - tapOn: "Page 1 of 5" +# - tapOn: " Add Widget" +# - tapOn: "Done" +# - tapOn: +# id: "DuckDuckGo" +# index: 0 + +# - assertVisible: "Unlock device to access passwords" +# - inputText: "Anything" +# - pressKey: Enter +# - assertVisible: "Search passwords" diff --git a/.maestro/setup_ui_tests.sh b/.maestro/setup_ui_tests.sh index 12cd6a5b52..64292c30a8 100755 --- a/.maestro/setup_ui_tests.sh +++ b/.maestro/setup_ui_tests.sh @@ -8,7 +8,7 @@ source $(dirname $0)/common.sh # The simulator command requires the hyphens target_device="iPhone-15" -target_os="iOS-17-2" +target_os="iOS-17-0" ## Functions From 3c07306e2f0176774a339764c3fce6d3f0cc1583 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 5 Jul 2024 17:41:43 +0100 Subject: [PATCH 12/15] widget UI tests (#3042) --- .maestro/release_tests/widgets.yaml | 77 +++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 .maestro/release_tests/widgets.yaml diff --git a/.maestro/release_tests/widgets.yaml b/.maestro/release_tests/widgets.yaml new file mode 100644 index 0000000000..a83460dbb0 --- /dev/null +++ b/.maestro/release_tests/widgets.yaml @@ -0,0 +1,77 @@ +# widgets.yaml +appId: com.duckduckgo.mobile.ios + +# Disabled until we can support iOS 17.2 on CI +# tags: +# - release + +--- + +# Set up +- clearState +- launchApp +- runFlow: + file: ../shared/onboarding.yaml + +# Load a website +- assertVisible: + id: "searchEntry" +- tapOn: + id: "searchEntry" +- inputText: "https://privacy-test-pages.site/" +- pressKey: Enter + +# Manage onboarding +- runFlow: + file: ../shared/onboarding_browsing.yaml + +# Prepare to add widgets +- pressKey: HOME + +# Swipe to first page to get from the app (removing the app from home screen doesn't work) +- swipe: + start: 5%, 70% + end: 95%, 70% + +# Validate search widget +- longPressOn: + point: 50%,50% +- tapOn: "Add Widget" +- tapOn: "Search Widgets" +- inputText: "DuckDuck" +- tapOn: "DuckDuckGo" +- tapOn: " Add Widget" +- tapOn: "Done" +- tapOn: "DuckDuckGo" +- inputText: "example.com" +- pressKey: Enter +- assertVisible: "Example Domain" +- assertVisible: "Tab Switcher" +- tapOn: "Tab Switcher" +- assertVisible: "Open \"Privacy Test Pages - Home\" at privacy-test-pages.site" + +# Validate favorites widget +- pressKey: HOME +- longPressOn: + point: 50%,50% +- tapOn: "Add Widget" +- tapOn: "Search Widgets" +- inputText: "DuckDuck" +- tapOn: "DuckDuckGo" +- assertVisible: "Search" +- swipe: + start: 90%, 50% + end: 10%, 50% +- assertVisible: "Search Passwords" +- swipe: + start: 90%, 50% + end: 10%, 50% +- assertVisible: "Search and Favorites" +- swipe: + start: 90%, 50% + end: 10%, 50% +- assertVisible: "Search and Favorites" +- swipe: + start: 90%, 50% + end: 10%, 50% +- assertVisible: "VPN" From d661bcbe2b862008247ec0cd90241caa9a4c7728 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 5 Jul 2024 21:32:44 +0100 Subject: [PATCH 13/15] fix bug not clearing ui properly on autoclear (#3050) --- DuckDuckGo/MainViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 9b68900b42..3210d8db58 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -2445,11 +2445,11 @@ extension MainViewController: AutoClearWorker { @MainActor func autoClearDidFinishClearing(_: AutoClear, isLaunching: Bool) { + autoClearInProgress = false if autoClearShouldRefreshUIAfterClear && isLaunching == false { refreshUIAfterClear() } - autoClearInProgress = false autoClearShouldRefreshUIAfterClear = true } From e5a6230f36b6adb7db3c3a28b07484fc64f128d8 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Fri, 5 Jul 2024 19:51:03 -0400 Subject: [PATCH 14/15] Release 7.127.0-1 (#3051) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3e1207a5f0..eafccead4c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8129,7 +8129,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8166,7 +8166,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8258,7 +8258,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8286,7 +8286,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8436,7 +8436,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8462,7 +8462,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8532,7 +8532,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8567,7 +8567,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8601,7 +8601,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8632,7 +8632,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8947,7 +8947,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8979,7 +8979,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9008,7 +9008,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9042,7 +9042,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9073,7 +9073,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9106,11 +9106,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9346,7 +9346,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9374,7 +9374,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9407,7 +9407,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9445,7 +9445,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9481,7 +9481,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9516,11 +9516,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9696,11 +9696,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9729,10 +9729,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 094fdf34624277425b09e524a99ad391f33eb1c6 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Sat, 6 Jul 2024 14:32:25 +0200 Subject: [PATCH 15/15] Add connection tester failure pixels (#3049) Task/Issue URL: https://app.asana.com/0/1206580121312550/1207743877093953/f macOS PR: https://github.com/duckduckgo/macos-browser/pull/2948 BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/881 ## Description Adds pixels to track connection tester failures and recovery. These should give us a better idea about how users are faring. --- Core/PixelEvent.swift | 9 +++++ DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- ...etworkProtectionPacketTunnelProvider.swift | 34 +++++++++++++++++++ .../NetworkProtection/VPNLogger.swift | 16 +++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 8b6ca67604..1d553eea15 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -337,6 +337,11 @@ extension Pixel { case networkProtectionServerMigrationAttemptSuccess case networkProtectionServerMigrationAttemptFailure + case networkProtectionConnectionTesterFailureDetected + case networkProtectionConnectionTesterFailureRecovered(failureCount: Int) + case networkProtectionConnectionTesterExtendedFailureDetected + case networkProtectionConnectionTesterExtendedFailureRecovered(failureCount: Int) + case networkProtectionTunnelFailureDetected case networkProtectionTunnelFailureRecovered @@ -1043,6 +1048,10 @@ extension Pixel.Event { case .networkProtectionEnableAttemptConnecting: return "m_netp_ev_enable_attempt" case .networkProtectionEnableAttemptSuccess: return "m_netp_ev_enable_attempt_success" case .networkProtectionEnableAttemptFailure: return "m_netp_ev_enable_attempt_failure" + case .networkProtectionConnectionTesterFailureDetected: return "m_netp_connection_tester_failure" + case .networkProtectionConnectionTesterFailureRecovered: return "m_netp_connection_tester_failure_recovered" + case .networkProtectionConnectionTesterExtendedFailureDetected: return "m_netp_connection_tester_extended_failure" + case .networkProtectionConnectionTesterExtendedFailureRecovered: return "m_netp_connection_tester_extended_failure_recovered" case .networkProtectionTunnelFailureDetected: return "m_netp_ev_tunnel_failure" case .networkProtectionTunnelFailureRecovered: return "m_netp_ev_tunnel_failure_recovered" case .networkProtectionLatency(let quality): return "m_netp_ev_\(quality.rawValue)_latency" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3c9f071b41..d99b9972f5 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9946,7 +9946,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 167.0.0; + version = 167.0.1; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d69794d5b7..6d2d55714a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "5954412504b0cf294f5c0d90d7a0c8dfcd009558", - "version" : "167.0.0" + "revision" : "0746af01b77d39a1e037bea93b46591534a13b5c", + "version" : "167.0.1" } }, { diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 8ef890d60c..4f2a0eaa5c 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -48,6 +48,40 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fire(pixel: .networkProtectionActiveUser, withAdditionalParameters: [PixelParameters.vpnCohort: UniquePixel.cohort(from: defaults.vpnFirstEnabled)], includedParameters: [.appVersion, .atb]) + case .connectionTesterStatusChange(let status, let server): + vpnLogger.log(status, server: server) + + switch status { + case .failed(let duration): + let pixel: Pixel.Event = { + switch duration { + case .immediate: + return .networkProtectionConnectionTesterFailureDetected + case .extended: + return .networkProtectionConnectionTesterExtendedFailureDetected + } + }() + + DailyPixel.fireDailyAndCount(pixel: pixel, + withAdditionalParameters: [PixelParameters.server: server], + includedParameters: [.appVersion, .atb]) + case .recovered(let duration, let failureCount): + let pixel: Pixel.Event = { + switch duration { + case .immediate: + return .networkProtectionConnectionTesterFailureRecovered(failureCount: failureCount) + case .extended: + return .networkProtectionConnectionTesterExtendedFailureRecovered(failureCount: failureCount) + } + }() + + DailyPixel.fireDailyAndCount(pixel: pixel, + withAdditionalParameters: [ + PixelParameters.count: String(failureCount), + PixelParameters.server: server + ], + includedParameters: [.appVersion, .atb]) + } case .reportConnectionAttempt(attempt: let attempt): vpnLogger.log(attempt) diff --git a/PacketTunnelProvider/NetworkProtection/VPNLogger.swift b/PacketTunnelProvider/NetworkProtection/VPNLogger.swift index e60e048d4e..e45a30a8eb 100644 --- a/PacketTunnelProvider/NetworkProtection/VPNLogger.swift +++ b/PacketTunnelProvider/NetworkProtection/VPNLogger.swift @@ -30,6 +30,7 @@ import OSLog public final class VPNLogger { public typealias AttemptStep = PacketTunnelProvider.AttemptStep public typealias ConnectionAttempt = PacketTunnelProvider.ConnectionAttempt + public typealias ConnectionTesterStatus = PacketTunnelProvider.ConnectionTesterStatus public typealias LogCallback = (OSLogType, OSLogMessage) -> Void public init() {} @@ -64,6 +65,21 @@ public final class VPNLogger { } } + public func log(_ status: ConnectionTesterStatus, server: String) { + let log = OSLog.networkProtectionConnectionTesterLog + + switch status { + case .failed(let duration): + os_log("🔴 Connection tester (%{public}@ - %{public}@) failure", log: log, type: .error, duration.rawValue, server) + case .recovered(let duration, let failureCount): + os_log("🟢 Connection tester (%{public}@ - %{public}@) recovery (after %{public}@ failures)", + log: log, + duration.rawValue, + server, + String(failureCount)) + } + } + public func log(_ step: FailureRecoveryStep) { let log = OSLog.networkProtectionTunnelFailureMonitorLog