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: | 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/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" 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 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 4e48109b4f..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 @@ -552,11 +557,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 @@ -608,17 +619,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 @@ -1047,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" @@ -1250,11 +1255,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" @@ -1315,19 +1326,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/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..ec929c8fbd 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,11 +205,15 @@ extension SyncErrorHandler { syncIsPaused(errorType: .badRequestBookmarks) case .credentials: syncIsPaused(errorType: .badRequestCredentials) + 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 } @@ -217,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 @@ -273,14 +281,63 @@ 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 + } + } + + 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 + } + } } } // 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/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 cb571c3ba4..7b6bee382b 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 */; }; @@ -447,7 +448,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 +609,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 */; }; @@ -744,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 */; }; @@ -753,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 */; }; @@ -768,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 */; }; @@ -938,6 +932,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 */; }; @@ -1400,6 +1398,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 = ""; }; @@ -1567,7 +1566,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 +2250,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 = ""; }; @@ -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 = ""; }; @@ -2620,6 +2612,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 +3731,7 @@ 84E341A91E2F7EFB00BDBA6F /* UnitTests */ = { isa = PBXGroup; children = ( - 85C011FA2992850A001E0A99 /* SyncUI */, + F1BDDC012C340DDF00459306 /* SyncUI */, F12D98401F266B30003C2EE3 /* DuckDuckGo */, F1E092B31E92A6B900732CCC /* Core */, F1134ED11F40EDB600B73467 /* TestUtils */, @@ -3961,14 +3957,6 @@ name = Web; sourceTree = ""; }; - 85C011FA2992850A001E0A99 /* SyncUI */ = { - isa = PBXGroup; - children = ( - 85C011FB29928524001E0A99 /* SyncManagementViewModelTests.swift */, - ); - name = SyncUI; - sourceTree = ""; - }; 85C11E4A209084DE00BFFEB4 /* HomeRow */ = { isa = PBXGroup; children = ( @@ -4217,15 +4205,6 @@ name = Themes; sourceTree = ""; }; - 9FA5E44C2BF1B14100BDEF02 /* Subscription */ = { - isa = PBXGroup; - children = ( - 9FA5E44D2BF1B16400BDEF02 /* SubscriptionContainerViewModelTests.swift */, - 9F2510132BF5809E0096DB16 /* SubscriptionFlowViewModelTests.swift */, - ); - name = Subscription; - sourceTree = ""; - }; AA4D6A8023DE4973007E8790 /* AppIcon */ = { isa = PBXGroup; children = ( @@ -4485,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 = ( @@ -4507,7 +4476,6 @@ CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */ = { isa = PBXGroup; children = ( - CB48D3302B90CE9F00631D8B /* UserBehaviorEvent.swift */, CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */, ); name = UserBehaviorMonitor; @@ -4742,6 +4710,7 @@ EE3766DC2AC5940A00AAB575 /* NetworkProtection */ = { isa = PBXGroup; children = ( + 7B0D52342C35FEAD0035A60E /* VPNLogger.swift */, EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */, EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */, ); @@ -4953,9 +4922,7 @@ isa = PBXGroup; children = ( 6F03CAFF2C32ED22004179A8 /* NewTabPage */, - 569437222BDD402600C0881B /* Sync */, 6FF9157F2B88E04F0042AC87 /* AdAttribution */, - CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */, F17669A21E411D63003D3222 /* Application */, 981FED7222045FFA008488D7 /* AutoClear */, 1E1D8B5B2994FF7800C96994 /* Autoconsent */, @@ -4969,10 +4936,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 +5298,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 = ( @@ -5389,7 +5376,6 @@ F1C5ECFA1E37B15B00C599A4 /* Main */ = { isa = PBXGroup; children = ( - CB2283F12BD79D7B0057DD0A /* BrokenSitePrompt */, 310742A52848CD780012660B /* BackForwardMenuHistoryItem.swift */, 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */, 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */, @@ -6462,6 +6448,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 */, @@ -6556,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 */, @@ -6635,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 */, @@ -6808,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 */, @@ -6963,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 */, @@ -7001,13 +6984,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 +7007,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 +7045,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 +7095,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 */, @@ -8185,7 +8169,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; @@ -8222,7 +8206,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; @@ -8312,7 +8296,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; LD_RUNPATH_SEARCH_PATHS = ( @@ -8339,7 +8323,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; @@ -8488,7 +8472,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; @@ -8513,7 +8497,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; LD_RUNPATH_SEARCH_PATHS = ( @@ -8582,7 +8566,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; @@ -8616,7 +8600,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; @@ -8649,7 +8633,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; LD_RUNPATH_SEARCH_PATHS = ( @@ -8679,7 +8663,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; @@ -8989,7 +8973,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; @@ -9020,7 +9004,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; LD_RUNPATH_SEARCH_PATHS = ( @@ -9048,7 +9032,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; LD_RUNPATH_SEARCH_PATHS = ( @@ -9081,7 +9065,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; @@ -9111,7 +9095,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; @@ -9144,11 +9128,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"; @@ -9381,7 +9365,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; @@ -9408,7 +9392,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; @@ -9440,7 +9424,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; @@ -9477,7 +9461,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; @@ -9512,7 +9496,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; @@ -9547,11 +9531,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"; @@ -9724,11 +9708,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"; @@ -9757,10 +9741,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"; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6f60eba534..e009e633d2 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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/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/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+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/MainViewController.swift b/DuckDuckGo/MainViewController.swift index d6d10b8449..422e2eac4c 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() @@ -2445,11 +2362,11 @@ extension MainViewController: AutoClearWorker { @MainActor func autoClearDidFinishClearing(_: AutoClear, isLaunching: Bool) { + autoClearInProgress = false if autoClearShouldRefreshUIAfterClear && isLaunching == false { refreshUIAfterClear() } - autoClearInProgress = false autoClearShouldRefreshUIAfterClear = true } 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/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 diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index d6c9a39cee..74a4202ed2 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,12 +736,24 @@ 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, *) 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/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 c9f1a16264..612f6f2c10 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") @@ -1143,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/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..d9900a1244 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."; @@ -1969,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/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/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/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/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/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 } 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/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/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) 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 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(), diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index e489512f0d..5e068e81bc 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 @@ -47,7 +48,43 @@ 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) + switch attempt { case .connecting: DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptConnecting, @@ -63,6 +100,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 +111,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 +121,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 +132,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 +143,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 +154,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 +165,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 +176,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 +189,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 +200,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { DailyPixel.fireDailyAndCount(pixel: .networkProtectionServerMigrationAttemptSuccess) } case .tunnelStartOnDemandWithoutAccessToken: + vpnLogger.logStartingWithoutAuthToken() DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelStartAttemptOnDemandWithoutAccessToken) } } @@ -392,5 +448,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..e45a30a8eb --- /dev/null +++ b/PacketTunnelProvider/NetworkProtection/VPNLogger.swift @@ -0,0 +1,124 @@ +// +// 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 ConnectionTesterStatus = PacketTunnelProvider.ConnectionTesterStatus + 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(_ 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 + + 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) + } + } +}