From b72ebc8b809412312fa9b22a61570f0c4752c8ea Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Fri, 26 Jun 2026 01:03:18 +0300 Subject: [PATCH 01/14] feat: enrich targeting api call --- Source/Core/EdgeAPI.swift | 27 +++++++++- Source/Misc/Bundle++.swift | 15 ++++++ Source/OptableSDK.swift | 16 +++--- .../Public/ObjCSupport/OptableSDK+ObjC.swift | 8 +-- Source/Public/OptableIdentifier.swift | 23 ++++++++ Tests/Integration/OptableSDKTests.swift | 2 +- Tests/Unit/EdgeAPITests.swift | 33 +++++++++--- Tests/Unit/OptableIdentifiersTests.swift | 53 +++++++++++++++++++ 8 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 Source/Misc/Bundle++.swift diff --git a/Source/Core/EdgeAPI.swift b/Source/Core/EdgeAPI.swift index 893c4ba..ced3966 100644 --- a/Source/Core/EdgeAPI.swift +++ b/Source/Core/EdgeAPI.swift @@ -63,12 +63,35 @@ final class EdgeAPI { return request } - func targeting(ids: [OptableIdentifier]) throws -> URLRequest? { + func targeting(ids: [OptableIdentifier], hids: [OptableIdentifier]) throws -> URLRequest? { guard var url = buildEdgeAPIURL(endpoint: "targeting") else { return nil } - let queryItems = ids + var queryItems = ids .compactMap({ $0.extendedIdentifier }) .compactMap({ URLQueryItem(name: "id", value: $0) }) + + let hids = hids.hids + .compactMap({ $0.extendedIdentifier }) + .compactMap({ URLQueryItem(name: "hid", value: $0) }) + + queryItems.append(contentsOf: hids) + + if let bundle = Bundle.main.bundleIdentifier { + queryItems.append(URLQueryItem(name: "bundle", value: bundle)) + } + + if let ver = Bundle.main.appVersionString { + queryItems.append(URLQueryItem(name: "ver", value: ver)) + } + + if let userAgent { + queryItems.append(URLQueryItem(name: "ua", value: userAgent)) + } + + if let targeting = storage.getTargeting(), let id5Signature = targeting.targetingData["id5_signature"] as? String { + queryItems.append(URLQueryItem(name: "id5_signature", value: id5Signature)) + } + url.compatAppend(queryItems: queryItems) let request = try buildRequest(.GET, url: url, headers: resolveHeaders()) diff --git a/Source/Misc/Bundle++.swift b/Source/Misc/Bundle++.swift new file mode 100644 index 0000000..60d23f1 --- /dev/null +++ b/Source/Misc/Bundle++.swift @@ -0,0 +1,15 @@ +// +// Bundle++.swift +// OptableSDK +// +// Copyright © 2026 Optable Technologies, Inc. All rights reserved. +// + +import Foundation + +extension Bundle { + /// The app's release version (`CFBundleShortVersionString`), if present. + var appVersionString: String? { + infoDictionary?["CFBundleShortVersionString"] as? String + } +} diff --git a/Source/OptableSDK.swift b/Source/OptableSDK.swift index 5ea6aeb..9196e36 100644 --- a/Source/OptableSDK.swift +++ b/Source/OptableSDK.swift @@ -116,7 +116,7 @@ public extension OptableSDK { // MARK: - Targeting public extension OptableSDK { /** - targeting(ids?, completion) calls the Optable Sandbox Targeting API and returns key-value targeting data + targeting(ids?, hids?, completion) calls the Optable Sandbox Targeting API and returns key-value targeting data for the current user/device/app. You may optionally supply identifiers to enrich the request. On completion, the handler receives: @@ -126,8 +126,8 @@ public extension OptableSDK { On success, the result is cached in client storage. You can read it using targetingFromCache() and clear it using targetingClearCache(). */ - func targeting(_ ids: [OptableIdentifier]? = nil, completion: @escaping (Result) -> Void) throws { - try _targeting(ids: ids, completion: completion) + func targeting(_ ids: [OptableIdentifier]? = nil, _ hids: [OptableIdentifier]? = nil, completion: @escaping (Result) -> Void) throws { + try _targeting(ids: ids, hids: hids, completion: completion) } /// targetingFromCache() returns the previously cached targeting data, if any. @@ -149,10 +149,10 @@ public extension OptableSDK { Instead of completion callbacks, results are returned via async/await. */ @available(iOS 13.0, *) - func targeting(_ ids: [OptableIdentifier]? = nil) async throws -> OptableTargeting { + func targeting(_ ids: [OptableIdentifier]? = nil, _ hids: [OptableIdentifier]? = nil) async throws -> OptableTargeting { return try await withCheckedThrowingContinuation({ [unowned self] continuation in do { - try self._targeting(ids: ids, completion: { continuation.resume(with: $0) }) + try self._targeting(ids: ids, hids: hids, completion: { continuation.resume(with: $0) }) } catch { continuation.resume(throwing: error) } @@ -311,12 +311,14 @@ extension OptableSDK { }).resume() } - func _targeting(ids: [OptableIdentifier]?, completion: @escaping (Result) -> Void) throws { + func _targeting(ids: [OptableIdentifier]?, hids: [OptableIdentifier]?, completion: @escaping (Result) -> Void) throws { var ids = ids ?? [] + var hids = hids ?? [] enrichIfNeeded(ids: &ids) + enrichIfNeeded(ids: &hids) - guard let request = try api.targeting(ids: ids) else { + guard let request = try api.targeting(ids: ids, hids: hids) else { throw OptableError.targeting("Failed to create targeting request") } diff --git a/Source/Public/ObjCSupport/OptableSDK+ObjC.swift b/Source/Public/ObjCSupport/OptableSDK+ObjC.swift index 5665f35..710f282 100644 --- a/Source/Public/ObjCSupport/OptableSDK+ObjC.swift +++ b/Source/Public/ObjCSupport/OptableSDK+ObjC.swift @@ -30,14 +30,16 @@ public extension OptableSDK { } /** - This is the Objective-C compatible version of the `targeting(completion)` API. + This is the Objective-C compatible version of the `targeting(ids, hids, completion)` API. Instead of completion callbacks, delegate methods are called. */ @objc - func targeting(_ ids: [OptableSDKIdentifier]) throws { + func targeting(_ ids: [OptableSDKIdentifier], _ hids: [OptableSDKIdentifier]) throws { let bridgedIds = ids.compactMap({ OptableIdentifier(objc: $0) }) - try self._targeting(ids: bridgedIds, completion: { result in + let bridgedHIds = hids.compactMap({ OptableIdentifier(objc: $0) }) + + try self._targeting(ids: bridgedIds, hids: bridgedHIds, completion: { result in switch result { case let .success(optableTargeting): self.delegate?.targetingOk(optableTargeting) diff --git a/Source/Public/OptableIdentifier.swift b/Source/Public/OptableIdentifier.swift index 3152a4e..e858a0a 100644 --- a/Source/Public/OptableIdentifier.swift +++ b/Source/Public/OptableIdentifier.swift @@ -84,6 +84,13 @@ extension OptableIdentifier: Encodable { } } +// MARK: - Equatable +extension OptableIdentifier: Equatable { + public static func == (lhs: OptableIdentifier, rhs: OptableIdentifier) -> Bool { + lhs.extendedIdentifier == rhs.extendedIdentifier + } +} + // MARK: - Init with ExtendedIdentifier public extension OptableIdentifier { init?(extendedIdentifier: String) { @@ -126,3 +133,19 @@ public extension OptableIdentifier { } } } + +// MARK: - HIDs +extension Array where Element == OptableIdentifier { + /// https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/resolver-specific-parameters#id5-mobile-in-app + var hids: [OptableIdentifier] { + filter { + switch $0 { + case .ipv6Address(_), .emailAddress(_), .phoneNumber(_), + .appleIDFA(_), .googleGAID(_), .custom(_, _): + return true + default: + return false + } + } + } +} diff --git a/Tests/Integration/OptableSDKTests.swift b/Tests/Integration/OptableSDKTests.swift index ea6c494..62cd5fb 100644 --- a/Tests/Integration/OptableSDKTests.swift +++ b/Tests/Integration/OptableSDKTests.swift @@ -73,7 +73,7 @@ class OptableSDKTests: XCTestCase { } func test_target_delegate() throws { - try sdk.targeting([OptableSDKIdentifier(type: .emailAddress, value: "test@test.com", customIdx: nil)]) + try sdk.targeting([OptableSDKIdentifier(type: .emailAddress, value: "test@test.com", customIdx: nil)], [OptableSDKIdentifier(type: .emailAddress, value: "test@test.com", customIdx: nil)]) wait(for: [targetExpectation], timeout: 10) } diff --git a/Tests/Unit/EdgeAPITests.swift b/Tests/Unit/EdgeAPITests.swift index 602ec0a..986684e 100644 --- a/Tests/Unit/EdgeAPITests.swift +++ b/Tests/Unit/EdgeAPITests.swift @@ -173,18 +173,39 @@ class EdgeAPITests: XCTestCase { For more info check: [](https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/optable-real-time-api-endpoints/targeting) */ func test_targeting_request_generation() throws { - let urlRequest = try sdk.api.targeting(ids: [.emailAddress("12345"), .phoneNumber("54321")]) - + // `id5_signature` is a resolver-specific parameter sourced from the stored targeting data. + sdk.api.storage.setTargeting(OptableTargeting(optableTargeting: ["id5_signature": "id5-sig-abc123"])) + + let email: OptableIdentifier = .emailAddress("12345") + let phone: OptableIdentifier = .phoneNumber("54321") + let urlRequest = try sdk.api.targeting(ids: [email, phone], hids: [email, phone]) + // Method XCTAssertEqual(urlRequest?.httpMethod, HTTPMethod.GET.rawValue) // Path let urlComponents = URLComponents(url: urlRequest!.url!, resolvingAgainstBaseURL: false)! XCTAssert(urlComponents.path.contains("targeting")) - - // Query - XCTAssert(urlComponents.queryItems?.contains(where: { $0.name == "id" && $0.value == "e:12345" }) != nil) - XCTAssert(urlComponents.queryItems?.contains(where: { $0.name == "id" && $0.value == "p:54321" }) != nil) + + // Query: every identifier is emitted as an `id` param (email/phone are SHA-256 hashed) + XCTAssertTrue(urlComponents.queryItems?.contains(where: { $0.name == "id" && $0.value == email.extendedIdentifier }) ?? false) + XCTAssertTrue(urlComponents.queryItems?.contains(where: { $0.name == "id" && $0.value == phone.extendedIdentifier }) ?? false) + + // HIDs: email and phone are part of the HID set, so they are also emitted as `hid` params + XCTAssertTrue(urlComponents.queryItems?.contains(where: { $0.name == "hid" && $0.value == email.extendedIdentifier }) ?? false) + XCTAssertTrue(urlComponents.queryItems?.contains(where: { $0.name == "hid" && $0.value == phone.extendedIdentifier }) ?? false) + + // Resolver-specific parameters + XCTAssertEqual(urlComponents.queryItems?.first(where: { $0.name == "ua" })?.value, T.api.userAgent) + XCTAssertEqual(urlComponents.queryItems?.first(where: { $0.name == "id5_signature" })?.value, "id5-sig-abc123") + + if let bundle = Bundle.main.bundleIdentifier { + XCTAssertEqual(urlComponents.queryItems?.first(where: { $0.name == "bundle" })?.value, bundle) + } + + if let ver = Bundle.main.appVersionString { + XCTAssertEqual(urlComponents.queryItems?.first(where: { $0.name == "ver" })?.value, ver) + } } /** diff --git a/Tests/Unit/OptableIdentifiersTests.swift b/Tests/Unit/OptableIdentifiersTests.swift index 0160427..cf7a880 100644 --- a/Tests/Unit/OptableIdentifiersTests.swift +++ b/Tests/Unit/OptableIdentifiersTests.swift @@ -63,4 +63,57 @@ class OptableIdentifiersTests: XCTestCase { XCTAssert(c_Idx < c2_Idx) XCTAssert(c2_Idx < c1_Idx) } + + // MARK: - hids + func test_hids_keepsOnlyHIDCases() throws { + let hidOnly: [OptableIdentifier] = [ + .emailAddress("foo@bar.com"), + .phoneNumber("+15123465890"), + .ipv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + .appleIDFA("496f5db5-681f-4392-acd5-0d4f6e2f6b88"), + .googleGAID("64873d9f-d5af-4770-8bcb-167a220eb17d"), + .custom(nil, "d29c551097b9dd0b82423827f65161232efaf7fc"), + .custom(1, "AaaZza.dh012"), + ] + + // Every element is a HID case, so contents and order are preserved. + XCTAssertEqual(hidOnly.hids.map(\.extendedIdentifier), hidOnly.map(\.extendedIdentifier)) + } + + func test_hids_dropsNonHIDCases() throws { + let nonHIDs: [OptableIdentifier] = [ + .postalCode("M5V 3L9"), + .ipv4Address("8.8.8.8"), + .rokuRIDA("0b179df0-6cd5-49f1-be21-425d002e0d22"), + .samsungTIFA("e0ef86a8-6ebf-4c9d-9127-e69407fe748d"), + .amazonFireAFAI("6e853799-ef31-4a30-8706-9742be254d38"), + .netID("_YV2v2Uhx3vqeH47Rrhzgr-4c3VNsxis4M1WY9qn--QTbVapax5VM2HJykoGAyWcwS5lKQ"), + .id5("ID5*UDWnp3JOtWV0ky-bHvEeU4xOVHXCmYeg24YigF8iAymUHplfYSElM3fy79h8p-Fg"), + .utiq("496f5db5-681f-4392-acd5-0d4f6e2f6b88"), + .optableVID("v-value"), + ] + + XCTAssertTrue(nonHIDs.hids.isEmpty) + } + + func test_hids_filtersMixedArrayPreservingOrder() throws { + let mixed: [OptableIdentifier] = [ + .postalCode("M5V 3L9"), + .emailAddress("foo@bar.com"), + .ipv4Address("8.8.8.8"), + .appleIDFA("496f5db5-681f-4392-acd5-0d4f6e2f6b88"), + .utiq("496f5db5-681f-4392-acd5-0d4f6e2f6b88"), + .ipv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + .custom(2, "ppid"), + ] + + let expected: [OptableIdentifier] = [ + .emailAddress("foo@bar.com"), + .appleIDFA("496f5db5-681f-4392-acd5-0d4f6e2f6b88"), + .ipv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + .custom(2, "ppid"), + ] + + XCTAssertEqual(mixed.hids.map(\.extendedIdentifier), expected.map(\.extendedIdentifier)) + } } From 1abc0182b6f1a99fb0ae672ad20278a641ae3e94 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Fri, 26 Jun 2026 14:49:30 +0300 Subject: [PATCH 02/14] fix: objc demo app --- demo-ios-objc/demo-ios-objc/GAMBannerViewController.m | 1 + demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m | 1 + 2 files changed, 2 insertions(+) diff --git a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m index dbdc66f..c0456d3 100644 --- a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m +++ b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m @@ -61,6 +61,7 @@ - (IBAction)loadBannerWithTargeting:(id)sender { [OPTABLE targeting: @[ [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_EmailAddress value:@"test@test.test"] ] + :@[] error:&error]; [OPTABLE witnessWithEvent: @"GAMBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } diff --git a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m index 73842ce..ca4ead3 100644 --- a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m +++ b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m @@ -70,6 +70,7 @@ - (IBAction)loadBannerWithTargeting:(id)sender { [OPTABLE targeting: @[ [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_EmailAddress value:@"test@test.test"] ] + :@[] error:&error]; [OPTABLE witnessWithEvent: @"PrebidBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } From 7e45db9e461b5309a32b332fda497f72d286cdd0 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 11:42:29 +0300 Subject: [PATCH 03/14] fix: add separately-named targeting overload --- Source/Public/ObjCSupport/OptableSDK+ObjC.swift | 16 +++++++++++++--- .../demo-ios-objc/GAMBannerViewController.m | 1 - .../demo-ios-objc/PrebidBannerViewController.m | 1 - 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Source/Public/ObjCSupport/OptableSDK+ObjC.swift b/Source/Public/ObjCSupport/OptableSDK+ObjC.swift index 710f282..41fd036 100644 --- a/Source/Public/ObjCSupport/OptableSDK+ObjC.swift +++ b/Source/Public/ObjCSupport/OptableSDK+ObjC.swift @@ -30,15 +30,25 @@ public extension OptableSDK { } /** - This is the Objective-C compatible version of the `targeting(ids, hids, completion)` API. + This is the Objective-C compatible version of the `targeting(ids, completion)` API. Instead of completion callbacks, delegate methods are called. */ @objc - func targeting(_ ids: [OptableSDKIdentifier], _ hids: [OptableSDKIdentifier]) throws { + func targeting(_ ids: [OptableSDKIdentifier]) throws { + try targeting(ids, hids: []) + } + + /** + This is the Objective-C compatible version of the `targeting(ids, hids, completion)` API. + + Instead of completion callbacks, delegate methods are called. + */ + @objc(targetingWithIds:hids:error:) + func targeting(_ ids: [OptableSDKIdentifier], hids: [OptableSDKIdentifier]) throws { let bridgedIds = ids.compactMap({ OptableIdentifier(objc: $0) }) let bridgedHIds = hids.compactMap({ OptableIdentifier(objc: $0) }) - + try self._targeting(ids: bridgedIds, hids: bridgedHIds, completion: { result in switch result { case let .success(optableTargeting): diff --git a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m index c0456d3..dbdc66f 100644 --- a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m +++ b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m @@ -61,7 +61,6 @@ - (IBAction)loadBannerWithTargeting:(id)sender { [OPTABLE targeting: @[ [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_EmailAddress value:@"test@test.test"] ] - :@[] error:&error]; [OPTABLE witnessWithEvent: @"GAMBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } diff --git a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m index ca4ead3..73842ce 100644 --- a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m +++ b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m @@ -70,7 +70,6 @@ - (IBAction)loadBannerWithTargeting:(id)sender { [OPTABLE targeting: @[ [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_EmailAddress value:@"test@test.test"] ] - :@[] error:&error]; [OPTABLE witnessWithEvent: @"PrebidBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } From 7d879344049567c3ddff077be04ee25e6906d7b4 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 13:19:23 +0300 Subject: [PATCH 04/14] fix: cleanup storage in tests --- Tests/Integration/OptableSDKTests.swift | 2 +- Tests/Unit/EdgeAPITests.swift | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/Integration/OptableSDKTests.swift b/Tests/Integration/OptableSDKTests.swift index 62cd5fb..6dcb627 100644 --- a/Tests/Integration/OptableSDKTests.swift +++ b/Tests/Integration/OptableSDKTests.swift @@ -73,7 +73,7 @@ class OptableSDKTests: XCTestCase { } func test_target_delegate() throws { - try sdk.targeting([OptableSDKIdentifier(type: .emailAddress, value: "test@test.com", customIdx: nil)], [OptableSDKIdentifier(type: .emailAddress, value: "test@test.com", customIdx: nil)]) + try sdk.targeting([OptableSDKIdentifier(type: .emailAddress, value: "test@test.com", customIdx: nil)], hids: [OptableSDKIdentifier(type: .emailAddress, value: "test@test.com", customIdx: nil)]) wait(for: [targetExpectation], timeout: 10) } diff --git a/Tests/Unit/EdgeAPITests.swift b/Tests/Unit/EdgeAPITests.swift index 986684e..70d31ff 100644 --- a/Tests/Unit/EdgeAPITests.swift +++ b/Tests/Unit/EdgeAPITests.swift @@ -17,6 +17,11 @@ class EdgeAPITests: XCTestCase { ) lazy var sdk = OptableSDK(config: config) + override func tearDown() { + sdk.api.storage.clearTargeting() + super.tearDown() + } + // MARK: URL-s /** Expected output: From 3a34d55449aca3ef6a2b98b2b5635f22502591f5 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 13:21:18 +0300 Subject: [PATCH 05/14] fix: add note regarding custom case ids --- Source/OptableSDK.swift | 4 ++++ Source/Public/OptableIdentifier.swift | 3 +++ 2 files changed, 7 insertions(+) diff --git a/Source/OptableSDK.swift b/Source/OptableSDK.swift index 9196e36..9cc5370 100644 --- a/Source/OptableSDK.swift +++ b/Source/OptableSDK.swift @@ -119,6 +119,10 @@ public extension OptableSDK { targeting(ids?, hids?, completion) calls the Optable Sandbox Targeting API and returns key-value targeting data for the current user/device/app. You may optionally supply identifiers to enrich the request. + Identifiers passed as `hids` are forwarded as resolver-specific `hid` parameters (e.g. ID5 Mobile In-App). + Only email address, phone number, IPv6 address, Apple IDFA, Google GAID and custom identifiers are sent; + custom (`cN`) prefixes must be configured on the DCN — unconfigured ones are ignored server-side. + On completion, the handler receives: - .success(OptableTargeting) on success - .failure(Error) on failure diff --git a/Source/Public/OptableIdentifier.swift b/Source/Public/OptableIdentifier.swift index e858a0a..4f4389c 100644 --- a/Source/Public/OptableIdentifier.swift +++ b/Source/Public/OptableIdentifier.swift @@ -137,6 +137,9 @@ public extension OptableIdentifier { // MARK: - HIDs extension Array where Element == OptableIdentifier { /// https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/resolver-specific-parameters#id5-mobile-in-app + /// + /// Note: all `.custom` identifiers are passed through, but only the custom (`cN`) prefixes + /// configured on the DCN are valid resolver hints — unconfigured ones are ignored server-side. var hids: [OptableIdentifier] { filter { switch $0 { From e6a6164366d09b81a93a06fab648fc7cc39d5ab4 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 13:24:15 +0300 Subject: [PATCH 06/14] fix: add hids label to parameter declaration --- Source/OptableSDK.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/OptableSDK.swift b/Source/OptableSDK.swift index 9cc5370..c4a8f50 100644 --- a/Source/OptableSDK.swift +++ b/Source/OptableSDK.swift @@ -130,7 +130,7 @@ public extension OptableSDK { On success, the result is cached in client storage. You can read it using targetingFromCache() and clear it using targetingClearCache(). */ - func targeting(_ ids: [OptableIdentifier]? = nil, _ hids: [OptableIdentifier]? = nil, completion: @escaping (Result) -> Void) throws { + func targeting(_ ids: [OptableIdentifier]? = nil, hids: [OptableIdentifier]? = nil, completion: @escaping (Result) -> Void) throws { try _targeting(ids: ids, hids: hids, completion: completion) } @@ -153,7 +153,7 @@ public extension OptableSDK { Instead of completion callbacks, results are returned via async/await. */ @available(iOS 13.0, *) - func targeting(_ ids: [OptableIdentifier]? = nil, _ hids: [OptableIdentifier]? = nil) async throws -> OptableTargeting { + func targeting(_ ids: [OptableIdentifier]? = nil, hids: [OptableIdentifier]? = nil) async throws -> OptableTargeting { return try await withCheckedThrowingContinuation({ [unowned self] continuation in do { try self._targeting(ids: ids, hids: hids, completion: { continuation.resume(with: $0) }) From 51695731bb1f2b7cf5aa35ed497197afa67b2f01 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 13:32:55 +0300 Subject: [PATCH 07/14] fix: explain parameters in codedoc --- Source/OptableSDK.swift | 32 +++++++++++++------ .../Public/ObjCSupport/OptableSDK+ObjC.swift | 6 +++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Source/OptableSDK.swift b/Source/OptableSDK.swift index c4a8f50..cf2d1c7 100644 --- a/Source/OptableSDK.swift +++ b/Source/OptableSDK.swift @@ -117,15 +117,24 @@ public extension OptableSDK { public extension OptableSDK { /** targeting(ids?, hids?, completion) calls the Optable Sandbox Targeting API and returns key-value targeting data - for the current user/device/app. You may optionally supply identifiers to enrich the request. - - Identifiers passed as `hids` are forwarded as resolver-specific `hid` parameters (e.g. ID5 Mobile In-App). - Only email address, phone number, IPv6 address, Apple IDFA, Google GAID and custom identifiers are sent; - custom (`cN`) prefixes must be configured on the DCN — unconfigured ones are ignored server-side. - - On completion, the handler receives: - - .success(OptableTargeting) on success - - .failure(Error) on failure + for the current user/device/app. + + - Parameters: + - ids: one or more identifiers to resolve, sent as repeated `id` query parameters. The DCN evaluates them + in the order they are listed and returns the profile of the first successful match (querying the + first-party graph before any third-party graphs). When provided, they take precedence over any + identifier in the passport. + - hids: hint identifiers, sent as `hid` query parameters in addition to `ids`. Hints drive resolver-specific + identity resolution on the DCN, such as ID5 Mobile In-App. Only the identifier types valid as hints are + forwarded: email address, phone number, IPv6 address, Apple IDFA, Google GAID and custom IDs — any other + type in `hids` is dropped client-side. Custom (`cN`) prefixes must be configured on the DCN; unconfigured + ones are ignored server-side. + - completion: on completion, the handler receives: + - .success(OptableTargeting) on success + - .failure(Error) on failure + + Unless `skipAdvertisingIdDetection` is set in the config, the device IDFA is automatically prepended to both + lists when ad tracking is authorized. On success, the result is cached in client storage. You can read it using targetingFromCache() and clear it using targetingClearCache(). @@ -148,7 +157,10 @@ public extension OptableSDK { // MARK: Async/Await support /** - This is the Swift Concurrency compatible version of the `targeting(completion)` API. + This is the Swift Concurrency compatible version of the `targeting(ids, hids, completion)` API: + `ids` match the user/device against the DCN, while `hids` are hint identifiers driving resolver-specific + identity resolution such as ID5 Mobile In-App — see `targeting(_:hids:completion:)` for details on which + identifier types are valid hints. Instead of completion callbacks, results are returned via async/await. */ diff --git a/Source/Public/ObjCSupport/OptableSDK+ObjC.swift b/Source/Public/ObjCSupport/OptableSDK+ObjC.swift index 41fd036..93e779a 100644 --- a/Source/Public/ObjCSupport/OptableSDK+ObjC.swift +++ b/Source/Public/ObjCSupport/OptableSDK+ObjC.swift @@ -40,7 +40,11 @@ public extension OptableSDK { } /** - This is the Objective-C compatible version of the `targeting(ids, hids, completion)` API. + This is the Objective-C compatible version of the `targeting(ids, hids, completion)` API: + `ids` match the user/device against the DCN, while `hids` are hint identifiers driving resolver-specific + identity resolution such as ID5 Mobile In-App. Only email address, phone number, IPv6 address, Apple IDFA, + Google GAID and custom identifiers are valid hints — any other type in `hids` is dropped client-side, and + custom (`cN`) prefixes not configured on the DCN are ignored server-side. Instead of completion callbacks, delegate methods are called. */ From 16a6357e2a8e216d1fb31d6e71938eea9743440b Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 13:34:42 +0300 Subject: [PATCH 08/14] fix: remove unused Equatable conformance --- Source/Public/OptableIdentifier.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Source/Public/OptableIdentifier.swift b/Source/Public/OptableIdentifier.swift index 4f4389c..6fa1e47 100644 --- a/Source/Public/OptableIdentifier.swift +++ b/Source/Public/OptableIdentifier.swift @@ -84,13 +84,6 @@ extension OptableIdentifier: Encodable { } } -// MARK: - Equatable -extension OptableIdentifier: Equatable { - public static func == (lhs: OptableIdentifier, rhs: OptableIdentifier) -> Bool { - lhs.extendedIdentifier == rhs.extendedIdentifier - } -} - // MARK: - Init with ExtendedIdentifier public extension OptableIdentifier { init?(extendedIdentifier: String) { From 855a69bf2d8a3e7f796eb635a39cede751c656fb Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 13:36:43 +0300 Subject: [PATCH 09/14] fix: rename local variable --- Source/Core/EdgeAPI.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/EdgeAPI.swift b/Source/Core/EdgeAPI.swift index ced3966..57a5532 100644 --- a/Source/Core/EdgeAPI.swift +++ b/Source/Core/EdgeAPI.swift @@ -70,11 +70,11 @@ final class EdgeAPI { .compactMap({ $0.extendedIdentifier }) .compactMap({ URLQueryItem(name: "id", value: $0) }) - let hids = hids.hids + let hidQueryItems = hids.hids .compactMap({ $0.extendedIdentifier }) .compactMap({ URLQueryItem(name: "hid", value: $0) }) - - queryItems.append(contentsOf: hids) + + queryItems.append(contentsOf: hidQueryItems) if let bundle = Bundle.main.bundleIdentifier { queryItems.append(URLQueryItem(name: "bundle", value: bundle)) From 5cf1b4ee21210507ae9f1de79a3b6fb768717e93 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Mon, 13 Jul 2026 13:43:35 +0300 Subject: [PATCH 10/14] fix: add usage docs --- docs/usage-objc.md | 19 ++++++++++++++++++- docs/usage-swift.md | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/usage-objc.md b/docs/usage-objc.md index 5ed211b..3a1d43a 100644 --- a/docs/usage-objc.md +++ b/docs/usage-objc.md @@ -117,10 +117,27 @@ To get the targeting key values associated by the configured DCN with the device @import OptableSDK; ... NSError *error = nil; -[OPTABLE targetingWithIds: @[@"c:1"] // NULL-able +[OPTABLE targeting: @[ + [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_EmailAddress value:@"test@test.test"] +] + error: &error]; +``` + +You may optionally supply hint identifiers (`hids`) which are forwarded as resolver-specific `hid` parameters, used by integrations such as ID5 Mobile In-App: + +```objective-c +[OPTABLE targetingWithIds: @[ + [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_EmailAddress value:@"test@test.test"] +] + hids: @[ + [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_PhoneNumber value:@"+1234567890"] +] error: &error]; ``` +> :information_source: For more details on `hid` parameters, including the supported identifier types, check: +> [Optable Real-Time API Integrations Guide > Resolver Specific Parameters > ID5 Mobile In-App](https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/resolver-specific-parameters#id5-mobile-in-app) + #### Caching Targeting Data The `targetingAndReturnError` method will automatically cache resulting key value data in client storage on success. You can subsequently retrieve the cached key value data as follows: diff --git a/docs/usage-swift.md b/docs/usage-swift.md index 715e10f..d93364a 100644 --- a/docs/usage-swift.md +++ b/docs/usage-swift.md @@ -138,6 +138,24 @@ do { On success, the resulting key values are typically sent as part of a subsequent ad call. Therefore we recommend that you either call `targeting()` before each ad call, or in parallel periodically, caching the resulting key values which you then provide in ad calls. +You may optionally supply identifiers to enrich the targeting request: `ids` are used to match the user, while `hids` are hint identifiers forwarded as resolver-specific `hid` parameters, used by integrations such as ID5 Mobile In-App: + +```swift +let ids: [OptableIdentifier] = [ + .emailAddress("test@test.test") +] +let hids: [OptableIdentifier] = [ + .phoneNumber("+1234567890") +] + +try OPTABLE!.targeting(ids, hids: hids) { result in + // ... +} +``` + +> :information_source: For more details on `hid` parameters, including the supported identifier types, check: +> [Optable Real-Time API Integrations Guide > Resolver Specific Parameters > ID5 Mobile In-App](https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/resolver-specific-parameters#id5-mobile-in-app) + #### Caching Targeting Data The `targeting` API will automatically cache resulting key value data in client storage on success. You can subsequently retrieve the cached key value data as follows: From d83794950497d96d8fa0d8a12fd74361ad47a90e Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Thu, 16 Jul 2026 12:41:31 +0300 Subject: [PATCH 11/14] feat: append cached id5 signature to request --- Source/Core/EdgeAPI.swift | 2 +- Source/Public/OptableTargeting.swift | 40 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Source/Core/EdgeAPI.swift b/Source/Core/EdgeAPI.swift index 57a5532..9f6bc6c 100644 --- a/Source/Core/EdgeAPI.swift +++ b/Source/Core/EdgeAPI.swift @@ -88,7 +88,7 @@ final class EdgeAPI { queryItems.append(URLQueryItem(name: "ua", value: userAgent)) } - if let targeting = storage.getTargeting(), let id5Signature = targeting.targetingData["id5_signature"] as? String { + if let targeting = storage.getTargeting(), let id5Signature = targeting.id5Signature { queryItems.append(URLQueryItem(name: "id5_signature", value: id5Signature)) } diff --git a/Source/Public/OptableTargeting.swift b/Source/Public/OptableTargeting.swift index 5710fff..f342c6c 100644 --- a/Source/Public/OptableTargeting.swift +++ b/Source/Public/OptableTargeting.swift @@ -32,3 +32,43 @@ public class OptableTargeting: NSObject { return desc } } + +// MARK: - Helpers + +extension OptableTargeting { + + var id5Signature: String? { + guard let ortb2 = targetingData["ortb2"] as? [String: Any], + let user = ortb2["user"] as? [String: Any], + let eids = user["eids"] as? [[String: Any]] else { + return nil + } + + guard let rootRefs = targetingData["refs"] as? [String: Any] else { return nil } + + for eid in eids { + guard let source = eid["source"] as? String, source.range(of: "id5", options: [.caseInsensitive]) != nil else { + continue + } + + guard let uids = eid["uids"] as? [[String: Any]] else { continue } + + for uid in uids { + guard let ext = uid["ext"] as? [String: Any], + let optable = ext["optable"] as? [String: Any], + let uidRef = optable["ref"] as? String else { + continue + } + + guard let ref = rootRefs[uidRef] as? [String: Any] else { continue } + + if let id5Signature = ref["signature"] as? String, + id5Signature.trimmingCharacters(in: .whitespaces).isEmpty == false { + return id5Signature + } + } + } + + return nil + } +} From be82f7710faa137df9be914e14c756062b1fe1f2 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Thu, 16 Jul 2026 13:11:48 +0300 Subject: [PATCH 12/14] feat: store id5 signature separately --- Source/Core/EdgeAPI.swift | 2 +- Source/Core/LocalStorage.swift | 11 +++++++++++ Source/OptableSDK.swift | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/Core/EdgeAPI.swift b/Source/Core/EdgeAPI.swift index 9f6bc6c..0cf63ab 100644 --- a/Source/Core/EdgeAPI.swift +++ b/Source/Core/EdgeAPI.swift @@ -88,7 +88,7 @@ final class EdgeAPI { queryItems.append(URLQueryItem(name: "ua", value: userAgent)) } - if let targeting = storage.getTargeting(), let id5Signature = targeting.id5Signature { + if let id5Signature = storage.getID5Signature() { queryItems.append(URLQueryItem(name: "id5_signature", value: id5Signature)) } diff --git a/Source/Core/LocalStorage.swift b/Source/Core/LocalStorage.swift index 84ff17f..0fe1df0 100644 --- a/Source/Core/LocalStorage.swift +++ b/Source/Core/LocalStorage.swift @@ -15,6 +15,7 @@ final class LocalStorage: NSObject { private let targetingDataKey: String private let gamTargetingKeywordsKey: String private let ortb2Key: String + private let id5SignatureKey: String let keyPfx: String = "OPTABLE" var passportKey: String @@ -33,6 +34,7 @@ final class LocalStorage: NSObject { self.targetingDataKey = targetingKey + "_targetingData" self.gamTargetingKeywordsKey = targetingKey + "_gamTargetingKeywords" self.ortb2Key = targetingKey + "_ortb2" + self.id5SignatureKey = targetingKey + "_id5Signature" } func getPassport() -> String? { @@ -68,5 +70,14 @@ final class LocalStorage: NSObject { UserDefaults.standard.removeObject(forKey: targetingDataKey) UserDefaults.standard.removeObject(forKey: gamTargetingKeywordsKey) UserDefaults.standard.removeObject(forKey: ortb2Key) + UserDefaults.standard.removeObject(forKey: id5SignatureKey) + } + + func getID5Signature() -> String? { + return UserDefaults.standard.string(forKey: id5SignatureKey) + } + + func setID5Signature(_ signature: String) { + UserDefaults.standard.set(signature, forKey: id5SignatureKey) } } diff --git a/Source/OptableSDK.swift b/Source/OptableSDK.swift index cf2d1c7..f53af85 100644 --- a/Source/OptableSDK.swift +++ b/Source/OptableSDK.swift @@ -358,6 +358,10 @@ extension OptableSDK { /// We cache the latest targeting result in client storage for targetingFromCache() users: self.api.storage.setTargeting(optableTargeting) + + if let id5Signature = optableTargeting.id5Signature { + self.api.storage.setID5Signature(id5Signature) + } completion(.success(optableTargeting)) } catch { From 87c4587ed740aafce679bd12d012a23ca876e5b6 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Thu, 16 Jul 2026 13:13:45 +0300 Subject: [PATCH 13/14] tests: tests for id5Signature --- OptableSDK.xcodeproj/project.pbxproj | 1 + Tests/Unit/EdgeAPITests.swift | 26 +++++++- Tests/Unit/LocalStorageTests.swift | 8 +++ Tests/Unit/OptableTargetingTests.swift | 86 ++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 Tests/Unit/OptableTargetingTests.swift diff --git a/OptableSDK.xcodeproj/project.pbxproj b/OptableSDK.xcodeproj/project.pbxproj index a05613f..fab643e 100644 --- a/OptableSDK.xcodeproj/project.pbxproj +++ b/OptableSDK.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ Unit/OptableIdentifiersTests.swift, Unit/OptableSDKHelpersIdentifiersEnrichmentTests.swift, Unit/OptableSDKHelpersTests.swift, + Unit/OptableTargetingTests.swift, ); target = 6352AB0324EAD403002E66EB /* OptableSDKTests */; }; diff --git a/Tests/Unit/EdgeAPITests.swift b/Tests/Unit/EdgeAPITests.swift index 70d31ff..8188277 100644 --- a/Tests/Unit/EdgeAPITests.swift +++ b/Tests/Unit/EdgeAPITests.swift @@ -178,8 +178,10 @@ class EdgeAPITests: XCTestCase { For more info check: [](https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/optable-real-time-api-endpoints/targeting) */ func test_targeting_request_generation() throws { - // `id5_signature` is a resolver-specific parameter sourced from the stored targeting data. - sdk.api.storage.setTargeting(OptableTargeting(optableTargeting: ["id5_signature": "id5-sig-abc123"])) + // `id5_signature` is a resolver-specific parameter cached in storage from the latest targeting response, + // and only sent alongside a cached targeting result: + sdk.api.storage.setTargeting(OptableTargeting(optableTargeting: ["resolved_ids": ["v:123"]])) + sdk.api.storage.setID5Signature("id5-sig-abc123") let email: OptableIdentifier = .emailAddress("12345") let phone: OptableIdentifier = .phoneNumber("54321") @@ -213,6 +215,26 @@ class EdgeAPITests: XCTestCase { } } + func test_targeting_request_omits_id5_signature_when_no_stored_targeting() throws { + // A cached signature alone is not enough — it is only sent alongside a cached targeting result: + sdk.api.storage.clearTargeting() + sdk.api.storage.setID5Signature("id5-sig-abc123") + + let urlRequest = try sdk.api.targeting(ids: [.emailAddress("12345")], hids: []) + let urlComponents = URLComponents(url: urlRequest!.url!, resolvingAgainstBaseURL: false)! + + XCTAssertNil(urlComponents.queryItems?.first(where: { $0.name == "id5_signature" })) + } + + func test_targeting_request_omits_id5_signature_when_no_cached_signature() throws { + sdk.api.storage.setTargeting(OptableTargeting(optableTargeting: ["resolved_ids": ["v:123"]])) + + let urlRequest = try sdk.api.targeting(ids: [.emailAddress("12345")], hids: []) + let urlComponents = URLComponents(url: urlRequest!.url!, resolvingAgainstBaseURL: false)! + + XCTAssertNil(urlComponents.queryItems?.first(where: { $0.name == "id5_signature" })) + } + /** For more info check: [](https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/optable-real-time-api-endpoints/profile) */ diff --git a/Tests/Unit/LocalStorageTests.swift b/Tests/Unit/LocalStorageTests.swift index 28b8aa3..a1df6c8 100644 --- a/Tests/Unit/LocalStorageTests.swift +++ b/Tests/Unit/LocalStorageTests.swift @@ -68,6 +68,14 @@ class LocalStorageTests: XCTestCase { XCTAssert(readTargeting!.ortb2 == nil) } + func testID5SignatureStoring() { + localStorage.setID5Signature("id5-sig-abc123") + XCTAssertEqual(localStorage.getID5Signature(), "id5-sig-abc123") + + localStorage.clearTargeting() + XCTAssertNil(localStorage.getID5Signature()) + } + func testClearOptableTargeting() { let optableTargetingFull = OptableTargeting( optableTargeting: kOptableTargeting as! [String : Any], diff --git a/Tests/Unit/OptableTargetingTests.swift b/Tests/Unit/OptableTargetingTests.swift new file mode 100644 index 0000000..ebc274b --- /dev/null +++ b/Tests/Unit/OptableTargetingTests.swift @@ -0,0 +1,86 @@ +// +// OptableTargetingTests.swift +// OptableSDK +// +// Copyright © 2026 Optable Technologies, Inc. All rights reserved. +// + +@testable import OptableSDK +import XCTest + +class OptableTargetingTests: XCTestCase { + /// Targeting data in the shape returned by the edge targeting endpoint: + /// `ortb2.user.eids[].uids[].ext.optable.ref` points into `refs`, where the signature lives. + private func targetingData(source: String = "id5-sync.com", signature: Any = "id5-sig-abc123") -> [String: Any] { + return [ + "ortb2": [ + "user": [ + "eids": [ + [ + "source": source, + "uids": [ + ["id": "ID5*uid", "ext": ["optable": ["ref": "0"]]], + ], + ], + ], + ], + ], + "refs": ["0": ["signature": signature]], + ] + } + + func test_id5Signature_extracted_from_valid_targeting_data() { + let targeting = OptableTargeting(optableTargeting: targetingData()) + XCTAssertEqual(targeting.id5Signature, "id5-sig-abc123") + } + + func test_id5Signature_source_matching_is_case_insensitive() { + let targeting = OptableTargeting(optableTargeting: targetingData(source: "ID5-Sync.com")) + XCTAssertEqual(targeting.id5Signature, "id5-sig-abc123") + } + + func test_id5Signature_nil_when_source_is_not_id5() { + let targeting = OptableTargeting(optableTargeting: targetingData(source: "liveramp.com")) + XCTAssertNil(targeting.id5Signature) + } + + func test_id5Signature_nil_when_signature_empty_or_whitespace() { + XCTAssertNil(OptableTargeting(optableTargeting: targetingData(signature: "")).id5Signature) + XCTAssertNil(OptableTargeting(optableTargeting: targetingData(signature: " ")).id5Signature) + } + + func test_id5Signature_nil_when_signature_is_not_a_string() { + let targeting = OptableTargeting(optableTargeting: targetingData(signature: 123)) + XCTAssertNil(targeting.id5Signature) + } + + func test_id5Signature_nil_when_targeting_data_empty() { + XCTAssertNil(OptableTargeting(optableTargeting: [:]).id5Signature) + } + + func test_id5Signature_nil_when_refs_missing() { + var data = targetingData() + data["refs"] = nil + XCTAssertNil(OptableTargeting(optableTargeting: data).id5Signature) + } + + func test_id5Signature_nil_when_ref_not_found_in_refs() { + var data = targetingData() + data["refs"] = ["other-ref": ["signature": "id5-sig-abc123"]] + XCTAssertNil(OptableTargeting(optableTargeting: data).id5Signature) + } + + func test_id5Signature_nil_when_uid_has_no_optable_ref() { + var data = targetingData() + data["ortb2"] = ["user": ["eids": [["source": "id5-sync.com", "uids": [["id": "ID5*uid"]]]]]] + XCTAssertNil(OptableTargeting(optableTargeting: data).id5Signature) + } + + func test_id5Signature_found_among_multiple_eids() { + var data = targetingData() + var eids = ((data["ortb2"] as! [String: Any])["user"] as! [String: Any])["eids"] as! [[String: Any]] + eids.insert(["source": "liveramp.com", "uids": [["id": "ramp-uid"]]], at: 0) + data["ortb2"] = ["user": ["eids": eids]] + XCTAssertEqual(OptableTargeting(optableTargeting: data).id5Signature, "id5-sig-abc123") + } +} From 2bd295a657f8119c3b3cad59d4760d6755be3e19 Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Thu, 16 Jul 2026 13:25:10 +0300 Subject: [PATCH 14/14] fix: edge api tests --- Tests/Unit/EdgeAPITests.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Tests/Unit/EdgeAPITests.swift b/Tests/Unit/EdgeAPITests.swift index 8188277..6d0428c 100644 --- a/Tests/Unit/EdgeAPITests.swift +++ b/Tests/Unit/EdgeAPITests.swift @@ -178,9 +178,6 @@ class EdgeAPITests: XCTestCase { For more info check: [](https://docs.optable.co/optable-documentation/guides/real-time-api-integrations-guide/optable-real-time-api-endpoints/targeting) */ func test_targeting_request_generation() throws { - // `id5_signature` is a resolver-specific parameter cached in storage from the latest targeting response, - // and only sent alongside a cached targeting result: - sdk.api.storage.setTargeting(OptableTargeting(optableTargeting: ["resolved_ids": ["v:123"]])) sdk.api.storage.setID5Signature("id5-sig-abc123") let email: OptableIdentifier = .emailAddress("12345") @@ -215,17 +212,6 @@ class EdgeAPITests: XCTestCase { } } - func test_targeting_request_omits_id5_signature_when_no_stored_targeting() throws { - // A cached signature alone is not enough — it is only sent alongside a cached targeting result: - sdk.api.storage.clearTargeting() - sdk.api.storage.setID5Signature("id5-sig-abc123") - - let urlRequest = try sdk.api.targeting(ids: [.emailAddress("12345")], hids: []) - let urlComponents = URLComponents(url: urlRequest!.url!, resolvingAgainstBaseURL: false)! - - XCTAssertNil(urlComponents.queryItems?.first(where: { $0.name == "id5_signature" })) - } - func test_targeting_request_omits_id5_signature_when_no_cached_signature() throws { sdk.api.storage.setTargeting(OptableTargeting(optableTargeting: ["resolved_ids": ["v:123"]]))