From f8675401c2ea5de1a89c48990c170d11b3c37ae7 Mon Sep 17 00:00:00 2001 From: Danyal Khan Date: Wed, 9 Sep 2026 15:10:42 -0400 Subject: [PATCH 1/6] Send the split serial ID on iOS exposure events Bump the iOS SDK to 3.17.0, which adds serialID to FlagAssignment, and send the value through the React Native bridge. The iOS bridge converts FlagAssignment to an NSDictionary with a fixed key list, and converts it back for exposure tracking. The key list omitted the serial ID, so the value was dropped in both directions. The code still compiled, so nothing reported the loss. The serial ID crosses the bridge as a string, for parity with Android, which sends a string because React Native converts integers to Double. An assignment with no serial ID omits the key instead of sending NSNull. The reader accepts a string only. NSNumber and Bool bridge into each other on Apple platforms, so a numeric branch would read a boolean true as serial ID 1. Int(String) rejects that. A malformed serial ID yields no serial ID and never rejects the assignment, because that would drop the exposure event for the whole flag. --- packages/core/DatadogSDKReactNative.podspec | 14 +-- .../ios/Sources/DdFlagsImplementation.swift | 13 ++- packages/core/ios/Tests/DdFlagsTests.swift | 99 +++++++++++++++++++ ...DatadogSDKReactNativeSessionReplay.podspec | 2 +- .../DatadogSDKReactNativeWebView.podspec | 4 +- 5 files changed, 120 insertions(+), 12 deletions(-) diff --git a/packages/core/DatadogSDKReactNative.podspec b/packages/core/DatadogSDKReactNative.podspec index e04f5fc2e..cb4dac1bc 100644 --- a/packages/core/DatadogSDKReactNative.podspec +++ b/packages/core/DatadogSDKReactNative.podspec @@ -19,15 +19,15 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the versions in sync with DatadogSDKReactNativeSessionReplay.podspec - s.dependency 'DatadogCore', '3.16.0' - s.dependency 'DatadogLogs', '3.16.0' - s.dependency 'DatadogTrace', '3.16.0' - s.dependency 'DatadogRUM', '3.16.0' - s.dependency 'DatadogCrashReporting', '3.16.0' - s.dependency 'DatadogFlags', '3.16.0' + s.dependency 'DatadogCore', '3.17.0' + s.dependency 'DatadogLogs', '3.17.0' + s.dependency 'DatadogTrace', '3.17.0' + s.dependency 'DatadogRUM', '3.17.0' + s.dependency 'DatadogCrashReporting', '3.17.0' + s.dependency 'DatadogFlags', '3.17.0' # DatadogWebViewTracking is not available for tvOS - s.ios.dependency 'DatadogWebViewTracking', '3.16.0' + s.ios.dependency 'DatadogWebViewTracking', '3.17.0' s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'ios/Tests/**/*.{swift,json}' diff --git a/packages/core/ios/Sources/DdFlagsImplementation.swift b/packages/core/ios/Sources/DdFlagsImplementation.swift index 34020f410..d0a050069 100644 --- a/packages/core/ios/Sources/DdFlagsImplementation.swift +++ b/packages/core/ios/Sources/DdFlagsImplementation.swift @@ -172,7 +172,7 @@ extension FlagAssignment { case .unknown: NSNull() } - return [ + var dictionary: [String: Any] = [ "key": flagKey, "value": value, "allocationKey": allocationKey, @@ -184,9 +184,17 @@ extension FlagAssignment { "variationValue": "", "extraLogging": [:], ] + + if let serialID { + dictionary[serialIDKey] = String(serialID) + } + + return dictionary } } +private let serialIDKey = "serialId" + extension NSDictionary { func asFlagAssignment() -> FlagAssignment? { guard @@ -213,7 +221,8 @@ extension NSDictionary { variationKey: variationKey, variation: variation, reason: reason, - doLog: doLog + doLog: doLog, + serialID: (object(forKey: serialIDKey) as? String).flatMap(Int.init) ) } } diff --git a/packages/core/ios/Tests/DdFlagsTests.swift b/packages/core/ios/Tests/DdFlagsTests.swift index 3fca72ddc..915843485 100644 --- a/packages/core/ios/Tests/DdFlagsTests.swift +++ b/packages/core/ios/Tests/DdFlagsTests.swift @@ -368,6 +368,40 @@ class DdFlagsTests: XCTestCase { XCTAssertEqual(dict["variationType"] as? String, "") XCTAssertEqual(dict["variationValue"] as? String, "") XCTAssertNotNil(dict["extraLogging"] as? [String: Any]) + // An assignment with no serial ID omits the key rather than sending NSNull. + XCTAssertNil(dict["serialId"]) + } + + func testFlagAssignmentToDictionaryCarriesSerialID() { + let assignment = FlagAssignment( + allocationKey: "alloc", + variationKey: "var", + variation: .boolean(true), + reason: "reason", + doLog: true, + serialID: 340132 + ) + + let dict = assignment.asDictionary(flagKey: "flag1") + + // Sent as a String because the React Native bridge converts integers to Double. + XCTAssertEqual(dict["serialId"] as? String, "340132") + } + + func testFlagAssignmentToDictionaryCarriesSerialIDZero() { + // Serial IDs are zero-based per org, so 0 is a real value, not an absent one. + let assignment = FlagAssignment( + allocationKey: "alloc", + variationKey: "var", + variation: .boolean(true), + reason: "reason", + doLog: true, + serialID: 0 + ) + + let dict = assignment.asDictionary(flagKey: "flag1") + + XCTAssertEqual(dict["serialId"] as? String, "0") } func testDictionaryToFlagAssignment() { @@ -389,6 +423,71 @@ class DdFlagsTests: XCTestCase { } else { XCTFail("Expected string variation") } + XCTAssertNil(assignment?.serialID) + } + + func testDictionaryToFlagAssignmentReadsSerialID() { + let dict: NSDictionary = [ + "allocationKey": "alloc", + "variationKey": "var", + "reason": "reason", + "doLog": true, + "value": "string_value", + "serialId": "340132" + ] + + XCTAssertEqual(dict.asFlagAssignment()?.serialID, 340132) + } + + func testDictionaryToFlagAssignmentReadsSerialIDZero() { + let dict: NSDictionary = [ + "allocationKey": "alloc", + "variationKey": "var", + "reason": "reason", + "doLog": true, + "value": "string_value", + "serialId": "0" + ] + + XCTAssertEqual(dict.asFlagAssignment()?.serialID, 0) + } + + func testDictionaryToFlagAssignmentIgnoresMalformedSerialID() { + // A malformed serial ID binds to the serial ID. It must not reject the assignment, + // because that would drop the exposure event for the whole flag. + let malformedValues: [Any] = ["not-a-number", "", true, 340132] + + for malformed in malformedValues { + let dict: NSDictionary = [ + "allocationKey": "alloc", + "variationKey": "var", + "reason": "reason", + "doLog": true, + "value": "string_value", + "serialId": malformed + ] + + let assignment = dict.asFlagAssignment() + + XCTAssertNotNil(assignment, "Expected an assignment for serialId \(malformed)") + XCTAssertNil(assignment?.serialID, "Expected no serial ID for \(malformed)") + } + } + + func testFlagAssignmentSerialIDSurvivesARoundTrip() { + let assignment = FlagAssignment( + allocationKey: "alloc", + variationKey: "var", + variation: .string("string_value"), + reason: "reason", + doLog: true, + serialID: 0 + ) + + let roundTripped = (assignment.asDictionary(flagKey: "flag1") as NSDictionary) + .asFlagAssignment() + + XCTAssertEqual(roundTripped?.serialID, 0) } } diff --git a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec index a78a62ca4..e80fb66ea 100644 --- a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec +++ b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec - s.dependency 'DatadogSessionReplay', '3.16.0' + s.dependency 'DatadogSessionReplay', '3.17.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec b/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec index 722e76634..fc9385219 100644 --- a/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec +++ b/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec @@ -23,8 +23,8 @@ Pod::Spec.new do |s| end # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec - s.dependency 'DatadogWebViewTracking', '3.16.0' - s.dependency 'DatadogInternal', '3.16.0' + s.dependency 'DatadogWebViewTracking', '3.17.0' + s.dependency 'DatadogInternal', '3.17.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| From 9cf32f3e5563740d012d5b725b361d4ed06ba16b Mon Sep 17 00:00:00 2001 From: Danyal Khan Date: Wed, 14 Oct 2026 13:54:22 -0400 Subject: [PATCH 2/6] Update Podfile.lock files for iOS SDK 3.17.0 --- benchmarks/ios/Podfile.lock | 80 ++++++++--------- example-new-architecture/ios/Podfile.lock | 78 ++++++++--------- example/ios/Podfile.lock | 100 +++++++++++----------- 3 files changed, 129 insertions(+), 129 deletions(-) diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index eba05292b..ae04b9f50 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -1,26 +1,26 @@ PODS: - boost (1.84.0) - - DatadogCore (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogCrashReporting (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogCore (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogCrashReporting (3.17.0): + - DatadogInternal (= 3.17.0) - KSCrash/Filters (= 2.5.1) - KSCrash/Recording (= 2.5.1) - - DatadogFlags (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogInternal (3.16.0) - - DatadogLogs (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogRUM (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogFlags (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogInternal (3.17.0) + - DatadogLogs (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogRUM (3.17.0): + - DatadogInternal (= 3.17.0) - DatadogSDKReactNative (3.10.0): - - DatadogCore (= 3.16.0) - - DatadogCrashReporting (= 3.16.0) - - DatadogFlags (= 3.16.0) - - DatadogLogs (= 3.16.0) - - DatadogRUM (= 3.16.0) - - DatadogTrace (= 3.16.0) - - DatadogWebViewTracking (= 3.16.0) + - DatadogCore (= 3.17.0) + - DatadogCrashReporting (= 3.17.0) + - DatadogFlags (= 3.17.0) + - DatadogLogs (= 3.17.0) + - DatadogRUM (= 3.17.0) + - DatadogTrace (= 3.17.0) + - DatadogWebViewTracking (= 3.17.0) - DoubleConversion - glog - hermes-engine @@ -43,7 +43,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay (3.10.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.16.0) + - DatadogSessionReplay (= 3.17.0) - DoubleConversion - glog - hermes-engine @@ -65,9 +65,9 @@ PODS: - ReactCommon/turbomodule/core - Yoga - DatadogSDKReactNativeWebView (3.10.0): - - DatadogInternal (= 3.16.0) + - DatadogInternal (= 3.17.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.16.0) + - DatadogWebViewTracking (= 3.17.0) - DoubleConversion - glog - hermes-engine @@ -88,13 +88,13 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSessionReplay (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogTrace (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogSessionReplay (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogTrace (3.17.0): + - DatadogInternal (= 3.17.0) - OpenTelemetry-Swift-Api (~> 2.5.0) - - DatadogWebViewTracking (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogWebViewTracking (3.17.0): + - DatadogInternal (= 3.17.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.78.2) @@ -2134,18 +2134,18 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DatadogCore: 7eed8fbaaa1575503c389f343c230858343c8baf - DatadogCrashReporting: 04f3c3a5bfd9921aef4d44b4ab80accb39c84ddb - DatadogFlags: 033cf48e769c271843e77068c015519880011866 - DatadogInternal: e5a4652ee266986e29c6b8491aff7f78dc3b731d - DatadogLogs: 52792add5827042f619771a23656e2bfb6b0f4e7 - DatadogRUM: b78d608e013f39c5679ed31ad97dad323c6113e8 - DatadogSDKReactNative: 8caef004cba94f3862e9141bdd042a3a6e2ffd95 - DatadogSDKReactNativeSessionReplay: ad1236c24b37d33784bed5ce244f5e46f03a0d69 - DatadogSDKReactNativeWebView: 124786b4d3c359263995405922a995c7c28de750 - DatadogSessionReplay: 932b2077b1a5367de1450479d72093621b9fd618 - DatadogTrace: 5ef84e254e2e1d337bc52e0cdf16396ca102aeb1 - DatadogWebViewTracking: e0470157f55a83680a549d79ba2985e0d17465db + DatadogCore: e2fadbba978370f56fe861ebc1e07c4d7be0449c + DatadogCrashReporting: bc57b765956053d99862ee5f7420b91ec881f971 + DatadogFlags: 93e4f3b84aa89d8307f054815cb359d72188081b + DatadogInternal: 71064dddf89dd1799cc86668efab2b1ad1bc5133 + DatadogLogs: cbdf7b4da6e53474525d4c402b0298e0f9d71c68 + DatadogRUM: cbc60301bee178b2d2c46f056209e72a14d41483 + DatadogSDKReactNative: 6bd6e8ce46e891b19a85e6a247e0ffa7d5752f5a + DatadogSDKReactNativeSessionReplay: 0ebd6d0fcd51475f3107e20ea1c06616e84c1ee8 + DatadogSDKReactNativeWebView: fe0f6473588a8b731f7b1da2a727e3da8365ca54 + DatadogSessionReplay: 70a9c19456527b7acee1d29f76713e77afcabc9c + DatadogTrace: 64b5ca26e0b2c89fffa2ee2771438937f5f7f99c + DatadogWebViewTracking: fb153779d1147ad340f9f9b771558830d98077b4 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: e32d34492c519a2194ec9d7f5e7a79d11b73f91c @@ -2155,7 +2155,7 @@ SPEC CHECKSUMS: KSCrash: 8c4464fd5da7de520f2ce4a00fdf63f169a80f18 OpenTelemetry-Swift-Api: 3be9043f6288eb4ac3cbe7548b5bd45fb4ed5849 OpenTelemetry-Swift-Sdk: d9fa7bc839350f5a81467a5e877f727a8a77712d - RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 + RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 RCTDeprecation: be794de7dc6ed8f9f7fbf525f86e7651b8b68746 RCTRequired: a83787b092ec554c2eb6019ff3f5b8d125472b3b RCTTypeSafety: 48ad3c858926b1c46f46a81a58822b476e178e2c diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index fcc53542d..d874e911f 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,26 +1,26 @@ PODS: - boost (1.84.0) - - DatadogCore (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogCrashReporting (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogCore (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogCrashReporting (3.17.0): + - DatadogInternal (= 3.17.0) - KSCrash/Filters (= 2.5.1) - KSCrash/Recording (= 2.5.1) - - DatadogFlags (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogInternal (3.16.0) - - DatadogLogs (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogRUM (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogFlags (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogInternal (3.17.0) + - DatadogLogs (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogRUM (3.17.0): + - DatadogInternal (= 3.17.0) - DatadogSDKReactNative (3.10.0): - - DatadogCore (= 3.16.0) - - DatadogCrashReporting (= 3.16.0) - - DatadogFlags (= 3.16.0) - - DatadogLogs (= 3.16.0) - - DatadogRUM (= 3.16.0) - - DatadogTrace (= 3.16.0) - - DatadogWebViewTracking (= 3.16.0) + - DatadogCore (= 3.17.0) + - DatadogCrashReporting (= 3.17.0) + - DatadogFlags (= 3.17.0) + - DatadogLogs (= 3.17.0) + - DatadogRUM (= 3.17.0) + - DatadogTrace (= 3.17.0) + - DatadogWebViewTracking (= 3.17.0) - DoubleConversion - glog - hermes-engine @@ -42,13 +42,13 @@ PODS: - ReactCommon/turbomodule/core - Yoga - DatadogSDKReactNative/Tests (3.10.0): - - DatadogCore (= 3.16.0) - - DatadogCrashReporting (= 3.16.0) - - DatadogFlags (= 3.16.0) - - DatadogLogs (= 3.16.0) - - DatadogRUM (= 3.16.0) - - DatadogTrace (= 3.16.0) - - DatadogWebViewTracking (= 3.16.0) + - DatadogCore (= 3.17.0) + - DatadogCrashReporting (= 3.17.0) + - DatadogFlags (= 3.17.0) + - DatadogLogs (= 3.17.0) + - DatadogRUM (= 3.17.0) + - DatadogTrace (= 3.17.0) + - DatadogWebViewTracking (= 3.17.0) - DoubleConversion - glog - hermes-engine @@ -69,11 +69,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogTrace (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogTrace (3.17.0): + - DatadogInternal (= 3.17.0) - OpenTelemetry-Swift-Api (~> 2.5.0) - - DatadogWebViewTracking (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogWebViewTracking (3.17.0): + - DatadogInternal (= 3.17.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1866,15 +1866,15 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: 7eed8fbaaa1575503c389f343c230858343c8baf - DatadogCrashReporting: 04f3c3a5bfd9921aef4d44b4ab80accb39c84ddb - DatadogFlags: 033cf48e769c271843e77068c015519880011866 - DatadogInternal: e5a4652ee266986e29c6b8491aff7f78dc3b731d - DatadogLogs: 52792add5827042f619771a23656e2bfb6b0f4e7 - DatadogRUM: b78d608e013f39c5679ed31ad97dad323c6113e8 - DatadogSDKReactNative: f42948b99dc104ed40598f3e037939dc5b140b2e - DatadogTrace: 5ef84e254e2e1d337bc52e0cdf16396ca102aeb1 - DatadogWebViewTracking: e0470157f55a83680a549d79ba2985e0d17465db + DatadogCore: e2fadbba978370f56fe861ebc1e07c4d7be0449c + DatadogCrashReporting: bc57b765956053d99862ee5f7420b91ec881f971 + DatadogFlags: 93e4f3b84aa89d8307f054815cb359d72188081b + DatadogInternal: 71064dddf89dd1799cc86668efab2b1ad1bc5133 + DatadogLogs: cbdf7b4da6e53474525d4c402b0298e0f9d71c68 + DatadogRUM: cbc60301bee178b2d2c46f056209e72a14d41483 + DatadogSDKReactNative: 65faca5c7a643ebecbc0698939f25354b321a44b + DatadogTrace: 64b5ca26e0b2c89fffa2ee2771438937f5f7f99c + DatadogWebViewTracking: fb153779d1147ad340f9f9b771558830d98077b4 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 @@ -1883,7 +1883,7 @@ SPEC CHECKSUMS: hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 KSCrash: 8c4464fd5da7de520f2ce4a00fdf63f169a80f18 OpenTelemetry-Swift-Api: 3be9043f6288eb4ac3cbe7548b5bd45fb4ed5849 - RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 + RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f72ec23b0..198e87ba9 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,64 +1,64 @@ PODS: - boost (1.84.0) - - DatadogCore (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogCrashReporting (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogCore (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogCrashReporting (3.17.0): + - DatadogInternal (= 3.17.0) - KSCrash/Filters (= 2.5.1) - KSCrash/Recording (= 2.5.1) - - DatadogFlags (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogInternal (3.16.0) - - DatadogLogs (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogRUM (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogFlags (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogInternal (3.17.0) + - DatadogLogs (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogRUM (3.17.0): + - DatadogInternal (= 3.17.0) - DatadogSDKReactNative (3.10.0): - - DatadogCore (= 3.16.0) - - DatadogCrashReporting (= 3.16.0) - - DatadogFlags (= 3.16.0) - - DatadogLogs (= 3.16.0) - - DatadogRUM (= 3.16.0) - - DatadogTrace (= 3.16.0) - - DatadogWebViewTracking (= 3.16.0) + - DatadogCore (= 3.17.0) + - DatadogCrashReporting (= 3.17.0) + - DatadogFlags (= 3.17.0) + - DatadogLogs (= 3.17.0) + - DatadogRUM (= 3.17.0) + - DatadogTrace (= 3.17.0) + - DatadogWebViewTracking (= 3.17.0) - React-Core - DatadogSDKReactNative/Tests (3.10.0): - - DatadogCore (= 3.16.0) - - DatadogCrashReporting (= 3.16.0) - - DatadogFlags (= 3.16.0) - - DatadogLogs (= 3.16.0) - - DatadogRUM (= 3.16.0) - - DatadogTrace (= 3.16.0) - - DatadogWebViewTracking (= 3.16.0) + - DatadogCore (= 3.17.0) + - DatadogCrashReporting (= 3.17.0) + - DatadogFlags (= 3.17.0) + - DatadogLogs (= 3.17.0) + - DatadogRUM (= 3.17.0) + - DatadogTrace (= 3.17.0) + - DatadogWebViewTracking (= 3.17.0) - React-Core - DatadogSDKReactNativeSessionReplay (3.10.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.16.0) + - DatadogSessionReplay (= 3.17.0) - React-Core - DatadogSDKReactNativeSessionReplay/Tests (3.10.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.16.0) + - DatadogSessionReplay (= 3.17.0) - React-Core - React-RCTText - DatadogSDKReactNativeWebView (3.10.0): - - DatadogInternal (= 3.16.0) + - DatadogInternal (= 3.17.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.16.0) + - DatadogWebViewTracking (= 3.17.0) - React-Core - DatadogSDKReactNativeWebView/Tests (3.10.0): - - DatadogInternal (= 3.16.0) + - DatadogInternal (= 3.17.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.16.0) + - DatadogWebViewTracking (= 3.17.0) - React-Core - react-native-webview - React-RCTText - - DatadogSessionReplay (3.16.0): - - DatadogInternal (= 3.16.0) - - DatadogTrace (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogSessionReplay (3.17.0): + - DatadogInternal (= 3.17.0) + - DatadogTrace (3.17.0): + - DatadogInternal (= 3.17.0) - OpenTelemetry-Swift-Api (~> 2.5.0) - - DatadogWebViewTracking (3.16.0): - - DatadogInternal (= 3.16.0) + - DatadogWebViewTracking (3.17.0): + - DatadogInternal (= 3.17.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1966,18 +1966,18 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: 7eed8fbaaa1575503c389f343c230858343c8baf - DatadogCrashReporting: 04f3c3a5bfd9921aef4d44b4ab80accb39c84ddb - DatadogFlags: 033cf48e769c271843e77068c015519880011866 - DatadogInternal: e5a4652ee266986e29c6b8491aff7f78dc3b731d - DatadogLogs: 52792add5827042f619771a23656e2bfb6b0f4e7 - DatadogRUM: b78d608e013f39c5679ed31ad97dad323c6113e8 - DatadogSDKReactNative: f2e6414d17e8f85ebe86fce34b44ca37c7d9ab14 - DatadogSDKReactNativeSessionReplay: 5b6cd51dad74a960ae5f05d0235673f2a64e0a97 - DatadogSDKReactNativeWebView: cc85c97abd690957b7519089e38244275b323200 - DatadogSessionReplay: 932b2077b1a5367de1450479d72093621b9fd618 - DatadogTrace: 5ef84e254e2e1d337bc52e0cdf16396ca102aeb1 - DatadogWebViewTracking: e0470157f55a83680a549d79ba2985e0d17465db + DatadogCore: e2fadbba978370f56fe861ebc1e07c4d7be0449c + DatadogCrashReporting: bc57b765956053d99862ee5f7420b91ec881f971 + DatadogFlags: 93e4f3b84aa89d8307f054815cb359d72188081b + DatadogInternal: 71064dddf89dd1799cc86668efab2b1ad1bc5133 + DatadogLogs: cbdf7b4da6e53474525d4c402b0298e0f9d71c68 + DatadogRUM: cbc60301bee178b2d2c46f056209e72a14d41483 + DatadogSDKReactNative: 87505fd8d04aa7106e75704d2bd4a5ff99e64091 + DatadogSDKReactNativeSessionReplay: 0a8dd7559f182c395897c3ed91bd750b3e2d8e6d + DatadogSDKReactNativeWebView: 52e9187fe9bb57b3b3ad99deba83a8b2d1427fe5 + DatadogSessionReplay: 70a9c19456527b7acee1d29f76713e77afcabc9c + DatadogTrace: 64b5ca26e0b2c89fffa2ee2771438937f5f7f99c + DatadogWebViewTracking: fb153779d1147ad340f9f9b771558830d98077b4 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 @@ -1987,7 +1987,7 @@ SPEC CHECKSUMS: HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352 KSCrash: 8c4464fd5da7de520f2ce4a00fdf63f169a80f18 OpenTelemetry-Swift-Api: 3be9043f6288eb4ac3cbe7548b5bd45fb4ed5849 - RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 + RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f From 1ad13d2ad543d96a2e393d3e787299a8f3676176 Mon Sep 17 00:00:00 2001 From: Danyal Khan Date: Wed, 14 Oct 2026 14:03:22 -0400 Subject: [PATCH 3/6] Fix the malformed serial ID test comment --- packages/core/ios/Tests/DdFlagsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ios/Tests/DdFlagsTests.swift b/packages/core/ios/Tests/DdFlagsTests.swift index 915843485..fb75182cc 100644 --- a/packages/core/ios/Tests/DdFlagsTests.swift +++ b/packages/core/ios/Tests/DdFlagsTests.swift @@ -453,7 +453,7 @@ class DdFlagsTests: XCTestCase { } func testDictionaryToFlagAssignmentIgnoresMalformedSerialID() { - // A malformed serial ID binds to the serial ID. It must not reject the assignment, + // A malformed serial ID yields no serial ID. It must not reject the assignment, // because that would drop the exposure event for the whole flag. let malformedValues: [Any] = ["not-a-number", "", true, 340132] From d57d77e468e985aaaa7c4d44efca0ea2ba5c83d5 Mon Sep 17 00:00:00 2001 From: Danyal Khan Date: Wed, 14 Oct 2026 14:15:18 -0400 Subject: [PATCH 4/6] Support iOS SDK 3.17.0 API changes --- packages/core/ios/Sources/DdFlagsImplementation.swift | 3 +++ packages/core/ios/Tests/DdFlagsTests.swift | 2 ++ packages/core/ios/Tests/MockDatadogCore.swift | 2 ++ .../internal-testing-tools/ios/Sources/DatadogCoreProxy.swift | 2 ++ .../ios/Tests/DdSessionReplayTests.swift | 2 ++ .../ios/Tests/DatadogSDKReactNativeWebViewTests.swift | 2 ++ 6 files changed, 13 insertions(+) diff --git a/packages/core/ios/Sources/DdFlagsImplementation.swift b/packages/core/ios/Sources/DdFlagsImplementation.swift index d0a050069..f20c2fc5e 100644 --- a/packages/core/ios/Sources/DdFlagsImplementation.swift +++ b/packages/core/ios/Sources/DdFlagsImplementation.swift @@ -94,6 +94,9 @@ public class DdFlagsImplementation: NSObject { case .networkError: errorCode = "NETWORK_ERROR" errorMessage = "A network error occurred while fetching feature flags for client '\(clientName)'." + case .initializationTimedOut: + errorCode = "INITIALIZATION_TIMED_OUT" + errorMessage = "Feature flags for client '\(clientName)' did not become ready before the initialization timeout." } reject(errorCode, errorMessage, error as NSError) } diff --git a/packages/core/ios/Tests/DdFlagsTests.swift b/packages/core/ios/Tests/DdFlagsTests.swift index fb75182cc..001a0f11f 100644 --- a/packages/core/ios/Tests/DdFlagsTests.swift +++ b/packages/core/ios/Tests/DdFlagsTests.swift @@ -492,6 +492,8 @@ class DdFlagsTests: XCTestCase { } private class FlagsTestCore: DatadogCoreProtocol { + var remoteConfiguration: RemoteConfiguration? { nil } + private var features: [String: DatadogFeature] = [:] func register(feature: T) throws where T : DatadogFeature { diff --git a/packages/core/ios/Tests/MockDatadogCore.swift b/packages/core/ios/Tests/MockDatadogCore.swift index 81556fb52..31ed4ba1e 100644 --- a/packages/core/ios/Tests/MockDatadogCore.swift +++ b/packages/core/ios/Tests/MockDatadogCore.swift @@ -9,6 +9,8 @@ import Foundation @testable import DatadogInternal internal class MockDatadogCore: DatadogCoreProtocol { + var remoteConfiguration: RemoteConfiguration? { nil } + func send(message: FeatureMessage, else fallback: @escaping () -> Void) { if // Configuration Telemetry Message case .telemetry(let telemetry) = message, diff --git a/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift b/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift index 0d7e1dec1..05810f924 100644 --- a/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift +++ b/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift @@ -10,6 +10,8 @@ import DatadogInternal internal class DatadogCoreProxy: DatadogCoreProtocol { let core: DatadogCoreProtocol + + var remoteConfiguration: RemoteConfiguration? { core.remoteConfiguration } private var featureScopeInterceptors: [String: FeatureScopeInterceptor] = [:] diff --git a/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift index 741f8318a..a99a01d2f 100644 --- a/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift +++ b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift @@ -310,6 +310,8 @@ private class MockFabricWrapper: RCTFabricWrapper { } private class MockDatadogCore: DatadogCoreProtocol { + var remoteConfiguration: RemoteConfiguration? { nil } + func mostRecentModifiedFileAt(before: Date) throws -> Date? { return nil } diff --git a/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift b/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift index 20f1b84fa..fa59697e5 100644 --- a/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift +++ b/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift @@ -261,6 +261,8 @@ private class MockDatadogWebViewDelegate: NSObject, RCTDatadogWebViewDelegate { } private class MockDatadogCore: DatadogCoreProtocol { + var remoteConfiguration: RemoteConfiguration? { nil } + func mostRecentModifiedFileAt(before: Date) throws -> Date? { return nil } From 2d41b21651e41a962b27ecbbf1f0c8732af803e9 Mon Sep 17 00:00:00 2001 From: Danyal Khan Date: Wed, 14 Oct 2026 14:27:54 -0400 Subject: [PATCH 5/6] Test the initialization timeout error mapping --- packages/core/ios/Tests/DdFlagsTests.swift | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/core/ios/Tests/DdFlagsTests.swift b/packages/core/ios/Tests/DdFlagsTests.swift index 001a0f11f..a574f9e93 100644 --- a/packages/core/ios/Tests/DdFlagsTests.swift +++ b/packages/core/ios/Tests/DdFlagsTests.swift @@ -126,6 +126,26 @@ class DdFlagsTests: XCTestCase { waitForExpectations(timeout: 1.0, handler: nil) } + func testSetEvaluationContextInitializationTimedOut() { + let expectation = self.expectation(description: "SetEvaluationContext rejects with initialization timeout") + + let mockClient = MockFlagsClient() + mockClient.errorToReturn = .initializationTimedOut + + implementation.clientProviders["test_client"] = { mockClient } + + implementation.setEvaluationContext("test_client", targetingKey: "user_1", attributes: [:], resolve: { result in + XCTFail("Should not resolve") + expectation.fulfill() + }, reject: { code, message, error in + XCTAssertEqual(code, "INITIALIZATION_TIMED_OUT") + XCTAssertNotNil(message) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1.0, handler: nil) + } + func testTrackEvaluation() { let expectation = self.expectation(description: "TrackEvaluation resolves") From 3cd669085b1c846288943bd8151ec26a9c5ea164 Mon Sep 17 00:00:00 2001 From: Danyal Khan Date: Wed, 14 Oct 2026 14:48:59 -0400 Subject: [PATCH 6/6] Fix the race in testTracingConcurrently --- packages/core/ios/Tests/DdTraceTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/ios/Tests/DdTraceTests.swift b/packages/core/ios/Tests/DdTraceTests.swift index 3de9978ee..46c4104b9 100644 --- a/packages/core/ios/Tests/DdTraceTests.swift +++ b/packages/core/ios/Tests/DdTraceTests.swift @@ -144,21 +144,21 @@ internal class DdTraceTests: XCTestCase { } func testTracingConcurrently() { - // It is possible and acceptable that a concurrent finishSpan resolve is called on a different thread before we get the lastResolveValue. - // This would override lastResolveValue to be `nil`. To avoid this, we use a different resolve. + // Each iteration captures its own span ID: the shared lastResolveValue can be overwritten by + // another iteration between startSpan and finishSpan, which would leave a span unfinished. func mockFinishResolve(args: Any?) {} let iterationCount = 30 DispatchQueue.concurrentPerform(iterations: iterationCount) { iteration in + var spanID: NSString? tracer.startSpan( operation: ("concurrent_test_span_\(iteration)" as NSString) as String, context: testTags, timestampMs: 0, - resolve: mockResolve, + resolve: { spanID = $0 as? NSString }, reject: mockReject ) - let spanID = lastResolveValue as! NSString - tracer.finishSpan(spanId: spanID, context: testTags, timestampMs: 100, resolve: mockFinishResolve, reject: mockReject) + tracer.finishSpan(spanId: spanID!, context: testTags, timestampMs: 100, resolve: mockFinishResolve, reject: mockReject) } XCTAssertEqual(mockNativeTracer.startedSpans.count, iterationCount, "\(mockNativeTracer.startedSpans)")