From 289979fc32ac9d9e8753b47d42b61d7d22994d6d Mon Sep 17 00:00:00 2001 From: John Rosendahl Date: Mon, 9 Nov 2026 18:51:04 -0700 Subject: [PATCH] Drop an app-supplied IDFA that matches the device's in any case enrichIfNeeded compared the app's .appleIDFA values to the system IDFA's uppercase uuidString exactly, so a lowercase (or space-padded) copy of the same IDFA wasn't removed and the request carried two identical a: IDs once encoded. Compare the encoded identifiers instead, as the Android SDK does. Co-Authored-By: Claude Opus 5.5 --- Source/OptableSDK.swift | 5 +++-- ...SDKHelpersIdentifiersEnrichmentTests.swift | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Source/OptableSDK.swift b/Source/OptableSDK.swift index 549d1a9..cb5fb5c 100644 --- a/Source/OptableSDK.swift +++ b/Source/OptableSDK.swift @@ -440,9 +440,10 @@ extension OptableSDK { ATT.advertisingIdentifier != UUID(uuid: uuid_t(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { let systemIDFA = ATT.advertisingIdentifier.uuidString - // Remove all matching systemIDFA (deduplicate) + // Remove all matching systemIDFA (deduplicate), compared as encoded so case and whitespace don't matter + let systemEID = OptableIdentifier.appleIDFA(systemIDFA).extendedIdentifier ids.removeAll { id in - if case let .appleIDFA(value) = id { return value == systemIDFA } + if case .appleIDFA = id { return id.extendedIdentifier == systemEID } return false } diff --git a/Tests/Unit/OptableSDKHelpersIdentifiersEnrichmentTests.swift b/Tests/Unit/OptableSDKHelpersIdentifiersEnrichmentTests.swift index ea53d53..1f35d67 100644 --- a/Tests/Unit/OptableSDKHelpersIdentifiersEnrichmentTests.swift +++ b/Tests/Unit/OptableSDKHelpersIdentifiersEnrichmentTests.swift @@ -93,6 +93,28 @@ class OptableSDKHelpersIdentifiersEnrichmentTests: XCTestCase { } } + func test_idfa_detection_enabled_enrich_system_idfa_same_as_lowercased_user_idfa_prepend() { + ATT.advertisingIdentifierAvailable_DebugOverride = true + ATT.advertisingIdentifier_DebugOverride = UUID(uuidString: systemIDFA) + + var identifiers = buildIdentifiers() + identifiers.append(.appleIDFA(" " + systemIDFA.lowercased())) + + let sdk = buildSDK() + sdk.config.skipAdvertisingIdDetection = false + sdk.enrichIfNeeded(ids: &identifiers) + + if case let .appleIDFA(value) = identifiers[0] { + XCTAssert(value == systemIDFA, "Prepended IDFA is not the same as system provided") + } else { + XCTFail("System IDFA is not prepended") + } + + if identifiers.count(where: { if case .appleIDFA = $0 { return true } else { return false } }) > 1 { + XCTFail("User IDFA persists. (duplicate)") + } + } + func test_idfa_detection_enabled_enrich_system_idfa_user_idfa_persist() { ATT.advertisingIdentifierAvailable_DebugOverride = true ATT.advertisingIdentifier_DebugOverride = UUID(uuidString: systemIDFA)