diff --git a/.github/workflows/pr-task-url.yml b/.github/workflows/pr-task-url.yml index d77a259008..cf7e3f9bfd 100644 --- a/.github/workflows/pr-task-url.yml +++ b/.github/workflows/pr-task-url.yml @@ -2,7 +2,7 @@ name: Asana PR Task URL on: pull_request: - types: [opened, edited, closed, unlabeled, synchronize] + types: [opened, edited, closed, unlabeled, synchronize, review_requested] jobs: @@ -112,6 +112,24 @@ jobs: if: ${{ needs.assert-project-membership.outputs.task_id }} run: exit ${{ needs.assert-project-membership.outputs.failure }} + # When reviewer is assigned create a subtask in Asana if not existing already + create-asana-pr-subtask-if-needed: + + name: "Create the PR subtask in Asana" + + runs-on: ubuntu-latest + if: github.event.action == 'review_requested' + + needs: [assert-project-membership] + + steps: + - name: Create or Update PR Subtask + uses: duckduckgo/apple-toolbox/actions/asana-create-pr-subtask@main + with: + access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} + asana-task-id: ${{ needs.assert-project-membership.outputs.task_id }} + github-reviewer-user: ${{ github.event.requested_reviewer.login }} + # When a PR is merged, move the task to the Waiting for Release section of the App Board. mark-waiting-for-release: diff --git a/Core/DailyPixel.swift b/Core/DailyPixel.swift index 1d03a8c619..db0b5f0b33 100644 --- a/Core/DailyPixel.swift +++ b/Core/DailyPixel.swift @@ -51,7 +51,7 @@ public final class DailyPixel { public static func fire(pixel: Pixel.Event, error: Swift.Error? = nil, withAdditionalParameters params: [String: String] = [:], - includedParameters: [Pixel.QueryParameters] = [.atb, .appVersion], + includedParameters: [Pixel.QueryParameters] = [.appVersion], onComplete: @escaping (Swift.Error?) -> Void = { _ in }) { var key: String = pixel.name @@ -79,7 +79,7 @@ public final class DailyPixel { public static func fireDailyAndCount(pixel: Pixel.Event, error: Swift.Error? = nil, withAdditionalParameters params: [String: String] = [:], - includedParameters: [Pixel.QueryParameters] = [.atb, .appVersion], + includedParameters: [Pixel.QueryParameters] = [.appVersion], onDailyComplete: @escaping (Swift.Error?) -> Void = { _ in }, onCountComplete: @escaping (Swift.Error?) -> Void = { _ in }) { let key: String = pixel.name diff --git a/Core/DefaultVariantManager.swift b/Core/DefaultVariantManager.swift index 1b20f2829a..f4fc7ccac0 100644 --- a/Core/DefaultVariantManager.swift +++ b/Core/DefaultVariantManager.swift @@ -62,9 +62,8 @@ public struct VariantIOS: Variant { VariantIOS(name: "sc", weight: doNotAllocate, isIncluded: When.always, features: []), VariantIOS(name: "sd", weight: doNotAllocate, isIncluded: When.always, features: []), VariantIOS(name: "se", weight: doNotAllocate, isIncluded: When.always, features: []), - - VariantIOS(name: "mc", weight: 1, isIncluded: When.inEnglish, features: [.newSuggestionLogic]), - VariantIOS(name: "md", weight: 1, isIncluded: When.inEnglish, features: [.history]), + VariantIOS(name: "mc", weight: doNotAllocate, isIncluded: When.inEnglish, features: [.newSuggestionLogic]), + VariantIOS(name: "md", weight: doNotAllocate, isIncluded: When.inEnglish, features: [.history]), returningUser ] diff --git a/Core/FaviconsHelper.swift b/Core/FaviconsHelper.swift index b566eb8c01..478da57e54 100644 --- a/Core/FaviconsHelper.swift +++ b/Core/FaviconsHelper.swift @@ -24,11 +24,11 @@ struct FaviconsHelper { // this function is now static and outside of Favicons, otherwise there is a circular dependency between // Favicons and NotFoundCachingDownloader - public static func defaultResource(forDomain domain: String?, sourcesProvider: FaviconSourcesProvider) -> Kingfisher.ImageResource? { + public static func defaultResource(forDomain domain: String?, sourcesProvider: FaviconSourcesProvider) -> KF.ImageResource? { guard let domain = domain, let source = sourcesProvider.mainSource(forDomain: domain) else { return nil } let key = Favicons.createHash(ofDomain: domain) - return ImageResource(downloadURL: source, cacheKey: key) + return KF.ImageResource(downloadURL: source, cacheKey: key) } } diff --git a/Core/Pixel.swift b/Core/Pixel.swift index 34539ccd98..48d6d22a32 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -179,7 +179,7 @@ public class Pixel { withAdditionalParameters params: [String: String] = [:], allowedQueryReservedCharacters: CharacterSet? = nil, withHeaders headers: APIRequest.Headers = APIRequest.Headers(), - includedParameters: [QueryParameters] = [.atb, .appVersion], + includedParameters: [QueryParameters] = [.appVersion], onComplete: @escaping (Error?) -> Void = { _ in }, debounce: Int = 0) { @@ -209,7 +209,7 @@ public class Pixel { withAdditionalParameters params: [String: String] = [:], allowedQueryReservedCharacters: CharacterSet? = nil, withHeaders headers: APIRequest.Headers = APIRequest.Headers(), - includedParameters: [QueryParameters] = [.atb, .appVersion], + includedParameters: [QueryParameters] = [.appVersion], onComplete: @escaping (Error?) -> Void = { _ in }) { var newParams = params if includedParameters.contains(.appVersion) { diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 3b79503529..b62f4a22ca 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -372,7 +372,12 @@ extension Pixel { case networkProtectionGeoswitchingSetNearest case networkProtectionGeoswitchingSetCustom case networkProtectionGeoswitchingNoLocations - + + case networkProtectionFailureRecoveryStarted + case networkProtectionFailureRecoveryFailed + case networkProtectionFailureRecoveryCompletedHealthy + case networkProtectionFailureRecoveryCompletedUnhealthy + // MARK: remote messaging pixels case remoteMessageShown @@ -1328,6 +1333,10 @@ extension Pixel.Event { case .privacyProOfferYearlyPriceClick: return "m_privacy-pro_offer_yearly-price_click" case .privacyProAddEmailSuccess: return "m_privacy-pro_app_add-email_success_u" case .privacyProWelcomeFAQClick: return "m_privacy-pro_welcome_faq_click_u" + case .networkProtectionFailureRecoveryStarted: return "m_netp_ev_failure_recovery_started" + case .networkProtectionFailureRecoveryFailed: return "m_netp_ev_failure_recovery_failed" + case .networkProtectionFailureRecoveryCompletedHealthy: return "m_netp_ev_failure_recovery_completed_server_healthy" + case .networkProtectionFailureRecoveryCompletedUnhealthy: return "m_netp_ev_failure_recovery_completed_server_unhealthy" // MARK: Secure Vault case .secureVaultL1KeyMigration: return "m_secure-vault_keystore_event_l1-key-migration" diff --git a/Core/PixelExperiment.swift b/Core/PixelExperiment.swift index f2701e06ec..3e741329ff 100644 --- a/Core/PixelExperiment.swift +++ b/Core/PixelExperiment.swift @@ -51,7 +51,6 @@ public enum PixelExperiment: String, CaseIterable { /// 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() } @@ -101,12 +100,10 @@ final internal class PixelExperimentLogic { // Allocate user to a cohort based on the random number let cohort: PixelExperiment - if randomNumber < 5 { + if randomNumber < 50 { cohort = .control - } else if randomNumber < 10 { - cohort = .newSettings } else { - cohort = .noVariant + cohort = .newSettings } // Store and use the selected cohort diff --git a/Core/UniquePixel.swift b/Core/UniquePixel.swift index 1032b6e6d2..5767d91a83 100644 --- a/Core/UniquePixel.swift +++ b/Core/UniquePixel.swift @@ -52,6 +52,7 @@ public final class UniquePixel { /// This requires the pixel name to end with `_u` public static func fire(pixel: Pixel.Event, withAdditionalParameters params: [String: String] = [:], + includedParameters: [Pixel.QueryParameters] = [.appVersion], onComplete: @escaping (Swift.Error?) -> Void = { _ in }) { guard pixel.name.hasSuffix("_u") else { assertionFailure("Unique pixel: must end with _u") @@ -59,7 +60,7 @@ public final class UniquePixel { } if !pixel.hasBeenFiredEver(uniquePixelStorage: storage) { - Pixel.fire(pixel: pixel, withAdditionalParameters: params, onComplete: onComplete) + Pixel.fire(pixel: pixel, withAdditionalParameters: params, includedParameters: includedParameters, onComplete: onComplete) storage.set(Date(), forKey: pixel.name) } else { onComplete(Error.alreadyFired) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d55e8e7d77..742583df0f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -7917,7 +7917,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; @@ -7954,7 +7954,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; @@ -8046,7 +8046,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8074,7 +8074,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; @@ -8224,7 +8224,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; @@ -8250,7 +8250,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8316,7 +8316,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; @@ -8351,7 +8351,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; @@ -8385,7 +8385,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8416,7 +8416,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; @@ -8731,7 +8731,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; @@ -8763,7 +8763,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8792,7 +8792,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8826,7 +8826,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; @@ -8857,7 +8857,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; @@ -8890,11 +8890,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"; @@ -9128,7 +9128,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; @@ -9156,7 +9156,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; @@ -9189,7 +9189,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; @@ -9227,7 +9227,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; @@ -9263,7 +9263,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; @@ -9298,11 +9298,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"; @@ -9476,11 +9476,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"; @@ -9509,10 +9509,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 bf572ecbfa..27fa778f27 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -174,7 +174,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "6c84fd19139414fc0edbf9673ade06e532a564f0", "version" : "2.0.0" diff --git a/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift b/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift index 1cd3248691..a6330819b0 100644 --- a/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift +++ b/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift @@ -21,7 +21,7 @@ import Foundation import Core protocol PixelFiring { - static func fire(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) async throws + static func fire(pixel: Pixel.Event, withAdditionalParameters params: [String: String], includedParameters: [Pixel.QueryParameters]) async throws } final class AdAttributionPixelReporter { @@ -52,7 +52,11 @@ final class AdAttributionPixelReporter { if attributionData.attribution { let parameters = self.pixelParametersForAttribution(attributionData) do { - try await pixelFiring.fire(pixel: .appleAdAttribution, withAdditionalParameters: parameters) + try await pixelFiring.fire( + pixel: .appleAdAttribution, + withAdditionalParameters: parameters, + includedParameters: [.appVersion, .atb] + ) } catch { return false } @@ -83,10 +87,10 @@ final class AdAttributionPixelReporter { } extension Pixel: PixelFiring { - static func fire(pixel: Event, withAdditionalParameters params: [String: String]) async throws { + static func fire(pixel: Event, withAdditionalParameters params: [String: String], includedParameters: [QueryParameters]) async throws { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - Pixel.fire(pixel: pixel, withAdditionalParameters: params) { error in + Pixel.fire(pixel: pixel, withAdditionalParameters: params, includedParameters: includedParameters) { error in if let error { continuation.resume(throwing: error) } else { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 05ee8b1793..cb3ca23c43 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -217,8 +217,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { DaxDialogs.shared.primeForUse() } - // Experiment installation will be uncommented once we decide to run the experiment -// PixelExperiment.install() + PixelExperiment.install() // MARK: Sync initialisation @@ -288,7 +287,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { autoClear = AutoClear(worker: main) Task { - await autoClear?.clearDataIfEnabled() + await autoClear?.clearDataIfEnabled(launching: true) } AppDependencyProvider.shared.voiceSearchHelper.migrateSettingsFlagIfNecessary() @@ -584,7 +583,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { PixelParameters.widgetError: "1", PixelParameters.widgetErrorCode: "\((error as NSError).code)", PixelParameters.widgetErrorDomain: (error as NSError).domain - ]) + ], includedParameters: [.appVersion, .atb]) case .success(let widgetInfo): let params = widgetInfo.reduce([String: String]()) { @@ -594,7 +593,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } return result } - Pixel.fire(pixel: .appLaunch, withAdditionalParameters: params) + Pixel.fire(pixel: .appLaunch, withAdditionalParameters: params, includedParameters: [.appVersion, .atb]) } } diff --git a/DuckDuckGo/AutoClear.swift b/DuckDuckGo/AutoClear.swift index 7abb856b6a..d1bcb84e68 100644 --- a/DuckDuckGo/AutoClear.swift +++ b/DuckDuckGo/AutoClear.swift @@ -45,7 +45,7 @@ class AutoClear { } @MainActor - func clearDataIfEnabled() async { + func clearDataIfEnabled(launching: Bool = false) async { guard let settings = AutoClearSettingsModel(settings: appSettings) else { return } if settings.action.contains(.clearTabs) { @@ -56,7 +56,9 @@ class AutoClear { await worker.forgetData() } - worker.clearDataFinished(self) + if !launching { + worker.clearDataFinished(self) + } } /// Note: function is parametrised because of tests. diff --git a/DuckDuckGo/Favicons.swift b/DuckDuckGo/Favicons.swift index bf8baa03e1..8ecdc75bb0 100644 --- a/DuckDuckGo/Favicons.swift +++ b/DuckDuckGo/Favicons.swift @@ -441,7 +441,7 @@ public class Favicons { return image } - public func defaultResource(forDomain domain: String?) -> Kingfisher.ImageResource? { + public func defaultResource(forDomain domain: String?) -> KF.ImageResource? { return FaviconsHelper.defaultResource(forDomain: domain, sourcesProvider: sourcesProvider) } diff --git a/DuckDuckGo/Feedback/VPNFeedbackSender.swift b/DuckDuckGo/Feedback/VPNFeedbackSender.swift index 4b80c1b961..869c99791f 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackSender.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackSender.swift @@ -34,7 +34,7 @@ struct DefaultVPNFeedbackSender: VPNFeedbackSender { "breakageCategory": category.rawValue, "breakageDescription": encodedUserText, "breakageMetadata": metadata.toBase64(), - ]) { error in + ], includedParameters: [.appVersion, .atb]) { error in if let error { continuation.resume(throwing: error) } else { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index 963cae47da..755b8ba95a 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -48,7 +48,8 @@ struct VPNMetadata: Encodable { struct VPNState: Encodable { let connectionState: String - let lastDisconnectError: String + let lastDisconnectError: LastDisconnectError? + let underlyingErrors: [LastDisconnectError]? let connectedServer: String let connectedServerIP: String } @@ -77,6 +78,12 @@ struct VPNMetadata: Encodable { let subscriptionActive: Bool } + struct LastDisconnectError: Encodable { + let domain: String + let code: Int + let description: String + } + let appInfo: AppInfo let deviceInfo: DeviceInfo let networkInfo: NetworkInfo @@ -220,61 +227,30 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { let connectionState = String(describing: statusObserver.recentValue) let connectedServer = serverInfoObserver.recentValue.serverLocation?.serverLocation ?? "none" let connectedServerIP = serverInfoObserver.recentValue.serverAddress ?? "none" + let lastDisconnectError = await lastDisconnectError() return .init(connectionState: connectionState, - lastDisconnectError: await lastDisconnectError(), + lastDisconnectError: lastDisconnectError?.error, + underlyingErrors: lastDisconnectError?.underlyingErrors, connectedServer: connectedServer, connectedServerIP: connectedServerIP) } - public func lastDisconnectError() async -> String { + public func lastDisconnectError() async -> (error: VPNMetadata.LastDisconnectError, underlyingErrors: [VPNMetadata.LastDisconnectError])? { if #available(iOS 16, *) { guard let tunnelManager = try? await NETunnelProviderManager.loadAllFromPreferences().first else { - return "none" + return nil } - return await withCheckedContinuation { continuation in - tunnelManager.connection.fetchLastDisconnectError { error in - let message = { - if let error = error as? NSError { - if error.domain == NEVPNConnectionErrorDomain, - let code = NEVPNConnectionError(rawValue: error.code) { - switch code { - case .overslept: return "overslept" - case .noNetworkAvailable: return "noNetworkAvailable" - case .unrecoverableNetworkChange: return "unrecoverableNetworkChange" - case .configurationFailed: return "configurationFailed" - case .serverAddressResolutionFailed: return "serverAddressResolutionFailed" - case .serverNotResponding: return "serverNotResponding" - case .serverDead: return "serverDead" - case .authenticationFailed: return "authenticationFailed" - case .clientCertificateInvalid: return "clientCertificateInvalid" - case .clientCertificateNotYetValid: return "clientCertificateNotYetValid" - case .clientCertificateExpired: return "clientCertificateExpired" - case .pluginFailed: return "pluginFailed" - case .configurationNotFound: return "configurationNotFound" - case .pluginDisabled: return "pluginDisabled" - case .negotiationFailed: return "negotiationFailed" - case .serverDisconnected: return "serverDisconnected" - case .serverCertificateInvalid: return "serverCertificateInvalid" - case .serverCertificateNotYetValid: return "serverCertificateNotYetValid" - case .serverCertificateExpired: return "serverCertificateExpired" - default: return error.localizedDescription - } - } else { - return error.localizedDescription - } - } - - return "none" - }() - - continuation.resume(returning: message) - } + do { + try await tunnelManager.connection.fetchLastDisconnectError() + return nil + } catch { + return (error as NSError).toMetadataError() } } - return "none" + return nil } func collectVPNSettingsState() -> VPNMetadata.VPNSettingsState { @@ -319,3 +295,23 @@ extension VPNMetadata.PrivacyProInfo.Source { } } } + +private extension NSError { + + @available(iOS 16.0, *) + func toMetadataError() -> (error: VPNMetadata.LastDisconnectError, underlyingErrors: [VPNMetadata.LastDisconnectError]) { + let metadataError = VPNMetadata.LastDisconnectError(domain: self.domain, code: self.code, description: self.localizedDescription) + + let underlyingErrors = self.underlyingErrors.compactMap { underlyingError in + let underlyingNSError = underlyingError as NSError + return VPNMetadata.LastDisconnectError( + domain: underlyingNSError.domain, + code: underlyingNSError.code, + description: underlyingNSError.localizedDescription + ) + } + + return (metadataError, underlyingErrors) + } + +} diff --git a/DuckDuckGo/HomeMessageViewModel.swift b/DuckDuckGo/HomeMessageViewModel.swift index 1f5a81b56a..e108700468 100644 --- a/DuckDuckGo/HomeMessageViewModel.swift +++ b/DuckDuckGo/HomeMessageViewModel.swift @@ -97,26 +97,26 @@ struct HomeMessageViewModel { case .bigSingleAction(_, _, _, let primaryActionText, let primaryAction): return [ HomeMessageButtonViewModel(title: primaryActionText, - actionStyle: primaryAction.actionStyle, + actionStyle: primaryAction.actionStyle(), action: mapActionToViewModel(remoteAction: primaryAction, buttonAction: .primaryAction(isShare: primaryAction.isShare), onDidClose: onDidClose)) ] case .bigTwoAction(_, _, _, let primaryActionText, let primaryAction, let secondaryActionText, let secondaryAction): return [ - HomeMessageButtonViewModel(title: primaryActionText, - actionStyle: primaryAction.actionStyle, - action: mapActionToViewModel(remoteAction: primaryAction, buttonAction: - .primaryAction(isShare: primaryAction.isShare), onDidClose: onDidClose)), - HomeMessageButtonViewModel(title: secondaryActionText, - actionStyle: secondaryAction.actionStyle, + actionStyle: secondaryAction.actionStyle(isSecondaryAction: true), action: mapActionToViewModel(remoteAction: secondaryAction, buttonAction: - .secondaryAction(isShare: primaryAction.isShare), onDidClose: onDidClose)) + .secondaryAction(isShare: secondaryAction.isShare), onDidClose: onDidClose)), + + HomeMessageButtonViewModel(title: primaryActionText, + actionStyle: primaryAction.actionStyle(), + action: mapActionToViewModel(remoteAction: primaryAction, buttonAction: + .primaryAction(isShare: primaryAction.isShare), onDidClose: onDidClose)) ] case .promoSingleAction(_, _, _, let actionText, let action): return [ HomeMessageButtonViewModel(title: actionText, - actionStyle: action.actionStyle, + actionStyle: action.actionStyle(), action: mapActionToViewModel(remoteAction: action, buttonAction: .action(isShare: action.isShare), onDidClose: onDidClose))] } diff --git a/DuckDuckGo/HomeMessageViewModelBuilder.swift b/DuckDuckGo/HomeMessageViewModelBuilder.swift index 91e1b425d0..4f54c60548 100644 --- a/DuckDuckGo/HomeMessageViewModelBuilder.swift +++ b/DuckDuckGo/HomeMessageViewModelBuilder.swift @@ -44,12 +44,15 @@ struct HomeMessageViewModelBuilder { extension RemoteAction { - var actionStyle: HomeMessageButtonViewModel.ActionStyle { + func actionStyle(isSecondaryAction: Bool = false) -> HomeMessageButtonViewModel.ActionStyle { switch self { case .share(let value, let title): return .share(value: value, title: title) case .appStore, .url, .surveyURL: + if isSecondaryAction { + return .cancel + } return .default case .dismiss: diff --git a/DuckDuckGo/HomeMessageViewSectionRenderer.swift b/DuckDuckGo/HomeMessageViewSectionRenderer.swift index b272151dca..8355f2466a 100644 --- a/DuckDuckGo/HomeMessageViewSectionRenderer.swift +++ b/DuckDuckGo/HomeMessageViewSectionRenderer.swift @@ -204,7 +204,7 @@ class HomeMessageViewSectionRenderer: NSObject, HomeViewSectionRenderer { extension RemoteAction { var isShare: Bool { - if case .share = self.actionStyle { + if case .share = self.actionStyle() { return true } return false diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index 5e0d632537..3aaedb8115 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -79,16 +79,16 @@ final class NetworkProtectionTunnelController: TunnelController { /// Starts the VPN connection used for Network Protection /// func start() async { - Pixel.fire(pixel: .networkProtectionControllerStartAttempt) + Pixel.fire(pixel: .networkProtectionControllerStartAttempt, includedParameters: [.appVersion, .atb]) do { try await startWithError() - Pixel.fire(pixel: .networkProtectionControllerStartSuccess) + Pixel.fire(pixel: .networkProtectionControllerStartSuccess, includedParameters: [.appVersion, .atb]) } catch { if case StartError.configSystemPermissionsDenied = error { return } - Pixel.fire(pixel: .networkProtectionControllerStartFailure, error: error) + Pixel.fire(pixel: .networkProtectionControllerStartFailure, error: error, includedParameters: [.appVersion, .atb]) #if DEBUG errorStore.lastErrorMessage = error.localizedDescription @@ -190,7 +190,7 @@ final class NetworkProtectionTunnelController: TunnelController { do { try tunnelManager.connection.startVPNTunnel(options: options) - UniquePixel.fire(pixel: .networkProtectionNewUser) { error in + UniquePixel.fire(pixel: .networkProtectionNewUser, includedParameters: [.appVersion, .atb]) { error in guard error != nil else { return } UserDefaults.networkProtectionGroupDefaults.vpnFirstEnabled = Pixel.Event.networkProtectionNewUser.lastFireDate( uniquePixelStorage: UniquePixel.storage diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index 751a15283d..1991df9c4f 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -174,17 +174,9 @@ class OmniBar: UIView { guard let range = field.selectedTextRange else { return } UIPasteboard.general.string = field.text(in: range) } - - textField.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector( onTextFieldTapped ))) } - var textFieldTapped = false - - @objc - private func onTextFieldTapped() { - textFieldTapped = true - textField.becomeFirstResponder() - } + var textFieldTapped = true private func configureSeparator() { separatorHeightConstraint.constant = 1.0 / UIScreen.main.scale @@ -374,6 +366,10 @@ class OmniBar: UIView { } @discardableResult override func becomeFirstResponder() -> Bool { + textFieldTapped = false + defer { + textFieldTapped = true + } return textField.becomeFirstResponder() } @@ -501,7 +497,6 @@ extension OmniBar: UITextFieldDelegate { func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { omniDelegate?.onTextFieldWillBeginEditing(self, tapped: textFieldTapped) - textFieldTapped = false return true } diff --git a/DuckDuckGo/SettingsRootView.swift b/DuckDuckGo/SettingsRootView.swift index 2c4cd10de4..daa5a1ae26 100644 --- a/DuckDuckGo/SettingsRootView.swift +++ b/DuckDuckGo/SettingsRootView.swift @@ -85,8 +85,11 @@ struct SettingsRootView: View { } }) - .onReceive(viewModel.$deepLinkTarget, perform: { link in - guard let link else { return } + .onReceive(viewModel.$deepLinkTarget.removeDuplicates(), perform: { link in + guard let link, link != self.deepLinkTarget else { + return + } + self.deepLinkTarget = link switch link.type { diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 2c34f7ee25..f75782be74 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1186,7 +1186,7 @@ extension TabViewController: WKNavigationDelegate { #if NETWORK_PROTECTION if webView.url?.isDuckDuckGoSearch == true, case .connected = netPConnectionStatus { - DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnabledOnSearch) + DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnabledOnSearch, includedParameters: [.appVersion, .atb]) } #endif } diff --git a/DuckDuckGoTests/AdAttributionPixelReporterTests.swift b/DuckDuckGoTests/AdAttributionPixelReporterTests.swift index 97eef8546e..bc36e3a538 100644 --- a/DuckDuckGoTests/AdAttributionPixelReporterTests.swift +++ b/DuckDuckGoTests/AdAttributionPixelReporterTests.swift @@ -86,6 +86,17 @@ final class AdAttributionPixelReporterTests: XCTestCase { XCTAssertEqual(pixelAttributes["ad_id"], "5") } + func testPixelAdditionalParameters() async throws { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse(attribution: true) + + await sut.reportAttributionIfNeeded() + + let pixelAttributes = try XCTUnwrap(PixelFiringMock.lastIncludedParams) + + XCTAssertEqual(pixelAttributes, [.appVersion, .atb]) + } + func testPixelAttributes_WhenPartialAttributionData() async throws { let sut = createSUT() attributionFetcher.fetchResponse = AdServicesAttributionResponse( @@ -172,9 +183,13 @@ final actor PixelFiringMock: PixelFiring { static var lastParams: [String: String]? static var lastPixel: Pixel.Event? - static func fire(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) async throws { + static var lastIncludedParams: [Pixel.QueryParameters]? + static func fire(pixel: Pixel.Event, + withAdditionalParameters params: [String: String], + includedParameters: [Pixel.QueryParameters]) async throws { lastParams = params lastPixel = pixel + lastIncludedParams = includedParameters if let expectedFireError { throw expectedFireError @@ -184,6 +199,7 @@ final actor PixelFiringMock: PixelFiring { static func tearDown() { lastParams = nil lastPixel = nil + lastIncludedParams = nil expectedFireError = nil } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index fa9c6fc454..7eff1b1b18 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -43,35 +43,39 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { switch event { case .userBecameActive: DailyPixel.fire(pixel: .networkProtectionActiveUser, - withAdditionalParameters: [PixelParameters.vpnCohort: UniquePixel.cohort(from: defaults.vpnFirstEnabled)]) + withAdditionalParameters: [PixelParameters.vpnCohort: UniquePixel.cohort(from: defaults.vpnFirstEnabled)], + includedParameters: [.appVersion, .atb]) case .reportConnectionAttempt(attempt: let attempt): switch attempt { case .connecting: - DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptConnecting) + DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptConnecting, + includedParameters: [.appVersion, .atb]) case .success: let versionStore = NetworkProtectionLastVersionRunStore(userDefaults: .networkProtectionGroupDefaults) versionStore.lastExtensionVersionRun = AppVersion.shared.versionAndBuildNumber - DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptSuccess) + DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptSuccess, + includedParameters: [.appVersion, .atb]) case .failure: - DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptFailure) + DailyPixel.fireDailyAndCount(pixel: .networkProtectionEnableAttemptFailure, + includedParameters: [.appVersion, .atb]) } case .reportTunnelFailure(result: let result): switch result { case .failureDetected: - DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelFailureDetected) + DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelFailureDetected, includedParameters: [.appVersion, .atb]) case .failureRecovered: - DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelFailureRecovered) + DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelFailureRecovered, includedParameters: [.appVersion, .atb]) case .networkPathChanged(let newPath): defaults.updateNetworkPath(with: newPath) } case .reportLatency(result: let result): switch result { case .error: - DailyPixel.fire(pixel: .networkProtectionLatencyError) + DailyPixel.fire(pixel: .networkProtectionLatencyError, includedParameters: [.appVersion, .atb]) case .quality(let quality): guard quality != .unknown else { return } - DailyPixel.fireDailyAndCount(pixel: .networkProtectionLatency(quality: quality)) + DailyPixel.fireDailyAndCount(pixel: .networkProtectionLatency(quality: quality), includedParameters: [.appVersion, .atb]) } case .rekeyAttempt(let step): switch step { @@ -118,6 +122,17 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { case .success: DailyPixel.fireDailyAndCount(pixel: .networkProtectionTunnelWakeSuccess) } + case .failureRecoveryAttempt(let step): + switch step { + case .started: + DailyPixel.fireDailyAndCount(pixel: .networkProtectionFailureRecoveryStarted) + case .completed(.healthy): + DailyPixel.fireDailyAndCount(pixel: .networkProtectionFailureRecoveryCompletedHealthy) + case .completed(.unhealthy): + DailyPixel.fireDailyAndCount(pixel: .networkProtectionFailureRecoveryCompletedUnhealthy) + case .failed(let error): + DailyPixel.fireDailyAndCount(pixel: .networkProtectionFailureRecoveryFailed, error: error) + } } } diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index 6cd7a9e939..6fd26d5972 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,3 +1,5 @@ -- Bug fixes and other improvements. +- Keep your passwords in easy reach with new widgets for your Home Screen and Lock Screen. +- Wondering how to import passwords from our desktop browser and other apps? We added instructions in Settings > Passwords > Import Passwords. +- Bug fixes and improvements. Join our fully distributed team and help raise the standard of trust online! https://duckduckgo.com/hiring