diff --git a/Package.swift b/Package.swift index 144da9b..0e32e24 100644 --- a/Package.swift +++ b/Package.swift @@ -54,7 +54,7 @@ let package = Package( ), .testTarget( name: "UID2PrebidTests", - dependencies: ["UID2Prebid"] + dependencies: ["UID2Prebid", "TestHelpers"] ), .target( name: "TestHelpers", diff --git a/Sources/UID2/Environment.swift b/Sources/UID2/Environment.swift index 533c7f4..e968521 100644 --- a/Sources/UID2/Environment.swift +++ b/Sources/UID2/Environment.swift @@ -13,17 +13,20 @@ struct Environment: Hashable, Sendable { /// API base URL let endpoint: URL let isProduction: Bool + let isEuid: Bool } extension Environment { init(_ environment: UID2.Environment) { endpoint = environment.endpoint isProduction = (environment == .production) + isEuid = false } init(_ environment: EUID.Environment) { endpoint = environment.endpoint isProduction = (environment == .production) + isEuid = true } } diff --git a/Sources/UID2/UID2Client.swift b/Sources/UID2/UID2Client.swift index 520173a..3096a79 100644 --- a/Sources/UID2/UID2Client.swift +++ b/Sources/UID2/UID2Client.swift @@ -17,7 +17,7 @@ import Foundation @available(iOS 13, tvOS 13, *) internal final class UID2Client: Sendable { private let clientVersion: String - private let environment: Environment + internal let environment: Environment private let session: NetworkSession private let log: OSLog private var baseURL: URL { environment.endpoint } diff --git a/Sources/UID2/UID2Manager.swift b/Sources/UID2/UID2Manager.swift index 7dc1c4d..1eaebac 100644 --- a/Sources/UID2/UID2Manager.swift +++ b/Sources/UID2/UID2Manager.swift @@ -107,6 +107,9 @@ public final actor UID2Manager { private let dateGenerator: DateGenerator + /// Returns `true` if this Manager is configured for the EUID environment. See also ``EUIDManager``. + public let isEuidEnvironment: Bool + // MARK: - Defaults init( @@ -118,7 +121,7 @@ public final actor UID2Manager { if let apiUrlOverride = Bundle.main.object(forInfoDictionaryKey: "UID2ApiUrl") as? String, !apiUrlOverride.isEmpty, let apiUrl = URL(string: apiUrlOverride) { - clientEnvironment = Environment(endpoint: apiUrl, isProduction: false) + clientEnvironment = Environment(endpoint: apiUrl, isProduction: false, isEuid: false) } else { clientEnvironment = environment } @@ -155,7 +158,8 @@ public final actor UID2Manager { self.sdkVersion = sdkVersion self.log = log self.dateGenerator = dateGenerator - + self.isEuidEnvironment = uid2Client.environment.isEuid + // Try to load from Keychain if available // Use case for app manually stopped and re-opened Task { diff --git a/Sources/UID2Prebid/UID2Prebid.swift b/Sources/UID2Prebid/UID2Prebid.swift index 1118f4c..773653b 100644 --- a/Sources/UID2Prebid/UID2Prebid.swift +++ b/Sources/UID2Prebid/UID2Prebid.swift @@ -23,7 +23,6 @@ public actor UID2Prebid { // https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/main/AdCOM%20v1.0%20FINAL.md#list_agenttypes // "A person-based ID, i.e., that is the same across devices." private let agentType: NSNumber = 3 - private let source = "uidapi.com" private var task: Task? let stateStream: () async -> AsyncStream @@ -60,22 +59,23 @@ public actor UID2Prebid { Task { await manager.addInitializationListener { [weak self] in guard let self else { return } - await self.updateExternalUserID(initialToken()) - await self.observeIdentityChanges() + let source = await manager.isEuidEnvironment ? "euid.eu" : "uidapi.com" + await self.updateExternalUserID(initialToken(), source: source) + await self.observeIdentityChanges(source: source) } } } - func observeIdentityChanges() { + func observeIdentityChanges(source: String) { self.task = Task { let identities = await stateStream() for await advertisingToken in identities.map({ $0?.identity?.advertisingToken }) { - await updateExternalUserID(advertisingToken) + await updateExternalUserID(advertisingToken, source: source) } } } - func updateExternalUserID(_ advertisingToken: String?) async { + func updateExternalUserID(_ advertisingToken: String?, source: String) async { var userIDs = await self.thirdPartyUserIDs() if let advertisingToken { userIDs.append(ExternalUserId( diff --git a/Tests/UID2PrebidTests/UID2PrebidTests.swift b/Tests/UID2PrebidTests/UID2PrebidTests.swift index 7c040c9..8985a92 100644 --- a/Tests/UID2PrebidTests/UID2PrebidTests.swift +++ b/Tests/UID2PrebidTests/UID2PrebidTests.swift @@ -121,6 +121,55 @@ final class UID2PrebidTests: XCTestCase { ) } + @MainActor + func testObservationOfEUIDSource() async throws { + let manager = UID2Manager( + uid2Client: UID2Client( + sdkVersion: "1.0", + environment: Environment(EUID.Environment.production) + ), + storage: .null, + sdkVersion: (1, 0, 0), + log: .disabled + ) + let updater = TestUserIDUpdater() + + let (stream, continuation) = AsyncStream.makeStream() + + prebid = UID2Prebid( + manager: manager, + userIDUpdater: updater, + initialToken: { + "cat" + }, + stateStream: { + stream + } + ) + await observation( + of: [ + ExternalUserId(source: "euid.eu", uids: [.init(id: "cat", aType: 3)]) + ], + by: updater + ) + + continuation.yield(.optout) + await observation( + of: [], + by: updater + ) + + continuation.yield( + .established(.established(advertisingToken: "turtle")) + ) + await observation( + of: [ + ExternalUserId(source: "euid.eu", uids: [.init(id: "turtle", aType: 3)]) + ], + by: updater + ) + } + @MainActor func observation(of expectedUserIds: [ExternalUserId], by updater: TestUserIDUpdater) async { let expectation = XCTestExpectation(description: "Expected Test Updater to observe specific value") diff --git a/Tests/UID2Tests/Helpers/Client.swift b/Tests/UID2Tests/Helpers/Client.swift index 9f00695..442fb15 100644 --- a/Tests/UID2Tests/Helpers/Client.swift +++ b/Tests/UID2Tests/Helpers/Client.swift @@ -5,18 +5,26 @@ import Foundation @testable import UID2 +extension Environment { + static func test() -> Environment { + .init( + endpoint: URL(string: "https://prod.uidapi.com")!, + isProduction: true, + isEuid: false, + ) + } +} + extension UID2Client { static func test( sdkVersion: String = "TEST", + environment: Environment = .test(), session: any NetworkSession = URLSession.shared, cryptoUtil: CryptoUtil = .liveValue ) -> UID2Client { .init( sdkVersion: sdkVersion, - environment: .init( - endpoint: URL(string: "https://prod.uidapi.com")!, - isProduction: true - ), + environment: environment, session: session, cryptoUtil: cryptoUtil ) diff --git a/Tests/UID2Tests/UID2ManagerTests.swift b/Tests/UID2Tests/UID2ManagerTests.swift index 8a3bc27..68c9838 100644 --- a/Tests/UID2Tests/UID2ManagerTests.swift +++ b/Tests/UID2Tests/UID2ManagerTests.swift @@ -26,6 +26,36 @@ final class UID2ManagerTests: XCTestCase { .failure(UnexpectedRequest(request: request)) } } + + func testNonEUIDEnvironment() async { + let manager = UID2Manager( + uid2Client: UID2Client.test(), + storage: .null, + sdkVersion: (1, 0, 0), + log: .disabled + ) + + let isEuidEnvironment = await manager.isEuidEnvironment + XCTAssertFalse(isEuidEnvironment) + } + + func testEUIDEnvironment() async { + let manager = UID2Manager( + uid2Client: UID2Client.test( + environment: .init( + endpoint: URL(string: "https://prod.euid.eu/v2")!, + isProduction: true, + isEuid: true + ) + ), + storage: .null, + sdkVersion: (1, 0, 0), + log: .disabled + ) + + let isEuidEnvironment = await manager.isEuidEnvironment + XCTAssertTrue(isEuidEnvironment) + } func testInitialState() async throws { let manager = UID2Manager(