From c58580f390350f711b1b4b26a44e41bbb55e8f53 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Fri, 28 Nov 2025 12:07:53 +0100 Subject: [PATCH 001/114] [webview, feat] Full support for new architecture --- .../react-native-webview/android/build.gradle | 9 +- .../webview/DdSdkReactNativeWebViewManager.kt | 125 ++++++++++++++ .../webview/DdSdkReactNativeWebViewPackage.kt | 8 +- .../webview/DdSdkReactNativeWebViewManager.kt | 11 +- .../reactnative/tools/unit/GenericAssert.kt | 2 +- .../webview/DatadogWebViewTest.kt | 3 +- .../reactnative/webview/DatadogWebViewTest.kt | 122 ++++++++++++++ .../WebviewDatadogInjectedJS.test.tsx | 88 +++------- .../src/__tests__/webview-js-utils.test.ts | 157 +++++++++++++++++- packages/react-native-webview/src/index.tsx | 37 ++--- .../src/specs/NativeDdWebView.ts | 8 +- .../src/utils/env-utils.ts | 9 - .../src/utils/webview-js-utils.ts | 106 ++++++------ 13 files changed, 514 insertions(+), 171 deletions(-) create mode 100644 packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt rename packages/react-native-webview/android/src/test/kotlin/{ => main}/com/datadog/reactnative/tools/unit/GenericAssert.kt (92%) rename packages/react-native-webview/android/src/test/{kotlin/com/datadog/reactnative => }/webview/DatadogWebViewTest.kt (96%) create mode 100644 packages/react-native-webview/android/src/testNewArch/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt delete mode 100644 packages/react-native-webview/src/utils/env-utils.ts diff --git a/packages/react-native-webview/android/build.gradle b/packages/react-native-webview/android/build.gradle index 973b3ec5f..a0c407316 100644 --- a/packages/react-native-webview/android/build.gradle +++ b/packages/react-native-webview/android/build.gradle @@ -134,7 +134,13 @@ android { } test { - java.srcDir("src/test/kotlin") + java.srcDir('src/test/kotlin') + + if (isNewArchitectureEnabled()) { + java.srcDirs += ['src/testNewArch/kotlin'] + } else { + java.srcDirs += ['src/testOldArch/kotlin'] + } } } @@ -208,6 +214,7 @@ dependencies { testImplementation "com.github.xgouchet.Elmyr:jvm:1.3.1" testImplementation "org.mockito.kotlin:mockito-kotlin:5.1.0" testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + testImplementation 'org.json:json:20160810' detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8" detektPlugins "io.gitlab.arturbosch.detekt:detekt-rules-libraries:1.23.8" diff --git a/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt b/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt new file mode 100644 index 000000000..71c30c406 --- /dev/null +++ b/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt @@ -0,0 +1,125 @@ +/* +* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +* This product includes software developed at Datadog (https://www.datadoghq.com/). +* Copyright 2016-Present Datadog, Inc. +*/ + +package com.datadog.reactnative.webview + +import android.annotation.SuppressLint +import com.datadog.android.api.SdkCore +import com.datadog.android.webview.WebViewTracking +import com.datadog.reactnative.DatadogSDKWrapperStorage +import com.facebook.react.bridge.ReactContext +import com.facebook.react.uimanager.ThemedReactContext +import com.reactnativecommunity.webview.RNCWebView +import com.reactnativecommunity.webview.RNCWebViewClient +import com.reactnativecommunity.webview.RNCWebViewManager +import com.reactnativecommunity.webview.RNCWebViewWrapper +import org.json.JSONArray + +/** + * The entry point to use Datadog auto-instrumented WebView feature. + */ +class DdSdkReactNativeWebViewManager( + private val reactContext: ReactContext +) : RNCWebViewManager() { + // The name used to reference this custom View from React Native. + override fun getName(): String { + return VIEW_NAME + } + + /** + * The instance of Datadog SDK Core. + */ + @Volatile private var _datadogCore: SdkCore? = null + val datadogCore: SdkCore? + get() = _datadogCore + + init { + DatadogSDKWrapperStorage.addOnInitializedListener { core -> + _datadogCore = core + } + } + + /** + * Intercepts the WebView wrapper instance before it is returned and ensures that + * JavaScript is enabled on the underlying WebView. JavaScript must be enabled + * for Datadog WebView tracking to function correctly. + */ + @SuppressLint("SetJavaScriptEnabled") + override fun createViewInstance(context: ThemedReactContext): RNCWebViewWrapper { + val viewInstance = super.createViewInstance(context) + viewInstance.webView.settings.javaScriptEnabled = true + return viewInstance + } + + /** + * Intercepts the JavaScript injected before the WebView loads. + * + * In the New Architecture, WebView props from React Native are ignored, + * so this callback is the only reliable place to extract the + * `// #allowedHosts=` configuration and apply Datadog WebView tracking. + */ + override fun setInjectedJavaScriptBeforeContentLoaded( + view: RNCWebViewWrapper?, + value: String? + ) { + val allowedHosts = value?.let { extractAllowedHosts(it) } + val webView = view?.webView + + if (allowedHosts != null && webView != null) { + configureWebViewTracking(webView, allowedHosts) + } + + super.setInjectedJavaScriptBeforeContentLoaded(view, value) + } + + private fun configureWebViewTracking(webView: RNCWebView, allowedHosts: List) { + val datadogCore = _datadogCore + if (datadogCore != null) { + WebViewTracking.enable( + webView, + allowedHosts = allowedHosts, + sdkCore = datadogCore + ) + } else { + DatadogSDKWrapperStorage.addOnInitializedListener { core -> + reactContext.runOnUiQueueThread { + WebViewTracking.enable( + webView, + allowedHosts = allowedHosts, + sdkCore = core + ) + } + } + } + } + + override fun addEventEmitters( + reactContext: ThemedReactContext, + view: RNCWebViewWrapper + ) { + view.webView.webViewClient = RNCWebViewClient() + } + + companion object { + // The name used to reference this custom View from React Native. + const val VIEW_NAME = "DdReactNativeWebView" + + private fun extractAllowedHosts(input: String): List? { + // Regex that captures everything after "// #allowedHosts=" + val regex = Regex("""//\s*#allowedHosts\s*=\s*(.+)""") + + val match = regex.find(input) ?: return null + val jsonString = match.groupValues[1].trim() + + return try { + val jsonArray = JSONArray(jsonString) + (0 until jsonArray.length()).map { jsonArray.getString(it) } + } catch (e: Exception) { + null + } + } + } +} diff --git a/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt b/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt index ada4cc97c..be3b22ee2 100644 --- a/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt +++ b/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt @@ -3,7 +3,6 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ - package com.datadog.reactnative.webview import com.facebook.react.TurboReactPackage @@ -12,17 +11,18 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.module.model.ReactModuleInfo import com.facebook.react.module.model.ReactModuleInfoProvider import com.facebook.react.uimanager.ViewManager -import com.reactnativecommunity.webview.RNCWebViewManager +import com.reactnativecommunity.webview.RNCWebViewModule +import com.reactnativecommunity.webview.RNCWebViewModuleImpl class DdSdkReactNativeWebViewPackage : TurboReactPackage() { override fun createViewManagers( reactContext: ReactApplicationContext ): MutableList> { return mutableListOf( - RNCWebViewManager() + DdSdkReactNativeWebViewManager(reactContext) ) } - + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { return null } diff --git a/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt index 911ccb989..cce4dffe5 100644 --- a/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt +++ b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt @@ -26,10 +26,9 @@ class DdSdkReactNativeWebViewManager( private val reactContext: ReactContext ) : RNCWebViewManager() { // The name used to reference this custom View from React Native. - companion object { - const val VIEW_NAME = "DdReactNativeWebView" + override fun getName(): String { + return VIEW_NAME } - /** * The instance of Datadog SDK Core. */ @@ -105,8 +104,8 @@ class DdSdkReactNativeWebViewManager( _isWebViewTrackingEnabled = true } - // The name used to reference this custom View from React Native. - override fun getName(): String { - return VIEW_NAME + companion object { + // The name used to reference this custom View from React Native. + const val VIEW_NAME = "DdReactNativeWebView" } } diff --git a/packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt b/packages/react-native-webview/android/src/test/kotlin/main/com/datadog/reactnative/tools/unit/GenericAssert.kt similarity index 92% rename from packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt rename to packages/react-native-webview/android/src/test/kotlin/main/com/datadog/reactnative/tools/unit/GenericAssert.kt index 8248b6224..4756fb86b 100644 --- a/packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt +++ b/packages/react-native-webview/android/src/test/kotlin/main/com/datadog/reactnative/tools/unit/GenericAssert.kt @@ -4,7 +4,7 @@ * Copyright 2016-Present Datadog, Inc. */ -package com.datadog.reactnative.tools.unit +package main.reactnative.tools.unit import org.assertj.core.api.AbstractAssert diff --git a/packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt b/packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt similarity index 96% rename from packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt rename to packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt index e9cf9ce03..0772698dc 100644 --- a/packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt +++ b/packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt @@ -10,7 +10,8 @@ import com.datadog.android.api.SdkCore import com.datadog.android.core.InternalSdkCore import com.datadog.android.webview.WebViewTracking import com.datadog.reactnative.DatadogSDKWrapperStorage -import com.datadog.reactnative.tools.unit.GenericAssert.Companion.assertThat +import com.datadog.reactnative.webview.DdSdkReactNativeWebViewManager +import main.reactnative.tools.unit.GenericAssert.Companion.assertThat import com.facebook.react.bridge.JavaOnlyArray import com.facebook.react.uimanager.ThemedReactContext import com.reactnativecommunity.webview.RNCWebView diff --git a/packages/react-native-webview/android/src/testNewArch/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt b/packages/react-native-webview/android/src/testNewArch/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt new file mode 100644 index 000000000..581add1f7 --- /dev/null +++ b/packages/react-native-webview/android/src/testNewArch/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt @@ -0,0 +1,122 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.reactnative.webview + +import com.datadog.android.api.SdkCore +import com.datadog.android.core.InternalSdkCore +import com.datadog.android.webview.WebViewTracking +import com.datadog.reactnative.DatadogSDKWrapperStorage +import main.reactnative.tools.unit.GenericAssert.Companion.assertThat +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.uimanager.ThemedReactContext +import com.reactnativecommunity.webview.RNCWebView +import com.reactnativecommunity.webview.RNCWebViewWrapper +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +internal class DatadogWebViewTest { + + @Mock + lateinit var themedReactContext: ThemedReactContext + + @Mock + lateinit var datadogCore: InternalSdkCore + + private lateinit var webViewTrackingMockedStatic: MockedStatic + + @BeforeEach + fun `set up`() { + whenever(themedReactContext.runOnUiQueueThread(any())).thenAnswer { answer -> + answer.getArgument(0).run() + true + } + + webViewTrackingMockedStatic = Mockito.mockStatic(WebViewTracking::class.java) + webViewTrackingMockedStatic.`when` { + WebViewTracking.enable( + webView = any(), // Mock the WebView parameter + allowedHosts = any(), // Mock the list of allowed hosts + logsSampleRate = any(), // Mock the logsSampleRate parameter + sdkCore = any() // Mock the SdkCore parameter + ) + }.then {} // Return Unit as the function has no return value + } + + @AfterEach + fun `tear down`() { + webViewTrackingMockedStatic.close() + } + + @Test + fun `Datadog Core is set once initialized`() { + val manager = DdSdkReactNativeWebViewManager(themedReactContext) + assertThat(manager.datadogCore).isNull() + + DatadogSDKWrapperStorage.notifyOnInitializedListeners(datadogCore) + + assertThat(manager.datadogCore).isNotNull() + assertThat(manager.datadogCore).isInstanceOf(SdkCore::class.java) + } + + @Test + fun `Registers to SdkCore listener if the SDK is not initialized`() { + // ========= + // Given + // ========= + val manager = DdSdkReactNativeWebViewManager(themedReactContext) + + // When first initialized, the WebView manager core should be null + assertThat(manager.datadogCore).isNull() + + // ========= + // When + // ========= + val rncWebView = mock(RNCWebView::class.java) + val rncWebViewWrapper = mock(RNCWebViewWrapper::class.java) + whenever(rncWebViewWrapper.webView) doReturn rncWebView + + // When JS sends allowedHosts through 'injectedJavaScriptBeforeContentLoaded' prop + manager.setInjectedJavaScriptBeforeContentLoaded( + rncWebViewWrapper, + "// #allowedHosts=[\"example.com\",\"test.com\"]" + ) + + // ========= + // Then + // ========= + + // When we notify listeners that the core is available... + DatadogSDKWrapperStorage.notifyOnInitializedListeners(datadogCore) + + // ...the WebView should enable WebView tracking in the UI Thread. + verify(themedReactContext).runOnUiQueueThread(any()) + + // Native WebView tracking should be called + val arrayList = ArrayList(listOf("example.com", "test.com")) + webViewTrackingMockedStatic.verify { + WebViewTracking.enable(rncWebView, arrayList, 100.0f, datadogCore) + } + } +} diff --git a/packages/react-native-webview/src/__tests__/WebviewDatadogInjectedJS.test.tsx b/packages/react-native-webview/src/__tests__/WebviewDatadogInjectedJS.test.tsx index f3627a3c8..9feb45a22 100644 --- a/packages/react-native-webview/src/__tests__/WebviewDatadogInjectedJS.test.tsx +++ b/packages/react-native-webview/src/__tests__/WebviewDatadogInjectedJS.test.tsx @@ -3,13 +3,11 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ - import { render } from '@testing-library/react-native'; import { WebView as RNWebView } from 'react-native-webview'; import React from 'react'; import { WebView } from '../index'; -import { isNewArchitecture } from '../utils/env-utils'; import { dedent } from './__utils__/string-utils'; @@ -28,7 +26,7 @@ jest.mock('react-native-webview', () => { const callfunction = jest.fn(); const postMessageMock = jest.fn(); -window['ReactNativeWebView'] = { +(window as any)['ReactNativeWebView'] = { postMessage: postMessageMock }; @@ -129,19 +127,18 @@ describe('Webview', () => { const realInjectedJs = dedent( mockedWebView.mock.calls[0][0].injectedJavaScript ?? '' ); - const expected = dedent(` - try{ - testInjectedJavaScript() - } - catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - window.ReactNativeWebView.postMessage(JSON.stringify({ - source: 'DATADOG', - type: 'ERROR', - message: errorMsg - })); - true; - }`); + const expected = dedent(`try{ + testInjectedJavaScript() +} +catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + window.ReactNativeWebView.postMessage(JSON.stringify({ + source: 'DATADOG', + type: 'ERROR', + message: errorMsg + })); + true; +}`); expect(realInjectedJs).toBe(expected); }); @@ -167,51 +164,20 @@ describe('Webview', () => { mockedWebView.mock.calls[0][0] .injectedJavaScriptBeforeContentLoaded ?? '' ); - let expected; - - if (isNewArchitecture()) { - expected = dedent(` - window.DatadogEventBridge = { - send(msg) { - window.ReactNativeWebView.postMessage(JSON.stringify({ - source: 'DATADOG', - type: 'NATIVE_EVENT', - message: msg - })); - true; - }, - getAllowedWebViewHosts() { - return '["localhost","example.com"]' - } - }; - try{ - testInjectedJavaScript() - } - catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - window.ReactNativeWebView.postMessage(JSON.stringify({ - source: 'DATADOG', - type: 'ERROR', - message: errorMsg - })); - true; - } - `); - } else { - expected = dedent(` - try{ - testInjectedJavaScript() - } - catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - window.ReactNativeWebView.postMessage(JSON.stringify({ - source: 'DATADOG', - type: 'ERROR', - message: errorMsg - })); - true; - }`); - } + const expected = dedent(`// #allowedHosts=["localhost","example.com"] + +try{ + testInjectedJavaScript() +} +catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + window.ReactNativeWebView.postMessage(JSON.stringify({ + source: 'DATADOG', + type: 'ERROR', + message: errorMsg + })); + true; +}`); expect(realInjectedJs).toBe(expected); }); diff --git a/packages/react-native-webview/src/__tests__/webview-js-utils.test.ts b/packages/react-native-webview/src/__tests__/webview-js-utils.test.ts index b09ea8dbc..6091ebd53 100644 --- a/packages/react-native-webview/src/__tests__/webview-js-utils.test.ts +++ b/packages/react-native-webview/src/__tests__/webview-js-utils.test.ts @@ -4,7 +4,10 @@ * Copyright 2016-Present Datadog, Inc. */ -import { wrapJsCodeInTryAndCatch } from '../utils/webview-js-utils'; +import { + wrapJsCodeInTryAndCatch, + wrapJsCodeWithAllowedHosts +} from '../utils/webview-js-utils'; import { dedent } from './__utils__/string-utils'; @@ -13,13 +16,73 @@ describe('WebView JS Utils', () => { jest.clearAllMocks(); }); - describe('M wrapJsCodeInTryCatch wraps JS code in try & catch with DD messaging W jsCode is not null', () => { - it('M returns the JS code wrapped in try and catch', () => { + describe('M wrapJsCodeWithAllowedHosts wraps JS code in try & catch with DD messaging W jsCode is not null', () => { + it('M returns the allowedHosts JS comment W { jsCode = undefined & valid allowedHosts }', () => { // Given + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const jsCode = undefined; + + // When + const wrappedCode = wrapJsCodeWithAllowedHosts(jsCode, [ + 'example.com', + 'test.com' + ]); + + // Then + const expected = '// #allowedHosts=["example.com","test.com"]\n'; + expect(wrappedCode).toBeDefined(); + expect(dedent(wrappedCode as string)).toBe(expected); + expect(warnSpy).not.toHaveBeenCalled(); + + warnSpy.mockRestore(); + }); + + it('M returns the JS code wrapped in try and catch with allowedHosts comment W { custom JS code & valid allowedHosts }', () => { + // Given + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); const jsCode = "console.log('test')"; // When - const wrappedCode = wrapJsCodeInTryAndCatch(jsCode); + const wrappedCode = wrapJsCodeWithAllowedHosts(jsCode, [ + 'example.com', + 'test.com' + ]); + + // Then + const expected = dedent(`// #allowedHosts=["example.com","test.com"] + +try{ + console.log('test') +} +catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + window.ReactNativeWebView.postMessage(JSON.stringify({ + source: 'DATADOG', + type: 'ERROR', + message: errorMsg + })); + true; +}`); + expect(wrappedCode).toBeDefined(); + expect(dedent(wrappedCode as string)).toBe(expected); + expect(warnSpy).not.toHaveBeenCalled(); + + warnSpy.mockRestore(); + }); + + it('M returns the JS code wrapped in try and catch W { custom JS code & allowedHosts = undefined }', () => { + // Given + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const jsCode = "console.log('test')"; + + // When + const wrappedCode = wrapJsCodeWithAllowedHosts(jsCode, undefined); // Then const expected = dedent(` @@ -37,15 +100,97 @@ describe('WebView JS Utils', () => { }`); expect(wrappedCode).toBeDefined(); expect(dedent(wrappedCode as string)).toBe(expected); + expect(warnSpy).toHaveBeenCalledWith( + "[@datadog/mobile-react-native-webview] Invalid 'allowedHosts' format: Error: allowedHosts is undefined" + ); + + warnSpy.mockRestore(); }); - it('M returns undefined W { jsCode = undefined }', () => { + it('M returns the JS code wrapped in try and catch W { custom JS code & invalid JSON allowedHosts }', () => { + // Given + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const jsCode = "console.log('test')"; + + // When + const wrappedCode = wrapJsCodeWithAllowedHosts( + jsCode, + (() => 'incompatible') as any + ); + + // Then + const expected = dedent(` + try{ + console.log('test') + } + catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + window.ReactNativeWebView.postMessage(JSON.stringify({ + source: 'DATADOG', + type: 'ERROR', + message: errorMsg + })); + true; + }`); + expect(wrappedCode).toBeDefined(); + expect(dedent(wrappedCode as string)).toBe(expected); + expect(warnSpy).toHaveBeenCalledWith( + "[@datadog/mobile-react-native-webview] Invalid 'allowedHosts' format: Error: JSON.stringify returned 'undefined' for the given hosts" + ); + + warnSpy.mockRestore(); + }); + + it('M returns undefined W { jsCode = undefined & allowedHosts = undefined }', () => { // Given const jsCode = undefined; + const allowedHosts = undefined; // When - const wrappedCode = wrapJsCodeInTryAndCatch(jsCode); + const wrappedCode = wrapJsCodeWithAllowedHosts( + jsCode, + allowedHosts + ); // Then expect(wrappedCode).toBe(undefined); }); }); + + describe('M wrapJsCodeInTryAndCatch wraps the given JS code in a try & catch block with DD messaging', () => { + it('M returns the JS code wrapped in try and catch W { custom JS code is defined }', () => { + // Given + const jsCode = "console.log('test')"; + + // When + const wrappedCode = wrapJsCodeInTryAndCatch(jsCode); + + // Then + const expected = dedent(`try{ + console.log('test') +} +catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + window.ReactNativeWebView.postMessage(JSON.stringify({ + source: 'DATADOG', + type: 'ERROR', + message: errorMsg + })); + true; +}`); + expect(wrappedCode).toBeDefined(); + expect(dedent(wrappedCode as string)).toBe(expected); + }); + + it('M returns undefined W { custom JS code is undefined }', () => { + // Given + const jsCode = undefined; + + // When + const wrappedCode = wrapJsCodeInTryAndCatch(jsCode); + + // Then + expect(wrappedCode).toBeUndefined(); + }); + }); }); diff --git a/packages/react-native-webview/src/index.tsx b/packages/react-native-webview/src/index.tsx index 3812e952c..775dd9ab9 100644 --- a/packages/react-native-webview/src/index.tsx +++ b/packages/react-native-webview/src/index.tsx @@ -5,15 +5,14 @@ */ import type { WebViewMessageEvent, WebViewProps } from 'react-native-webview'; import { WebView as RNWebView } from 'react-native-webview'; -import React, { forwardRef, useCallback } from 'react'; +import React, { forwardRef, useCallback, useMemo } from 'react'; import NativeDdLogs from './ext-specs/NativeDdLogs'; import { NativeDdSdk } from './ext-specs/NativeDdSdk'; import { NativeDdWebView } from './specs/NativeDdWebView'; -import { isNewArchitecture } from './utils/env-utils'; import { - getWebViewEventBridgingJS, - wrapJsCodeInTryAndCatch + wrapJsCodeInTryAndCatch, + wrapJsCodeWithAllowedHosts } from './utils/webview-js-utils'; import type { DatadogMessageFormat } from './utils/webview-js-utils'; @@ -71,18 +70,16 @@ const WebViewComponent = (props: Props, ref: React.Ref>) => { [userDefinedOnMessage, props.logUserCodeErrors] ); - const getInjectedJavascriptBeforeContentLoaded = (): string | undefined => { - if (isNewArchitecture()) { - return getWebViewEventBridgingJS( - props.allowedHosts, - props.injectedJavaScriptBeforeContentLoaded - ); - } else { - return wrapJsCodeInTryAndCatch( - props.injectedJavaScriptBeforeContentLoaded - ); - } - }; + const injectedJavascript = useMemo(() => { + return wrapJsCodeInTryAndCatch(props.injectedJavaScript); + }, [props.injectedJavaScript]); + + const injectedJavascriptBeforeContentLoaded = useMemo(() => { + return wrapJsCodeWithAllowedHosts( + props.injectedJavaScriptBeforeContentLoaded, + props.allowedHosts + ); + }, [props.injectedJavaScriptBeforeContentLoaded, props.allowedHosts]); return ( >) => { allowedHosts: props.allowedHosts } }} - injectedJavaScript={wrapJsCodeInTryAndCatch( - props.injectedJavaScript - )} - injectedJavaScriptBeforeContentLoaded={getInjectedJavascriptBeforeContentLoaded()} + injectedJavaScript={injectedJavascript} + injectedJavaScriptBeforeContentLoaded={ + injectedJavascriptBeforeContentLoaded + } ref={ref} /> ); diff --git a/packages/react-native-webview/src/specs/NativeDdWebView.ts b/packages/react-native-webview/src/specs/NativeDdWebView.ts index 351b26a16..a3c3ed28a 100644 --- a/packages/react-native-webview/src/specs/NativeDdWebView.ts +++ b/packages/react-native-webview/src/specs/NativeDdWebView.ts @@ -7,10 +7,8 @@ import type { CommonNativeWebViewProps } from 'react-native-webview/lib/WebViewTypes'; import { requireNativeComponent } from 'react-native'; -import { isNewArchitecture } from '../utils/env-utils'; - -const NativeDdWebView = !isNewArchitecture() - ? requireNativeComponent('DdReactNativeWebView') - : undefined; +const NativeDdWebView = requireNativeComponent( + 'DdReactNativeWebView' +); export { NativeDdWebView }; diff --git a/packages/react-native-webview/src/utils/env-utils.ts b/packages/react-native-webview/src/utils/env-utils.ts deleted file mode 100644 index d30542bc6..000000000 --- a/packages/react-native-webview/src/utils/env-utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -export const isNewArchitecture = (): boolean => { - return (global as any)?.nativeFabricUIManager !== undefined; -}; diff --git a/packages/react-native-webview/src/utils/webview-js-utils.ts b/packages/react-native-webview/src/utils/webview-js-utils.ts index 101ea1873..c6c9be3b4 100644 --- a/packages/react-native-webview/src/utils/webview-js-utils.ts +++ b/packages/react-native-webview/src/utils/webview-js-utils.ts @@ -29,70 +29,59 @@ export type DatadogMessageFormat = { }; /** - * Wraps the given JS Code in a try and catch block. + * Wraps the given JS Code in a try and catch block, including a + * comment with the given allowedHosts in JSON format * @param javascriptCode The JS Code to wrap in a try and catch block. * @returns the wrapped JS code. */ -export const wrapJsCodeInTryAndCatch = ( - javascriptCode?: string -): string | undefined => - javascriptCode - ? ` - try{ - ${javascriptCode} +export function wrapJsCodeWithAllowedHosts( + javascriptCode?: string, + allowedHosts?: string[] +): string | undefined { + let jsCode = ''; + const hosts = formatAllowedHosts(allowedHosts); + if (hosts) { + jsCode = `// #allowedHosts=${JSON.stringify(allowedHosts)}\n`; } - catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - window.ReactNativeWebView.postMessage(JSON.stringify({ - source: 'DATADOG', - type: 'ERROR', - message: errorMsg - })); - true; - }` + + return javascriptCode + ? `${jsCode}\n${wrapJsCodeInTryAndCatch(javascriptCode)}` + : jsCode.trim().length > 0 + ? jsCode : undefined; +} -/** - * Legacy JS code for bridging the WebView events to DataDog native SDKs for consumption. - * @param allowedHosts The list of allowed hosts. - * @param customJavaScriptCode Custom user JS code to inject along with the Datadog bridging logic. - * @returns The JS code block as a string. - */ -export const getWebViewEventBridgingJS = ( - allowedHosts?: string[], - customJavaScriptCode?: string -): string => - ` - window.DatadogEventBridge = { - send(msg) { - window.ReactNativeWebView.postMessage(JSON.stringify({ - source: 'DATADOG', - type: 'NATIVE_EVENT', - message: msg - })); - true; - }, - getAllowedWebViewHosts() { - return ${formatAllowedHosts(allowedHosts)} - } - }; - try{ - ${customJavaScriptCode} - } - catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - window.ReactNativeWebView.postMessage(JSON.stringify({ - source: 'DATADOG', - type: 'ERROR', - message: errorMsg - })); - true; - } - `; +export function wrapJsCodeInTryAndCatch( + javascriptCode: string | undefined +): string | undefined { + return javascriptCode + ? `try{ + ${javascriptCode} +} +catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + window.ReactNativeWebView.postMessage(JSON.stringify({ + source: 'DATADOG', + type: 'ERROR', + message: errorMsg + })); + true; +}` + : undefined; +} -function formatAllowedHosts(allowedHosts?: string[]): string { +function formatAllowedHosts(allowedHosts?: string[]): string | undefined { try { - return `'${JSON.stringify(allowedHosts)}'`; + if (!allowedHosts) { + throw new Error('allowedHosts is undefined'); + } + const jsonHosts = JSON.stringify(allowedHosts); + if (!jsonHosts) { + throw new Error( + "JSON.stringify returned 'undefined' for the given hosts" + ); + } + return jsonHosts; } catch (e: any) { if (NativeDdSdk) { NativeDdSdk.telemetryError( @@ -101,7 +90,10 @@ function formatAllowedHosts(allowedHosts?: string[]): string { 'AllowedHostsError' ); } - return "'[]'"; + console.warn( + `[@datadog/mobile-react-native-webview] Invalid 'allowedHosts' format: ${e}` + ); + return undefined; } } From 84ce7ad0f59a0fc451182094c5b89b451ab740a8 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Fri, 17 Oct 2025 12:41:09 +0200 Subject: [PATCH 002/114] Inject debugId in error logs and improve context validation --- packages/core/src/logs/DdLogs.ts | 36 ++++++---- .../core/src/logs/__tests__/DdLogs.test.ts | 72 ++++++++++++++++--- packages/core/src/rum/DdAttributes.ts | 9 ++- packages/core/src/rum/DdRum.ts | 37 +++++++--- packages/core/src/rum/__tests__/DdRum.test.ts | 18 ++++- .../instrumentation/DdRumErrorTracking.tsx | 10 +-- packages/core/src/utils/argsUtils.ts | 6 ++ packages/core/src/utils/errorUtils.ts | 13 ---- 8 files changed, 145 insertions(+), 56 deletions(-) diff --git a/packages/core/src/logs/DdLogs.ts b/packages/core/src/logs/DdLogs.ts index 9ac35fa73..2a77fc453 100644 --- a/packages/core/src/logs/DdLogs.ts +++ b/packages/core/src/logs/DdLogs.ts @@ -6,6 +6,7 @@ import { DATADOG_MESSAGE_PREFIX, InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; +import { debugId } from '../metro/debugIdResolver'; import type { DdNativeLogsType } from '../nativeModulesTypes'; import { DdAttributes } from '../rum/DdAttributes'; import type { ErrorSource } from '../rum/types'; @@ -54,12 +55,12 @@ class DdLogsWrapper implements DdLogsType { args[1], args[2], args[3], - validateContext(args[4]), + args[4] ?? {}, 'debug', args[5] ); } - return this.log(args[0], validateContext(args[1]), 'debug'); + return this.log(args[0], args[1] ?? {}, 'debug'); }; info = (...args: LogArguments | LogWithErrorArguments): Promise => { @@ -69,12 +70,12 @@ class DdLogsWrapper implements DdLogsType { args[1], args[2], args[3], - validateContext(args[4]), + args[4] ?? {}, 'info', args[5] ); } - return this.log(args[0], validateContext(args[1]), 'info'); + return this.log(args[0], args[1] ?? {}, 'info'); }; warn = (...args: LogArguments | LogWithErrorArguments): Promise => { @@ -84,12 +85,12 @@ class DdLogsWrapper implements DdLogsType { args[1], args[2], args[3], - validateContext(args[4]), + args[4] ?? {}, 'warn', args[5] ); } - return this.log(args[0], validateContext(args[1]), 'warn'); + return this.log(args[0], args[1] ?? {}, 'warn'); }; error = (...args: LogArguments | LogWithErrorArguments): Promise => { @@ -99,13 +100,13 @@ class DdLogsWrapper implements DdLogsType { args[1], args[2], args[3], - validateContext(args[4]), + args[4] ?? {}, 'error', args[5], args[6] ); } - return this.log(args[0], validateContext(args[1]), 'error'); + return this.log(args[0], args[1] ?? {}, 'error'); }; /** @@ -158,9 +159,14 @@ class DdLogsWrapper implements DdLogsType { return generateEmptyPromise(); } + const validatedContext = validateContext(event.context); + this.printLogTracked(event.message, status); try { - return await this.nativeLogs[status](event.message, event.context); + return await this.nativeLogs[status]( + event.message, + validatedContext + ); } catch (error) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -203,15 +209,19 @@ class DdLogsWrapper implements DdLogsType { this.printLogTracked(mappedEvent.message, status); try { - const updatedContext = { - ...mappedEvent.context, - [DdAttributes.errorSourceType]: 'react-native' - }; + const updatedContext = validateContext(mappedEvent.context); + + updatedContext[DdAttributes.errorSourceType] = 'react-native'; if (fingerprint && fingerprint !== '') { updatedContext[DdAttributes.errorFingerprint] = fingerprint; } + const _debugId = debugId; + if (_debugId) { + updatedContext[DdAttributes.debugId] = _debugId; + } + return await this.nativeLogs[`${status}WithError`]( mappedEvent.message, (mappedEvent as NativeLogWithError).errorKind, diff --git a/packages/core/src/logs/__tests__/DdLogs.test.ts b/packages/core/src/logs/__tests__/DdLogs.test.ts index f99280bfe..42f96bae3 100644 --- a/packages/core/src/logs/__tests__/DdLogs.test.ts +++ b/packages/core/src/logs/__tests__/DdLogs.test.ts @@ -702,7 +702,15 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking debug log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining( + 'The given context is an array, it will be nested' + ), SdkVerbosity.WARN ); @@ -732,7 +740,13 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking debug log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining('Context will be empty.'), SdkVerbosity.ERROR ); @@ -792,7 +806,15 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking warn log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining( + 'The given context is an array, it will be nested' + ), SdkVerbosity.WARN ); @@ -820,7 +842,13 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking warn log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining('Context will be empty.'), SdkVerbosity.ERROR ); @@ -880,7 +908,15 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking info log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining( + 'The given context is an array, it will be nested' + ), SdkVerbosity.WARN ); @@ -908,7 +944,13 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking info log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining('Context will be empty.'), SdkVerbosity.ERROR ); @@ -968,7 +1010,15 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking error log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining( + 'The given context is an array, it will be nested' + ), SdkVerbosity.WARN ); @@ -998,7 +1048,13 @@ describe('DdLogs', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Tracking error log'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining('Context will be empty.'), SdkVerbosity.ERROR ); diff --git a/packages/core/src/rum/DdAttributes.ts b/packages/core/src/rum/DdAttributes.ts index 91e20d5b3..9d92e600e 100644 --- a/packages/core/src/rum/DdAttributes.ts +++ b/packages/core/src/rum/DdAttributes.ts @@ -16,5 +16,12 @@ export const DdAttributes = { * Custom fingerprint to an error. * Expects {@link String} value. */ - errorFingerprint: '_dd.error.fingerprint' + errorFingerprint: '_dd.error.fingerprint', + + /** + * Debug ID attached to a log or a RUM event. + * The Debug ID establishes a unique connection between a bundle and its corresponding sourcemap. + * Expects {@link String} value. + */ + debugId: '_dd.debug_id' }; diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index cbd06f4fe..37ae3a30e 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -7,12 +7,12 @@ import type { GestureResponderEvent } from 'react-native'; import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; +import { debugId } from '../metro/debugIdResolver'; import type { DdNativeRumType } from '../nativeModulesTypes'; import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; import { DdSdk } from '../sdk/DdSdk'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; import { validateContext } from '../utils/argsUtils'; -import { getErrorContext } from '../utils/errorUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; import type { TimeProvider } from '../utils/time-provider/TimeProvider'; @@ -141,13 +141,16 @@ class DdRumWrapper implements DdRumType { const mappedEvent = this.actionEventMapper.applyEventMapper({ type, name, - context: validateContext(context), + context, timestampMs, actionContext }); if (!mappedEvent) { return generateEmptyPromise(); } + + const validatedContext = validateContext(mappedEvent.context); + InternalLog.log( `Adding RUM Action “${name}” (${type})`, SdkVerbosity.DEBUG @@ -156,7 +159,7 @@ class DdRumWrapper implements DdRumType { this.nativeRum.addAction( mappedEvent.type, mappedEvent.name, - mappedEvent.context, + validatedContext, mappedEvent.timestampMs ) ); @@ -199,7 +202,7 @@ class DdRumWrapper implements DdRumType { statusCode, kind, size, - context: validateContext(context), + context, timestampMs, resourceContext }); @@ -223,6 +226,8 @@ class DdRumWrapper implements DdRumType { ); } + const validatedContext = validateContext(mappedEvent.context); + InternalLog.log( `Stopping RUM Resource #${key} status:${statusCode}`, SdkVerbosity.DEBUG @@ -233,7 +238,7 @@ class DdRumWrapper implements DdRumType { mappedEvent.statusCode, mappedEvent.kind, mappedEvent.size, - mappedEvent.context, + validatedContext, mappedEvent.timestampMs ) ); @@ -251,7 +256,7 @@ class DdRumWrapper implements DdRumType { message, source, stacktrace, - context: getErrorContext(validateContext(context)), + context, timestampMs, fingerprint: fingerprint ?? '' }); @@ -260,8 +265,16 @@ class DdRumWrapper implements DdRumType { return generateEmptyPromise(); } InternalLog.log(`Adding RUM Error “${message}”`, SdkVerbosity.DEBUG); - const updatedContext: any = mappedEvent.context; + + const updatedContext: any = validateContext(mappedEvent.context); + updatedContext[DdAttributes.errorSourceType] = 'react-native'; + + const _debugId = debugId; + if (_debugId) { + updatedContext[DdAttributes.debugId] = _debugId; + } + return bufferVoidNativeCall(() => this.nativeRum.addError( mappedEvent.message, @@ -393,7 +406,7 @@ class DdRumWrapper implements DdRumType { const mappedEvent = this.actionEventMapper.applyEventMapper({ type, name, - context: validateContext(context), + context, timestampMs }); if (!mappedEvent) { @@ -409,11 +422,13 @@ class DdRumWrapper implements DdRumType { ); } + const validatedContext = validateContext(mappedEvent.context); + return bufferVoidNativeCall(() => this.nativeRum.stopAction( mappedEvent.type, mappedEvent.name, - mappedEvent.context, + validatedContext, mappedEvent.timestampMs ) ); @@ -440,7 +455,7 @@ class DdRumWrapper implements DdRumType { return [ args[0], args[1], - validateContext(args[2]), + args[2] || {}, args[3] || this.timeProvider.now() ]; } @@ -453,7 +468,7 @@ class DdRumWrapper implements DdRumType { return [ type, name, - validateContext(args[0]), + args[0] || {}, args[1] || this.timeProvider.now() ]; } diff --git a/packages/core/src/rum/__tests__/DdRum.test.ts b/packages/core/src/rum/__tests__/DdRum.test.ts index 527492891..a3b9ca2b7 100644 --- a/packages/core/src/rum/__tests__/DdRum.test.ts +++ b/packages/core/src/rum/__tests__/DdRum.test.ts @@ -1181,7 +1181,13 @@ describe('DdRum', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Adding RUM Error'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining('Context will be empty'), SdkVerbosity.ERROR ); @@ -1208,7 +1214,15 @@ describe('DdRum', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 1, - expect.anything(), + expect.stringContaining('Adding RUM Error'), + SdkVerbosity.DEBUG + ); + + expect(InternalLog.log).toHaveBeenNthCalledWith( + 2, + expect.stringContaining( + 'The given context is an array, it will be nested' + ), SdkVerbosity.WARN ); diff --git a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx index 04b01290c..ecdc99228 100644 --- a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx +++ b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx @@ -14,8 +14,7 @@ import { getErrorStackTrace, EMPTY_STACK_TRACE, getErrorName, - DEFAULT_ERROR_NAME, - getErrorContext + DEFAULT_ERROR_NAME } from '../../utils/errorUtils'; import { executeWithDelay } from '../../utils/jsUtils'; import { DdRum } from '../DdRum'; @@ -146,12 +145,7 @@ export class DdRumErrorTracking { context: object = {} ): Promise<[void, void]> => { return Promise.all([ - DdRum.addError( - message, - source, - stacktrace, - getErrorContext(context) - ), + DdRum.addError(message, source, stacktrace, context), DdLogs.error( message, errorName, diff --git a/packages/core/src/utils/argsUtils.ts b/packages/core/src/utils/argsUtils.ts index d7a07255f..72406e037 100644 --- a/packages/core/src/utils/argsUtils.ts +++ b/packages/core/src/utils/argsUtils.ts @@ -8,6 +8,12 @@ import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; +/** + * Validates the given context before passing it to the native SDKs. + * @param context the original context + * @returns the original context if it's a plain object, an object wrapping the context if it's an array, + * or an empty object if the context is of any other type. + */ export const validateContext = (context: any) => { if (!context) { return {}; diff --git a/packages/core/src/utils/errorUtils.ts b/packages/core/src/utils/errorUtils.ts index 5fc16dcbc..ca7b3a715 100644 --- a/packages/core/src/utils/errorUtils.ts +++ b/packages/core/src/utils/errorUtils.ts @@ -4,8 +4,6 @@ * Copyright 2016-Present Datadog, Inc. */ -import { debugId } from '../metro/debugIdResolver'; - export const EMPTY_MESSAGE = 'Unknown Error'; export const EMPTY_STACK_TRACE = ''; export const DEFAULT_ERROR_NAME = 'Error'; @@ -73,14 +71,3 @@ export const getErrorName = (error: unknown): string => { } return DEFAULT_ERROR_NAME; }; - -export const getErrorContext = (originalContext: any): Record => { - const _debugId = debugId; - if (!_debugId) { - return originalContext; - } - return { - ...originalContext, - '_dd.debug_id': _debugId - }; -}; From e8eb9d39965cd9f46a16929e5c30ce04c5b6efc4 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Wed, 3 Dec 2025 13:23:52 +0100 Subject: [PATCH 003/114] Tweaks and bump of stale action to 10.1.0 --- .github/workflows/stale.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d1a506cbe..f1013691a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,11 +13,11 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | - This issue has been automatically marked as stale because it has not had recent activity and has the `awaiting-response` label. + This issue has been automatically marked as stale because it has not had recent activity and has the `awaiting response` label. It will be closed if no further activity occurs within 3 days. @@ -25,7 +25,7 @@ jobs: stale-issue-label: 'stale' # The label that will be added to the issues when automatically marked as stale close-issue-label: 'automatically closed' - # Only target issues with 'awaiting response' label + # Only target issues with 'awaiting-response' label only-labels: 'awaiting response' # Mark issues as stale after 14 days days-before-issue-stale: 14 @@ -33,6 +33,8 @@ jobs: days-before-issue-close: 3 # Automatically remove the stale label when the issues or the pull requests are updated remove-stale-when-updated: true + # Specify the reason used when closing issues: `completed` or `not_planned` + close-issue-reason: completed # Run the stale workflow as dry-run. # No GitHub API calls that can alter the issues and pull requests will happen. # Useful to debug or when you want to configure the stale workflow safely. From 56811b03bfae9bedadfe08173905901242cefe53 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Dec 2025 12:15:57 +0100 Subject: [PATCH 004/114] Force resolution of jws to 4.0.1 --- package.json | 3 ++- yarn.lock | 33 ++++++--------------------------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 4d77a4ac4..807253ffd 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "snyk": "^1.1298.3", "form-data": "4.0.4", "on-headers": "1.1.0", - "tmp": "0.2.4" + "tmp": "0.2.4", + "jws": "^4.0.1" } } diff --git a/yarn.lock b/yarn.lock index dfcec9595..b57eb29ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15462,18 +15462,7 @@ __metadata: languageName: node linkType: hard -"jwa@npm:^1.4.1": - version: 1.4.2 - resolution: "jwa@npm:1.4.2" - dependencies: - buffer-equal-constant-time: ^1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: ^5.0.1 - checksum: fd1a6de6c649a4b16f0775439ac9173e4bc9aa0162c7f3836699af47736ae000fafe89f232a2345170de6c14021029cb94b488f7882c6caf61e6afef5fce6494 - languageName: node - linkType: hard - -"jwa@npm:^2.0.0": +"jwa@npm:^2.0.1": version: 2.0.1 resolution: "jwa@npm:2.0.1" dependencies: @@ -15484,23 +15473,13 @@ __metadata: languageName: node linkType: hard -"jws@npm:^3.2.2": - version: 3.2.2 - resolution: "jws@npm:3.2.2" - dependencies: - jwa: ^1.4.1 - safe-buffer: ^5.0.1 - checksum: f0213fe5b79344c56cd443428d8f65c16bf842dc8cb8f5aed693e1e91d79c20741663ad6eff07a6d2c433d1831acc9814e8d7bada6a0471fbb91d09ceb2bf5c2 - languageName: node - linkType: hard - -"jws@npm:^4.0.0": - version: 4.0.0 - resolution: "jws@npm:4.0.0" +"jws@npm:^4.0.1": + version: 4.0.1 + resolution: "jws@npm:4.0.1" dependencies: - jwa: ^2.0.0 + jwa: ^2.0.1 safe-buffer: ^5.0.1 - checksum: d68d07aa6d1b8cb35c363a9bd2b48f15064d342a5d9dc18a250dbbce8dc06bd7e4792516c50baa16b8d14f61167c19e851fd7f66b59ecc68b7f6a013759765f7 + checksum: c33a060b2cce1e0e49f85054a49a951f9d52a9e2ae732d720f0fc51843c9ac07a68aacd8e9d086ef4c7c4437d42978b698b57a3e7c9bc4a91c0b74276ea85a9a languageName: node linkType: hard From 36e8ac9aabee60389926783eebca78ba2fe5886c Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 9 Dec 2025 11:28:25 +0100 Subject: [PATCH 005/114] webview, oldarch: removed isWebViewTrackingEnabled check --- .../webview/DdSdkReactNativeWebViewManager.kt | 13 ------------- .../android/src/test/webview/DatadogWebViewTest.kt | 6 ------ 2 files changed, 19 deletions(-) diff --git a/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt index cce4dffe5..094e95b91 100644 --- a/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt +++ b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewManager.kt @@ -36,13 +36,6 @@ class DdSdkReactNativeWebViewManager( val datadogCore: SdkCore? get() = _datadogCore - /** - * Whether WebView tracking has been enabled or not. - */ - @Volatile private var _isWebViewTrackingEnabled: Boolean = false - val isWebViewTrackingEnabled: Boolean - get() = _isWebViewTrackingEnabled - init { DatadogSDKWrapperStorage.addOnInitializedListener { core -> _datadogCore = core @@ -91,17 +84,11 @@ class DdSdkReactNativeWebViewManager( sdkCore: SdkCore, allowedHosts: List ) { - if (_isWebViewTrackingEnabled) { - return - } - WebViewTracking.enable( webView, allowedHosts = allowedHosts, sdkCore = sdkCore ) - - _isWebViewTrackingEnabled = true } companion object { diff --git a/packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt b/packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt index 0772698dc..8bd514379 100644 --- a/packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt +++ b/packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt @@ -91,9 +91,6 @@ internal class DatadogWebViewTest { // When first initialized, the WebView manager core should be null assertThat(manager.datadogCore).isNull() - // When first initialized, the WebView tracking should be disabled - assertThat(manager.isWebViewTrackingEnabled).isEqualTo(false) - // ========= // When // ========= @@ -120,8 +117,5 @@ internal class DatadogWebViewTest { webViewTrackingMockedStatic.verify { WebViewTracking.enable(rncWebView, listOf("example.com"), 100.0f, datadogCore) } - - // At this point 'isWebViewTrackingEnabled' should be true. - assertThat(manager.isWebViewTrackingEnabled).isEqualTo(true) } } From e380484bb7162c47fa101fa96a6f0857bf1fdd0a Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:18:40 +0200 Subject: [PATCH 006/114] Improve runtime for generate-svg-assets --- .../src/cli/generate-sr-assets.ts | 10 +++++++- .../react-native-babel-plugin/src/index.ts | 6 +++-- .../src/libraries/react-native-svg/index.ts | 25 ++++++++++++++++--- .../src/types/general.ts | 1 + 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts index c94485c6d..3eb506bba 100644 --- a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts +++ b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts @@ -15,6 +15,7 @@ import { clearAssetsDir, getAssetsPath } from '../libraries/react-native-svg/processing/fs'; +import { ReactNativeSVG } from '../libraries/react-native-svg'; type SvgIndexEntry = { offset: number; @@ -141,6 +142,12 @@ function generateSessionReplayAssets() { let errorCount = 0; const errors: Array<{ file: string; error: string }> = []; + const reactNativeSVG = new ReactNativeSVG( + process.cwd(), + assetsPath, + true + ); + for (const file of files) { try { const code = fs.readFileSync(file, 'utf8'); @@ -155,7 +162,8 @@ function generateSessionReplayAssets() { sessionReplay: { svgTracking: true }, - __internal_saveSvgMapToDisk: true + __internal_saveSvgMapToDisk: true, + __internal_reactNativeSVG: reactNativeSVG } ] ], diff --git a/packages/react-native-babel-plugin/src/index.ts b/packages/react-native-babel-plugin/src/index.ts index 8379c600f..463b65f64 100644 --- a/packages/react-native-babel-plugin/src/index.ts +++ b/packages/react-native-babel-plugin/src/index.ts @@ -39,7 +39,7 @@ export default declare( } }; - let reactNativeSVG: ReactNativeSVG | null = null; + let reactNativeSVG: ReactNativeSVG | undefined = undefined; let assetsPath: string | null = null; @@ -53,14 +53,16 @@ export default declare( assetsPath = getAssetsPath(); } + reactNativeSVG = options.__internal_reactNativeSVG; if (!reactNativeSVG && assetsPath) { reactNativeSVG = new ReactNativeSVG( - api.types, process.cwd(), assetsPath, options.__internal_saveSvgMapToDisk || false ); + reactNativeSVG.buildSvgMap(); } + reactNativeSVG?.setApiTypes(api.types); }, visitor: { Program: { diff --git a/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts b/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts index 1ef61ff28..ef6301ff1 100644 --- a/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts +++ b/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts @@ -42,13 +42,18 @@ export class ReactNativeSVG { localSvgMap: Record = {}; + t: typeof Babel.types | null = null; + constructor( - private t: typeof Babel.types, private rootDir: string, private assetsPath: string, private saveSvgMapToDisk: boolean = false ) { - this.buildSvgMap(); + + } + + setApiTypes(t: typeof Babel.types) { + this.t = t; } /** @@ -70,6 +75,10 @@ export class ReactNativeSVG { * after scanning. */ buildSvgMap() { + if (!this.t) { + return; + } + // If not saving to disk, try to load from existing svg-map.json first if (!this.saveSvgMapToDisk) { // Resolve to package root: from lib/commonjs/libraries/react-native-svg -> package root @@ -122,10 +131,13 @@ export class ReactNativeSVG { 'classProperties', 'dynamicImport' ] - }); + }); traverse(ast, { ImportDeclaration: path => { + if (!this.t) { + return; + } const source = path.node.source.value; if (!source.endsWith('.svg')) { return; @@ -145,6 +157,9 @@ export class ReactNativeSVG { } }, ExportNamedDeclaration: path => { + if (!this.t) { + return; + } const source = path.node.source?.value; if (!source?.endsWith('.svg')) { return; @@ -213,6 +228,10 @@ export class ReactNativeSVG { * or `undefined` if no transformation could be performed. */ processItem(path: Babel.NodePath, name: string) { + if (!this.t) { + return; + } + try { const dimensions: { width?: string; height?: string } = {}; diff --git a/packages/react-native-babel-plugin/src/types/general.ts b/packages/react-native-babel-plugin/src/types/general.ts index 0d17ff8c2..e9869e5b0 100644 --- a/packages/react-native-babel-plugin/src/types/general.ts +++ b/packages/react-native-babel-plugin/src/types/general.ts @@ -44,6 +44,7 @@ export type PluginOptions = { }; // Internal option used by CLI - not meant for end users __internal_saveSvgMapToDisk?: boolean; + __internal_reactNativeSVG?: ReactNativeSVG; }; export type PluginPassState = Babel.PluginPass & { From f87aef68fe7badc4b35dbbaff254976fa2a5e602 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:11:52 +0200 Subject: [PATCH 007/114] Add cli flags --- .../src/cli/generate-sr-assets.ts | 233 ++++++++++++++-- .../react-native-babel-plugin/src/index.ts | 2 +- .../src/libraries/react-native-svg/index.ts | 6 +- .../test/generate-sr-assets.test.ts | 256 ++++++++++++++++++ 4 files changed, 470 insertions(+), 27 deletions(-) create mode 100644 packages/react-native-babel-plugin/test/generate-sr-assets.test.ts diff --git a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts index 3eb506bba..237d3516f 100644 --- a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts +++ b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts @@ -24,6 +24,110 @@ type SvgIndexEntry = { type SvgIndex = Record; +export type CliOptions = { + ignore: string[]; + verbose: boolean; + path: string | null; + followSymlinks: boolean; +}; + +export const DEFAULT_IGNORE_PATTERNS = [ + '**/node_modules/**', + '**/lib/**', + '**/dist/**', + '**/build/**', + '**/*.d.ts', + '**/*.test.*', + '**/*.spec.*', + '**/*.config.js', + '**/__tests__/**', + '**/__mocks__/**' +]; + +/** + * Parses command line arguments for the generate-sr-assets CLI. + * + * Supported arguments: + * --ignore Additional glob patterns to ignore (can be specified multiple times) + * --verbose, -v Enable verbose output for debugging + * --path, -p Path to the root directory to scan (defaults to current working directory) + * --followSymlinks Follow symbolic links during traversal (default: false) + * --help, -h Show help message + * + * @param args - Optional array of arguments (defaults to process.argv.slice(2)) + * @returns Parsed CLI options + */ +export function parseCliArgs(args?: string[]): CliOptions { + const argv = args ?? process.argv.slice(2); + const options: CliOptions = { + ignore: [], + verbose: false, + path: null, + followSymlinks: false + }; + + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + + if (arg === '--help' || arg === '-h') { + printHelp(); + process.exit(0); + } else if (arg === '--ignore' || arg === '-i') { + const value = argv[++i]; + if (value && !value.startsWith('-')) { + options.ignore.push(value); + } else { + console.warn( + 'Warning: --ignore flag requires a pattern argument' + ); + i--; // Reprocess this arg if it's another flag + } + } else if (arg === '--verbose' || arg === '-v') { + options.verbose = true; + } else if (arg === '--path' || arg === '-p') { + const value = argv[++i]; + if (value && !value.startsWith('-')) { + options.path = value; + } else { + console.warn('Warning: --path flag requires a directory path'); + i--; // Reprocess this arg if it's another flag + } + } else if (arg === '--followSymlinks') { + options.followSymlinks = true; + } + } + + return options; +} + +/** + * Prints the help message for the CLI. + */ +function printHelp(): void { + console.info(` +Usage: npx datadog-generate-sr-assets [options] + +Pre-generate SVG assets for Datadog Session Replay. + +Options: + --ignore, -i Additional glob patterns to ignore during scanning. + Can be specified multiple times. + Example: --ignore "**/legacy/**" + --path, -p Path to the root directory to scan. + Defaults to the current working directory. + --verbose, -v Enable verbose output for debugging. + --followSymlinks Follow symbolic links during directory traversal. + Default: false (symlinks are ignored). + --help, -h Show this help message. + +Examples: + npx datadog-generate-sr-assets + npx datadog-generate-sr-assets --path ./src + npx datadog-generate-sr-assets --ignore "**/legacy/**" --verbose + npx datadog-generate-sr-assets -p ./src -i "**/old/**" -v +`); +} + /** * Merges all individual SVG files into assets.bin and creates an index in assets.json. * This function reads all .svg files from the assets directory and packs them into @@ -105,48 +209,97 @@ function mergeSvgAssets(assetsDir: string) { * references are available during the build process. * * Usage: - * npx @datadog/mobile-react-native-babel-plugin generate-sr-assets + * npx @datadog/mobile-react-native-babel-plugin generate-sr-assets [options] * or - * npx datadog-generate-sr-assets + * npx datadog-generate-sr-assets [options] + * + * Options: + * --ignore, -i pattern Additional glob patterns to ignore during scanning. + * Can be specified multiple times. + * Example: --ignore "**\/legacy\/**" --ignore "**\/vendor\/**" + * --verbose, -v Enable verbose output for debugging. + * --path, -p path Path to the root directory to scan. + * Defaults to the current working directory. + * Example: --path ./src + * --followSymlinks Follow symbolic links during directory traversal. + * Default: false (symlinks are ignored). */ function generateSessionReplayAssets() { - const rootDir = process.cwd(); + const cliOptions = parseCliArgs(); + const { verbose } = cliOptions; + + // Resolve the root directory from --path flag or default to cwd + const rootDir = cliOptions.path + ? path.resolve(process.cwd(), cliOptions.path) + : process.cwd(); + + // Validate the path exists + if (cliOptions.path && !fs.existsSync(rootDir)) { + console.error(`Error: Path does not exist: ${rootDir}`); + process.exit(1); + } + + if (cliOptions.path && !fs.statSync(rootDir).isDirectory()) { + console.error(`Error: Path is not a directory: ${rootDir}`); + process.exit(1); + } + const assetsPath = getAssetsPath(); + const startTime = Date.now(); + if (!assetsPath) { + if (verbose) { + console.info( + '[verbose] No assets path found. Session Replay module may not be installed.' + ); + } process.exit(0); } console.info(`Scanning for session replay assets in ${rootDir}...`); + if (verbose) { + console.info(`[verbose] Assets output path: ${assetsPath}`); + } + // Clear existing assets to ensure a fresh state clearAssetsDir(assetsPath); + // Merge default ignore patterns with user-provided ones + // Convert simple folder names to glob patterns (e.g., "legacy" → "**/legacy/**") + const userIgnorePatterns = cliOptions.ignore.map(pattern => { + // If it looks like a glob pattern, use as-is + if (pattern.includes('*') || pattern.includes('?')) { + return pattern; + } + // Otherwise, treat it as a folder name and convert to glob pattern + return `**/${pattern}/**`; + }); + const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS, ...userIgnorePatterns]; + + if (verbose) { + console.info(`[verbose] Follow symlinks: ${cliOptions.followSymlinks}`); + console.info('[verbose] Ignore patterns:'); + ignorePatterns.forEach(pattern => console.info(` - ${pattern}`)); + } + const files = glob.sync(['**/*.{js,jsx,ts,tsx}'], { cwd: rootDir, absolute: true, - ignore: [ - '**/node_modules/**', - '**/lib/**', - '**/dist/**', - '**/build/**', - '**/*.d.ts', - '**/*.test.*', - '**/*.spec.*', - '**/*.config.js', - '**/__tests__/**', - '**/__mocks__/**' - ] + ignore: ignorePatterns, + followSymbolicLinks: cliOptions.followSymlinks }); + if (verbose) { + console.info(`[verbose] Found ${files.length} files to scan`); + } + let errorCount = 0; + let processedCount = 0; const errors: Array<{ file: string; error: string }> = []; - const reactNativeSVG = new ReactNativeSVG( - process.cwd(), - assetsPath, - true - ); + const reactNativeSVG = new ReactNativeSVG(rootDir, assetsPath, true); for (const file of files) { try { @@ -178,11 +331,19 @@ function generateSessionReplayAssets() { code: false, ast: false }); + + processedCount++; } catch (error) { errorCount++; const errorMessage = error instanceof Error ? error.message : String(error); errors.push({ file, error: errorMessage }); + + if (verbose) { + const relativePath = path.relative(rootDir, file); + console.warn(`[verbose] Error processing ${relativePath}:`); + console.warn(` ${errorMessage}`); + } } } @@ -193,6 +354,32 @@ function generateSessionReplayAssets() { // Merge all individual SVG files into assets.bin and assets.json mergeSvgAssets(assetsPath); + const duration = Date.now() - startTime; + + if (verbose) { + console.info('\n[verbose] Summary:'); + console.info(` Files scanned: ${files.length}`); + console.info(` Files processed successfully: ${processedCount}`); + console.info(` Files with errors: ${errorCount}`); + console.info(` Duration: ${duration}ms`); + + if (errors.length > 0 && errors.length <= 10) { + console.info('\n[verbose] Files with errors:'); + errors.forEach(({ file }) => { + const relativePath = path.relative(rootDir, file); + console.info(` - ${relativePath}`); + }); + } else if (errors.length > 10) { + console.info( + `\n[verbose] ${errors.length} files had errors (showing first 10):` + ); + errors.slice(0, 10).forEach(({ file }) => { + const relativePath = path.relative(rootDir, file); + console.info(` - ${relativePath}`); + }); + } + } + if (errorCount > 0) { console.info( 'Asset generation finished, but some files encountered errors.' @@ -202,5 +389,7 @@ function generateSessionReplayAssets() { console.info('Your assets are now ready to be used by Session Replay.'); } -// TODO: Add flag support [e.g., --verbose] (RUM-12186) -generateSessionReplayAssets(); +// Only run when executed directly (not when imported for testing) +if (require.main === module) { + generateSessionReplayAssets(); +} diff --git a/packages/react-native-babel-plugin/src/index.ts b/packages/react-native-babel-plugin/src/index.ts index 463b65f64..d9be0d57b 100644 --- a/packages/react-native-babel-plugin/src/index.ts +++ b/packages/react-native-babel-plugin/src/index.ts @@ -39,7 +39,7 @@ export default declare( } }; - let reactNativeSVG: ReactNativeSVG | undefined = undefined; + let reactNativeSVG: ReactNativeSVG | undefined; let assetsPath: string | null = null; diff --git a/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts b/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts index ef6301ff1..38b4dcd30 100644 --- a/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts +++ b/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts @@ -48,9 +48,7 @@ export class ReactNativeSVG { private rootDir: string, private assetsPath: string, private saveSvgMapToDisk: boolean = false - ) { - - } + ) {} setApiTypes(t: typeof Babel.types) { this.t = t; @@ -131,7 +129,7 @@ export class ReactNativeSVG { 'classProperties', 'dynamicImport' ] - }); + }); traverse(ast, { ImportDeclaration: path => { diff --git a/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts b/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts new file mode 100644 index 000000000..966c5c360 --- /dev/null +++ b/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts @@ -0,0 +1,256 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import { + parseCliArgs, + DEFAULT_IGNORE_PATTERNS +} from '../src/cli/generate-sr-assets'; + +describe('generate-sr-assets CLI', () => { + describe('parseCliArgs', () => { + describe('default values', () => { + it('should return default options when no arguments are provided', () => { + const result = parseCliArgs([]); + + expect(result).toEqual({ + ignore: [], + verbose: false, + path: null, + followSymlinks: false + }); + }); + }); + + describe('--ignore flag', () => { + it('should parse single --ignore flag', () => { + const result = parseCliArgs(['--ignore', '**/legacy/**']); + + expect(result.ignore).toEqual(['**/legacy/**']); + }); + + it('should parse -i shorthand flag', () => { + const result = parseCliArgs(['-i', '**/vendor/**']); + + expect(result.ignore).toEqual(['**/vendor/**']); + }); + + it('should parse multiple --ignore flags', () => { + const result = parseCliArgs([ + '--ignore', + '**/legacy/**', + '--ignore', + '**/vendor/**', + '-i', + '**/old/**' + ]); + + expect(result.ignore).toEqual([ + '**/legacy/**', + '**/vendor/**', + '**/old/**' + ]); + }); + + it('should warn and skip when --ignore has no value', () => { + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(); + + const result = parseCliArgs(['--ignore']); + + expect(result.ignore).toEqual([]); + expect(warnSpy).toHaveBeenCalledWith( + 'Warning: --ignore flag requires a pattern argument' + ); + + warnSpy.mockRestore(); + }); + + it('should warn and skip when --ignore is followed by another flag', () => { + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(); + + const result = parseCliArgs(['--ignore', '--verbose']); + + expect(result.ignore).toEqual([]); + expect(result.verbose).toBe(true); + expect(warnSpy).toHaveBeenCalledWith( + 'Warning: --ignore flag requires a pattern argument' + ); + + warnSpy.mockRestore(); + }); + }); + + describe('--verbose flag', () => { + it('should parse --verbose flag', () => { + const result = parseCliArgs(['--verbose']); + + expect(result.verbose).toBe(true); + }); + + it('should parse -v shorthand flag', () => { + const result = parseCliArgs(['-v']); + + expect(result.verbose).toBe(true); + }); + }); + + describe('--path flag', () => { + it('should parse --path flag', () => { + const result = parseCliArgs(['--path', './src']); + + expect(result.path).toBe('./src'); + }); + + it('should parse -p shorthand flag', () => { + const result = parseCliArgs(['-p', '/absolute/path']); + + expect(result.path).toBe('/absolute/path'); + }); + + it('should warn and skip when --path has no value', () => { + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(); + + const result = parseCliArgs(['--path']); + + expect(result.path).toBeNull(); + expect(warnSpy).toHaveBeenCalledWith( + 'Warning: --path flag requires a directory path' + ); + + warnSpy.mockRestore(); + }); + + it('should warn and skip when --path is followed by another flag', () => { + const warnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(); + + const result = parseCliArgs(['--path', '-v']); + + expect(result.path).toBeNull(); + expect(result.verbose).toBe(true); + expect(warnSpy).toHaveBeenCalledWith( + 'Warning: --path flag requires a directory path' + ); + + warnSpy.mockRestore(); + }); + }); + + describe('--followSymlinks flag', () => { + it('should parse --followSymlinks flag', () => { + const result = parseCliArgs(['--followSymlinks']); + + expect(result.followSymlinks).toBe(true); + }); + + it('should default to false when not provided', () => { + const result = parseCliArgs([]); + + expect(result.followSymlinks).toBe(false); + }); + }); + + describe('combined flags', () => { + it('should parse all flags together', () => { + const result = parseCliArgs([ + '--path', + './src', + '--ignore', + '**/legacy/**', + '--verbose', + '--followSymlinks', + '-i', + '**/vendor/**' + ]); + + expect(result).toEqual({ + path: './src', + ignore: ['**/legacy/**', '**/vendor/**'], + verbose: true, + followSymlinks: true + }); + }); + + it('should parse shorthand flags together', () => { + const result = parseCliArgs([ + '-p', + './app', + '-i', + '**/test/**', + '-v' + ]); + + expect(result).toEqual({ + path: './app', + ignore: ['**/test/**'], + verbose: true, + followSymlinks: false + }); + }); + }); + + describe('unknown flags', () => { + it('should ignore unknown flags', () => { + const result = parseCliArgs([ + '--unknown', + '--verbose', + '--another-unknown', + 'value' + ]); + + expect(result.verbose).toBe(true); + expect(result.ignore).toEqual([]); + expect(result.path).toBeNull(); + }); + }); + }); + + describe('DEFAULT_IGNORE_PATTERNS', () => { + it('should include node_modules', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/node_modules/**'); + }); + + it('should include lib directory', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/lib/**'); + }); + + it('should include dist directory', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/dist/**'); + }); + + it('should include build directory', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/build/**'); + }); + + it('should include TypeScript declaration files', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.d.ts'); + }); + + it('should include test files', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.test.*'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.spec.*'); + }); + + it('should include config files', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.config.js'); + }); + + it('should include __tests__ and __mocks__ directories', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/__tests__/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/__mocks__/**'); + }); + + it('should have expected number of patterns', () => { + expect(DEFAULT_IGNORE_PATTERNS).toHaveLength(10); + }); + }); +}); From c1c12a158384d903f4876fa9074c0db7ef2903c7 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:12:55 +0200 Subject: [PATCH 008/114] Add some additional folders to ignore --- .../src/cli/generate-sr-assets.ts | 58 +++++++++---- .../test/generate-sr-assets.test.ts | 83 +++++++++++++++++-- 2 files changed, 120 insertions(+), 21 deletions(-) diff --git a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts index 237d3516f..2b5125eb4 100644 --- a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts +++ b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts @@ -31,17 +31,52 @@ export type CliOptions = { followSymlinks: boolean; }; +/** + * Converts a user-provided ignore pattern to a glob pattern. + * Simple folder names are converted to glob patterns (e.g., "legacy" becomes "** /legacy/** "). + * Patterns that already contain glob characters (* or ?) are used as-is. + * + * @param pattern - The user-provided pattern + * @returns A glob-compatible pattern + */ +export function normalizeIgnorePattern(pattern: string): string { + // If it looks like a glob pattern, use as-is + if (pattern.includes('*') || pattern.includes('?')) { + return pattern; + } + // Otherwise, treat it as a folder name and convert to glob pattern + return `**/${pattern}/**`; +} + export const DEFAULT_IGNORE_PATTERNS = [ + // Dependencies and build output '**/node_modules/**', '**/lib/**', '**/dist/**', '**/build/**', + '**/vendor/**', + // Native code (not React components) + '**/ios/**', + '**/android/**', + '**/Pods/**', + // Expo + '**/.expo/**', + // Cache and metadata + '**/.git/**', + '**/.cache/**', + '**/.yarn/**', + // TypeScript declarations '**/*.d.ts', + // Test files and directories '**/*.test.*', '**/*.spec.*', - '**/*.config.js', '**/__tests__/**', - '**/__mocks__/**' + '**/__mocks__/**', + '**/__snapshots__/**', + '**/coverage/**', + // Config files + '**/*.config.js', + '**/*.config.ts' ]; /** @@ -110,9 +145,10 @@ Usage: npx datadog-generate-sr-assets [options] Pre-generate SVG assets for Datadog Session Replay. Options: - --ignore, -i Additional glob patterns to ignore during scanning. + --ignore, -i Additional patterns to ignore during scanning. + Can be a folder name or a glob pattern. + Folder names are auto-converted to glob patterns. Can be specified multiple times. - Example: --ignore "**/legacy/**" --path, -p Path to the root directory to scan. Defaults to the current working directory. --verbose, -v Enable verbose output for debugging. @@ -123,8 +159,9 @@ Options: Examples: npx datadog-generate-sr-assets npx datadog-generate-sr-assets --path ./src - npx datadog-generate-sr-assets --ignore "**/legacy/**" --verbose - npx datadog-generate-sr-assets -p ./src -i "**/old/**" -v + npx datadog-generate-sr-assets --ignore legacy --ignore vendor + npx datadog-generate-sr-assets --ignore "**/custom-pattern/**" --verbose + npx datadog-generate-sr-assets -p ./src -i old-code -v `); } @@ -268,14 +305,7 @@ function generateSessionReplayAssets() { // Merge default ignore patterns with user-provided ones // Convert simple folder names to glob patterns (e.g., "legacy" → "**/legacy/**") - const userIgnorePatterns = cliOptions.ignore.map(pattern => { - // If it looks like a glob pattern, use as-is - if (pattern.includes('*') || pattern.includes('?')) { - return pattern; - } - // Otherwise, treat it as a folder name and convert to glob pattern - return `**/${pattern}/**`; - }); + const userIgnorePatterns = cliOptions.ignore.map(normalizeIgnorePattern); const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS, ...userIgnorePatterns]; if (verbose) { diff --git a/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts b/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts index 966c5c360..e034c8aa1 100644 --- a/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts +++ b/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts @@ -6,7 +6,8 @@ import { parseCliArgs, - DEFAULT_IGNORE_PATTERNS + DEFAULT_IGNORE_PATTERNS, + normalizeIgnorePattern } from '../src/cli/generate-sr-assets'; describe('generate-sr-assets CLI', () => { @@ -231,26 +232,94 @@ describe('generate-sr-assets CLI', () => { expect(DEFAULT_IGNORE_PATTERNS).toContain('**/build/**'); }); + it('should include vendor directory', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/vendor/**'); + }); + + it('should include native code directories', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/ios/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/android/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/Pods/**'); + }); + + it('should include Expo directory', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/.expo/**'); + }); + + it('should include cache and metadata directories', () => { + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/.git/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/.cache/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/.yarn/**'); + }); + it('should include TypeScript declaration files', () => { expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.d.ts'); }); - it('should include test files', () => { + it('should include test files and directories', () => { expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.test.*'); expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.spec.*'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/__tests__/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/__mocks__/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/__snapshots__/**'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/coverage/**'); }); it('should include config files', () => { expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.config.js'); + expect(DEFAULT_IGNORE_PATTERNS).toContain('**/*.config.ts'); }); - it('should include __tests__ and __mocks__ directories', () => { - expect(DEFAULT_IGNORE_PATTERNS).toContain('**/__tests__/**'); - expect(DEFAULT_IGNORE_PATTERNS).toContain('**/__mocks__/**'); + it('should have expected number of patterns', () => { + expect(DEFAULT_IGNORE_PATTERNS).toHaveLength(21); }); + }); - it('should have expected number of patterns', () => { - expect(DEFAULT_IGNORE_PATTERNS).toHaveLength(10); + describe('normalizeIgnorePattern', () => { + describe('folder names (no glob characters)', () => { + it('should convert simple folder name to glob pattern', () => { + expect(normalizeIgnorePattern('legacy')).toBe('**/legacy/**'); + }); + + it('should convert folder name with hyphen to glob pattern', () => { + expect(normalizeIgnorePattern('old-code')).toBe( + '**/old-code/**' + ); + }); + + it('should convert folder name with underscore to glob pattern', () => { + expect(normalizeIgnorePattern('temp_files')).toBe( + '**/temp_files/**' + ); + }); + + it('should convert nested path to glob pattern', () => { + expect(normalizeIgnorePattern('src/legacy')).toBe( + '**/src/legacy/**' + ); + }); + }); + + describe('glob patterns (with * or ?)', () => { + it('should keep pattern with ** as-is', () => { + expect(normalizeIgnorePattern('**/custom/**')).toBe( + '**/custom/**' + ); + }); + + it('should keep pattern with single * as-is', () => { + expect(normalizeIgnorePattern('*.backup')).toBe('*.backup'); + }); + + it('should keep pattern with ? as-is', () => { + expect(normalizeIgnorePattern('file?.txt')).toBe('file?.txt'); + }); + + it('should keep complex glob pattern as-is', () => { + expect(normalizeIgnorePattern('**/src/**/*.test.ts')).toBe( + '**/src/**/*.test.ts' + ); + }); }); }); }); From 71ca6709fd53a3145963165840e86f7e2541e8a0 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:01:11 +0200 Subject: [PATCH 009/114] Gracefully continue on failure to parse assets --- .../react-native-svg/handlers/RNSvgHandler.ts | 5 +- .../src/libraries/react-native-svg/index.ts | 70 +++++++++++-------- .../test/react-native-svg.test.ts | 16 ++++- 3 files changed, 56 insertions(+), 35 deletions(-) diff --git a/packages/react-native-babel-plugin/src/libraries/react-native-svg/handlers/RNSvgHandler.ts b/packages/react-native-babel-plugin/src/libraries/react-native-svg/handlers/RNSvgHandler.ts index d65c3cfdb..659fec676 100644 --- a/packages/react-native-babel-plugin/src/libraries/react-native-svg/handlers/RNSvgHandler.ts +++ b/packages/react-native-babel-plugin/src/libraries/react-native-svg/handlers/RNSvgHandler.ts @@ -106,9 +106,10 @@ export class RNSvgHandler implements SvgHandler { if (isJSXIdentifierOpen) { openingNode.name = convertAttributeCasing(openingNode.name); if (!svgElements.has(openingNode.name)) { - throw new Error( - `RNSvgHandler[transformElement]: Failed to transform element: "${openingNode.name}" is not supported` + console.warn( + `RNSvgHandler[transformElement]: Skipping unsupported element: "${openingNode.name}"` ); + return; // Skip unsupported elements instead of crashing } } diff --git a/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts b/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts index 38b4dcd30..325f9f2d9 100644 --- a/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts +++ b/packages/react-native-babel-plugin/src/libraries/react-native-svg/index.ts @@ -253,41 +253,49 @@ export class ReactNativeSVG { const id = uuidv4(); - const optimized = output.startsWith('http') - ? output - : optimize(output, { - multipass: true, - plugins: ['preset-default'] - }).data; - - const hash = createHash('md5') - .update(optimized, 'utf8') - .digest('hex'); - - const wrapper = this.wrapElementForSessionReplay( - this.t, - path, - id, - hash, - dimensions - ); - path.replaceWith(wrapper); + try { + const optimized = output.startsWith('http') + ? output + : optimize(output, { + multipass: true, + plugins: ['preset-default'] + }).data; + + const hash = createHash('md5') + .update(optimized, 'utf8') + .digest('hex'); + + const wrapper = this.wrapElementForSessionReplay( + this.t, + path, + id, + hash, + dimensions + ); + + path.replaceWith(wrapper); - path.node.extra = { - __wrappedForSR: true - }; + path.node.extra = { + __wrappedForSR: true + }; - this.svgMap[id] = { - file: optimized, - ...dimensions - }; + this.svgMap[id] = { + file: optimized, + ...dimensions + }; - writeAssetToDisk(this.assetsPath, id, hash, optimized); + writeAssetToDisk(this.assetsPath, id, hash, optimized); - return { original: output, optimized }; - } catch (err) { - console.warn(err); - return { original: null, optimized: null }; + return { original: output, optimized }; + } catch (err) { + console.warn(err); + return { original: null, optimized: null }; + } + } catch (svgoError) { + console.warn( + 'ReactNativeSVG[processItem]: Skipping SVG with dynamic expressions (cannot be optimized)' + ); + return; } } diff --git a/packages/react-native-babel-plugin/test/react-native-svg.test.ts b/packages/react-native-babel-plugin/test/react-native-svg.test.ts index e3070a95f..c57542a82 100644 --- a/packages/react-native-babel-plugin/test/react-native-svg.test.ts +++ b/packages/react-native-babel-plugin/test/react-native-svg.test.ts @@ -729,9 +729,21 @@ describe('React Native SVG Processing - RNSvgHandler', () => { }); describe('Error Handling', () => { - it('should throw error or warn for unsupported element names', () => { + it('should warn for unsupported element names but still include them in output', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const input = ''; - expect(() => transformSvg(input)).toThrow(); + const output = transformSvg(input); + + // Unsupported elements are converted to lowercase and included + expect(output).toMatchInlineSnapshot( + `""` + ); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('Skipping unsupported element') + ); + + warnSpy.mockRestore(); }); it('should handle malformed transform array gracefully', () => { From 506c58646c9d5d5c52e90679387e03d0783dff02 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:41:10 +0200 Subject: [PATCH 010/114] Make ignore handling more robust --- .../src/cli/generate-sr-assets.ts | 56 ++++++++-- .../test/generate-sr-assets.test.ts | 100 +++++++++++++++--- 2 files changed, 131 insertions(+), 25 deletions(-) diff --git a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts index 2b5125eb4..c6cffe00e 100644 --- a/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts +++ b/packages/react-native-babel-plugin/src/cli/generate-sr-assets.ts @@ -33,18 +33,53 @@ export type CliOptions = { /** * Converts a user-provided ignore pattern to a glob pattern. - * Simple folder names are converted to glob patterns (e.g., "legacy" becomes "** /legacy/** "). - * Patterns that already contain glob characters (* or ?) are used as-is. + * + * Handling rules: + * - Glob patterns (containing * ? [ ] { } ( )) are left unchanged + * - Absolute paths are appended with /** if not already present + * - Relative paths (./ or ../ or containing path separator) are resolved to absolute and appended with /** + * - Simple folder names (no slashes, no globs) are wrapped with ** /name/** for wildcard matching * * @param pattern - The user-provided pattern + * @param cwd - The base directory for resolving relative paths (required) * @returns A glob-compatible pattern */ -export function normalizeIgnorePattern(pattern: string): string { - // If it looks like a glob pattern, use as-is - if (pattern.includes('*') || pattern.includes('?')) { +export function normalizeIgnorePattern(pattern: string, cwd: string): string { + // Detect glob patterns - leave unchanged + const isGlob = /[*?[\]{}()]/.test(pattern); + if (isGlob) { return pattern; } - // Otherwise, treat it as a folder name and convert to glob pattern + + const isAbsolute = path.isAbsolute(pattern); + const isRelative = + pattern.startsWith('./') || + pattern.startsWith('../') || + pattern.startsWith('.\\') || + pattern.startsWith('..\\') || + pattern.includes(path.sep) || + pattern.includes('/'); // Also check for forward slash on all platforms + + // Absolute paths - append /** if needed + if (isAbsolute) { + if (pattern.endsWith('/**') || pattern.endsWith(`${path.sep}**`)) { + return pattern; + } + return path.join(pattern, '**'); + } + + // Relative paths - resolve to absolute and append /** + if (isRelative) { + const resolved = path.resolve(cwd, pattern); + + if (resolved.endsWith('/**') || resolved.endsWith(`${path.sep}**`)) { + return resolved; + } + + return path.join(resolved, '**'); + } + + // Simple folder names (no slashes, no glob characters) - wildcard match return `**/${pattern}/**`; } @@ -304,8 +339,13 @@ function generateSessionReplayAssets() { clearAssetsDir(assetsPath); // Merge default ignore patterns with user-provided ones - // Convert simple folder names to glob patterns (e.g., "legacy" → "**/legacy/**") - const userIgnorePatterns = cliOptions.ignore.map(normalizeIgnorePattern); + // Convert folder names/paths to glob patterns based on type: + // - Simple names: "legacy" → "**/legacy/**" + // - Relative paths: "./android" → "/abs/path/android/**" + // - Absolute paths: "/home/dev/android" → "/home/dev/android/**" + const userIgnorePatterns = cliOptions.ignore.map(pattern => + normalizeIgnorePattern(pattern, rootDir) + ); const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS, ...userIgnorePatterns]; if (verbose) { diff --git a/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts b/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts index e034c8aa1..973ff0579 100644 --- a/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts +++ b/packages/react-native-babel-plugin/test/generate-sr-assets.test.ts @@ -276,50 +276,116 @@ describe('generate-sr-assets CLI', () => { }); describe('normalizeIgnorePattern', () => { - describe('folder names (no glob characters)', () => { - it('should convert simple folder name to glob pattern', () => { - expect(normalizeIgnorePattern('legacy')).toBe('**/legacy/**'); + const mockCwd = '/home/user/project'; + + describe('simple folder names (no slashes, no glob)', () => { + it('should convert simple folder name to wildcard glob pattern', () => { + expect(normalizeIgnorePattern('legacy', mockCwd)).toBe( + '**/legacy/**' + ); }); - it('should convert folder name with hyphen to glob pattern', () => { - expect(normalizeIgnorePattern('old-code')).toBe( + it('should convert folder name with hyphen to wildcard glob pattern', () => { + expect(normalizeIgnorePattern('old-code', mockCwd)).toBe( '**/old-code/**' ); }); - it('should convert folder name with underscore to glob pattern', () => { - expect(normalizeIgnorePattern('temp_files')).toBe( + it('should convert folder name with underscore to wildcard glob pattern', () => { + expect(normalizeIgnorePattern('temp_files', mockCwd)).toBe( '**/temp_files/**' ); }); + }); + + describe('relative paths (with ./ or ../)', () => { + it('should resolve relative path starting with ./', () => { + const result = normalizeIgnorePattern('./android', mockCwd); + expect(result).toBe('/home/user/project/android/**'); + }); + + it('should resolve relative path starting with ../', () => { + const result = normalizeIgnorePattern('../other', mockCwd); + expect(result).toBe('/home/user/other/**'); + }); - it('should convert nested path to glob pattern', () => { - expect(normalizeIgnorePattern('src/legacy')).toBe( - '**/src/legacy/**' + it('should resolve nested relative path', () => { + const result = normalizeIgnorePattern('./src/legacy', mockCwd); + expect(result).toBe('/home/user/project/src/legacy/**'); + }); + }); + + describe('paths containing slashes (treated as relative)', () => { + it('should resolve path with forward slash as relative', () => { + const result = normalizeIgnorePattern('packages/app', mockCwd); + expect(result).toBe('/home/user/project/packages/app/**'); + }); + + it('should resolve nested path as relative', () => { + const result = normalizeIgnorePattern( + 'src/components/legacy', + mockCwd + ); + expect(result).toBe( + '/home/user/project/src/components/legacy/**' ); }); }); - describe('glob patterns (with * or ?)', () => { + describe('absolute paths', () => { + it('should append /** to absolute path', () => { + const result = normalizeIgnorePattern( + '/home/dev/app/android', + mockCwd + ); + expect(result).toBe('/home/dev/app/android/**'); + }); + + it('should keep absolute path with /** unchanged', () => { + const result = normalizeIgnorePattern( + '/home/dev/app/android/**', + mockCwd + ); + expect(result).toBe('/home/dev/app/android/**'); + }); + }); + + describe('glob patterns (with * ? [ ] { } ( ))', () => { it('should keep pattern with ** as-is', () => { - expect(normalizeIgnorePattern('**/custom/**')).toBe( + expect(normalizeIgnorePattern('**/custom/**', mockCwd)).toBe( '**/custom/**' ); }); it('should keep pattern with single * as-is', () => { - expect(normalizeIgnorePattern('*.backup')).toBe('*.backup'); + expect(normalizeIgnorePattern('*.backup', mockCwd)).toBe( + '*.backup' + ); }); it('should keep pattern with ? as-is', () => { - expect(normalizeIgnorePattern('file?.txt')).toBe('file?.txt'); + expect(normalizeIgnorePattern('file?.txt', mockCwd)).toBe( + 'file?.txt' + ); }); - it('should keep complex glob pattern as-is', () => { - expect(normalizeIgnorePattern('**/src/**/*.test.ts')).toBe( - '**/src/**/*.test.ts' + it('should keep pattern with brackets as-is', () => { + expect(normalizeIgnorePattern('[abc].txt', mockCwd)).toBe( + '[abc].txt' + ); + }); + + it('should keep pattern with braces as-is', () => { + expect(normalizeIgnorePattern('*.{js,ts}', mockCwd)).toBe( + '*.{js,ts}' ); }); + + it('should keep complex glob pattern as-is', () => { + expect( + normalizeIgnorePattern('**/src/**/*.test.ts', mockCwd) + ).toBe('**/src/**/*.test.ts'); + }); }); }); }); From 314f0446fb22cbe8e972d22f01d911f2388eb3b1 Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Thu, 11 Dec 2025 14:00:13 +0000 Subject: [PATCH 011/114] Bump to version 2.14.0 --- NATIVE_SDK_VERSIONS.md | 1 + benchmarks/ios/Podfile.lock | 12 ++++++------ example-new-architecture/ios/Podfile.lock | 6 +++--- example/ios/Podfile.lock | 18 +++++++++--------- lerna.json | 2 +- packages/codepush/package.json | 4 ++-- .../com/datadog/reactnative/SdkVersion.kt | 2 +- packages/core/ios/Sources/SdkVersion.swift | 2 +- packages/core/package.json | 2 +- packages/core/src/version.ts | 2 +- packages/internal-testing-tools/package.json | 2 +- .../react-native-apollo-client/package.json | 2 +- .../react-native-babel-plugin/package.json | 2 +- packages/react-native-navigation/package.json | 4 ++-- .../react-native-session-replay/package.json | 2 +- .../release-content.txt | 2 ++ packages/react-native-webview/package.json | 2 +- packages/react-navigation/package.json | 4 ++-- yarn.lock | 8 ++++---- 19 files changed, 41 insertions(+), 38 deletions(-) diff --git a/NATIVE_SDK_VERSIONS.md b/NATIVE_SDK_VERSIONS.md index 678affc0e..6ebb13627 100644 --- a/NATIVE_SDK_VERSIONS.md +++ b/NATIVE_SDK_VERSIONS.md @@ -1,5 +1,6 @@ | React Native | iOS Bridge / iOS SDK | Android Bridge / Android SDK | |-------------|---------------------|-----------------------------| +| 2.14.0 | 2.30.2 | 2.26.2 | | 2.13.2 | 2.30.2 | 2.26.2 | | 2.13.1 | 2.30.2 | 2.26.2 | | 2.13.0 | 2.30.2 | 2.26.2 | diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index f42e0cd31..681b5f169 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -10,7 +10,7 @@ PODS: - DatadogInternal (= 2.30.2) - DatadogRUM (2.30.2): - DatadogInternal (= 2.30.2) - - DatadogSDKReactNative (2.13.2): + - DatadogSDKReactNative (2.14.0): - DatadogCore (= 2.30.2) - DatadogCrashReporting (= 2.30.2) - DatadogLogs (= 2.30.2) @@ -37,7 +37,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeSessionReplay (2.13.2): + - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - DatadogSessionReplay (= 2.30.2) - DoubleConversion @@ -60,7 +60,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.13.2): + - DatadogSDKReactNativeWebView (2.14.0): - DatadogInternal (= 2.30.2) - DatadogSDKReactNative - DatadogWebViewTracking (= 2.30.2) @@ -2075,9 +2075,9 @@ SPEC CHECKSUMS: DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 DatadogLogs: d683aa9e0c9339f5ae679ead70bbdbe41cdc32f6 DatadogRUM: 8b794aa458e6323ea9b1cef3f820fd3d092cbe27 - DatadogSDKReactNative: 4138a93c3168b4378de498052d0e00ea9560339d - DatadogSDKReactNativeSessionReplay: baa4de76d97d5f03303dde0e0db922a2fd812019 - DatadogSDKReactNativeWebView: 399e43d18902e3012c968fb9b7fd634b3936a882 + DatadogSDKReactNative: 5febb3635248ca87c73b9c74db0a9f214a9ab876 + DatadogSDKReactNativeSessionReplay: 38b40554550ecf191980cc22f1d9afebe1a914bd + DatadogSDKReactNativeWebView: 597c2fa95d43260228a326d1f33ec08500b74949 DatadogSessionReplay: 56a91d799fe34967c5ae79a222364e37d67020f5 DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index 84b5f1d8c..f37de03b0 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -10,7 +10,7 @@ PODS: - DatadogInternal (= 2.30.2) - DatadogRUM (2.30.2): - DatadogInternal (= 2.30.2) - - DatadogSDKReactNative (2.13.2): + - DatadogSDKReactNative (2.14.0): - DatadogCore (= 2.30.2) - DatadogCrashReporting (= 2.30.2) - DatadogLogs (= 2.30.2) @@ -37,7 +37,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNative/Tests (2.13.2): + - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) - DatadogCrashReporting (= 2.30.2) - DatadogLogs (= 2.30.2) @@ -1855,7 +1855,7 @@ SPEC CHECKSUMS: DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 DatadogLogs: d683aa9e0c9339f5ae679ead70bbdbe41cdc32f6 DatadogRUM: 8b794aa458e6323ea9b1cef3f820fd3d092cbe27 - DatadogSDKReactNative: afd267f110a3de4e179acc0bea6a492b0b8b00cf + DatadogSDKReactNative: b641dbbbe1b80c9551f55d86cdddac8d71568263 DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 45a7a2dea..d31ef2ba3 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -10,7 +10,7 @@ PODS: - DatadogInternal (= 2.30.2) - DatadogRUM (2.30.2): - DatadogInternal (= 2.30.2) - - DatadogSDKReactNative (2.13.2): + - DatadogSDKReactNative (2.14.0): - DatadogCore (= 2.30.2) - DatadogCrashReporting (= 2.30.2) - DatadogLogs (= 2.30.2) @@ -18,7 +18,7 @@ PODS: - DatadogTrace (= 2.30.2) - DatadogWebViewTracking (= 2.30.2) - React-Core - - DatadogSDKReactNative/Tests (2.13.2): + - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) - DatadogCrashReporting (= 2.30.2) - DatadogLogs (= 2.30.2) @@ -26,7 +26,7 @@ PODS: - DatadogTrace (= 2.30.2) - DatadogWebViewTracking (= 2.30.2) - React-Core - - DatadogSDKReactNativeSessionReplay (2.13.2): + - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - DatadogSessionReplay (= 2.30.2) - DoubleConversion @@ -49,7 +49,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeSessionReplay/Tests (2.13.2): + - DatadogSDKReactNativeSessionReplay/Tests (2.14.0): - DatadogSDKReactNative - DatadogSessionReplay (= 2.30.2) - DoubleConversion @@ -73,12 +73,12 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.13.2): + - DatadogSDKReactNativeWebView (2.14.0): - DatadogInternal (= 2.30.2) - DatadogSDKReactNative - DatadogWebViewTracking (= 2.30.2) - React-Core - - DatadogSDKReactNativeWebView/Tests (2.13.2): + - DatadogSDKReactNativeWebView/Tests (2.14.0): - DatadogInternal (= 2.30.2) - DatadogSDKReactNative - DatadogWebViewTracking (= 2.30.2) @@ -1993,9 +1993,9 @@ SPEC CHECKSUMS: DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 DatadogLogs: d683aa9e0c9339f5ae679ead70bbdbe41cdc32f6 DatadogRUM: 8b794aa458e6323ea9b1cef3f820fd3d092cbe27 - DatadogSDKReactNative: b04b5f9fd71aaa8a26affc43b1b0a2a43674a072 - DatadogSDKReactNativeSessionReplay: 102b0ecb56fb2baf3e8183e7c54631c5e31bf24a - DatadogSDKReactNativeWebView: 4343376f9a6be5e2af685444173f4b54e8b15d83 + DatadogSDKReactNative: 7f1df67f855450c33ff4169adeb901548d32fc8d + DatadogSDKReactNativeSessionReplay: b76757bea41273d7a69485d444194d8e00f53e27 + DatadogSDKReactNativeWebView: 49b59eda4795e53b6f065d768712cb3d2eb2c46d DatadogSessionReplay: 56a91d799fe34967c5ae79a222364e37d67020f5 DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be diff --git a/lerna.json b/lerna.json index 25572f225..08e58b2d8 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "npmClient": "yarn", - "version": "2.13.2", + "version": "2.14.0", "packages": [ "packages/*" ], diff --git a/packages/codepush/package.json b/packages/codepush/package.json index b52a8f017..0dbcc5b45 100644 --- a/packages/codepush/package.json +++ b/packages/codepush/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-native-code-push", - "version": "2.13.2", + "version": "2.14.0", "description": "A client-side React Native module to interact with Appcenter Codepush and Datadog", "keywords": [ "datadog", @@ -38,7 +38,7 @@ "prepare": "rm -rf lib && yarn bob build" }, "devDependencies": { - "@datadog/mobile-react-native": "workspace:2.13.2", + "@datadog/mobile-react-native": "workspace:2.14.0", "@testing-library/react-native": "7.0.2", "react-native-builder-bob": "0.26.0", "react-native-code-push": "7.1.0" diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/SdkVersion.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/SdkVersion.kt index f1f47e06c..3c4be8539 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/SdkVersion.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/SdkVersion.kt @@ -7,4 +7,4 @@ package com.datadog.reactnative // This is automatically updated by the update-version.sh script -internal const val SDK_VERSION = "2.13.2" +internal const val SDK_VERSION = "2.14.0" diff --git a/packages/core/ios/Sources/SdkVersion.swift b/packages/core/ios/Sources/SdkVersion.swift index 0d6ed2d5b..c71ba5440 100644 --- a/packages/core/ios/Sources/SdkVersion.swift +++ b/packages/core/ios/Sources/SdkVersion.swift @@ -7,4 +7,4 @@ import Foundation // This is automatically updated by the update-version.sh script -let SdkVersion = "2.13.2" +let SdkVersion = "2.14.0" diff --git a/packages/core/package.json b/packages/core/package.json index 56818b180..7e47b7244 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-native", - "version": "2.13.2", + "version": "2.14.0", "description": "A client-side React Native module to interact with Datadog", "keywords": [ "datadog", diff --git a/packages/core/src/version.ts b/packages/core/src/version.ts index 00d00d874..f52edffe8 100644 --- a/packages/core/src/version.ts +++ b/packages/core/src/version.ts @@ -1,2 +1,2 @@ // generated by genversion -export const version = '2.13.2'; +export const version = '2.14.0'; diff --git a/packages/internal-testing-tools/package.json b/packages/internal-testing-tools/package.json index 7d647fa0a..bebc02165 100644 --- a/packages/internal-testing-tools/package.json +++ b/packages/internal-testing-tools/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/react-native-internal-testing-tools", - "version": "2.13.2", + "version": "2.14.0", "description": "Internal tools for testing the Datadog React Native SDK.", "keywords": [ "datadog", diff --git a/packages/react-native-apollo-client/package.json b/packages/react-native-apollo-client/package.json index f7dfa6095..c8814a0ce 100644 --- a/packages/react-native-apollo-client/package.json +++ b/packages/react-native-apollo-client/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-native-apollo-client", - "version": "2.13.2", + "version": "2.14.0", "description": "A client-side React Native module to interact with Apollo Client and Datadog", "keywords": [ "datadog", diff --git a/packages/react-native-babel-plugin/package.json b/packages/react-native-babel-plugin/package.json index 17507d192..83287d9e9 100644 --- a/packages/react-native-babel-plugin/package.json +++ b/packages/react-native-babel-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-native-babel-plugin", - "version": "2.13.2", + "version": "2.14.0", "description": "A Babel plugin that enhances Datadog's React Native SDK by automatically enriching React components with contextual metadata.", "keywords": [ "babel", diff --git a/packages/react-native-navigation/package.json b/packages/react-native-navigation/package.json index 715145949..a2839ad4d 100644 --- a/packages/react-native-navigation/package.json +++ b/packages/react-native-navigation/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-native-navigation", - "version": "2.13.2", + "version": "2.14.0", "description": "A client-side React Native module to interact with Datadog", "keywords": [ "datadog", @@ -36,7 +36,7 @@ "prepare": "rm -rf lib && yarn bob build" }, "devDependencies": { - "@datadog/mobile-react-native": "^2.13.2", + "@datadog/mobile-react-native": "^2.14.0", "@testing-library/react-native": "7.0.2", "react-native-builder-bob": "0.26.0", "react-native-gesture-handler": "1.10.3", diff --git a/packages/react-native-session-replay/package.json b/packages/react-native-session-replay/package.json index afec1307f..99532c842 100644 --- a/packages/react-native-session-replay/package.json +++ b/packages/react-native-session-replay/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-native-session-replay", - "version": "2.13.2", + "version": "2.14.0", "description": "A client-side React Native module to enable session replay with Datadog", "keywords": [ "datadog", diff --git a/packages/react-native-session-replay/release-content.txt b/packages/react-native-session-replay/release-content.txt index 62b4912f4..9b51b3854 100644 --- a/packages/react-native-session-replay/release-content.txt +++ b/packages/react-native-session-replay/release-content.txt @@ -41,6 +41,8 @@ package/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/extensions package/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt package/android/src/rn80/kotlin/com/datadog/reactnative/sessionreplay/extensions/ComputedBorderRadiusExt.kt package/android/src/rn80/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +package/android/src/rn83/kotlin/com/datadog/reactnative/sessionreplay/extensions/ComputedBorderRadiusExt.kt +package/android/src/rn83/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt package/android/src/rnlegacy/kotlin/com.datadog.reactnative.sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt package/android/src/rnpost74/kotlin/com/datadog/reactnative/sessionreplay/DdSDKReactNativeSessionReplayPackage.kt package/android/src/rnpre74/kotlin/com/datadog/reactnative/sessionreplay/DdSDKReactNativeSessionReplayPackage.kt diff --git a/packages/react-native-webview/package.json b/packages/react-native-webview/package.json index e0fb2182b..cdff26f17 100644 --- a/packages/react-native-webview/package.json +++ b/packages/react-native-webview/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-native-webview", - "version": "2.13.2", + "version": "2.14.0", "description": "A client-side React Native module to interact with react-native-webview and Datadog", "keywords": [ "datadog", diff --git a/packages/react-navigation/package.json b/packages/react-navigation/package.json index b2f15d9dc..09bf15306 100644 --- a/packages/react-navigation/package.json +++ b/packages/react-navigation/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/mobile-react-navigation", - "version": "2.13.2", + "version": "2.14.0", "description": "A client-side React Native module to interact with Datadog", "keywords": [ "datadog", @@ -36,7 +36,7 @@ "prepare": "rm -rf lib && yarn bob build" }, "devDependencies": { - "@datadog/mobile-react-native": "^2.13.2", + "@datadog/mobile-react-native": "^2.14.0", "@react-navigation/native-v5": "npm:@react-navigation/native@5.9.8", "@react-navigation/native-v6": "npm:@react-navigation/native@6.1.2", "@react-navigation/stack-v5": "npm:@react-navigation/stack@5.14.2", diff --git a/yarn.lock b/yarn.lock index b57eb29ec..22b120805 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3804,7 +3804,7 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/mobile-react-native-code-push@workspace:packages/codepush" dependencies: - "@datadog/mobile-react-native": "workspace:2.13.2" + "@datadog/mobile-react-native": "workspace:2.14.0" "@testing-library/react-native": 7.0.2 react-native-builder-bob: 0.26.0 react-native-code-push: 7.1.0 @@ -3820,7 +3820,7 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/mobile-react-native-navigation@workspace:packages/react-native-navigation" dependencies: - "@datadog/mobile-react-native": ^2.13.2 + "@datadog/mobile-react-native": ^2.14.0 "@testing-library/react-native": 7.0.2 react-native-builder-bob: 0.26.0 react-native-gesture-handler: 1.10.3 @@ -3862,7 +3862,7 @@ __metadata: languageName: unknown linkType: soft -"@datadog/mobile-react-native@^2.13.2, @datadog/mobile-react-native@workspace:2.13.2, @datadog/mobile-react-native@workspace:packages/core": +"@datadog/mobile-react-native@^2.14.0, @datadog/mobile-react-native@workspace:2.14.0, @datadog/mobile-react-native@workspace:packages/core": version: 0.0.0-use.local resolution: "@datadog/mobile-react-native@workspace:packages/core" dependencies: @@ -3879,7 +3879,7 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/mobile-react-navigation@workspace:packages/react-navigation" dependencies: - "@datadog/mobile-react-native": ^2.13.2 + "@datadog/mobile-react-native": ^2.14.0 "@react-navigation/native-v5": "npm:@react-navigation/native@5.9.8" "@react-navigation/native-v6": "npm:@react-navigation/native@6.1.2" "@react-navigation/stack-v5": "npm:@react-navigation/stack@5.14.2" From 81c083e37387ccbeedc70bd39d8c294758b437d4 Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Mon, 7 Apr 2025 14:55:53 +0200 Subject: [PATCH 012/114] RUM-9023 use session id to sample network traces --- .../distributedTracing/distributedTracing.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx index e529f3cd1..9c4fcbff7 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx +++ b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx @@ -77,7 +77,12 @@ export const generateTracingAttributesWithSampling = ( } const traceId = TracingIdentifier.createTraceId(); - const hash = Number(traceId.id.multiply(knuthFactor).remainder(twoPow64)); + // for a UUID with value aaaaaaaa-bbbb-Mccc-Nddd-1234567890ab + // we use as the input id the last part : 0x1234567890ab + const baseId = rumSessionId + ? BigInt(rumSessionId.split('-')[4], 16) + : traceId.id; + const hash = Number(baseId.multiply(knuthFactor).remainder(twoPow64)); const threshold = (tracingSamplingRate / 100) * Number(twoPow64); const isSampled = hash <= threshold; From 88474ca503fbc18adfa3de6d8a0b4ed1b296c7ac Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Mon, 28 Apr 2025 13:55:23 +0200 Subject: [PATCH 013/114] RUM-7747 update default tracing sampling rate --- packages/core/ios/Sources/RNDdSdkConfiguration.swift | 2 +- packages/core/src/DdSdkReactNativeConfiguration.tsx | 2 +- .../src/__tests__/DdSdkReactNativeConfiguration.test.ts | 6 +++--- .../sdk/DatadogProvider/__tests__/initialization.test.tsx | 2 +- .../__tests__/FileBasedConfiguration.test.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/ios/Sources/RNDdSdkConfiguration.swift b/packages/core/ios/Sources/RNDdSdkConfiguration.swift index a765e9759..66437c481 100644 --- a/packages/core/ios/Sources/RNDdSdkConfiguration.swift +++ b/packages/core/ios/Sources/RNDdSdkConfiguration.swift @@ -194,7 +194,7 @@ extension NSArray { internal struct DefaultConfiguration { static let nativeCrashReportEnabled = false static let sessionSamplingRate = 100.0 - static let resourceTracingSamplingRate = 20.0 + static let resourceTracingSamplingRate = 100.0 static let longTaskThresholdMs = 0.0 static let nativeLongTaskThresholdMs = 200.0 static let nativeViewTracking = false diff --git a/packages/core/src/DdSdkReactNativeConfiguration.tsx b/packages/core/src/DdSdkReactNativeConfiguration.tsx index 158d258eb..9be08fa28 100644 --- a/packages/core/src/DdSdkReactNativeConfiguration.tsx +++ b/packages/core/src/DdSdkReactNativeConfiguration.tsx @@ -109,7 +109,7 @@ export const formatFirstPartyHosts = ( export const DEFAULTS = { nativeCrashReportEnabled: false, sessionSamplingRate: 100.0, - resourceTracingSamplingRate: 20.0, + resourceTracingSamplingRate: 100.0, site: 'US1', longTaskThresholdMs: 0, nativeLongTaskThresholdMs: 200, diff --git a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts index 6d9d081af..b1522ef7f 100644 --- a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts +++ b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts @@ -51,7 +51,7 @@ describe('DdSdkReactNativeConfiguration', () => { "nativeViewTracking": false, "proxyConfig": undefined, "resourceEventMapper": null, - "resourceTracingSamplingRate": 20, + "resourceTracingSamplingRate": 100, "serviceName": undefined, "sessionSamplingRate": 100, "site": "US1", @@ -79,7 +79,7 @@ describe('DdSdkReactNativeConfiguration', () => { trackInteractions: true, trackResources: true, firstPartyHosts: ['api.com'], - resourceTracingSamplingRate: 100, + resourceTracingSamplingRate: 80, logEventMapper: event => event, errorEventMapper: event => event, resourceEventMapper: event => event, @@ -161,7 +161,7 @@ describe('DdSdkReactNativeConfiguration', () => { "type": "https", }, "resourceEventMapper": [Function], - "resourceTracingSamplingRate": 100, + "resourceTracingSamplingRate": 80, "serviceName": "com.test.app", "sessionSamplingRate": 80, "site": "EU", diff --git a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx index 590c34b98..c654cd24b 100644 --- a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx +++ b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx @@ -93,7 +93,7 @@ describe('DatadogProvider', () => { "nativeLongTaskThresholdMs": 200, "nativeViewTracking": false, "proxyConfig": undefined, - "resourceTracingSamplingRate": 20, + "resourceTracingSamplingRate": 100, "sampleRate": 100, "serviceName": undefined, "site": "US1", diff --git a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts index 523cf67cc..716243e86 100644 --- a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts +++ b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts @@ -134,7 +134,7 @@ describe('FileBasedConfiguration', () => { "nativeViewTracking": false, "proxyConfig": undefined, "resourceEventMapper": null, - "resourceTracingSamplingRate": 20, + "resourceTracingSamplingRate": 100, "serviceName": undefined, "sessionSamplingRate": 100, "site": "US1", From 12939e267b7a11cbce79d269657e109dfaa826a8 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Tue, 2 Sep 2025 10:49:34 +0200 Subject: [PATCH 014/114] Remove fatal errors from logs --- .../DdRumErrorTracking.test.tsx | 202 +----------------- .../core/src/logs/__tests__/DdLogs.test.ts | 4 +- .../instrumentation/DdRumErrorTracking.tsx | 31 +-- 3 files changed, 9 insertions(+), 228 deletions(-) diff --git a/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx b/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx index 7f66bb58e..fc34de5b0 100644 --- a/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx +++ b/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx @@ -6,17 +6,13 @@ import { NativeModules } from 'react-native'; -import type { - DdNativeLogsType, - DdNativeRumType -} from '../../../nativeModulesTypes'; +import type { DdNativeRumType } from '../../../nativeModulesTypes'; import { DdRumErrorTracking } from '../../../rum/instrumentation/DdRumErrorTracking'; import { BufferSingleton } from '../../../sdk/DatadogProvider/Buffer/BufferSingleton'; jest.mock('../../../utils/jsUtils'); const DdRum = NativeModules.DdRum as DdNativeRumType; -const DdLogs = NativeModules.DdLogs as DdNativeLogsType; let baseErrorHandlerCalled = false; const baseErrorHandler = (error: any, isFatal?: boolean) => { @@ -77,19 +73,6 @@ it('M intercept and send a RUM event W onGlobalError() {no message}', async () = '' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - '[object Object]', - 'Error', - '[object Object]', - 'doSomething() at ./path/to/file.js:67:3', - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onGlobalError() {empty stack trace}', async () => { @@ -119,19 +102,6 @@ it('M intercept and send a RUM event W onGlobalError() {empty stack trace}', asy '' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - '', - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onGlobalError() {Error object}', async () => { @@ -162,19 +132,6 @@ it('M intercept and send a RUM event W onGlobalError() {Error object}', async () '/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - expect.stringContaining('Error: Something bad happened'), - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onGlobalError() {CustomError object}', async () => { @@ -209,19 +166,6 @@ it('M intercept and send a RUM event W onGlobalError() {CustomError object}', as '/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'CustomError', - 'Something bad happened', - expect.stringContaining('Error: Something bad happened'), - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onGlobalError() {with source file info}', async () => { @@ -254,19 +198,6 @@ it('M intercept and send a RUM event W onGlobalError() {with source file info}', '' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - 'at ./path/to/file.js:1038:57', - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onGlobalError() {with component stack}', async () => { @@ -301,19 +232,6 @@ it('M intercept and send a RUM event W onGlobalError() {with component stack}', '' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onGlobalError() {with stack}', async () => { @@ -348,19 +266,6 @@ it('M intercept and send a RUM event W onGlobalError() {with stack}', async () = '' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onGlobalError() {with stacktrace}', async () => { @@ -396,19 +301,6 @@ it('M intercept and send a RUM event W onGlobalError() {with stacktrace}', async ); expect(baseErrorHandlerCalled).toStrictEqual(true); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M not report error in console handler W onGlobalError() {with console reporting handler}', async () => { @@ -450,19 +342,6 @@ it('M not report error in console handler W onGlobalError() {with console report expect(consoleReportingErrorHandler).toBeCalledTimes(1); expect(baseConsoleErrorCalled).toStrictEqual(false); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onConsole() {Error with source file info}', async () => { @@ -493,17 +372,6 @@ it('M intercept and send a RUM event W onConsole() {Error with source file info} '' ); expect(baseConsoleErrorCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Oops I did it again! Something bad happened', - 'Error', - 'Oops I did it again! Something bad happened', - 'at ./path/to/file.js:1038:57', - { - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onConsole() {Error with component stack}', async () => { @@ -536,17 +404,6 @@ it('M intercept and send a RUM event W onConsole() {Error with component stack}' '' ); expect(baseConsoleErrorCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Oops I did it again! Something bad happened', - 'Error', - 'Oops I did it again! Something bad happened', - 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', - { - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onConsole() {message only}', async () => { @@ -571,17 +428,6 @@ it('M intercept and send a RUM event W onConsole() {message only}', async () => '' ); expect(baseConsoleErrorCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - '', - { - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onConsole() {Error with source file and name}', async () => { @@ -613,17 +459,6 @@ it('M intercept and send a RUM event W onConsole() {Error with source file and n '' ); expect(baseConsoleErrorCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Oops I did it again! Something bad happened', - 'CustomConsoleError', - 'Oops I did it again! Something bad happened', - 'at ./path/to/file.js:1038:57', - { - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); describe.each([ @@ -661,17 +496,6 @@ describe.each([ '' ); expect(baseConsoleErrorCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - errorMessage, - 'Error', - errorMessage, - '', - { - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); }); @@ -704,19 +528,6 @@ it('M intercept and send a RUM event W on error() {called from RNErrorHandler}', '/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx' ); expect(baseErrorHandlerCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Something bad happened', - 'Error', - 'Something bad happened', - expect.stringContaining('Error: Something bad happened'), - { - '_dd.error.raw': error, - '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); it('M intercept and send a RUM event W onConsole() {called from RNErrorHandler}', async () => { @@ -742,17 +553,6 @@ it('M intercept and send a RUM event W onConsole() {called from RNErrorHandler}' '' ); expect(baseConsoleErrorCalled).toStrictEqual(true); - expect(DdLogs.errorWithError).toHaveBeenCalledTimes(1); - expect(DdLogs.errorWithError).toHaveBeenCalledWith( - 'Oops I did it again!', - 'Error', - 'Oops I did it again!', - '', - { - '_dd.error.source_type': 'react-native', - '_dd.error_log.is_crash': true - } - ); }); /** diff --git a/packages/core/src/logs/__tests__/DdLogs.test.ts b/packages/core/src/logs/__tests__/DdLogs.test.ts index 42f96bae3..b8d0f4121 100644 --- a/packages/core/src/logs/__tests__/DdLogs.test.ts +++ b/packages/core/src/logs/__tests__/DdLogs.test.ts @@ -225,7 +225,7 @@ describe('DdLogs', () => { console.error('console-error-message'); expect(NativeModules.DdLogs.error).not.toHaveBeenCalled(); expect(InternalLog.log).toHaveBeenCalledWith( - 'error log dropped by log mapper: "console-error-message"', + 'Adding RUM Error “console-error-message”', 'debug' ); @@ -278,7 +278,7 @@ describe('DdLogs', () => { console.error('console-error-message'); expect(NativeModules.DdLogs.error).not.toHaveBeenCalled(); expect(InternalLog.log).toHaveBeenCalledWith( - 'Tracking error log "console-error-message"', + 'Adding RUM Error “console-error-message”', 'debug' ); }); diff --git a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx index ecdc99228..774fa70f5 100644 --- a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx +++ b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx @@ -8,7 +8,6 @@ import type { ErrorHandlerCallback } from 'react-native'; import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; -import { DdLogs } from '../../logs/DdLogs'; import { getErrorMessage, getErrorStackTrace, @@ -70,8 +69,7 @@ export class DdRumErrorTracking { static onGlobalError = (error: any, isFatal?: boolean): void => { const message = getErrorMessage(error); const stacktrace = getErrorStackTrace(error); - const errorName = getErrorName(error); - this.reportError(message, ErrorSource.SOURCE, stacktrace, errorName, { + this.reportError(message, ErrorSource.SOURCE, stacktrace, { '_dd.error.is_crash': isFatal, '_dd.error.raw': error }).then(async () => { @@ -130,34 +128,17 @@ export class DdRumErrorTracking { }) .join(' '); - this.reportError(message, ErrorSource.CONSOLE, stack, errorName).then( - () => { - DdRumErrorTracking.defaultConsoleError.apply(console, params); - } - ); + this.reportError(message, ErrorSource.CONSOLE, stack).then(() => { + DdRumErrorTracking.defaultConsoleError.apply(console, params); + }); }; private static reportError = ( message: string, source: ErrorSource, stacktrace: string, - errorName: string, context: object = {} - ): Promise<[void, void]> => { - return Promise.all([ - DdRum.addError(message, source, stacktrace, context), - DdLogs.error( - message, - errorName, - message, - stacktrace, - { - ...context, - '_dd.error_log.is_crash': true - }, - undefined, - source - ) - ]); + ): Promise => { + return DdRum.addError(message, source, stacktrace, context); }; } From b32bd2b2a83e91ee82d6b82b1ed399c4787838dd Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Tue, 2 Sep 2025 17:06:28 +0200 Subject: [PATCH 015/114] Improve module wrapper singleton creation --- packages/core/src/logs/DdLogs.ts | 4 +- packages/core/src/rum/DdRum.ts | 5 +- packages/core/src/trace/DdTrace.ts | 10 ++- .../utils/__tests__/singletonUtils.test.ts | 75 +++++++++++++++++++ packages/core/src/utils/singletonUtils.ts | 12 +++ 5 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/utils/__tests__/singletonUtils.test.ts create mode 100644 packages/core/src/utils/singletonUtils.ts diff --git a/packages/core/src/logs/DdLogs.ts b/packages/core/src/logs/DdLogs.ts index 2a77fc453..17ec3af1b 100644 --- a/packages/core/src/logs/DdLogs.ts +++ b/packages/core/src/logs/DdLogs.ts @@ -11,6 +11,7 @@ import type { DdNativeLogsType } from '../nativeModulesTypes'; import { DdAttributes } from '../rum/DdAttributes'; import type { ErrorSource } from '../rum/types'; import { validateContext } from '../utils/argsUtils'; +import { getGlobalInstance } from '../utils/singletonUtils'; import { generateEventMapper } from './eventMapper'; import type { @@ -22,6 +23,7 @@ import type { RawLogWithError } from './types'; +const LOGS_MODULE = 'com.datadog.reactnative.logs'; const SDK_NOT_INITIALIZED_MESSAGE = 'DD_INTERNAL_LOG_SENT_BEFORE_SDK_INIT'; const generateEmptyPromise = () => new Promise(resolve => resolve()); @@ -250,4 +252,4 @@ class DdLogsWrapper implements DdLogsType { } } -export const DdLogs = new DdLogsWrapper(); +export const DdLogs = getGlobalInstance(LOGS_MODULE, () => new DdLogsWrapper()); diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index 37ae3a30e..1241c73a4 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -13,6 +13,7 @@ import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNative import { DdSdk } from '../sdk/DdSdk'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; import { validateContext } from '../utils/argsUtils'; +import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; import type { TimeProvider } from '../utils/time-provider/TimeProvider'; @@ -44,6 +45,8 @@ import type { PropagatorType } from './types'; +const RUM_MODULE = 'com.datadog.reactnative.rum'; + const generateEmptyPromise = () => new Promise(resolve => resolve()); class DdRumWrapper implements DdRumType { @@ -518,4 +521,4 @@ const isOldStopActionAPI = ( return typeof args[0] === 'object' || typeof args[0] === 'undefined'; }; -export const DdRum = new DdRumWrapper(); +export const DdRum = getGlobalInstance(RUM_MODULE, () => new DdRumWrapper()); diff --git a/packages/core/src/trace/DdTrace.ts b/packages/core/src/trace/DdTrace.ts index b106a97d8..119716914 100644 --- a/packages/core/src/trace/DdTrace.ts +++ b/packages/core/src/trace/DdTrace.ts @@ -13,8 +13,11 @@ import { } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; import type { DdTraceType } from '../types'; import { validateContext } from '../utils/argsUtils'; +import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; +const TRACE_MODULE = 'com.datadog.reactnative.trace'; + const timeProvider = new DefaultTimeProvider(); class DdTraceWrapper implements DdTraceType { @@ -59,6 +62,7 @@ class DdTraceWrapper implements DdTraceType { }; } -const DdTrace: DdTraceType = new DdTraceWrapper(); - -export { DdTrace }; +export const DdTrace: DdTraceType = getGlobalInstance( + TRACE_MODULE, + () => new DdTraceWrapper() +); diff --git a/packages/core/src/utils/__tests__/singletonUtils.test.ts b/packages/core/src/utils/__tests__/singletonUtils.test.ts new file mode 100644 index 000000000..f424562c6 --- /dev/null +++ b/packages/core/src/utils/__tests__/singletonUtils.test.ts @@ -0,0 +1,75 @@ +import { getGlobalInstance } from '../singletonUtils'; + +describe('singletonUtils', () => { + const createdSymbols: symbol[] = []; + const g = (globalThis as unknown) as Record; + + afterEach(() => { + for (const symbol of createdSymbols) { + delete g[symbol]; + } + + createdSymbols.length = 0; + jest.restoreAllMocks(); + }); + + it('only creates one instance for the same key', () => { + const key = 'com.datadog.reactnative.test'; + const symbol = Symbol.for(key); + createdSymbols.push(symbol); + + const objectConstructor = jest.fn(() => ({ id: 1 })); + const a = getGlobalInstance(key, objectConstructor); + const b = getGlobalInstance(key, objectConstructor); + + expect(a).toBe(b); + expect(objectConstructor).toHaveBeenCalledTimes(1); + expect(g[symbol]).toBe(a); + }); + + it('returns a pre-existing instance without creating a new one for the same key', () => { + const key = 'com.datadog.reactnative.test'; + const symbol = Symbol.for(key); + createdSymbols.push(symbol); + + const existing = { pre: true }; + g[symbol] = existing; + + const objectConstructor = jest.fn(() => ({ created: true })); + const result = getGlobalInstance(key, objectConstructor); + + expect(result).toBe(existing); + expect(objectConstructor).not.toHaveBeenCalled(); + }); + + it('creates a new instance for a different key', () => { + const keyA = 'com.datadog.reactnative.test.a'; + const keyB = 'com.datadog.reactnative.test.b'; + const symbolA = Symbol.for(keyA); + const symbolB = Symbol.for(keyB); + createdSymbols.push(symbolA, symbolB); + + const a = getGlobalInstance(keyA, () => ({ id: 'A' })); + const b = getGlobalInstance(keyB, () => ({ id: 'B' })); + + expect(a).not.toBe(b); + expect((a as any).id).toBe('A'); + expect((b as any).id).toBe('B'); + }); + + it('does not overwrite existing instance if called with a different constructor', () => { + const key = 'com.datadog.reactnative.test'; + const symbol = Symbol.for(key); + createdSymbols.push(symbol); + + const firstObjectConstructor = jest.fn(() => ({ id: 1 })); + const first = getGlobalInstance(key, firstObjectConstructor); + + const secondObjectConstructor = jest.fn(() => ({ id: 2 })); + const second = getGlobalInstance(key, secondObjectConstructor); + + expect(first).toBe(second); + expect(firstObjectConstructor).toHaveBeenCalledTimes(1); + expect(secondObjectConstructor).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/core/src/utils/singletonUtils.ts b/packages/core/src/utils/singletonUtils.ts new file mode 100644 index 000000000..9f00c2cd0 --- /dev/null +++ b/packages/core/src/utils/singletonUtils.ts @@ -0,0 +1,12 @@ +export const getGlobalInstance = ( + key: string, + objectConstructor: () => T +): T => { + const symbol = Symbol.for(key); + const g = (globalThis as unknown) as Record; + + if (!(symbol in g)) { + g[symbol] = objectConstructor(); + } + return g[symbol] as T; +}; From 65e81de0e50694d7e5c08c41d6979adf9b7bc523 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Sep 2025 17:47:29 +0200 Subject: [PATCH 016/114] Use native sdk's core instance instead of the one inside RN SDK wrapper --- .../datadog/reactnative/DatadogSDKWrapper.kt | 90 +------------------ .../com/datadog/reactnative/DatadogWrapper.kt | 48 ---------- .../reactnative/DdLogsImplementation.kt | 3 +- .../reactnative/DdSdkImplementation.kt | 9 +- .../reactnative/DdSdkNativeInitialization.kt | 13 ++- .../reactnative/DdSdkReactNativePackage.kt | 3 +- .../com/datadog/reactnative/DdTelemetry.kt | 56 ++++++++++++ .../kotlin/com/datadog/reactnative/DdSdk.kt | 3 +- .../kotlin/com/datadog/reactnative/DdSdk.kt | 5 +- .../DdSdkNativeInitializationTest.kt | 4 + .../com/datadog/reactnative/DdSdkTest.kt | 6 +- .../DdInternalTestingImplementation.kt | 2 +- .../DdInternalTestingImplementationTest.kt | 74 ++++++++------- .../DdSessionReplayImplementation.kt | 8 +- 14 files changed, 140 insertions(+), 184 deletions(-) create mode 100644 packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index c9865a5d7..da0841ac4 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -8,23 +8,14 @@ package com.datadog.reactnative import android.content.Context -import android.util.Log import com.datadog.android.Datadog -import com.datadog.android._InternalProxy import com.datadog.android.api.InternalLogger -import com.datadog.android.api.SdkCore import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.configuration.Configuration -import com.datadog.android.log.Logs -import com.datadog.android.log.LogsConfiguration import com.datadog.android.privacy.TrackingConsent import com.datadog.android.rum.GlobalRumMonitor -import com.datadog.android.rum.Rum -import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumMonitor -import com.datadog.android.trace.Trace -import com.datadog.android.trace.TraceConfiguration import com.datadog.android.webview.WebViewTracking import com.facebook.react.bridge.ReadableMap @@ -50,50 +41,18 @@ object DatadogSDKWrapperStorage { listener(ddCore) } } - - /** - * Sets instance of core SDK to be used to initialize features. - */ - fun setSdkCore(core: InternalSdkCore?) { - this.core = core - } - - /** - * Returns the core set by setSdkCore or the default core instance by default. - */ - fun getSdkCore(): SdkCore { - core?.let { - return it - } - Log.d( - DatadogSDKWrapperStorage::class.java.canonicalName, - "SdkCore was not set in DatadogSDKWrapperStorage, using default instance." - ) - return Datadog.getInstance() - } } internal class DatadogSDKWrapper : DatadogWrapper { override var bundleLogsWithRum = DefaultConfiguration.bundleLogsWithRum override var bundleLogsWithTraces = DefaultConfiguration.bundleLogsWithTraces - // We use Kotlin backing field here to initialize once the telemetry proxy - // and make sure it is only after SDK is initialized. - private var telemetryProxy: _InternalProxy._TelemetryProxy? = null - get() { - if (field == null && isInitialized()) { - field = Datadog._internalProxy()._telemetry - } - - return field - } - // We use Kotlin backing field here to initialize once the telemetry proxy // and make sure it is only after SDK is initialized. private var webViewProxy: WebViewTracking._InternalWebViewProxy? = null get() { if (field == null && isInitialized()) { - field = WebViewTracking._InternalWebViewProxy(DatadogSDKWrapperStorage.getSdkCore()) + field = WebViewTracking._InternalWebViewProxy(Datadog.getInstance()) } return field @@ -109,20 +68,7 @@ internal class DatadogSDKWrapper : DatadogWrapper { consent: TrackingConsent ) { val core = Datadog.initialize(context, configuration, consent) - DatadogSDKWrapperStorage.setSdkCore(core as InternalSdkCore) - DatadogSDKWrapperStorage.notifyOnInitializedListeners(core) - } - - override fun enableRum(configuration: RumConfiguration) { - Rum.enable(configuration, DatadogSDKWrapperStorage.getSdkCore()) - } - - override fun enableLogs(configuration: LogsConfiguration) { - Logs.enable(configuration, DatadogSDKWrapperStorage.getSdkCore()) - } - - override fun enableTrace(configuration: TraceConfiguration) { - Trace.enable(configuration, DatadogSDKWrapperStorage.getSdkCore()) + DatadogSDKWrapperStorage.notifyOnInitializedListeners(core as InternalSdkCore) } @Deprecated("Use setUserInfo instead; the user ID is now required.") @@ -161,34 +107,6 @@ internal class DatadogSDKWrapper : DatadogWrapper { Datadog.setTrackingConsent(trackingConsent) } - override fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap) { - val core = DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore? - val logger = core?.internalLogger; - - val additionalProperties = attributes.toMap() - val telemetryConfig = config.toMap() - - logger?.log( - level = InternalLogger.Level.INFO, - target = InternalLogger.Target.TELEMETRY, - messageBuilder = { message }, - onlyOnce = (telemetryConfig["onlyOnce"] as? Boolean) ?: true, - additionalProperties = additionalProperties - ) - } - - override fun telemetryDebug(message: String) { - telemetryProxy?.debug(message) - } - - override fun telemetryError(message: String, stack: String?, kind: String?) { - telemetryProxy?.error(message, stack, kind) - } - - override fun telemetryError(message: String, throwable: Throwable?) { - telemetryProxy?.error(message, throwable) - } - override fun consumeWebviewEvent(message: String) { webViewProxy?.consumeWebviewEvent(message) } @@ -198,11 +116,11 @@ internal class DatadogSDKWrapper : DatadogWrapper { } override fun getRumMonitor(): RumMonitor { - return GlobalRumMonitor.get(DatadogSDKWrapperStorage.getSdkCore()) + return GlobalRumMonitor.get(Datadog.getInstance()) } override fun clearAllData() { - return Datadog.clearAllData(DatadogSDKWrapperStorage.getSdkCore()) + return Datadog.clearAllData(Datadog.getInstance()) } } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index 41e86f4d5..9ac591d80 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -64,33 +64,6 @@ interface DatadogWrapper { consent: TrackingConsent ) - /** - * Enables the RUM feature of the SDK. - * - * @param configuration the configuration for the RUM feature - */ - fun enableRum( - configuration: RumConfiguration - ) - - /** - * Enables the Logs feature of the SDK. - * - * @param configuration the configuration for the Logs feature - */ - fun enableLogs( - configuration: LogsConfiguration - ) - - /** - * Enables the Trace feature of the SDK. - * - * @param configuration the configuration for the Trace feature - */ - fun enableTrace( - configuration: TraceConfiguration - ) - /** * Sets the user information. * @@ -144,27 +117,6 @@ interface DatadogWrapper { */ fun setTrackingConsent(trackingConsent: TrackingConsent) - - /** - * Sends telemetry event with attributes. - */ - fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap) - - /** - * Sends telemetry debug event. - */ - fun telemetryDebug(message: String) - - /** - * Sends telemetry error. - */ - fun telemetryError(message: String, stack: String?, kind: String?) - - /** - * Sends telemetry error. - */ - fun telemetryError(message: String, throwable: Throwable?) - /** * Sends Webview events. */ diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdLogsImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdLogsImplementation.kt index 9a84496ec..2f9bceff2 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdLogsImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdLogsImplementation.kt @@ -7,6 +7,7 @@ package com.datadog.reactnative import android.util.Log as AndroidLog +import com.datadog.android.Datadog import com.datadog.android.log.Logger import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReadableMap @@ -22,7 +23,7 @@ class DdLogsImplementation( val bundleLogsWithRum = datadog.bundleLogsWithRum val bundleLogsWithTraces = datadog.bundleLogsWithTraces - logger ?: Logger.Builder(DatadogSDKWrapperStorage.getSdkCore()) + logger ?: Logger.Builder(Datadog.getInstance()) .setLogcatLogsEnabled(true) .setBundleWithRumEnabled(bundleLogsWithRum) .setBundleWithTraceEnabled(bundleLogsWithTraces) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index cdd6b0614..b04a2ddf3 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean class DdSdkImplementation( private val reactContext: ReactApplicationContext, private val datadog: DatadogWrapper = DatadogSDKWrapper(), + private val ddTelemetry: DdTelemetry = DdTelemetry(), private val uiThreadExecutor: UiThreadExecutor = ReactUiThreadExecutor() ) { internal val appContext: Context = reactContext.applicationContext @@ -39,7 +40,7 @@ class DdSdkImplementation( fun initialize(configuration: ReadableMap, promise: Promise) { val ddSdkConfiguration = configuration.asDdSdkConfiguration() - val nativeInitialization = DdSdkNativeInitialization(appContext, datadog) + val nativeInitialization = DdSdkNativeInitialization(appContext, datadog, ddTelemetry) nativeInitialization.initialize(ddSdkConfiguration) this.frameRateProvider = createFrameRateProvider(ddSdkConfiguration) @@ -145,7 +146,7 @@ class DdSdkImplementation( * @param config Configuration object, can take 'onlyOnce: Boolean' */ fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap, promise: Promise) { - datadog.sendTelemetryLog(message, attributes, config) + ddTelemetry.sendTelemetryLog(message, attributes, config) promise.resolve(null) } @@ -154,7 +155,7 @@ class DdSdkImplementation( * @param message Debug message. */ fun telemetryDebug(message: String, promise: Promise) { - datadog.telemetryDebug(message) + ddTelemetry.telemetryDebug(message) promise.resolve(null) } @@ -165,7 +166,7 @@ class DdSdkImplementation( * @param kind Error kind. */ fun telemetryError(message: String, stack: String, kind: String, promise: Promise) { - datadog.telemetryError(message, stack, kind) + ddTelemetry.telemetryError(message, stack, kind) promise.resolve(null) } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index 9c8e8370d..ee55d08fe 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -9,14 +9,17 @@ package com.datadog.reactnative import android.content.Context import android.content.pm.PackageManager import android.util.Log +import com.datadog.android.Datadog import com.datadog.android.DatadogSite import com.datadog.android.core.configuration.BatchProcessingLevel import com.datadog.android.core.configuration.BatchSize import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.configuration.UploadFrequency import com.datadog.android.event.EventMapper +import com.datadog.android.log.Logs import com.datadog.android.log.LogsConfiguration import com.datadog.android.privacy.TrackingConsent +import com.datadog.android.rum.Rum import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum._RumInternalProxy import com.datadog.android.rum.configuration.VitalsUpdateFrequency @@ -25,6 +28,7 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy import com.datadog.android.telemetry.model.TelemetryConfigurationEvent +import com.datadog.android.trace.Trace import com.datadog.android.trace.TraceConfiguration import com.google.gson.Gson import java.util.Locale @@ -37,6 +41,7 @@ import kotlin.time.Duration.Companion.seconds class DdSdkNativeInitialization internal constructor( private val appContext: Context, private val datadog: DatadogWrapper = DatadogSDKWrapper(), + private val ddTelemetry: DdTelemetry = DdTelemetry(), private val jsonFileReader: JSONFileReader = JSONFileReader() ) { internal fun initialize(ddSdkConfiguration: DdSdkConfiguration) { @@ -59,11 +64,11 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) - datadog.enableRum(rumConfiguration) + Rum.enable(rumConfiguration, Datadog.getInstance()) - datadog.enableTrace(traceConfiguration) + Logs.enable(logsConfiguration, Datadog.getInstance()) - datadog.enableLogs(logsConfiguration) + Trace.enable(traceConfiguration, Datadog.getInstance()) } private fun configureRumAndTracesForLogs(configuration: DdSdkConfiguration) { @@ -95,7 +100,7 @@ class DdSdkNativeInitialization internal constructor( try { appContext.packageManager.getPackageInfo(packageName, 0) } catch (e: PackageManager.NameNotFoundException) { - datadog.telemetryError(e.message ?: DdSdkImplementation.PACKAGE_INFO_NOT_FOUND_ERROR_MESSAGE, e) + ddTelemetry.telemetryError(e.message ?: DdSdkImplementation.PACKAGE_INFO_NOT_FOUND_ERROR_MESSAGE, e) return DdSdkImplementation.DEFAULT_APP_VERSION } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkReactNativePackage.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkReactNativePackage.kt index bac9f49f5..3a5b022c1 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkReactNativePackage.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkReactNativePackage.kt @@ -18,9 +18,10 @@ import com.facebook.react.module.model.ReactModuleInfoProvider */ class DdSdkReactNativePackage : TurboReactPackage() { private val sdkWrapper = DatadogSDKWrapper() + private val ddTelemetry = DdTelemetry() override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { return when (name) { - DdSdkImplementation.NAME -> DdSdk(reactContext, sdkWrapper) + DdSdkImplementation.NAME -> DdSdk(reactContext, sdkWrapper, ddTelemetry) DdRumImplementation.NAME -> DdRum(reactContext, sdkWrapper) DdTraceImplementation.NAME -> DdTrace(reactContext) DdLogsImplementation.NAME -> DdLogs(reactContext, sdkWrapper) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt new file mode 100644 index 000000000..2d60df004 --- /dev/null +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt @@ -0,0 +1,56 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.reactnative + +import com.datadog.android.Datadog +import com.datadog.android._InternalProxy +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.FeatureSdkCore +import com.facebook.react.bridge.ReadableMap + +class DdTelemetry { + + // We use Kotlin backing field here to initialize once the telemetry proxy + // and make sure it is only after SDK is initialized. + private var telemetryProxy: _InternalProxy._TelemetryProxy? = null + get() { + if (field == null && Datadog.isInitialized()) { + field = Datadog._internalProxy()._telemetry + } + + return field + } + + fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap) { + val core = Datadog.getInstance() as FeatureSdkCore? + val logger = core?.internalLogger; + + val additionalProperties = attributes.toMap() + val telemetryConfig = config.toMap() + + logger?.log( + level = InternalLogger.Level.INFO, + target = InternalLogger.Target.TELEMETRY, + messageBuilder = { message }, + onlyOnce = (telemetryConfig["onlyOnce"] as? Boolean) ?: true, + additionalProperties = additionalProperties + ) + } + + fun telemetryDebug(message: String) { + telemetryProxy?.debug(message) + } + + fun telemetryError(message: String, stack: String?, kind: String?) { + telemetryProxy?.error(message, stack, kind) + } + + fun telemetryError(message: String, throwable: Throwable?) { + telemetryProxy?.error(message, throwable) + } +} + diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt index d46e53ade..5bc470947 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -19,9 +19,10 @@ import com.facebook.react.modules.core.DeviceEventManagerModule class DdSdk( reactContext: ReactApplicationContext, datadogWrapper: DatadogWrapper = DatadogSDKWrapper() + ddTelemetry: DdTelemetry = DdTelemetry() ) : NativeDdSdkSpec(reactContext) { - private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper) + private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index b41eff1db..af8f87c29 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -17,10 +17,11 @@ import com.facebook.react.bridge.ReadableMap /** The entry point to initialize Datadog's features. */ class DdSdk( reactContext: ReactApplicationContext, - datadogWrapper: DatadogWrapper = DatadogSDKWrapper() + datadogWrapper: DatadogWrapper = DatadogSDKWrapper(), + ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper) + private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt index d05d43c57..e25bfe999 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt @@ -48,6 +48,9 @@ internal class DdSdkNativeInitializationTest { @Mock lateinit var mockDatadog: DatadogWrapper + @Mock + lateinit var mockDdTelemetry: DdTelemetry + @Mock lateinit var mockJSONFileReader: JSONFileReader @@ -64,6 +67,7 @@ internal class DdSdkNativeInitializationTest { testedNativeInitialization = DdSdkNativeInitialization( mockContext, mockDatadog, + mockDdTelemetry, mockJSONFileReader ) } diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 5cda53616..9e7d671fd 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -120,6 +120,9 @@ internal class DdSdkTest { @Mock lateinit var mockDatadog: DatadogWrapper + @Mock + lateinit var mockDdTelemetry: DdTelemetry + @Forgery lateinit var fakeConfiguration: DdSdkConfiguration @@ -157,9 +160,8 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, TestUiThreadExecutor()) + testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) - DatadogSDKWrapperStorage.setSdkCore(null) DatadogSDKWrapperStorage.onInitializedListeners.clear() } diff --git a/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt b/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt index 197a9df75..df0030fb5 100644 --- a/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt +++ b/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt @@ -7,6 +7,7 @@ package com.datadog.reactnative.internaltesting import com.datadog.android.api.InternalLogger +import com.datadog.android.Datadog import com.datadog.android.api.context.DatadogContext import com.datadog.android.api.context.NetworkInfo import com.datadog.android.api.context.TimeInfo @@ -53,7 +54,6 @@ class DdInternalTestingImplementation { fun enable(promise: Promise) { DatadogSDKWrapperStorage.addOnInitializedListener { ddCore -> this.wrappedCore = StubSDKCore(ddCore) - DatadogSDKWrapperStorage.setSdkCore(this.wrappedCore) } promise.resolve(null) } diff --git a/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt b/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt index 6c278026a..c542ed6bc 100644 --- a/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt +++ b/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt @@ -7,6 +7,8 @@ package com.datadog.reactnative.internaltesting import android.content.Context +import com.datadog.android.Datadog +import com.datadog.android.api.SdkCore import com.datadog.android.api.context.DatadogContext import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureScope @@ -24,6 +26,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings @@ -57,37 +60,47 @@ internal class DdInternalTestingImplementationTest { @Test fun `M return captured events W enable()`() { - // Given - val mockFeature = MockFeature("mockFeature") - val mockFeatureScope = MockFeatureScope(mockFeature) - whenever(mockCore.getFeature(mockFeature.name)).doReturn( - mockFeatureScope - ) - whenever(mockCore.getDatadogContext()).doReturn( - mockContext - ) - - // When - testedInternalTesting.enable(mockPromise) - // Simulating DdSdkImplementation initialization - DatadogSDKWrapperStorage.setSdkCore(mockCore) - DatadogSDKWrapperStorage.notifyOnInitializedListeners(mockCore) - - val wrappedCore = DatadogSDKWrapperStorage.getSdkCore() as StubSDKCore - wrappedCore.registerFeature(mockFeature) - requireNotNull(wrappedCore.getFeature(mockFeature.name)) - .withWriteContext { _, eventBatchWriter -> - eventBatchWriter.write( - RawBatchEvent(data = "mock event for test".toByteArray()), - batchMetadata = null, - eventType = EventType.DEFAULT + Mockito.mockStatic(Datadog::class.java).use { datadogStatic -> + // Given + datadogStatic.`when` { + Datadog.getInstance() + }.thenReturn(mockCore) + + val mockFeature = MockFeature("mockFeature") + val mockFeatureScope = MockFeatureScope(mockFeature) + whenever(mockCore.getFeature(mockFeature.name)).doReturn( + mockFeatureScope + ) + whenever(mockCore.getDatadogContext()).doReturn( + mockContext + ) + + // When + testedInternalTesting.enable(mockPromise) + // Simulating DdSdkImplementation initialization + DatadogSDKWrapperStorage.notifyOnInitializedListeners(mockCore) + + val wrappedCore = Datadog.getInstance() as StubSDKCore + wrappedCore.registerFeature(mockFeature) + requireNotNull(wrappedCore.getFeature(mockFeature.name)) + .withWriteContext { _, eventBatchWriter -> + eventBatchWriter.write( + RawBatchEvent(data = "mock event for test".toByteArray()), + batchMetadata = null, + eventType = EventType.DEFAULT + ) + } + + // Then + assertThat( + wrappedCore.featureScopes[mockFeature.name] + ?.eventsWritten() + ?.first() + ) + .isEqualTo( + "mock event for test" ) - } - - // Then - assertThat(wrappedCore.featureScopes[mockFeature.name]?.eventsWritten()?.first()).isEqualTo( - "mock event for test" - ) + } } } @@ -96,6 +109,7 @@ internal class MockFeatureScope(private val feature: Feature) : FeatureScope { override fun sendEvent(event: Any) {} + @Suppress("UNCHECKED_CAST") override fun unwrap(): T { return feature as T } diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt index ef5467bc4..cc2fd64dc 100644 --- a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt @@ -7,10 +7,10 @@ package com.datadog.reactnative.sessionreplay import android.annotation.SuppressLint +import com.datadog.android.Datadog import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.SessionReplayConfiguration import com.datadog.android.sessionreplay._SessionReplayInternalProxy -import com.datadog.reactnative.DatadogSDKWrapperStorage import com.datadog.reactnative.sessionreplay.utils.text.TextViewUtils import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactContext @@ -40,7 +40,7 @@ class DdSessionReplayImplementation( startRecordingImmediately: Boolean, promise: Promise ) { - val sdkCore = DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore + val sdkCore = Datadog.getInstance() as FeatureSdkCore val logger = sdkCore.internalLogger val textViewUtils = TextViewUtils.create(reactContext, logger) val internalCallback = ReactNativeInternalCallback(reactContext) @@ -68,7 +68,7 @@ class DdSessionReplayImplementation( */ fun startRecording(promise: Promise) { sessionReplayProvider().startRecording( - DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore + Datadog.getInstance() as FeatureSdkCore ) promise.resolve(null) } @@ -78,7 +78,7 @@ class DdSessionReplayImplementation( */ fun stopRecording(promise: Promise) { sessionReplayProvider().stopRecording( - DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore + Datadog.getInstance() as FeatureSdkCore ) promise.resolve(null) } From 41b483686420058a7a572d767af53f3d885321e4 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Mon, 22 Sep 2025 16:20:27 +0200 Subject: [PATCH 017/114] Fixed internal testing tools and unit tests --- .../com/datadog/reactnative/DatadogWrapper.kt | 8 +- .../reactnative/DdSdkNativeInitialization.kt | 2 - .../com/datadog/reactnative/DdTelemetry.kt | 40 + .../kotlin/com/datadog/reactnative/DdSdk.kt | 6 +- .../com/datadog/reactnative/DdSdkTest.kt | 2628 ++++++++++------- .../DdInternalTestingImplementation.kt | 6 + .../DdInternalTestingImplementationTest.kt | 4 +- 7 files changed, 1695 insertions(+), 999 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index 9ac591d80..19b25e587 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -18,7 +18,7 @@ import com.facebook.react.bridge.ReadableMap import java.lang.IllegalArgumentException /** - * Wrapper around [Datadog]. + * Wrapper around [com.datadog.android.Datadog]. */ @Suppress("ComplexInterface", "TooManyFunctions") interface DatadogWrapper { @@ -49,10 +49,8 @@ interface DatadogWrapper { /** * Initializes the Datadog SDK. * @param context your application context - * @param credentials your organization credentials * @param configuration the configuration for the SDK library - * @param trackingConsent as the initial state of the tracking consent flag. - * @see [Credentials] + * @param consent as the initial state of the tracking consent flag. * @see [Configuration] * @see [TrackingConsent] * @throws IllegalArgumentException if the env name is using illegal characters and your @@ -99,7 +97,7 @@ interface DatadogWrapper { /** * Sets the user information. - * @param extraUserInfo: The additional information. (To set the id, name or email please user setUserInfo). + * @param extraInfo: The additional information. (To set the id, name or email please user setUserInfo). */ fun addUserExtraInfo( extraInfo: Map diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index ee55d08fe..4388ad5f6 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,9 +65,7 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) - Logs.enable(logsConfiguration, Datadog.getInstance()) - Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt index 2d60df004..24354ce78 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTelemetry.kt @@ -12,6 +12,13 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.FeatureSdkCore import com.facebook.react.bridge.ReadableMap +/** + * **[INTERNAL USAGE]** + * + * Utility class used by React Native modules to forward telemetry events to the Datadog SDK. + * + * This class is **public only for Datadog internal package visibility** and should not be used. + */ class DdTelemetry { // We use Kotlin backing field here to initialize once the telemetry proxy @@ -25,6 +32,15 @@ class DdTelemetry { return field } + /** + * **[INTERNAL USAGE]** + * + * Sends a telemetry log message with additional attributes and configuration options. + * + * @param message the message to log + * @param attributes additional key–value properties to include in the log + * @param config configuration options for the telemetry log (e.g. `onlyOnce` flag) + */ fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap) { val core = Datadog.getInstance() as FeatureSdkCore? val logger = core?.internalLogger; @@ -41,14 +57,38 @@ class DdTelemetry { ) } + /** + * **[INTERNAL USAGE]** + * + * Sends a debug-level telemetry message. + * + * @param message the debug message + */ fun telemetryDebug(message: String) { telemetryProxy?.debug(message) } + /** + * **[INTERNAL USAGE]** + * + * Sends an error-level telemetry message with optional details. + * + * @param message the error message + * @param stack an optional stack trace string + * @param kind an optional error kind or category + */ fun telemetryError(message: String, stack: String?, kind: String?) { telemetryProxy?.error(message, stack, kind) } + /** + * **[INTERNAL USAGE]** + * + * Sends an error-level telemetry message with an attached [Throwable]. + * + * @param message the error message + * @param throwable the throwable associated with the error + */ fun telemetryError(message: String, throwable: Throwable?) { telemetryProxy?.error(message, throwable) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index af8f87c29..17acd6d20 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -21,7 +21,11 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) + private val implementation = DdSdkImplementation( + reactContext, + datadog = datadogWrapper, + ddTelemetry + ) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 9e7d671fd..a39485dae 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -16,8 +16,10 @@ import com.datadog.android.core.configuration.BatchSize import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.configuration.UploadFrequency import com.datadog.android.event.EventMapper +import com.datadog.android.log.Logs import com.datadog.android.log.LogsConfiguration import com.datadog.android.privacy.TrackingConsent +import com.datadog.android.rum.Rum import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumPerformanceMetric import com.datadog.android.rum._RumInternalProxy @@ -27,6 +29,7 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy import com.datadog.android.telemetry.model.TelemetryConfigurationEvent +import com.datadog.android.trace.Trace import com.datadog.android.trace.TraceConfiguration import com.datadog.android.trace.TracingHeaderType import com.datadog.tools.unit.GenericAssert.Companion.assertThat @@ -160,7 +163,12 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) + testedBridgeSdk = DdSdkImplementation( + mockReactContext, + mockDatadog, + mockDdTelemetry, + TestUiThreadExecutor() + ) DatadogSDKWrapperStorage.onInitializedListeners.clear() } @@ -181,35 +189,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo("crashReportsEnabled", true) - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo("crashReportsEnabled", true) + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -221,75 +243,104 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo("crashReportsEnabled", false) - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo("crashReportsEnabled", false) + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test fun `𝕄 initialize native SDK 𝕎 initialize() {nativeCrashReportEnabled=null}`() { // Given fakeConfiguration = fakeConfiguration.copy(nativeCrashReportEnabled = false, site = null) + val sdkConfigCaptor = argumentCaptor() val rumConfigCaptor = argumentCaptor() val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo("crashReportsEnabled", false) - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo("crashReportsEnabled", false) + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -305,37 +356,50 @@ internal class DdSdkTest { val traceConfigCaptor = argumentCaptor() val expectedRumSampleRate = fakeConfiguration.sampleRate?.toFloat() ?: 100f - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("sampleRate", expectedRumSampleRate) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("sampleRate", expectedRumSampleRate) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -351,37 +415,50 @@ internal class DdSdkTest { val traceConfigCaptor = argumentCaptor() val expectedTelemetrySampleRate = fakeConfiguration.telemetrySampleRate?.toFloat() ?: 20f - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("telemetrySampleRate", expectedTelemetrySampleRate) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("telemetrySampleRate", expectedTelemetrySampleRate) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -397,31 +474,44 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo("additionalConfig", emptyMap()) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo("additionalConfig", emptyMap()) + assertThat(rumConfigCaptor.firstValue) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -432,34 +522,47 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -477,35 +580,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.US1) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.US1) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -520,35 +637,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.US1) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.US1) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -563,35 +694,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.US3) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.US3) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -606,35 +751,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.US5) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.US5) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -649,35 +808,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.US1_FED) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.US1_FED) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -692,35 +865,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.EU1) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.EU1) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -735,35 +922,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.AP1) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.AP1) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -778,35 +979,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) - it.hasFieldEqualTo("site", DatadogSite.AP2) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) - .hasFieldEqualTo("env", fakeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo( - "additionalConfig", - fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + it.hasFieldEqualTo("site", DatadogSite.AP2) + } + .hasFieldEqualTo("clientToken", fakeConfiguration.clientToken) + .hasFieldEqualTo("env", fakeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo( + "additionalConfig", + fakeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", fakeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -822,19 +1037,33 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - eq(TrackingConsent.PENDING) - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + eq(TrackingConsent.PENDING) + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() } } @@ -850,19 +1079,33 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - eq(TrackingConsent.PENDING) - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + eq(TrackingConsent.PENDING) + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() } } @@ -878,19 +1121,33 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - eq(TrackingConsent.GRANTED) - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + eq(TrackingConsent.GRANTED) + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() } } @@ -906,19 +1163,33 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - eq(TrackingConsent.NOT_GRANTED) - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + eq(TrackingConsent.NOT_GRANTED) + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() } } @@ -937,24 +1208,38 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("viewTrackingStrategy", NoOpViewTrackingStrategy) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("viewTrackingStrategy", NoOpViewTrackingStrategy) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -970,24 +1255,38 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("viewTrackingStrategy", ActivityViewTrackingStrategy(false)) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("viewTrackingStrategy", ActivityViewTrackingStrategy(false)) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1003,24 +1302,38 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("userActionTracking", false) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("userActionTracking", false) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1036,24 +1349,38 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("trackFrustrations", true) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("trackFrustrations", true) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1064,29 +1391,44 @@ internal class DdSdkTest { val bridgeConfiguration = configuration.copy( trackFrustrations = false ) + val sdkConfigCaptor = argumentCaptor() val rumConfigCaptor = argumentCaptor() val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("trackFrustrations", false) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("trackFrustrations", false) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1102,24 +1444,39 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("userActionTracking", true) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("userActionTracking", true) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1177,35 +1534,49 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { - it.hasFieldEqualTo("needsClearTextHttp", false) - it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - .hasFieldEqualTo("clientToken", bridgeConfiguration.clientToken) - .hasFieldEqualTo("env", bridgeConfiguration.env) - .hasFieldEqualTo("variant", "") - .hasFieldEqualTo("service", serviceName) - .hasFieldEqualTo( - "additionalConfig", - bridgeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() - ) - assertThat(rumConfigCaptor.firstValue) - .hasFieldEqualTo("applicationId", bridgeConfiguration.applicationId) + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { + it.hasFieldEqualTo("needsClearTextHttp", false) + it.hasFieldEqualTo("firstPartyHostsWithHeaderTypes", emptyMap()) + } + .hasFieldEqualTo("clientToken", bridgeConfiguration.clientToken) + .hasFieldEqualTo("env", bridgeConfiguration.env) + .hasFieldEqualTo("variant", "") + .hasFieldEqualTo("service", serviceName) + .hasFieldEqualTo( + "additionalConfig", + bridgeConfiguration.additionalConfig?.filterValues { it != null }.orEmpty() + ) + assertThat(rumConfigCaptor.firstValue) + .hasFieldEqualTo("applicationId", bridgeConfiguration.applicationId) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1224,31 +1595,45 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { rumConfig -> - rumConfig.hasField("longTaskTrackingStrategy") { longTaskTrackingStrategy -> - longTaskTrackingStrategy - .isInstanceOf( - "com.datadog.android.rum.internal.instrumentation." + - "MainLooperLongTaskStrategy" - ) - .hasFieldEqualTo("thresholdMs", threshold.toLong()) - } + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { rumConfig -> + rumConfig.hasField("longTaskTrackingStrategy") { longTaskTrackingStrategy -> + longTaskTrackingStrategy + .isInstanceOf( + "com.datadog.android.rum.internal.instrumentation." + + "MainLooperLongTaskStrategy" + ) + .hasFieldEqualTo("thresholdMs", threshold.toLong()) + } + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1265,24 +1650,38 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { rumConfig -> - rumConfig.doesNotHaveField("longTaskTrackingStrategy") + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { rumConfig -> + rumConfig.doesNotHaveField("longTaskTrackingStrategy") + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1326,27 +1725,41 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { coreConfig -> - coreConfig.hasFieldEqualTo( - "firstPartyHostsWithHeaderTypes", - tracingHosts + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { coreConfig -> + coreConfig.hasFieldEqualTo( + "firstPartyHostsWithHeaderTypes", + tracingHosts + ) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1385,27 +1798,41 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { coreConfig -> - coreConfig.hasFieldEqualTo( - "firstPartyHostsWithHeaderTypes", - tracingHosts + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { coreConfig -> + coreConfig.hasFieldEqualTo( + "firstPartyHostsWithHeaderTypes", + tracingHosts + ) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1451,27 +1878,41 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { coreConfig -> - coreConfig.hasFieldEqualTo( - "firstPartyHostsWithHeaderTypes", - tracingHosts + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { coreConfig -> + coreConfig.hasFieldEqualTo( + "firstPartyHostsWithHeaderTypes", + tracingHosts + ) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @ParameterizedTest @@ -1490,27 +1931,41 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { coreConfig -> - coreConfig.hasFieldEqualTo( - "uploadFrequency", - expectedUploadFrequency + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { coreConfig -> + coreConfig.hasFieldEqualTo( + "uploadFrequency", + expectedUploadFrequency + ) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @ParameterizedTest @@ -1529,27 +1984,41 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { coreConfig -> - coreConfig.hasFieldEqualTo( - "batchSize", - expectedBatchSize + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { coreConfig -> + coreConfig.hasFieldEqualTo( + "batchSize", + expectedBatchSize + ) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @ParameterizedTest @@ -1568,27 +2037,41 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasField("coreConfig") { coreConfig -> - coreConfig.hasFieldEqualTo( - "batchProcessingLevel", - expectedBatchSize + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(sdkConfigCaptor.firstValue) + .hasField("coreConfig") { coreConfig -> + coreConfig.hasFieldEqualTo( + "batchProcessingLevel", + expectedBatchSize + ) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1606,24 +2089,38 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("backgroundEventTracking", trackBackgroundEvents ?: false) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("backgroundEventTracking", trackBackgroundEvents ?: false) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1639,28 +2136,42 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("vitalsMonitorUpdateFrequency", VitalsUpdateFrequency.RARE) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("vitalsMonitorUpdateFrequency", VitalsUpdateFrequency.RARE) + } - argumentCaptor { - verify(mockChoreographer).postFrameCallback(capture()) - assertThat(firstValue).isInstanceOf(FpsFrameCallback::class.java) + argumentCaptor { + verify(mockChoreographer).postFrameCallback(capture()) + assertThat(firstValue).isInstanceOf(FpsFrameCallback::class.java) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() } } @@ -1679,25 +2190,37 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("vitalsMonitorUpdateFrequency", VitalsUpdateFrequency.NEVER) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - verifyNoInteractions(mockChoreographer) + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("vitalsMonitorUpdateFrequency", VitalsUpdateFrequency.NEVER) + } + verifyNoInteractions(mockChoreographer) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1719,41 +2242,56 @@ internal class DdSdkTest { val traceConfigCaptor = argumentCaptor() val frameDurationNs = threshold + frameDurationOverThreshold - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) - - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("vitalsMonitorUpdateFrequency", VitalsUpdateFrequency.AVERAGE) - } - argumentCaptor { - verify(mockChoreographer).postFrameCallback(capture()) - assertThat(firstValue).isInstanceOf(FpsFrameCallback::class.java) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // When - firstValue.doFrame(timestampNs) - firstValue.doFrame(timestampNs + frameDurationNs) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) - // then - verify(mockRumMonitor._getInternal()!!).updatePerformanceMetric( - RumPerformanceMetric.JS_FRAME_TIME, - frameDurationNs.toDouble() - ) - verify(mockRumMonitor._getInternal()!!, never()).addLongTask( - frameDurationNs, - "javascript" - ) + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } + } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo( + "vitalsMonitorUpdateFrequency", + VitalsUpdateFrequency.AVERAGE + ) + } + argumentCaptor { + verify(mockChoreographer).postFrameCallback(capture()) + assertThat(firstValue).isInstanceOf(FpsFrameCallback::class.java) + + // When + firstValue.doFrame(timestampNs) + firstValue.doFrame(timestampNs + frameDurationNs) + + // then + verify(mockRumMonitor._getInternal()!!).updatePerformanceMetric( + RumPerformanceMetric.JS_FRAME_TIME, + frameDurationNs.toDouble() + ) + verify(mockRumMonitor._getInternal()!!, never()).addLongTask( + frameDurationNs, + "javascript" + ) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() } } @@ -1846,25 +2384,37 @@ internal class DdSdkTest { val traceConfigCaptor = argumentCaptor() val defaultTimeBasedIdentifier = TimeBasedInitialResourceIdentifier(100) - // When - testedBridgeSdk.initialize(configuration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(configuration.toReadableJavaOnlyMap(), mockPromise) - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("initialResourceIdentifier", defaultTimeBasedIdentifier) + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("initialResourceIdentifier", defaultTimeBasedIdentifier) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -1884,25 +2434,37 @@ internal class DdSdkTest { thresholdInSeconds.seconds.inWholeMilliseconds ) - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("initialResourceIdentifier", timeBasedIdentifier) + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("initialResourceIdentifier", timeBasedIdentifier) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -1925,28 +2487,42 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(sdkConfigCaptor.firstValue) - .hasFieldEqualTo( - "additionalConfig", - mapOf( - DdSdkImplementation.DD_VERSION_SUFFIX to versionSuffix, - DdSdkImplementation.DD_VERSION to mockPackageInfo.versionName + versionSuffix + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() ) - ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } + } + assertThat(sdkConfigCaptor.firstValue) + .hasFieldEqualTo( + "additionalConfig", + mapOf( + DdSdkImplementation.DD_VERSION_SUFFIX to versionSuffix, + DdSdkImplementation.DD_VERSION to ( + mockPackageInfo.versionName + versionSuffix + ) + ) + ) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -1985,47 +2561,59 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - val configurationMapper = it - .getActualValue>( - "telemetryConfigurationMapper" - ) - val result = configurationMapper.map(telemetryConfigurationEvent)!! - assertThat(result.telemetry.configuration.trackNativeErrors!!).isEqualTo( - trackNativeErrors - ) - assertThat(result.telemetry.configuration.trackCrossPlatformLongTasks!!) - .isEqualTo(false) - assertThat(result.telemetry.configuration.trackLongTask!!) - .isEqualTo(false) - assertThat(result.telemetry.configuration.trackNativeLongTasks!!) - .isEqualTo(false) - - assertThat(result.telemetry.configuration.initializationType!!) - .isEqualTo(initializationType) - assertThat(result.telemetry.configuration.trackInteractions!!) - .isEqualTo(trackInteractions) - assertThat(result.telemetry.configuration.trackErrors!!).isEqualTo(trackErrors) - assertThat(result.telemetry.configuration.trackResources!!) - .isEqualTo(trackNetworkRequests) - assertThat(result.telemetry.configuration.trackNetworkRequests!!) - .isEqualTo(trackNetworkRequests) + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + val configurationMapper = it + .getActualValue>( + "telemetryConfigurationMapper" + ) + val result = configurationMapper.map(telemetryConfigurationEvent)!! + assertThat(result.telemetry.configuration.trackNativeErrors!!).isEqualTo( + trackNativeErrors + ) + assertThat(result.telemetry.configuration.trackCrossPlatformLongTasks!!) + .isEqualTo(false) + assertThat(result.telemetry.configuration.trackLongTask!!) + .isEqualTo(false) + assertThat(result.telemetry.configuration.trackNativeLongTasks!!) + .isEqualTo(false) + + assertThat(result.telemetry.configuration.initializationType!!) + .isEqualTo(initializationType) + assertThat(result.telemetry.configuration.trackInteractions!!) + .isEqualTo(trackInteractions) + assertThat(result.telemetry.configuration.trackErrors!!).isEqualTo(trackErrors) + assertThat(result.telemetry.configuration.trackResources!!) + .isEqualTo(trackNetworkRequests) + assertThat(result.telemetry.configuration.trackNetworkRequests!!) + .isEqualTo(trackNetworkRequests) + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -2042,27 +2630,39 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - val resourceMapper = it - .getActualValue>("resourceEventMapper") - val notDroppedEvent = resourceMapper.map(resourceEvent) - assertThat(notDroppedEvent).isNotNull + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + val resourceMapper = it + .getActualValue>("resourceEventMapper") + val notDroppedEvent = resourceMapper.map(resourceEvent) + assertThat(notDroppedEvent).isNotNull + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -2076,27 +2676,39 @@ internal class DdSdkTest { val traceConfigCaptor = argumentCaptor() resourceEvent.context?.additionalProperties?.put("_dd.resource.drop_resource", true) - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - val resourceMapper = it - .getActualValue>("resourceEventMapper") - val droppedEvent = resourceMapper.map(resourceEvent) - assertThat(droppedEvent).isNull() + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + val resourceMapper = it + .getActualValue>("resourceEventMapper") + val droppedEvent = resourceMapper.map(resourceEvent) + assertThat(droppedEvent).isNull() + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -2113,27 +2725,39 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - val actionMapper = it - .getActualValue>("actionEventMapper") - val notDroppedEvent = actionMapper.map(actionEvent) - assertThat(notDroppedEvent).isNotNull + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + val actionMapper = it + .getActualValue>("actionEventMapper") + val notDroppedEvent = actionMapper.map(actionEvent) + assertThat(notDroppedEvent).isNotNull + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test @@ -2147,27 +2771,39 @@ internal class DdSdkTest { val traceConfigCaptor = argumentCaptor() actionEvent.context?.additionalProperties?.put("_dd.action.drop_action", true) - // When - testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).initialize( - same(mockContext), - sdkConfigCaptor.capture(), - any() - ) - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - val actionMapper = it - .getActualValue>("actionEventMapper") - val droppedEvent = actionMapper.map(actionEvent) - assertThat(droppedEvent).isNull() + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(fakeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + val actionMapper = it + .getActualValue>("actionEventMapper") + val droppedEvent = actionMapper.map(actionEvent) + assertThat(droppedEvent).isNull() + } + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } // endregion @@ -2580,24 +3216,36 @@ internal class DdSdkTest { val logsConfigCaptor = argumentCaptor() val traceConfigCaptor = argumentCaptor() - // When - testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java) + val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java) + val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java) - // Then - inOrder(mockDatadog) { - verify(mockDatadog).enableRum(rumConfigCaptor.capture()) - verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) - verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) - } + try { + rumMock.`when` { Rum.enable(any(), any()) }.then { } + logsMock.`when` { Logs.enable(any(), any()) }.then { } + traceMock.`when` { Trace.enable(any(), any()) }.then { } // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) - assertThat(rumConfigCaptor.firstValue) - .hasField("featureConfiguration") { - it.hasFieldEqualTo("customEndpointUrl", customRumEndpoint) + // Then + inOrder(mockDatadog) { + rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) } + traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) } + logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) } } - assertThat(logsConfigCaptor.firstValue) - .hasFieldEqualTo("customEndpointUrl", customLogsEndpoint) - assertThat(traceConfigCaptor.firstValue) - .hasFieldEqualTo("customEndpointUrl", customTraceEndpoint) + + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("customEndpointUrl", customRumEndpoint) + } + assertThat(logsConfigCaptor.firstValue) + .hasFieldEqualTo("customEndpointUrl", customLogsEndpoint) + assertThat(traceConfigCaptor.firstValue) + .hasFieldEqualTo("customEndpointUrl", customTraceEndpoint) + } finally { + rumMock.close() + logsMock.close() + traceMock.close() + } } @Test diff --git a/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt b/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt index df0030fb5..b33bef6de 100644 --- a/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt +++ b/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt @@ -58,6 +58,12 @@ class DdInternalTestingImplementation { promise.resolve(null) } + /** + * Get wrapped core instance. + */ + internal fun getWrappedCore(): StubSDKCore? { + return wrappedCore + } internal companion object { internal const val NAME = "DdInternalTesting" diff --git a/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt b/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt index c542ed6bc..4a6938f9b 100644 --- a/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt +++ b/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt @@ -80,7 +80,9 @@ internal class DdInternalTestingImplementationTest { // Simulating DdSdkImplementation initialization DatadogSDKWrapperStorage.notifyOnInitializedListeners(mockCore) - val wrappedCore = Datadog.getInstance() as StubSDKCore + val wrappedCore = testedInternalTesting.getWrappedCore() + requireNotNull(wrappedCore) + wrappedCore.registerFeature(mockFeature) requireNotNull(wrappedCore.getFeature(mockFeature.name)) .withWriteContext { _, eventBatchWriter -> From bce1ca47ce43fdcd5943124cab8ca0312d3b5a08 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Sep 2025 15:29:37 +0200 Subject: [PATCH 018/114] Remove type interdependencies between modules --- packages/core/src/{rum => }/DdAttributes.ts | 0 .../src/DdSdkReactNativeConfiguration.tsx | 2 +- .../src/__tests__/DdSdkReactNative.test.tsx | 3 +- packages/core/src/index.tsx | 3 +- packages/core/src/logs/DdLogs.ts | 5 +- .../core/src/logs/__tests__/DdLogs.test.ts | 4 +- .../src/logs/__tests__/eventMapper.test.ts | 2 +- packages/core/src/logs/eventMapper.ts | 3 +- packages/core/src/logs/types.ts | 21 +------- packages/core/src/rum/DdRum.ts | 4 +- packages/core/src/rum/__tests__/DdRum.test.ts | 3 +- .../src/rum/eventMappers/errorEventMapper.ts | 2 +- .../instrumentation/DdRumErrorTracking.tsx | 2 +- packages/core/src/rum/types.ts | 10 +--- packages/core/src/types.tsx | 49 ++++++++++++++++--- 15 files changed, 62 insertions(+), 51 deletions(-) rename packages/core/src/{rum => }/DdAttributes.ts (100%) diff --git a/packages/core/src/rum/DdAttributes.ts b/packages/core/src/DdAttributes.ts similarity index 100% rename from packages/core/src/rum/DdAttributes.ts rename to packages/core/src/DdAttributes.ts diff --git a/packages/core/src/DdSdkReactNativeConfiguration.tsx b/packages/core/src/DdSdkReactNativeConfiguration.tsx index 9be08fa28..44debb2d2 100644 --- a/packages/core/src/DdSdkReactNativeConfiguration.tsx +++ b/packages/core/src/DdSdkReactNativeConfiguration.tsx @@ -7,12 +7,12 @@ import type { ProxyConfiguration } from './ProxyConfiguration'; import type { SdkVerbosity } from './SdkVerbosity'; import { TrackingConsent } from './TrackingConsent'; -import type { LogEventMapper } from './logs/types'; import type { ActionEventMapper } from './rum/eventMappers/actionEventMapper'; import type { ErrorEventMapper } from './rum/eventMappers/errorEventMapper'; import type { ResourceEventMapper } from './rum/eventMappers/resourceEventMapper'; import type { FirstPartyHost } from './rum/types'; import { PropagatorType } from './rum/types'; +import type { LogEventMapper } from './types'; export enum VitalsUpdateFrequency { FREQUENT = 'FREQUENT', diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 49c0bd1f2..18bf060ce 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -17,11 +17,12 @@ import { DdRum } from '../rum/DdRum'; import { DdRumErrorTracking } from '../rum/instrumentation/DdRumErrorTracking'; import { DdRumUserInteractionTracking } from '../rum/instrumentation/interactionTracking/DdRumUserInteractionTracking'; import { DdRumResourceTracking } from '../rum/instrumentation/resourceTracking/DdRumResourceTracking'; -import { ErrorSource, PropagatorType, RumActionType } from '../rum/types'; +import { PropagatorType, RumActionType } from '../rum/types'; import { AttributesSingleton } from '../sdk/AttributesSingleton/AttributesSingleton'; import { DdSdk } from '../sdk/DdSdk'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; import { UserInfoSingleton } from '../sdk/UserInfoSingleton/UserInfoSingleton'; +import { ErrorSource } from '../types'; import type { DdSdkConfiguration } from '../types'; import { version as sdkVersion } from '../version'; diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index 9332354dc..062fecc90 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -37,11 +37,12 @@ import { DATADOG_GRAPH_QL_VARIABLES_HEADER } from './rum/instrumentation/resourceTracking/graphql/graphqlHeaders'; import type { FirstPartyHost } from './rum/types'; -import { ErrorSource, PropagatorType, RumActionType } from './rum/types'; +import { PropagatorType, RumActionType } from './rum/types'; import { DatadogProvider } from './sdk/DatadogProvider/DatadogProvider'; import { DdSdk } from './sdk/DdSdk'; import { FileBasedConfiguration } from './sdk/FileBasedConfiguration/FileBasedConfiguration'; import { DdTrace } from './trace/DdTrace'; +import { ErrorSource } from './types'; import { DefaultTimeProvider } from './utils/time-provider/DefaultTimeProvider'; import type { Timestamp } from './utils/time-provider/TimeProvider'; import { TimeProvider } from './utils/time-provider/TimeProvider'; diff --git a/packages/core/src/logs/DdLogs.ts b/packages/core/src/logs/DdLogs.ts index 17ec3af1b..32d3dac59 100644 --- a/packages/core/src/logs/DdLogs.ts +++ b/packages/core/src/logs/DdLogs.ts @@ -4,12 +4,12 @@ * Copyright 2016-Present Datadog, Inc. */ +import { DdAttributes } from '../DdAttributes'; import { DATADOG_MESSAGE_PREFIX, InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; import { debugId } from '../metro/debugIdResolver'; import type { DdNativeLogsType } from '../nativeModulesTypes'; -import { DdAttributes } from '../rum/DdAttributes'; -import type { ErrorSource } from '../rum/types'; +import type { ErrorSource, LogEventMapper } from '../types'; import { validateContext } from '../utils/argsUtils'; import { getGlobalInstance } from '../utils/singletonUtils'; @@ -17,7 +17,6 @@ import { generateEventMapper } from './eventMapper'; import type { DdLogsType, LogArguments, - LogEventMapper, LogWithErrorArguments, NativeLogWithError, RawLogWithError diff --git a/packages/core/src/logs/__tests__/DdLogs.test.ts b/packages/core/src/logs/__tests__/DdLogs.test.ts index b8d0f4121..2dd15f46a 100644 --- a/packages/core/src/logs/__tests__/DdLogs.test.ts +++ b/packages/core/src/logs/__tests__/DdLogs.test.ts @@ -11,9 +11,9 @@ import { DdSdkReactNative } from '../../DdSdkReactNative'; import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; import type { DdNativeLogsType } from '../../nativeModulesTypes'; -import { ErrorSource } from '../../rum/types'; +import { ErrorSource } from '../../types'; +import type { LogEventMapper } from '../../types'; import { DdLogs } from '../DdLogs'; -import type { LogEventMapper } from '../types'; jest.mock('../../InternalLog', () => { return { diff --git a/packages/core/src/logs/__tests__/eventMapper.test.ts b/packages/core/src/logs/__tests__/eventMapper.test.ts index 0999a6058..cd505f811 100644 --- a/packages/core/src/logs/__tests__/eventMapper.test.ts +++ b/packages/core/src/logs/__tests__/eventMapper.test.ts @@ -5,7 +5,7 @@ */ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { ErrorSource } from '../../rum/types'; +import { ErrorSource } from '../../types'; import { formatRawLogToLogEvent } from '../eventMapper'; describe('formatRawLogToLogEvent', () => { diff --git a/packages/core/src/logs/eventMapper.ts b/packages/core/src/logs/eventMapper.ts index 2bbd398cb..eb7b5f22c 100644 --- a/packages/core/src/logs/eventMapper.ts +++ b/packages/core/src/logs/eventMapper.ts @@ -7,10 +7,9 @@ import type { Attributes } from '../sdk/AttributesSingleton/types'; import { EventMapper } from '../sdk/EventMappers/EventMapper'; import type { UserInfo } from '../sdk/UserInfoSingleton/types'; +import type { LogEvent, LogEventMapper } from '../types'; import type { - LogEvent, - LogEventMapper, NativeLog, NativeLogWithError, RawLog, diff --git a/packages/core/src/logs/types.ts b/packages/core/src/logs/types.ts index 9c1b3cb09..18cdbc533 100644 --- a/packages/core/src/logs/types.ts +++ b/packages/core/src/logs/types.ts @@ -4,8 +4,7 @@ * Copyright 2016-Present Datadog, Inc. */ -import type { ErrorSource } from '../rum/types'; -import type { UserInfo } from '../sdk/UserInfoSingleton/types'; +import type { LogStatus, ErrorSource } from '../types'; /** * The entry point to use Datadog's Logs feature. @@ -75,24 +74,6 @@ export type NativeLogWithError = { fingerprint?: string; }; -export type LogStatus = 'debug' | 'info' | 'warn' | 'error'; - -export type LogEvent = { - message: string; - context: object; - errorKind?: string; - errorMessage?: string; - stacktrace?: string; - fingerprint?: string; - readonly source?: ErrorSource; - // readonly date: number; // TODO: RUMM-2446 & RUMM-2447 - readonly status: LogStatus; - readonly userInfo: UserInfo; - readonly attributes?: object; -}; - -export type LogEventMapper = (logEvent: LogEvent) => LogEvent | null; - export type LogArguments = [message: string, context?: object]; export type LogWithErrorArguments = [ diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index 1241c73a4..b4a8632d7 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -5,6 +5,7 @@ */ import type { GestureResponderEvent } from 'react-native'; +import { DdAttributes } from '../DdAttributes'; import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; import { debugId } from '../metro/debugIdResolver'; @@ -12,12 +13,12 @@ import type { DdNativeRumType } from '../nativeModulesTypes'; import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; import { DdSdk } from '../sdk/DdSdk'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; +import type { ErrorSource } from '../types'; import { validateContext } from '../utils/argsUtils'; import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; import type { TimeProvider } from '../utils/time-provider/TimeProvider'; -import { DdAttributes } from './DdAttributes'; import { generateActionEventMapper } from './eventMappers/actionEventMapper'; import type { ActionEventMapper } from './eventMappers/actionEventMapper'; import { generateErrorEventMapper } from './eventMappers/errorEventMapper'; @@ -37,7 +38,6 @@ import { setCachedSessionId } from './sessionId/sessionIdHelper'; import type { - ErrorSource, DdRumType, RumActionType, ResourceKind, diff --git a/packages/core/src/rum/__tests__/DdRum.test.ts b/packages/core/src/rum/__tests__/DdRum.test.ts index a3b9ca2b7..ec765ed5d 100644 --- a/packages/core/src/rum/__tests__/DdRum.test.ts +++ b/packages/core/src/rum/__tests__/DdRum.test.ts @@ -12,6 +12,7 @@ import { SdkVerbosity } from '../../SdkVerbosity'; import { BufferSingleton } from '../../sdk/DatadogProvider/Buffer/BufferSingleton'; import { DdSdk } from '../../sdk/DdSdk'; import { GlobalState } from '../../sdk/GlobalState/GlobalState'; +import { ErrorSource } from '../../types'; import { DdRum } from '../DdRum'; import type { ActionEventMapper } from '../eventMappers/actionEventMapper'; import type { ErrorEventMapper } from '../eventMappers/errorEventMapper'; @@ -22,7 +23,7 @@ import { TracingIdFormat } from '../instrumentation/resourceTracking/distributed import { TracingIdentifierUtils } from '../instrumentation/resourceTracking/distributedTracing/__tests__/__utils__/TracingIdentifierUtils'; import { setCachedSessionId } from '../sessionId/sessionIdHelper'; import type { FirstPartyHost } from '../types'; -import { ErrorSource, PropagatorType, RumActionType } from '../types'; +import { PropagatorType, RumActionType } from '../types'; import * as TracingContextUtils from './__utils__/TracingHeadersUtils'; diff --git a/packages/core/src/rum/eventMappers/errorEventMapper.ts b/packages/core/src/rum/eventMappers/errorEventMapper.ts index 462754d2c..6630d59a4 100644 --- a/packages/core/src/rum/eventMappers/errorEventMapper.ts +++ b/packages/core/src/rum/eventMappers/errorEventMapper.ts @@ -6,7 +6,7 @@ import type { AdditionalEventDataForMapper } from '../../sdk/EventMappers/EventMapper'; import { EventMapper } from '../../sdk/EventMappers/EventMapper'; -import type { ErrorSource } from '../types'; +import type { ErrorSource } from '../../types'; type RawError = { message: string; diff --git a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx index 774fa70f5..09cd447fb 100644 --- a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx +++ b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx @@ -8,6 +8,7 @@ import type { ErrorHandlerCallback } from 'react-native'; import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; +import { ErrorSource } from '../../types'; import { getErrorMessage, getErrorStackTrace, @@ -17,7 +18,6 @@ import { } from '../../utils/errorUtils'; import { executeWithDelay } from '../../utils/jsUtils'; import { DdRum } from '../DdRum'; -import { ErrorSource } from '../types'; /** * Provides RUM auto-instrumentation feature to track errors as RUM events. diff --git a/packages/core/src/rum/types.ts b/packages/core/src/rum/types.ts index 5834123a6..fc8d07c02 100644 --- a/packages/core/src/rum/types.ts +++ b/packages/core/src/rum/types.ts @@ -4,6 +4,8 @@ * Copyright 2016-Present Datadog, Inc. */ +import type { ErrorSource } from '../types'; + import type { DatadogTracingContext } from './instrumentation/resourceTracking/distributedTracing/DatadogTracingContext'; import type { DatadogTracingIdentifier } from './instrumentation/resourceTracking/distributedTracing/DatadogTracingIdentifier'; @@ -233,14 +235,6 @@ export type ResourceKind = | 'other' | 'native'; -export enum ErrorSource { - NETWORK = 'NETWORK', - SOURCE = 'SOURCE', - CONSOLE = 'CONSOLE', - WEBVIEW = 'WEBVIEW', - CUSTOM = 'CUSTOM' -} - /** * Type of instrumentation on the host. * - DATADOG: Datadog’s propagator (`x-datadog-*`) diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 4db877469..e1c5096fb 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -5,6 +5,7 @@ */ import type { BatchProcessingLevel } from './DdSdkReactNativeConfiguration'; +import type { UserInfo as UserInfoSingleton } from './sdk/UserInfoSingleton/types'; declare global { // eslint-disable-next-line no-var, vars-on-top @@ -118,13 +119,6 @@ export type DdSdkType = { setTrackingConsent(trackingConsent: string): Promise; }; -export type UserInfo = { - id: string; - name?: string; - email?: string; - extraInfo?: object; -}; - /** * The entry point to use Datadog's Trace feature. */ @@ -153,3 +147,44 @@ export type DdTraceType = { timestampMs?: number ): Promise; }; + +// Shared types across modules + +// Core + +export type UserInfo = { + id: string; + name?: string; + email?: string; + extraInfo?: object; +}; + +// DdLogs + +export type LogStatus = 'debug' | 'info' | 'warn' | 'error'; + +export type LogEvent = { + message: string; + context: object; + errorKind?: string; + errorMessage?: string; + stacktrace?: string; + fingerprint?: string; + readonly source?: ErrorSource; + // readonly date: number; // TODO: RUMM-2446 & RUMM-2447 + readonly status: LogStatus; + readonly userInfo: UserInfoSingleton; + readonly attributes?: object; +}; + +export type LogEventMapper = (logEvent: LogEvent) => LogEvent | null; + +// DdRum + +export enum ErrorSource { + NETWORK = 'NETWORK', + SOURCE = 'SOURCE', + CONSOLE = 'CONSOLE', + WEBVIEW = 'WEBVIEW', + CUSTOM = 'CUSTOM' +} From bcb2c29fc33b1d3fbd8d0932e2b7ed88ea39c615 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 9 Sep 2025 12:08:35 +0200 Subject: [PATCH 019/114] iOS: Always use SDK default core instance --- .../core/ios/Sources/DatadogSDKWrapper.swift | 44 ++++++-------- .../ios/Sources/DdLogsImplementation.swift | 7 ++- .../ios/Sources/DdSdkImplementation.swift | 17 ++++-- .../Sources/DdSdkNativeInitialization.swift | 22 ++++--- packages/core/ios/Sources/DdTelemetry.swift | 49 ++++++++++++++++ .../ios/Tests/DatadogSdkWrapperTests.swift | 17 +++--- packages/core/ios/Tests/DdSdkTests.swift | 58 ++++++++----------- .../DdSessionReplayImplementation.swift | 28 ++------- .../ios/Tests/DdSessionReplayTests.swift | 6 +- .../Sources/RCTDatadogWebViewTracking.swift | 18 +++--- .../DatadogSDKReactNativeWebViewTests.swift | 14 +++-- 11 files changed, 158 insertions(+), 122 deletions(-) create mode 100644 packages/core/ios/Sources/DdTelemetry.swift diff --git a/packages/core/ios/Sources/DatadogSDKWrapper.swift b/packages/core/ios/Sources/DatadogSDKWrapper.swift index 9cbac3bd8..3c56688b0 100644 --- a/packages/core/ios/Sources/DatadogSDKWrapper.swift +++ b/packages/core/ios/Sources/DatadogSDKWrapper.swift @@ -13,11 +13,15 @@ import DatadogCrashReporting import DatadogInternal import Foundation +<<<<<<< HEAD #if os(iOS) import DatadogWebViewTracking #endif public typealias OnCoreInitializedListener = (DatadogCoreProtocol) -> Void +======= +public typealias OnSdkInitializedListener = () -> Void +>>>>>>> 0443e0ff (iOS: Always use SDK default core instance) /// Wrapper around the Datadog SDK. Use DatadogSDKWrapper.shared to access the instance. public class DatadogSDKWrapper { @@ -25,25 +29,14 @@ public class DatadogSDKWrapper { public static var shared = DatadogSDKWrapper() // Initialization callbacks - internal var onCoreInitializedListeners: [OnCoreInitializedListener] = [] - internal var loggerConfiguration = DatadogLogs.Logger.Configuration() - // Core instance - private var coreInstance: DatadogCoreProtocol? = nil + internal var onSdkInitializedListeners: [OnSdkInitializedListener] = [] - private init() { } - - public func addOnCoreInitializedListener(listener:@escaping OnCoreInitializedListener) { - onCoreInitializedListeners.append(listener) - } + internal private(set) var loggerConfiguration = DatadogLogs.Logger.Configuration() - /// This is intended for internal testing only. - public func setCoreInstance(core: DatadogCoreProtocol?) { - self.coreInstance = core - } + private init() { } - /// This is not supposed to be used in the SDK itself, rather by other SDKs like Session Replay. - public func getCoreInstance() -> DatadogCoreProtocol? { - return coreInstance + public func addOnSdkInitializedListener(listener:@escaping OnSdkInitializedListener) { + onSdkInitializedListeners.append(listener) } // SDK Wrapper @@ -52,15 +45,16 @@ public class DatadogSDKWrapper { loggerConfiguration: DatadogLogs.Logger.Configuration, trackingConsent: TrackingConsent ) -> Void { - let core = Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent) - setCoreInstance(core: core) - for listener in onCoreInitializedListeners { - listener(core) + Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent) + + for listener in onSdkInitializedListeners { + listener() } self.loggerConfiguration = loggerConfiguration } +<<<<<<< HEAD internal func isInitialized() -> Bool { return Datadog.isInitialized() } @@ -161,17 +155,15 @@ public class DatadogSDKWrapper { #if os(iOS) +======= +>>>>>>> 0443e0ff (iOS: Always use SDK default core instance) // Webview private var webviewMessageEmitter: InternalExtension.AbstractMessageEmitter? internal func enableWebviewTracking() { - if let core = coreInstance { - webviewMessageEmitter = WebViewTracking._internal.messageEmitter(in: core) - } else { - consolePrint("Core instance was not found when initializing Webview tracking.", .critical) - } + webviewMessageEmitter = WebViewTracking._internal.messageEmitter(in: CoreRegistry.default) } - + internal func sendWebviewMessage(body: NSString) throws { try self.webviewMessageEmitter?.send(body: body) } diff --git a/packages/core/ios/Sources/DdLogsImplementation.swift b/packages/core/ios/Sources/DdLogsImplementation.swift index 264a3d0b3..fe3fde092 100644 --- a/packages/core/ios/Sources/DdLogsImplementation.swift +++ b/packages/core/ios/Sources/DdLogsImplementation.swift @@ -5,7 +5,9 @@ */ import Foundation +import DatadogInternal import DatadogLogs +import DatadogCore @objc public class DdLogsImplementation: NSObject { @@ -20,7 +22,10 @@ public class DdLogsImplementation: NSObject { @objc public override convenience init() { - self.init({ DatadogSDKWrapper.shared.createLogger() }, { DatadogSDKWrapper.shared.isInitialized() }) + self.init( + { DatadogLogs.Logger.create(with: DatadogSDKWrapper.shared.loggerConfiguration) }, + { Datadog.isInitialized() } + ) } @objc diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index b4ddc59fd..973b8db9f 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -139,42 +139,47 @@ public class DdSdkImplementation: NSObject { public func sendTelemetryLog(message: NSString, attributes: NSDictionary, config: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { let castedAttributes = castAttributesToSwift(attributes) let castedConfig = castAttributesToSwift(config) - DatadogSDKWrapper.shared.sendTelemetryLog(message: message as String, attributes: castedAttributes, config: castedConfig) + DdTelemetry.sendTelemetryLog(message: message as String, attributes: castedAttributes, config: castedConfig) resolve(nil) } @objc public func telemetryDebug(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - DatadogSDKWrapper.shared.telemetryDebug(id: "datadog_react_native:\(message)", message: message as String) + DdTelemetry.telemetryDebug(id: "datadog_react_native:\(message)", message: message as String) resolve(nil) } @objc public func telemetryError(message: NSString, stack: NSString, kind: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - DatadogSDKWrapper.shared.telemetryError(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as String, stack: stack as String) + DdTelemetry.telemetryError(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as String, stack: stack as String) resolve(nil) } +<<<<<<< HEAD #if os(iOS) +======= + +>>>>>>> 0443e0ff (iOS: Always use SDK default core instance) @objc public func consumeWebviewEvent(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { do{ try DatadogSDKWrapper.shared.sendWebviewMessage(body: message) } catch { - DatadogSDKWrapper.shared.telemetryError(id: "datadog_react_native:\(error.localizedDescription)", message: "The message being sent was:\(message)" as String, kind: "WebViewEventBridgeError" as String, stack: String(describing: error) as String) + DdTelemetry.telemetryError(id: "datadog_react_native:\(error.localizedDescription)", message: "The message being sent was:\(message)" as String, kind: "WebViewEventBridgeError" as String, stack: String(describing: error) as String) } + resolve(nil) } #endif @objc public func clearAllData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - DatadogSDKWrapper.shared.clearAllData() + Datadog.clearAllData() resolve(nil) } func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) -> Void { - DatadogSDKWrapper.shared.overrideTelemetryConfiguration( + DdTelemetry.overrideTelemetryConfiguration( initializationType: rnConfiguration.configurationForTelemetry?.initializationType as? String, reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion as? String, reactVersion: rnConfiguration.configurationForTelemetry?.reactVersion as? String, diff --git a/packages/core/ios/Sources/DdSdkNativeInitialization.swift b/packages/core/ios/Sources/DdSdkNativeInitialization.swift index dec3cbcb2..4ea229871 100644 --- a/packages/core/ios/Sources/DdSdkNativeInitialization.swift +++ b/packages/core/ios/Sources/DdSdkNativeInitialization.swift @@ -33,12 +33,13 @@ public class DdSdkNativeInitialization: NSObject { } internal func initialize(sdkConfiguration: DdSdkConfiguration) { - // TODO: see if this `if` is still needed - if DatadogSDKWrapper.shared.isInitialized() { - // Initializing the SDK twice results in Global.rum and - // Global.sharedTracer to be set to no-op instances + if Datadog.isInitialized(instanceName: CoreRegistry.defaultInstanceName) { + // Initializing the SDK twice results in Global.rum and Global.sharedTracer to be set to no-op instances consolePrint("Datadog SDK is already initialized, skipping initialization.", .debug) - DatadogSDKWrapper.shared.telemetryDebug(id: "datadog_react_native: RN SDK was already initialized in native", message: "RN SDK was already initialized in native") + DdTelemetry.telemetryDebug( + id: "datadog_react_native: RN SDK was already initialized in native", + message: "RN SDK was already initialized in native" + ) RUMMonitor.shared().currentSessionID { sessionId in guard let id = sessionId else { return } @@ -81,21 +82,24 @@ public class DdSdkNativeInitialization: NSObject { func enableFeatures(sdkConfiguration: DdSdkConfiguration) { let rumConfig = buildRUMConfiguration(configuration: sdkConfiguration) - DatadogSDKWrapper.shared.enableRUM(with: rumConfig) + RUM.enable(with: rumConfig) let logsConfig = buildLogsConfiguration(configuration: sdkConfiguration) - DatadogSDKWrapper.shared.enableLogs(with: logsConfig) + Logs.enable(with: logsConfig) let traceConfig = buildTraceConfiguration(configuration: sdkConfiguration) - DatadogSDKWrapper.shared.enableTrace(with: traceConfig) + Trace.enable(with: traceConfig) if sdkConfiguration.nativeCrashReportEnabled ?? false { - DatadogSDKWrapper.shared.enableCrashReporting() + CrashReporting.enable() } +<<<<<<< HEAD #if os(iOS) DatadogSDKWrapper.shared.enableWebviewTracking() #endif +======= +>>>>>>> 0443e0ff (iOS: Always use SDK default core instance) } func buildSDKConfiguration(configuration: DdSdkConfiguration, defaultAppVersion: String = getDefaultAppVersion()) -> Datadog.Configuration { diff --git a/packages/core/ios/Sources/DdTelemetry.swift b/packages/core/ios/Sources/DdTelemetry.swift new file mode 100644 index 000000000..cb1896db8 --- /dev/null +++ b/packages/core/ios/Sources/DdTelemetry.swift @@ -0,0 +1,49 @@ + +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2025 Datadog, Inc. + */ +import DatadogCore +import DatadogInternal + +public class DdTelemetry { + public static func sendTelemetryLog(message: String, attributes: [String: any Encodable], config: [String: any Encodable]) { + let id = (config["onlyOnce"] as? Bool) == true ? message : UUID().uuidString + CoreRegistry.default.telemetry.debug(id: id, message: message, attributes: attributes) + } + + public static func telemetryDebug(id: String, message: String) { + return Datadog._internal.telemetry.debug(id: id, message: message) + } + + public static func telemetryError(id: String, message: String, kind: String?, stack: String?) { + return Datadog._internal.telemetry.error(id: id, message: message, kind: kind, stack: stack) + } + + public static func overrideTelemetryConfiguration( + initializationType: String? = nil, + reactNativeVersion: String? = nil, + reactVersion: String? = nil, + trackCrossPlatformLongTasks: Bool? = nil, + trackErrors: Bool? = nil, + trackInteractions: Bool? = nil, + trackLongTask: Bool? = nil, + trackNativeErrors: Bool? = nil, + trackNativeLongTasks: Bool? = nil, + trackNetworkRequests: Bool? = nil + ) { + CoreRegistry.default.telemetry.configuration( + initializationType: initializationType, + reactNativeVersion: reactNativeVersion, + reactVersion: reactVersion, + trackCrossPlatformLongTasks: trackCrossPlatformLongTasks, + trackErrors: trackErrors, + trackLongTask: trackLongTask, + trackNativeErrors: trackNativeErrors, + trackNativeLongTasks: trackNativeLongTasks, + trackNetworkRequests: trackNetworkRequests, + trackUserInteractions: trackInteractions + ) + } +} diff --git a/packages/core/ios/Tests/DatadogSdkWrapperTests.swift b/packages/core/ios/Tests/DatadogSdkWrapperTests.swift index 4811b4c1d..a445b2bbe 100644 --- a/packages/core/ios/Tests/DatadogSdkWrapperTests.swift +++ b/packages/core/ios/Tests/DatadogSdkWrapperTests.swift @@ -8,22 +8,23 @@ import XCTest @testable import DatadogSDKReactNative import DatadogTrace import DatadogInternal - +import DatadogRUM +import DatadogLogs internal class DatadogSdkWrapperTests: XCTestCase { override func setUp() { super.setUp() - DatadogSDKWrapper.shared.setCoreInstance(core: nil) - DatadogSDKWrapper.shared.onCoreInitializedListeners = [] + DatadogSDKWrapper.shared.onSdkInitializedListeners = [] } - func testItSetsCoreUsedForFeatures() { + func testOverrideCoreRegistryDefault() { let coreMock = MockDatadogCore() - DatadogSDKWrapper.shared.setCoreInstance(core: coreMock) + CoreRegistry.register(default: coreMock) + defer { CoreRegistry.unregisterDefault() } - DatadogSDKWrapper.shared.enableTrace(with: .init()) - DatadogSDKWrapper.shared.enableRUM(with: .init(applicationID: "app-id")) - DatadogSDKWrapper.shared.enableLogs(with: .init()) + Trace.enable(with: .init()) + RUM.enable(with: .init(applicationID: "app-id")) + Logs.enable(with: .init()) XCTAssertNotNil(coreMock.features["tracing"]) XCTAssertNotNil(coreMock.features["rum"]) diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index edbbba4dd..bcc3252a8 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -34,8 +34,7 @@ class DdSdkTests: XCTestCase { private func mockReject(args _: String?, arg _: String?, err _: Error?) {} override func tearDown() { - DatadogSDKWrapper.shared.setCoreInstance(core: nil) - DatadogSDKWrapper.shared.onCoreInitializedListeners = [] + DatadogSDKWrapper.shared.onSdkInitializedListeners = [] Datadog.internalFlushAndDeinitialize() } @@ -84,11 +83,10 @@ class DdSdkTests: XCTestCase { let bridge = DispatchQueueMock() let mockJSRefreshRateMonitor = MockJSRefreshRateMonitor() let mockListener = MockOnCoreInitializedListener() - DatadogSDKWrapper.shared.addOnCoreInitializedListener(listener: mockListener.listener) + DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: mockListener.listener) - let expectation = self.expectation(description: "Core is set when promise resolves") + let expectation = self.expectation(description: "Listener is called when promise resolves") func mockPromiseResolve(_: Any?) { - XCTAssertNotNil(mockListener.core) expectation.fulfill() } @@ -276,9 +274,9 @@ class DdSdkTests: XCTestCase { } func testSDKInitializationWithOnInitializedCallback() { - var coreFromCallback: DatadogCoreProtocol? = nil - DatadogSDKWrapper.shared.addOnCoreInitializedListener(listener: { core in - coreFromCallback = core + var isInitialized = false + DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: { + isInitialized = Datadog.isInitialized() }) DdSdkImplementation( @@ -293,14 +291,16 @@ class DdSdkTests: XCTestCase { reject: mockReject ) - XCTAssertNotNil(coreFromCallback) + XCTAssertTrue(isInitialized) } func testEnableAllFeatures() { let core = MockDatadogCore() + CoreRegistry.register(default: core) + defer { CoreRegistry.unregisterDefault() } + let configuration: DdSdkConfiguration = .mockAny() - DatadogSDKWrapper.shared.setCoreInstance(core: core) DdSdkNativeInitialization().enableFeatures( sdkConfiguration: configuration ) @@ -479,9 +479,11 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationWithCrashReport() { let core = MockDatadogCore() + CoreRegistry.register(default: core) + defer { CoreRegistry.unregisterDefault() } + let configuration: DdSdkConfiguration = .mockAny(nativeCrashReportEnabled: true) - DatadogSDKWrapper.shared.setCoreInstance(core: core) DdSdkNativeInitialization().enableFeatures( sdkConfiguration: configuration ) @@ -1233,6 +1235,9 @@ class DdSdkTests: XCTestCase { func testConfigurationTelemetryOverride() throws { let core = MockDatadogCore() + CoreRegistry.register(default: core) + defer { CoreRegistry.unregisterDefault() } + let configuration: DdSdkConfiguration = .mockAny( nativeCrashReportEnabled: false, nativeLongTaskThresholdMs: 0.0, @@ -1244,7 +1249,6 @@ class DdSdkTests: XCTestCase { ] ) - DatadogSDKWrapper.shared.setCoreInstance(core: core) DdSdkImplementation().overrideReactNativeTelemetry(rnConfiguration: configuration) XCTAssertEqual(core.configuration?.initializationType, "LEGACY") @@ -1313,11 +1317,12 @@ class DdSdkTests: XCTestCase { XCTAssertTrue(bridge.isSameQueue(queue: mockJSRefreshRateMonitor.jsQueue!)) } - func testCallsOnCoreInitializedListeners() throws { + func testCallsOnSdkInitializedListeners() throws { let bridge = DispatchQueueMock() let mockJSRefreshRateMonitor = MockJSRefreshRateMonitor() let mockListener = MockOnCoreInitializedListener() - DatadogSDKWrapper.shared.addOnCoreInitializedListener(listener: mockListener.listener) + + DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: mockListener.listener) DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -1331,23 +1336,7 @@ class DdSdkTests: XCTestCase { reject: mockReject ) - XCTAssertNotNil(mockListener.core) - } - - func testConsumeWebviewEvent() throws { - let configuration: DdSdkConfiguration = .mockAny() - let core = MockDatadogCore() - - DatadogSDKWrapper.shared.setCoreInstance(core: core) - DdSdkNativeInitialization().enableFeatures( - sdkConfiguration: configuration - ) - - DdSdkImplementation().consumeWebviewEvent( - message: "{\"eventType\":\"rum\",\"event\":{\"blabla\":\"custom message\"}}", - resolve: mockResolve, reject: mockReject) - - XCTAssertNotNil(core.baggages["browser-rum-event"]) + XCTAssertTrue(mockListener.called) } func testInitialResourceThreshold() { @@ -1601,9 +1590,8 @@ extension DdSdkImplementation { } class MockOnCoreInitializedListener { - var core: DatadogCoreProtocol? - - func listener(core: DatadogCoreProtocol) { - self.core = core + var called = false + func listener() { + self.called = true } } diff --git a/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift b/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift index 45cfc2582..0fc9c6637 100644 --- a/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift +++ b/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift @@ -6,6 +6,7 @@ import Foundation @_spi(Internal) import DatadogSessionReplay +import DatadogCore import DatadogInternal import DatadogSDKReactNative import React @@ -66,8 +67,6 @@ public class DdSessionReplayImplementation: NSObject { customEndpoint: customEndpointURL ) -// let bundle = Bundle(for: DdSessionReplayImplementation.self) - var svgMap: [String: SVGData] = [:] if let bundle = Bundle.ddSessionReplayResources, @@ -92,38 +91,21 @@ public class DdSessionReplayImplementation: NSObject { fabricWrapper: fabricWrapper ) ]) - - if let core = DatadogSDKWrapper.shared.getCoreInstance() { - sessionReplay.enable( - with: sessionReplayConfiguration, - in: core - ) - } else { - consolePrint("Core instance was not found when initializing Session Replay.", .critical) - } + + sessionReplay.enable(with: sessionReplayConfiguration, in: CoreRegistry.default) resolve(nil) } @objc public func startRecording(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - if let core = DatadogSDKWrapper.shared.getCoreInstance() { - sessionReplay.startRecording(in: core) - } else { - consolePrint("Core instance was not found when calling startRecording in Session Replay.", .critical) - } - + sessionReplay.startRecording(in: CoreRegistry.default) resolve(nil) } @objc public func stopRecording(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - if let core = DatadogSDKWrapper.shared.getCoreInstance() { - sessionReplay.stopRecording(in: core) - } else { - consolePrint("Core instance was not found when calling stopRecording in Session Replay.", .critical) - } - + sessionReplay.stopRecording(in: CoreRegistry.default) resolve(nil) } diff --git a/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift index aa9102850..067a11df0 100644 --- a/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift +++ b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift @@ -35,7 +35,11 @@ internal class DdSessionReplayTests: XCTestCase { override func setUp() { super.setUp() let mockDatadogCore = MockDatadogCore() - DatadogSDKWrapper.shared.setCoreInstance(core: mockDatadogCore) + CoreRegistry.register(default: mockDatadogCore) + } + + override func tearDown() { + CoreRegistry.unregisterDefault() } func testEnablesSessionReplayWithZeroReplaySampleRate() { diff --git a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift index 97fd835b5..45f11e452 100644 --- a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift +++ b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift @@ -8,26 +8,27 @@ import WebKit import DatadogWebViewTracking import DatadogSDKReactNative import DatadogCore +import DatadogInternal @objc public class RCTDatadogWebViewTracking: NSObject { var webView: RCTDatadogWebView? = nil var allowedHosts: Set = Set() - var coreListener: OnCoreInitializedListener? + var onSdkInitializedListener: OnSdkInitializedListener? public override init() { super.init() - self.coreListener = { [weak self] (core: DatadogCoreProtocol) in + self.onSdkInitializedListener = { [weak self] in guard let strongSelf = self, let webView = strongSelf.webView else { return } strongSelf.enableWebViewTracking( webView: webView, allowedHosts: strongSelf.allowedHosts, - core: core + core: CoreRegistry.default ) } } - + /** Enables tracking on the given WebView. @@ -42,15 +43,16 @@ import DatadogCore guard !webView.isTrackingEnabled else { return } - if let core = DatadogSDKWrapper.shared.getCoreInstance() { - enableWebViewTracking(webView: webView, allowedHosts: allowedHosts, core: core) - } else if let coreListener = self.coreListener { - DatadogSDKWrapper.shared.addOnCoreInitializedListener(listener: coreListener) + if CoreRegistry.isRegistered(instanceName: CoreRegistry.defaultInstanceName) { + enableWebViewTracking(webView: webView, allowedHosts: allowedHosts, core: CoreRegistry.default) + } else if let onSdkInitializedListener = self.onSdkInitializedListener { + DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: onSdkInitializedListener) } else { // TODO: Report initialization problem } } + private func enableWebViewTracking( webView: RCTDatadogWebView, allowedHosts: Set, diff --git a/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift b/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift index 7ea36bda8..20f1b84fa 100644 --- a/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift +++ b/packages/react-native-webview/ios/Tests/DatadogSDKReactNativeWebViewTests.swift @@ -17,7 +17,11 @@ internal class DatadogSDKReactNativeWebViewTests: XCTestCase { override func setUp() { super.setUp() let mockDatadogCore = MockDatadogCore() - DatadogSDKWrapper.shared.setCoreInstance(core: mockDatadogCore) + CoreRegistry.register(default: mockDatadogCore) + } + + override func tearDown() { + CoreRegistry.unregisterDefault() } func testDatadogWebViewManagerReturnsDatadogWebView() { @@ -41,9 +45,10 @@ internal class DatadogSDKReactNativeWebViewTests: XCTestCase { XCTAssertFalse(view.isTrackingEnabled) } - func testDatadogWebViewTrackingIsDisabledIfCoreIsNotReady() { + func testDatadogWebViewTrackingIsDisabledIfSdkIsNotInitialized() { // Given - DatadogSDKWrapper.shared.setCoreInstance(core: nil) + CoreRegistry.unregisterDefault() + let viewManager = RCTDatadogWebViewManager() let allowedHosts = NSArray(objects: "example1.com", "example2.com") @@ -82,7 +87,7 @@ internal class DatadogSDKReactNativeWebViewTests: XCTestCase { view.addSubview(WKWebView()) - DatadogSDKWrapper.shared.setCoreInstance(core: nil) + CoreRegistry.unregisterDefault() // Given let selector = NSSelectorFromString("setupDatadogWebView:view:") @@ -92,7 +97,6 @@ internal class DatadogSDKReactNativeWebViewTests: XCTestCase { XCTAssertFalse(view.isTrackingEnabled) // When - DatadogSDKWrapper.shared.setCoreInstance(core: MockDatadogCore()) DatadogSDKWrapper.shared.callInitialize() let expectation = self.expectation(description: "WebView tracking is enabled through the listener.") From a0cb6d13d7b9d544475f199f0eb3d429c3743da3 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 8 Sep 2025 15:27:17 +0200 Subject: [PATCH 020/114] Bump native SDK dependencies to 3.0.0 --- benchmarks/android/app/build.gradle | 2 +- benchmarks/ios/Podfile.lock | 74 +++++++------- bump-native-dd-sdk.sh | 2 +- example-new-architecture/ios/Podfile.lock | 52 ++++++++-- example/ios/Podfile.lock | 96 +++++++++---------- packages/core/DatadogSDKReactNative.podspec | 12 +-- packages/core/android/build.gradle | 10 +- ...DatadogSDKReactNativeSessionReplay.podspec | 2 +- .../android/build.gradle | 4 +- .../DatadogSDKReactNativeWebView.podspec | 4 +- .../react-native-webview/android/build.gradle | 2 +- 11 files changed, 148 insertions(+), 112 deletions(-) diff --git a/benchmarks/android/app/build.gradle b/benchmarks/android/app/build.gradle index 86d26791f..7dc4b3707 100644 --- a/benchmarks/android/app/build.gradle +++ b/benchmarks/android/app/build.gradle @@ -129,5 +129,5 @@ dependencies { // Benchmark tools from dd-sdk-android are used for vitals recording // Remember to bump thid alongside the main dd-sdk-android dependencies - implementation("com.datadoghq:dd-sdk-android-benchmark-internal:2.25.0") + implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.0.0") } diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index 681b5f169..61bf401c6 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -1,22 +1,22 @@ PODS: - boost (1.84.0) - - DatadogCore (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogCrashReporting (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogCore (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogCrashReporting (3.0.0): + - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (2.30.2) - - DatadogLogs (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogRUM (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogSDKReactNative (2.14.0): - - DatadogCore (= 2.30.2) - - DatadogCrashReporting (= 2.30.2) - - DatadogLogs (= 2.30.2) - - DatadogRUM (= 2.30.2) - - DatadogTrace (= 2.30.2) - - DatadogWebViewTracking (= 2.30.2) + - DatadogInternal (3.0.0) + - DatadogLogs (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogRUM (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogSDKReactNative (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -39,7 +39,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 2.30.2) + - DatadogSessionReplay (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -60,10 +60,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.14.0): - - DatadogInternal (= 2.30.2) + - DatadogSDKReactNativeWebView (2.12.1): + - DatadogInternal (= 3.0.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 2.30.2) + - DatadogWebViewTracking (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -84,13 +84,13 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSessionReplay (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogTrace (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogSessionReplay (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogTrace (3.0.0): + - DatadogInternal (= 3.0.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogWebViewTracking (3.0.0): + - DatadogInternal (= 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.78.2) @@ -2070,17 +2070,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c - DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b - DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 - DatadogLogs: d683aa9e0c9339f5ae679ead70bbdbe41cdc32f6 - DatadogRUM: 8b794aa458e6323ea9b1cef3f820fd3d092cbe27 - DatadogSDKReactNative: 5febb3635248ca87c73b9c74db0a9f214a9ab876 - DatadogSDKReactNativeSessionReplay: 38b40554550ecf191980cc22f1d9afebe1a914bd - DatadogSDKReactNativeWebView: 597c2fa95d43260228a326d1f33ec08500b74949 - DatadogSessionReplay: 56a91d799fe34967c5ae79a222364e37d67020f5 - DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 - DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be + DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 + DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e + DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 + DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd + DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f + DatadogSDKReactNative: 241bf982c16ceff03d94a58e6d005e55c47b99c6 + DatadogSDKReactNativeSessionReplay: bba36092686e3183e97c1a0c7f4ca8142582ae28 + DatadogSDKReactNativeWebView: 6da060df20e235abac533e582d9fc2b3a5070840 + DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 + DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 + DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: e32d34492c519a2194ec9d7f5e7a79d11b73f91c diff --git a/bump-native-dd-sdk.sh b/bump-native-dd-sdk.sh index 8545802db..67d99a0e7 100755 --- a/bump-native-dd-sdk.sh +++ b/bump-native-dd-sdk.sh @@ -23,7 +23,7 @@ podspec_files=( "packages/react-native-webview/DatadogSDKReactNativeWebView.podspec" ) -ios_pattern="('Datadog[^']+', '~> )[0-9.]+'" +ios_pattern="('Datadog[^']+', ')[0-9.]+'" android_pattern='(com\.datadoghq:dd-sdk-android-[^:"]+):[0-9.]+' if [[ "$sdk" == "ios" ]]; then diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index f37de03b0..6f4d1150c 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,10 +1,11 @@ PODS: - boost (1.84.0) - - DatadogCore (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogCrashReporting (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogCore (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogCrashReporting (3.0.0): + - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) +<<<<<<< HEAD - DatadogInternal (2.30.2) - DatadogLogs (2.30.2): - DatadogInternal (= 2.30.2) @@ -17,6 +18,20 @@ PODS: - DatadogRUM (= 2.30.2) - DatadogTrace (= 2.30.2) - DatadogWebViewTracking (= 2.30.2) +======= + - DatadogInternal (3.0.0) + - DatadogLogs (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogRUM (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogSDKReactNative (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) +>>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -37,6 +52,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga +<<<<<<< HEAD - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) - DatadogCrashReporting (= 2.30.2) @@ -44,6 +60,15 @@ PODS: - DatadogRUM (= 2.30.2) - DatadogTrace (= 2.30.2) - DatadogWebViewTracking (= 2.30.2) +======= + - DatadogSDKReactNative/Tests (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) +>>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -64,11 +89,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogTrace (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogTrace (3.0.0): + - DatadogInternal (= 3.0.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogWebViewTracking (3.0.0): + - DatadogInternal (= 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1850,6 +1875,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 +<<<<<<< HEAD DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 @@ -1858,6 +1884,16 @@ SPEC CHECKSUMS: DatadogSDKReactNative: b641dbbbe1b80c9551f55d86cdddac8d71568263 DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be +======= + DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 + DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e + DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 + DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd + DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f + DatadogSDKReactNative: 0a80aa75958d595a99be54d2838db53eda7a08af + DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 + DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 +>>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d31ef2ba3..238b7d8c9 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,34 +1,34 @@ PODS: - boost (1.84.0) - - DatadogCore (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogCrashReporting (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogCore (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogCrashReporting (3.0.0): + - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (2.30.2) - - DatadogLogs (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogRUM (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogSDKReactNative (2.14.0): - - DatadogCore (= 2.30.2) - - DatadogCrashReporting (= 2.30.2) - - DatadogLogs (= 2.30.2) - - DatadogRUM (= 2.30.2) - - DatadogTrace (= 2.30.2) - - DatadogWebViewTracking (= 2.30.2) + - DatadogInternal (3.0.0) + - DatadogLogs (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogRUM (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogSDKReactNative (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) - React-Core - - DatadogSDKReactNative/Tests (2.14.0): - - DatadogCore (= 2.30.2) - - DatadogCrashReporting (= 2.30.2) - - DatadogLogs (= 2.30.2) - - DatadogRUM (= 2.30.2) - - DatadogTrace (= 2.30.2) - - DatadogWebViewTracking (= 2.30.2) + - DatadogSDKReactNative/Tests (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) - React-Core - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 2.30.2) + - DatadogSessionReplay (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -51,7 +51,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay/Tests (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 2.30.2) + - DatadogSessionReplay (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -73,25 +73,25 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.14.0): - - DatadogInternal (= 2.30.2) + - DatadogSDKReactNativeWebView (2.12.1): + - DatadogInternal (= 3.0.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 2.30.2) + - DatadogWebViewTracking (= 3.0.0) - React-Core - - DatadogSDKReactNativeWebView/Tests (2.14.0): - - DatadogInternal (= 2.30.2) + - DatadogSDKReactNativeWebView/Tests (2.12.1): + - DatadogInternal (= 3.0.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 2.30.2) + - DatadogWebViewTracking (= 3.0.0) - React-Core - react-native-webview - React-RCTText - - DatadogSessionReplay (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogTrace (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogSessionReplay (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogTrace (3.0.0): + - DatadogInternal (= 3.0.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (2.30.2): - - DatadogInternal (= 2.30.2) + - DatadogWebViewTracking (3.0.0): + - DatadogInternal (= 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1988,17 +1988,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c - DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b - DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 - DatadogLogs: d683aa9e0c9339f5ae679ead70bbdbe41cdc32f6 - DatadogRUM: 8b794aa458e6323ea9b1cef3f820fd3d092cbe27 - DatadogSDKReactNative: 7f1df67f855450c33ff4169adeb901548d32fc8d - DatadogSDKReactNativeSessionReplay: b76757bea41273d7a69485d444194d8e00f53e27 - DatadogSDKReactNativeWebView: 49b59eda4795e53b6f065d768712cb3d2eb2c46d - DatadogSessionReplay: 56a91d799fe34967c5ae79a222364e37d67020f5 - DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 - DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be + DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 + DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e + DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 + DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd + DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f + DatadogSDKReactNative: e430b3f4d7adb0fac07b61e2fb65ceacdaf82b3e + DatadogSDKReactNativeSessionReplay: 4b2a3d166a79581f18522795b40141c34cf3685d + DatadogSDKReactNativeWebView: 35dc2b9736e1aaa82b366bf6b8a8a959a9b088c5 + DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 + DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 + DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/packages/core/DatadogSDKReactNative.podspec b/packages/core/DatadogSDKReactNative.podspec index ff3b91232..a2f41ddd1 100644 --- a/packages/core/DatadogSDKReactNative.podspec +++ b/packages/core/DatadogSDKReactNative.podspec @@ -19,14 +19,14 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the versions in sync with DatadogSDKReactNativeSessionReplay.podspec - s.dependency 'DatadogCore', '2.30.2' - s.dependency 'DatadogLogs', '2.30.2' - s.dependency 'DatadogTrace', '2.30.2' - s.dependency 'DatadogRUM', '2.30.2' - s.dependency 'DatadogCrashReporting', '2.30.2' + s.dependency 'DatadogCore', '3.0.0' + s.dependency 'DatadogLogs', '3.0.0' + s.dependency 'DatadogTrace', '3.0.0' + s.dependency 'DatadogRUM', '3.0.0' + s.dependency 'DatadogCrashReporting', '3.0.0' # DatadogWebViewTracking is not available for tvOS - s.ios.dependency 'DatadogWebViewTracking', '2.30.2' + s.ios.dependency 'DatadogWebViewTracking', '3.0.0' s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'ios/Tests/**/*.{swift,json}' diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 0396ae323..a8d09d4d1 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -201,16 +201,16 @@ dependencies { // This breaks builds if the React Native target is below 0.76.0. as it relies on Gradle 8.5.0. // To avoid this, we enforce 1.0.0-beta01 on RN < 0.76.0 if (reactNativeMinorVersion < 76) { - implementation("com.datadoghq:dd-sdk-android-rum:2.26.2") { + implementation("com.datadoghq:dd-sdk-android-rum:3.0.0") { exclude group: "androidx.metrics", module: "metrics-performance" } implementation "androidx.metrics:metrics-performance:1.0.0-beta01" } else { - implementation "com.datadoghq:dd-sdk-android-rum:2.26.2" + implementation "com.datadoghq:dd-sdk-android-rum:3.0.0" } - implementation "com.datadoghq:dd-sdk-android-logs:2.26.2" - implementation "com.datadoghq:dd-sdk-android-trace:2.26.2" - implementation "com.datadoghq:dd-sdk-android-webview:2.26.2" + implementation "com.datadoghq:dd-sdk-android-logs:3.0.0" + implementation "com.datadoghq:dd-sdk-android-trace:3.0.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" implementation "com.google.code.gson:gson:2.10.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec index 915fc4129..3a2d5ff48 100644 --- a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec +++ b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec - s.dependency 'DatadogSessionReplay', '2.30.2' + s.dependency 'DatadogSessionReplay', '3.0.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle index 5851ee598..6d6fe20b1 100644 --- a/packages/react-native-session-replay/android/build.gradle +++ b/packages/react-native-session-replay/android/build.gradle @@ -216,8 +216,8 @@ dependencies { api "com.facebook.react:react-android:$reactNativeVersion" } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.datadoghq:dd-sdk-android-session-replay:2.26.2" - implementation "com.datadoghq:dd-sdk-android-internal:2.26.2" + implementation "com.datadoghq:dd-sdk-android-session-replay:3.0.0" + implementation "com.datadoghq:dd-sdk-android-internal:3.0.0" implementation project(path: ':datadog_mobile-react-native') testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" diff --git a/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec b/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec index 3d4668bb0..26e160bbc 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', '2.30.2' - s.dependency 'DatadogInternal', '2.30.2' + s.dependency 'DatadogWebViewTracking', '3.0.0' + s.dependency 'DatadogInternal', '3.0.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-webview/android/build.gradle b/packages/react-native-webview/android/build.gradle index a0c407316..8eeaea88e 100644 --- a/packages/react-native-webview/android/build.gradle +++ b/packages/react-native-webview/android/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "com.facebook.react:react-android:$reactNativeVersion" } - implementation "com.datadoghq:dd-sdk-android-webview:2.26.2" + implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(path: ':datadog_mobile-react-native') From eaaacc52f19985ddf50b33846fa520bbd990376c Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 12 Sep 2025 14:43:25 +0200 Subject: [PATCH 021/114] Remove setUser --- packages/codepush/__mocks__/react-native.ts | 3 - packages/core/__mocks__/react-native.ts | 3 - .../datadog/reactnative/DatadogSDKWrapper.kt | 10 -- .../com/datadog/reactnative/DatadogWrapper.kt | 17 -- .../reactnative/DdSdkImplementation.kt | 17 +- .../kotlin/com/datadog/reactnative/DdSdk.kt | 14 +- .../kotlin/com/datadog/reactnative/DdSdk.kt | 11 -- .../com/datadog/reactnative/DdSdkTest.kt | 158 ------------------ packages/core/ios/Sources/DdSdk.mm | 11 -- .../ios/Sources/DdSdkImplementation.swift | 15 +- packages/core/ios/Tests/DdSdkTests.swift | 132 --------------- packages/core/ios/Tests/MockRUMMonitor.swift | 16 ++ packages/core/jest/mock.js | 3 - packages/core/src/DdSdkReactNative.tsx | 25 +-- .../src/__tests__/DdSdkReactNative.test.tsx | 16 -- packages/core/src/logs/eventMapper.ts | 6 +- .../core/src/sdk/EventMappers/EventMapper.ts | 2 +- .../UserInfoSingleton/UserInfoSingleton.ts | 4 +- .../__tests__/UserInfoSingleton.test.ts | 6 +- .../core/src/sdk/UserInfoSingleton/types.ts | 5 +- packages/core/src/specs/NativeDdSdk.ts | 7 - packages/core/src/types.tsx | 10 +- .../__mocks__/react-native.ts | 3 - 23 files changed, 42 insertions(+), 452 deletions(-) diff --git a/packages/codepush/__mocks__/react-native.ts b/packages/codepush/__mocks__/react-native.ts index b35df4e31..046ced2f6 100644 --- a/packages/codepush/__mocks__/react-native.ts +++ b/packages/codepush/__mocks__/react-native.ts @@ -18,9 +18,6 @@ actualRN.NativeModules.DdSdk = { initialize: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - setUser: jest.fn().mockImplementation( - () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, setAttributes: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, diff --git a/packages/core/__mocks__/react-native.ts b/packages/core/__mocks__/react-native.ts index 9507ec33f..260fe68a7 100644 --- a/packages/core/__mocks__/react-native.ts +++ b/packages/core/__mocks__/react-native.ts @@ -18,9 +18,6 @@ actualRN.NativeModules.DdSdk = { initialize: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - setUser: jest.fn().mockImplementation( - () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, setUserInfo: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index da0841ac4..198061d14 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -71,16 +71,6 @@ internal class DatadogSDKWrapper : DatadogWrapper { DatadogSDKWrapperStorage.notifyOnInitializedListeners(core as InternalSdkCore) } - @Deprecated("Use setUserInfo instead; the user ID is now required.") - override fun setUser( - id: String?, - name: String?, - email: String?, - extraInfo: Map - ) { - Datadog.setUserInfo(id, name, email, extraInfo) - } - override fun setUserInfo( id: String, name: String?, diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index 19b25e587..3ae3e6266 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -62,23 +62,6 @@ interface DatadogWrapper { consent: TrackingConsent ) - /** - * Sets the user information. - * - * @param id (nullable) a unique user identifier (relevant to your business domain) - * @param name (nullable) the user name or alias - * @param email (nullable) the user email - * @param extraInfo additional information. An extra information can be - * nested up to 8 levels deep. Keys using more than 8 levels will be sanitized by SDK. - */ - @Deprecated("Use setUserInfo instead; the user ID is now required.") - fun setUser( - id: String?, - name: String?, - email: String?, - extraInfo: Map - ) - /** * Sets the user information. * diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index b04a2ddf3..7a5c6848c 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -78,21 +78,6 @@ class DdSdkImplementation( promise.resolve(null) } - /** - * Set the user information. - * @param user The user object (use builtin attributes: 'id', 'email', 'name', and/or any custom - * attribute). - */ - @Deprecated("Use setUserInfo instead; the user ID is now required.") - fun setUser(user: ReadableMap, promise: Promise) { - val extraInfo = user.toHashMap().toMutableMap() - val id = extraInfo.remove("id")?.toString() - val name = extraInfo.remove("name")?.toString() - val email = extraInfo.remove("email")?.toString() - datadog.setUser(id, name, email, extraInfo) - promise.resolve(null) - } - /** * Set the user information. * @param userInfo The user object (use builtin attributes: 'id', 'email', 'name', and any custom @@ -110,7 +95,7 @@ class DdSdkImplementation( if (id != null) { datadog.setUserInfo(id, name, email, extraInfo) } else { - datadog.setUser(null, name, email, extraInfo) + // TO DO - Log warning? } promise.resolve(null) diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt index 5bc470947..cfafffffe 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -51,19 +51,7 @@ class DdSdk( /** * Set the user information. - * @param user The user object (use builtin attributes: 'id', 'email', 'name', and/or any custom - * attribute). - */ - @Deprecated("Use setUserInfo instead; the user ID is now required.") - @ReactMethod - override fun setUser(user: ReadableMap, promise: Promise) { - implementation.setUser(user, promise) - } - - /** - * Set the user information. - * @param user The user object (use builtin attributes: 'id', 'email', 'name', and any custom - * attribute inside 'extraInfo'). + * @param user The user object (use builtin attributes: 'id', 'email', 'name', and any custom * attribute inside 'extraInfo'). */ @ReactMethod override fun setUserInfo(user: ReadableMap, promise: Promise) { diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 17acd6d20..97acb2ebf 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -75,17 +75,6 @@ class DdSdk( implementation.setAttributes(attributes, promise) } - /** - * Set the user information. - * @param user The user object (use builtin attributes: 'id', 'email', 'name', and/or any custom - * attribute). - */ - @Deprecated("Use setUserInfo instead; the user ID is now required.") - @ReactMethod - fun setUser(user: ReadableMap, promise: Promise) { - implementation.setUser(user, promise) - } - /** * Set the user information. * @param user The user object (use builtin attributes: 'id', 'email', 'name', and any custom * attribute inside 'extraInfo'). diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index a39485dae..8797bcc64 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -2810,164 +2810,6 @@ internal class DdSdkTest { // region misc - @Test - fun `𝕄 set native user info 𝕎 setUser()`( - @MapForgery( - key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), - value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) - ) extraInfo: Map - ) { - // When - testedBridgeSdk.setUser(extraInfo.toReadableMap(), mockPromise) - - // Then - argumentCaptor> { - verify(mockDatadog) - .setUser( - isNull(), - isNull(), - isNull(), - capture() - ) - - assertThat(firstValue) - .containsAllEntriesOf(extraInfo) - .hasSize(extraInfo.size) - } - } - - @Test - fun `𝕄 set native user info 𝕎 setUser() {with id}`( - @StringForgery id: String, - @MapForgery( - key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), - value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) - ) extraInfo: Map - ) { - // Given - val user = extraInfo.toMutableMap().also { - it.put("id", id) - } - - // When - testedBridgeSdk.setUser(user.toReadableMap(), mockPromise) - - // Then - argumentCaptor> { - verify(mockDatadog) - .setUser( - eq(id), - isNull(), - isNull(), - capture() - ) - - assertThat(firstValue) - .containsAllEntriesOf(extraInfo) - .hasSize(extraInfo.size) - } - } - - @Test - fun `𝕄 set native user info 𝕎 setUser() {with name}`( - @StringForgery name: String, - @MapForgery( - key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), - value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) - ) extraInfo: Map - ) { - // Given - val user = extraInfo.toMutableMap().also { - it.put("name", name) - } - - // When - testedBridgeSdk.setUser(user.toReadableMap(), mockPromise) - - // Then - argumentCaptor> { - verify(mockDatadog) - .setUser( - isNull(), - eq(name), - isNull(), - capture() - ) - - assertThat(firstValue) - .containsAllEntriesOf(extraInfo) - .hasSize(extraInfo.size) - } - } - - @Test - fun `𝕄 set native user info 𝕎 setUser() {with email}`( - @StringForgery(regex = "\\w+@\\w+\\.[a-z]{3}") email: String, - @MapForgery( - key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), - value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) - ) extraInfo: Map - ) { - // Given - val user = extraInfo.toMutableMap().also { - it.put("email", email) - } - - // When - testedBridgeSdk.setUser(user.toReadableMap(), mockPromise) - - // Then - argumentCaptor> { - verify(mockDatadog) - .setUser( - isNull(), - isNull(), - eq(email), - capture() - ) - - assertThat(firstValue) - .containsAllEntriesOf(extraInfo) - .hasSize(extraInfo.size) - } - } - - @Test - fun `𝕄 set native user info 𝕎 setUser() {with id, name and email}`( - @StringForgery id: String, - @StringForgery name: String, - @StringForgery(regex = "\\w+@\\w+\\.[a-z]{3}") email: String, - @MapForgery( - key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), - value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) - ) extraInfo: Map - ) { - // Given - val user = extraInfo.toMutableMap().also { - it.put("id", id) - it.put("name", name) - it.put("email", email) - } - - // When - testedBridgeSdk.setUser(user.toReadableMap(), mockPromise) - - // Then - argumentCaptor> { - verify(mockDatadog) - .setUser( - eq(id), - eq(name), - eq(email), - capture() - ) - - assertThat(firstValue) - .containsAllEntriesOf(extraInfo) - .hasSize(extraInfo.size) - } - } - @Test fun `𝕄 set native user info 𝕎 setUserInfo() {with id}`( @StringForgery id: String diff --git a/packages/core/ios/Sources/DdSdk.mm b/packages/core/ios/Sources/DdSdk.mm index 590452c86..918a8db03 100644 --- a/packages/core/ios/Sources/DdSdk.mm +++ b/packages/core/ios/Sources/DdSdk.mm @@ -37,13 +37,6 @@ + (void)initFromNative { [self setAttributes:attributes resolve:resolve reject:reject]; } -RCT_REMAP_METHOD(setUser, withUser:(NSDictionary*)user - withResolver:(RCTPromiseResolveBlock)resolve - withRejecter:(RCTPromiseRejectBlock)reject) -{ - [self setUser:user resolve:resolve reject:reject]; -} - RCT_REMAP_METHOD(setUserInfo, withUserInfo:(NSDictionary*)userInfo withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) @@ -146,10 +139,6 @@ - (void)setTrackingConsent:(NSString *)trackingConsent resolve:(RCTPromiseResolv [self.ddSdkImplementation setTrackingConsentWithTrackingConsent:trackingConsent resolve:resolve reject:reject]; } -- (void)setUser:(NSDictionary *)user resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { - [self.ddSdkImplementation setUserWithUser:user resolve:resolve reject:reject]; -} - - (void)setUserInfo:(NSDictionary *)userInfo resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self.ddSdkImplementation setUserInfoWithUserInfo:userInfo resolve:resolve reject:reject]; } diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 973b8db9f..b2e610635 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -87,18 +87,6 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - @objc - public func setUser(user: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - var castedUser = castAttributesToSwift(user) - let id = castedUser.removeValue(forKey: "id") as? String - let name = castedUser.removeValue(forKey: "name") as? String - let email = castedUser.removeValue(forKey: "email") as? String - let extraInfo: [String: Encodable] = castedUser // everything what's left is an `extraInfo` - - Datadog.setUserInfo(id: id, name: name, email: email, extraInfo: extraInfo) - resolve(nil) - } - @objc public func setUserInfo(userInfo: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { let castedUserInfo = castAttributesToSwift(userInfo) @@ -115,8 +103,9 @@ public class DdSdkImplementation: NSObject { if let validId = id { Datadog.setUserInfo(id: validId, name: name, email: email, extraInfo: extraInfo) } else { - Datadog.setUserInfo(name: name, email: email, extraInfo: extraInfo) + // TO DO - log warning message? } + resolve(nil) } diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index bcc3252a8..6be5e0994 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -535,85 +535,6 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(ddConfig.trackFrustrations, false) } - func testSetUser() throws { - let bridge = DdSdkImplementation( - mainDispatchQueue: DispatchQueueMock(), - jsDispatchQueue: DispatchQueueMock(), - jsRefreshRateMonitor: JSRefreshRateMonitor(), - RUMMonitorProvider: { MockRUMMonitor() }, - RUMMonitorInternalProvider: { nil } - ) - bridge.initialize( - configuration: .mockAny(), - resolve: mockResolve, - reject: mockReject - ) - - bridge.setUser( - user: NSDictionary( - dictionary: [ - "id": "id_123", - "name": "John Doe", - "email": "john@doe.com", - "extra-info-1": 123, - "extra-info-2": "abc", - "extra-info-3": true, - ] - ), - resolve: mockResolve, - reject: mockReject - ) - - let ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() - let userInfo = try XCTUnwrap(ddContext.userInfo) - - XCTAssertEqual(userInfo.id, "id_123") - XCTAssertEqual(userInfo.name, "John Doe") - XCTAssertEqual(userInfo.email, "john@doe.com") - XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) - XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") - XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) - } - - func testSetUserOptionalId() throws { - let bridge = DdSdkImplementation( - mainDispatchQueue: DispatchQueueMock(), - jsDispatchQueue: DispatchQueueMock(), - jsRefreshRateMonitor: JSRefreshRateMonitor(), - RUMMonitorProvider: { MockRUMMonitor() }, - RUMMonitorInternalProvider: { nil } - ) - bridge.initialize( - configuration: .mockAny(), - resolve: mockResolve, - reject: mockReject - ) - - bridge.setUser( - user: NSDictionary( - dictionary: [ - "name": "John Doe", - "email": "john@doe.com", - "extra-info-1": 123, - "extra-info-2": "abc", - "extra-info-3": true, - ] - ), - resolve: mockResolve, - reject: mockReject - ) - - let ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() - let userInfo = try XCTUnwrap(ddContext.userInfo) - - XCTAssertEqual(userInfo.id, nil) - XCTAssertEqual(userInfo.name, "John Doe") - XCTAssertEqual(userInfo.email, "john@doe.com") - XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) - XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") - XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) - } - func testSetUserInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -668,59 +589,6 @@ class DdSdkTests: XCTestCase { } } - func testSetUserInfoOptionalId() throws { - let bridge = DdSdkImplementation( - mainDispatchQueue: DispatchQueueMock(), - jsDispatchQueue: DispatchQueueMock(), - jsRefreshRateMonitor: JSRefreshRateMonitor(), - RUMMonitorProvider: { MockRUMMonitor() }, - RUMMonitorInternalProvider: { nil } - ) - bridge.initialize( - configuration: .mockAny(), - resolve: mockResolve, - reject: mockReject - ) - - bridge.setUserInfo( - userInfo: NSDictionary( - dictionary: [ - "name": "John Doe", - "email": "john@doe.com", - "extraInfo": [ - "extra-info-1": 123, - "extra-info-2": "abc", - "extra-info-3": true, - "extra-info-4": [ - "nested-extra-info-1": 456 - ], - ], - ] - ), - resolve: mockResolve, - reject: mockReject - ) - - let ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() - let userInfo = try XCTUnwrap(ddContext.userInfo) - - XCTAssertEqual(userInfo.id, nil) - XCTAssertEqual(userInfo.name, "John Doe") - XCTAssertEqual(userInfo.email, "john@doe.com") - XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) - XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") - XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) - - if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] - as? DatadogSDKReactNative.AnyEncodable, - let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] - { - XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) - } else { - XCTFail("extra-info-4 is not of expected type or value") - } - } - func testAddUserExtraInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index fe17e7748..f0fa03364 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,6 +10,22 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { + func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { + // not implemented + } + + func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { + // not implemented + } + + func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { + // not implemented + } + + func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { + // not implemented + } + func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } diff --git a/packages/core/jest/mock.js b/packages/core/jest/mock.js index 9c08f5335..a8161295a 100644 --- a/packages/core/jest/mock.js +++ b/packages/core/jest/mock.js @@ -27,9 +27,6 @@ module.exports = { .fn() .mockImplementation(() => new Promise(resolve => resolve())), isInitialized: jest.fn().mockImplementation(() => true), - setUser: jest - .fn() - .mockImplementation(() => new Promise(resolve => resolve())), setUserInfo: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index aa7e2ec36..668ae09f3 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -38,7 +38,6 @@ import { DdSdk } from './sdk/DdSdk'; import { FileBasedConfiguration } from './sdk/FileBasedConfiguration/FileBasedConfiguration'; import { GlobalState } from './sdk/GlobalState/GlobalState'; import { UserInfoSingleton } from './sdk/UserInfoSingleton/UserInfoSingleton'; -import type { UserInfo } from './sdk/UserInfoSingleton/types'; import { DdSdkConfiguration } from './types'; import { adaptLongTaskThreshold } from './utils/longTasksUtils'; import { version as sdkVersion } from './version'; @@ -192,22 +191,6 @@ export class DdSdkReactNative { AttributesSingleton.getInstance().setAttributes(attributes); }; - /** - * Set the user information. - * @deprecated UserInfo id property is now mandatory (please user setUserInfo instead) - * @param user: The user object (use builtin attributes: 'id', 'email', 'name', and/or any custom attribute). - * @returns a Promise. - */ - // eslint-disable-next-line @typescript-eslint/ban-types - static setUser = async (user: UserInfo): Promise => { - InternalLog.log( - `Setting user ${JSON.stringify(user)}`, - SdkVerbosity.DEBUG - ); - await DdSdk.setUser(user); - UserInfoSingleton.getInstance().setUserInfo(user); - }; - /** * Sets the user information. * @param id: A mandatory unique user identifier (relevant to your business domain). @@ -245,6 +228,14 @@ export class DdSdkReactNative { ); const userInfo = UserInfoSingleton.getInstance().getUserInfo(); + if (!userInfo) { + InternalLog.log( + 'Skipped adding User Extra Info: User Info is currently undefined. A user ID must be set before adding extra info. Please call setUserInfo() first.', + SdkVerbosity.WARN + ); + + return; + } const updatedUserInfo = { ...userInfo, extraInfo: { diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 18bf060ce..f9405aa51 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -63,7 +63,6 @@ beforeEach(async () => { DdSdkReactNative['wasAutoInstrumented'] = false; NativeModules.DdSdk.initialize.mockClear(); NativeModules.DdSdk.setAttributes.mockClear(); - NativeModules.DdSdk.setUser.mockClear(); NativeModules.DdSdk.setTrackingConsent.mockClear(); NativeModules.DdSdk.onRUMSessionStarted.mockClear(); @@ -1064,21 +1063,6 @@ describe('DdSdkReactNative', () => { }); }); - describe('setUser', () => { - it('calls SDK method when setUser, and sets the user in UserProvider', async () => { - // GIVEN - const user = { id: 'id', foo: 'bar' }; - - // WHEN - await DdSdkReactNative.setUser(user); - - // THEN - expect(DdSdk.setUser).toHaveBeenCalledTimes(1); - expect(DdSdk.setUser).toHaveBeenCalledWith(user); - expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual(user); - }); - }); - describe('setUserInfo', () => { it('calls SDK method when setUserInfo, and sets the user in UserProvider', async () => { // GIVEN diff --git a/packages/core/src/logs/eventMapper.ts b/packages/core/src/logs/eventMapper.ts index eb7b5f22c..939e882a5 100644 --- a/packages/core/src/logs/eventMapper.ts +++ b/packages/core/src/logs/eventMapper.ts @@ -31,13 +31,15 @@ export const formatRawLogToNativeEvent = ( export const formatRawLogToLogEvent = ( rawLog: RawLog | RawLogWithError, additionalInformation: { - userInfo: UserInfo; + userInfo?: UserInfo; attributes: Attributes; } ): LogEvent => { + const userInfo = additionalInformation?.userInfo; + return { ...rawLog, - userInfo: additionalInformation.userInfo, + ...(userInfo !== undefined ? { userInfo } : {}), attributes: additionalInformation.attributes }; }; diff --git a/packages/core/src/sdk/EventMappers/EventMapper.ts b/packages/core/src/sdk/EventMappers/EventMapper.ts index beafcf420..9ca252d72 100644 --- a/packages/core/src/sdk/EventMappers/EventMapper.ts +++ b/packages/core/src/sdk/EventMappers/EventMapper.ts @@ -15,7 +15,7 @@ import type { UserInfo } from '../UserInfoSingleton/types'; import { deepClone } from './utils/deepClone'; export type AdditionalEventDataForMapper = { - userInfo: UserInfo; + userInfo?: UserInfo; attributes: Attributes; }; diff --git a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts index c3862aabc..26392d794 100644 --- a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts +++ b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts @@ -7,13 +7,13 @@ import type { UserInfo } from './types'; class UserInfoProvider { - private userInfo: UserInfo = {}; + private userInfo: UserInfo | undefined = undefined; setUserInfo = (userInfo: UserInfo) => { this.userInfo = userInfo; }; - getUserInfo = (): UserInfo => { + getUserInfo = (): UserInfo | undefined => { return this.userInfo; }; } diff --git a/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts b/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts index 78a722c01..1f7ae84e7 100644 --- a/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts +++ b/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts @@ -9,6 +9,7 @@ import { UserInfoSingleton } from '../UserInfoSingleton'; describe('UserInfoSingleton', () => { it('sets, returns and resets the user info', () => { UserInfoSingleton.getInstance().setUserInfo({ + id: 'test', email: 'user@mail.com', extraInfo: { loggedIn: true @@ -16,6 +17,7 @@ describe('UserInfoSingleton', () => { }); expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({ + id: 'test', email: 'user@mail.com', extraInfo: { loggedIn: true @@ -24,6 +26,8 @@ describe('UserInfoSingleton', () => { UserInfoSingleton.reset(); - expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({}); + expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual( + undefined + ); }); }); diff --git a/packages/core/src/sdk/UserInfoSingleton/types.ts b/packages/core/src/sdk/UserInfoSingleton/types.ts index 97a03ae7f..dd14eb150 100644 --- a/packages/core/src/sdk/UserInfoSingleton/types.ts +++ b/packages/core/src/sdk/UserInfoSingleton/types.ts @@ -5,11 +5,8 @@ */ export type UserInfo = { - readonly id?: string /** @deprecated To be made mandatory when removing DdSdkReactnative.setUser */; + readonly id: string; readonly name?: string; readonly email?: string; readonly extraInfo?: Record; - readonly [ - key: string - ]: unknown /** @deprecated To be removed alongside DdSdkReactnative.setUser */; }; diff --git a/packages/core/src/specs/NativeDdSdk.ts b/packages/core/src/specs/NativeDdSdk.ts index 6f1ce82a5..bbf2572ee 100644 --- a/packages/core/src/specs/NativeDdSdk.ts +++ b/packages/core/src/specs/NativeDdSdk.ts @@ -31,13 +31,6 @@ export interface Spec extends TurboModule { */ setAttributes(attributes: Object): Promise; - /** - * Set the user information. - * @deprecated: Use setUserInfo instead - * @param user: The user object (use builtin attributes: 'id', 'email', 'name', and/or any custom attribute). - */ - setUser(user: Object): Promise; - /** * Set the user information. * @param user: The user object (use builtin attributes: 'id', 'email', 'name', and any custom attribute under extraInfo). diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index e1c5096fb..fe1a5895c 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -5,7 +5,6 @@ */ import type { BatchProcessingLevel } from './DdSdkReactNativeConfiguration'; -import type { UserInfo as UserInfoSingleton } from './sdk/UserInfoSingleton/types'; declare global { // eslint-disable-next-line no-var, vars-on-top @@ -90,13 +89,6 @@ export type DdSdkType = { */ setAttributes(attributes: object): Promise; - /** - * Sets the user information. - * @deprecated UserInfo id property is now mandatory (please user setUserInfo instead) - * @param user: The user object (use builtin attributes: 'id', 'email', 'name', and/or any custom attribute). - */ - setUser(user: object): Promise; - /** * Sets the user information. * @param id: A unique user identifier (relevant to your business domain) @@ -173,7 +165,7 @@ export type LogEvent = { readonly source?: ErrorSource; // readonly date: number; // TODO: RUMM-2446 & RUMM-2447 readonly status: LogStatus; - readonly userInfo: UserInfoSingleton; + readonly userInfo?: UserInfo; readonly attributes?: object; }; diff --git a/packages/react-native-apollo-client/__mocks__/react-native.ts b/packages/react-native-apollo-client/__mocks__/react-native.ts index b35df4e31..046ced2f6 100644 --- a/packages/react-native-apollo-client/__mocks__/react-native.ts +++ b/packages/react-native-apollo-client/__mocks__/react-native.ts @@ -18,9 +18,6 @@ actualRN.NativeModules.DdSdk = { initialize: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - setUser: jest.fn().mockImplementation( - () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, setAttributes: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, From 35267616fa5f8da6e82ce0792df77dced78578d8 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 12 Sep 2025 17:09:36 +0200 Subject: [PATCH 022/114] Update Tracer imports for Android to remove opentracing dependencies --- .../reactnative/DdTraceImplementation.kt | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTraceImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTraceImplementation.kt index 901ec3f6a..3ed77eb75 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTraceImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdTraceImplementation.kt @@ -6,30 +6,28 @@ package com.datadog.reactnative -import com.datadog.android.trace.AndroidTracer -import com.datadog.android.trace.Trace -import com.datadog.android.trace.TraceConfiguration +import com.datadog.android.Datadog +import com.datadog.android.trace.DatadogTracing import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReadableMap -import io.opentracing.Scope -import io.opentracing.Span -import io.opentracing.Tracer -import io.opentracing.util.GlobalTracer +import com.datadog.android.trace.api.scope.DatadogScope +import com.datadog.android.trace.api.span.DatadogSpan +import com.datadog.android.trace.api.tracer.DatadogTracer +import com.datadog.android.trace.GlobalDatadogTracer import java.util.concurrent.TimeUnit /** * The entry point to use Datadog's Trace feature. */ class DdTraceImplementation( - private val tracerProvider: () -> Tracer = { - val tracer = AndroidTracer.Builder().build() - GlobalTracer.registerIfAbsent(tracer) - - GlobalTracer.get() + private val tracerProvider: () -> DatadogTracer = { + val tracer = DatadogTracing.newTracerBuilder(Datadog.getInstance()).build() + GlobalDatadogTracer.registerIfAbsent(tracer) + GlobalDatadogTracer.get() } ) { - private val spanMap: MutableMap = mutableMapOf() - private val scopeMap: MutableMap = mutableMapOf() + private val spanMap: MutableMap = mutableMapOf() + private val scopeMap: MutableMap = mutableMapOf() // lazy here is on purpose. The thing is that this class will be instantiated even // before Sdk.initialize is called, but Tracer can be created only after SDK is initialized. @@ -47,15 +45,18 @@ class DdTraceImplementation( .start() // This is required for traces to be able to be bundled with logs. - val scope = tracer.scopeManager().activate(span) - + val scope = tracer.activateSpan(span) val spanContext = span.context() span.setTags(context.toHashMap()) span.setTags(GlobalState.globalAttributes) - val spanId = spanContext.toSpanId() + val spanId = spanContext.spanId.toString() + spanMap[spanId] = span - scopeMap[spanId] = scope + if (scope != null) { + scopeMap[spanId] = scope + } + promise.resolve(spanId) } @@ -82,7 +83,7 @@ class DdTraceImplementation( promise.resolve(null) } - private fun Span.setTags(tags: Map) { + private fun DatadogSpan.setTags(tags: Map) { for ((key, value) in tags) { when (value) { is Boolean -> setTag(key, value) From d410e20b6beac444244e5a10975f3727b85722fe Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 18 Sep 2025 10:13:53 +0200 Subject: [PATCH 023/114] Fix android tests --- .../com/datadog/reactnative/DdTraceTest.kt | 84 ++++++++++--------- .../com/datadog/tools/unit/MockRumMonitor.kt | 10 +-- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdTraceTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdTraceTest.kt index 16d459a57..8c22f88e1 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdTraceTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdTraceTest.kt @@ -6,6 +6,12 @@ package com.datadog.reactnative +import com.datadog.android.trace.api.scope.DatadogScope +import com.datadog.android.trace.api.span.DatadogSpan +import com.datadog.android.trace.api.span.DatadogSpanBuilder +import com.datadog.android.trace.api.span.DatadogSpanContext +import com.datadog.android.trace.api.trace.DatadogTraceId +import com.datadog.android.trace.api.tracer.DatadogTracer import com.datadog.tools.unit.toReadableMap import com.facebook.react.bridge.Promise import fr.xgouchet.elmyr.annotation.AdvancedForgery @@ -15,11 +21,6 @@ import fr.xgouchet.elmyr.annotation.MapForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.annotation.StringForgeryType import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.opentracing.Scope -import io.opentracing.ScopeManager -import io.opentracing.Span -import io.opentracing.SpanContext -import io.opentracing.Tracer import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assumptions.assumeTrue @@ -51,22 +52,19 @@ internal class DdTraceTest { lateinit var testedTrace: DdTraceImplementation @Mock - lateinit var mockTracer: Tracer + lateinit var mockTracer: DatadogTracer @Mock - lateinit var mockSpanBuilder: Tracer.SpanBuilder + lateinit var mockSpanBuilder: DatadogSpanBuilder @Mock - lateinit var mockSpanContext: SpanContext + lateinit var mockSpanContext: DatadogSpanContext @Mock - lateinit var mockScopeManager: ScopeManager + lateinit var mockSpan: DatadogSpan @Mock - lateinit var mockSpan: Span - - @Mock - lateinit var mockScope: Scope + lateinit var mockScope: DatadogScope @StringForgery lateinit var fakeOperation: String @@ -74,11 +72,11 @@ internal class DdTraceTest { @DoubleForgery(1000000000000.0, 2000000000000.0) var fakeTimestamp: Double = 0.0 - @StringForgery(type = StringForgeryType.HEXADECIMAL) - lateinit var fakeSpanId: String + @LongForgery(100L, 2000L) + var fakeSpanId: Long = 0 - @StringForgery(type = StringForgeryType.HEXADECIMAL) - lateinit var fakeTraceId: String + @Mock + lateinit var fakeTraceId: DatadogTraceId @MapForgery( key = AdvancedForgery(string = [StringForgery()]), @@ -102,7 +100,6 @@ internal class DdTraceTest { @BeforeEach fun `set up`() { whenever(mockTracer.buildSpan(fakeOperation)) doReturn mockSpanBuilder - whenever(mockTracer.scopeManager()) doReturn mockScopeManager whenever( mockSpanBuilder.withStartTimestamp( fakeTimestamp.toLong() * 1000 @@ -110,9 +107,9 @@ internal class DdTraceTest { ) doReturn mockSpanBuilder whenever(mockSpanBuilder.start()) doReturn mockSpan whenever(mockSpan.context()) doReturn mockSpanContext - whenever(mockSpanContext.toSpanId()) doReturn fakeSpanId - whenever(mockSpanContext.toTraceId()) doReturn fakeTraceId - whenever(mockScopeManager.activate(mockSpan)) doReturn mockScope + whenever(mockSpanContext.spanId) doReturn fakeSpanId + whenever(mockSpanContext.traceId) doReturn fakeTraceId + whenever(mockTracer.activateSpan(mockSpan)) doReturn mockScope testedTrace = DdTraceImplementation(tracerProvider = { mockTracer }) } @@ -133,7 +130,7 @@ internal class DdTraceTest { ) // Then - assertThat(lastResolvedValue).isEqualTo(fakeSpanId) + assertThat(lastResolvedValue.toString()).isEqualTo(fakeSpanId.toString()) } @Test @@ -154,18 +151,20 @@ internal class DdTraceTest { fakeTimestamp, mockPromise ) - val id = lastResolvedValue - testedTrace.finishSpan(id as String, fakeContext.toReadableMap(), endTimestamp, mockPromise) + val id = lastResolvedValue.toString() + testedTrace.finishSpan(id, fakeContext.toReadableMap(), endTimestamp, mockPromise) // Then - assertThat(id).isEqualTo(fakeSpanId) + assertThat(id).isEqualTo(fakeSpanId.toString()) verify(mockSpan).finish(endTimestamp.toLong() * 1000) } @Test fun `M do nothing W startSpan() + finishSpan() with unknown id`( @LongForgery(100L, 2000L) duration: Long, - @StringForgery(type = StringForgeryType.HEXADECIMAL) otherSpanId: String + @StringForgery(type = StringForgeryType.HEXADECIMAL) + @LongForgery(100L, 2000L) + otherSpanId: Long ) { // Given assumeTrue(otherSpanId != fakeSpanId) @@ -178,11 +177,16 @@ internal class DdTraceTest { fakeTimestamp, mockPromise ) - val id = lastResolvedValue - testedTrace.finishSpan(otherSpanId, fakeContext.toReadableMap(), endTimestamp, mockPromise) + val id = lastResolvedValue.toString() + testedTrace.finishSpan( + otherSpanId.toString(), + fakeContext.toReadableMap(), + endTimestamp, + mockPromise + ) // Then - assertThat(id).isEqualTo(fakeSpanId) + assertThat(id).isEqualTo(fakeSpanId.toString()) verify(mockSpan, never()).finish(any()) } @@ -200,7 +204,7 @@ internal class DdTraceTest { fakeTimestamp, mockPromise ) - val id = lastResolvedValue + val id = lastResolvedValue.toString() testedTrace.finishSpan( id as String, emptyMap().toReadableMap(), @@ -209,7 +213,7 @@ internal class DdTraceTest { ) // Then - assertThat(id).isEqualTo(fakeSpanId) + assertThat(id).isEqualTo(fakeSpanId.toString()) verify(mockSpan).context() verify(mockSpan).finish(endTimestamp.toLong() * 1000) fakeContext.forEach { @@ -232,11 +236,11 @@ internal class DdTraceTest { fakeTimestamp, mockPromise ) - val id = lastResolvedValue - testedTrace.finishSpan(id as String, fakeContext.toReadableMap(), endTimestamp, mockPromise) + val id = lastResolvedValue.toString() + testedTrace.finishSpan(id, fakeContext.toReadableMap(), endTimestamp, mockPromise) // Then - assertThat(id).isEqualTo(fakeSpanId) + assertThat(id).isEqualTo(fakeSpanId.toString()) verify(mockSpan).context() verify(mockSpan).finish(endTimestamp.toLong() * 1000) fakeContext.forEach { @@ -262,16 +266,16 @@ internal class DdTraceTest { fakeTimestamp, mockPromise ) - val id = lastResolvedValue + val id = lastResolvedValue.toString() testedTrace.finishSpan( - id as String, + id, emptyMap().toReadableMap(), endTimestamp, mockPromise ) // Then - assertThat(id).isEqualTo(fakeSpanId) + assertThat(id).isEqualTo(fakeSpanId.toString()) verify(mockSpan).context() verify(mockSpan).finish(endTimestamp.toLong() * 1000) fakeContext.forEach { @@ -298,14 +302,14 @@ internal class DdTraceTest { fakeTimestamp, mockPromise ) - val id = lastResolvedValue + val id = lastResolvedValue.toString() fakeGlobalState.forEach { (k, v) -> GlobalState.addAttribute(k, v) } - testedTrace.finishSpan(id as String, fakeContext.toReadableMap(), endTimestamp, mockPromise) + testedTrace.finishSpan(id, fakeContext.toReadableMap(), endTimestamp, mockPromise) // Then - assertThat(id).isEqualTo(fakeSpanId) + assertThat(id).isEqualTo(fakeSpanId.toString()) verify(mockSpan).context() verify(mockSpan).finish(endTimestamp.toLong() * 1000) expectedAttributes.forEach { diff --git a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt index a2e79d630..7c5585edd 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt @@ -29,6 +29,8 @@ class MockRumMonitor : RumMonitor { override fun addAttribute(key: String, value: Any?) {} + override fun addViewAttributes(attributes: Map) {} + override fun addError( message: String, source: RumErrorSource, @@ -61,6 +63,7 @@ class MockRumMonitor : RumMonitor { override fun getCurrentSessionId(callback: (String?) -> Unit) {} override fun removeAttribute(key: String) {} + override fun removeViewAttributes(attributes: Collection) {} override fun startAction( type: RumActionType, @@ -75,13 +78,6 @@ class MockRumMonitor : RumMonitor { attributes: Map ) {} - override fun startResource( - key: String, - method: String, - url: String, - attributes: Map - ) {} - override fun startView( key: Any, name: String, From 183f856c01a1aad20d2c162161a968c7b9372e5d Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 18 Sep 2025 10:50:27 +0200 Subject: [PATCH 024/114] Fix iOS tests --- packages/core/ios/Tests/DdSdkTests.swift | 2 +- packages/core/ios/Tests/RUMMocks.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index 6be5e0994..555ce4549 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -738,7 +738,7 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(actualFirstPartyHosts, expectedFirstPartyHosts) XCTAssertEqual(actualTracingSamplingRate, 66) - XCTAssertEqual(actualTraceContextInjection, .all) + XCTAssertEqual(actualTraceContextInjection, .sampled) } func testBuildTelemetrySampleRate() { diff --git a/packages/core/ios/Tests/RUMMocks.swift b/packages/core/ios/Tests/RUMMocks.swift index 6a6fd94e7..01d0f9d0d 100644 --- a/packages/core/ios/Tests/RUMMocks.swift +++ b/packages/core/ios/Tests/RUMMocks.swift @@ -213,14 +213,14 @@ extension RUMActionID: RandomMockable { } } -extension RUMDevice.RUMDeviceType: RandomMockable { - static func mockRandom() -> RUMDevice.RUMDeviceType { +extension Device.DeviceType: RandomMockable { + static func mockRandom() -> Device.DeviceType { return [.mobile, .desktop, .tablet, .tv, .gamingConsole, .bot, .other].randomElement()! } } -extension RUMDevice: RandomMockable { - static func mockRandom() -> RUMDevice { +extension Device: RandomMockable { + static func mockRandom() -> Device { return .init( architecture: .mockRandom(), brand: .mockRandom(), @@ -231,8 +231,8 @@ extension RUMDevice: RandomMockable { } } -extension RUMOperatingSystem: RandomMockable { - static func mockRandom() -> RUMOperatingSystem { +extension OperatingSystem: RandomMockable { + static func mockRandom() -> OperatingSystem { return .init( build: .mockRandom(length: 5), name: .mockRandom(length: 5), From 264e1a63b3bae959e7a19c7bc7eee67386230075 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 11:45:00 +0200 Subject: [PATCH 025/114] Bump Native SDKs to 3.1.0 --- benchmarks/android/app/build.gradle | 2 +- benchmarks/ios/Podfile.lock | 70 +++++++-------- example-new-architecture/ios/Podfile.lock | 51 +++++++++-- example/ios/Podfile.lock | 88 +++++++++---------- packages/core/DatadogSDKReactNative.podspec | 12 +-- packages/core/android/build.gradle | 10 +-- .../com/datadog/tools/unit/MockRumMonitor.kt | 23 +++++ packages/core/ios/Tests/DdLogsTests.swift | 2 + ...DatadogSDKReactNativeSessionReplay.podspec | 2 +- .../android/build.gradle | 4 +- .../DatadogSDKReactNativeWebView.podspec | 4 +- .../react-native-webview/android/build.gradle | 2 +- 12 files changed, 165 insertions(+), 105 deletions(-) diff --git a/benchmarks/android/app/build.gradle b/benchmarks/android/app/build.gradle index 7dc4b3707..04c240bd0 100644 --- a/benchmarks/android/app/build.gradle +++ b/benchmarks/android/app/build.gradle @@ -129,5 +129,5 @@ dependencies { // Benchmark tools from dd-sdk-android are used for vitals recording // Remember to bump thid alongside the main dd-sdk-android dependencies - implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.0.0") + implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.1.0") } diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index 61bf401c6..4bfda721d 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -1,22 +1,22 @@ PODS: - boost (1.84.0) - - DatadogCore (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogCrashReporting (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogCore (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogCrashReporting (3.1.0): + - DatadogInternal (= 3.1.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.0.0) - - DatadogLogs (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogRUM (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogInternal (3.1.0) + - DatadogLogs (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogRUM (3.1.0): + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative (2.12.1): - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -39,7 +39,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.0.0) + - DatadogSessionReplay (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -61,9 +61,9 @@ PODS: - ReactCommon/turbomodule/core - Yoga - DatadogSDKReactNativeWebView (2.12.1): - - DatadogInternal (= 3.0.0) + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.0.0) + - DatadogWebViewTracking (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -84,13 +84,13 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSessionReplay (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogTrace (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogSessionReplay (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogTrace (3.1.0): + - DatadogInternal (= 3.1.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogWebViewTracking (3.1.0): + - DatadogInternal (= 3.1.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.78.2) @@ -2070,17 +2070,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 - DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e - DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 - DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd - DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f - DatadogSDKReactNative: 241bf982c16ceff03d94a58e6d005e55c47b99c6 - DatadogSDKReactNativeSessionReplay: bba36092686e3183e97c1a0c7f4ca8142582ae28 - DatadogSDKReactNativeWebView: 6da060df20e235abac533e582d9fc2b3a5070840 - DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 - DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 - DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 + DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d + DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d + DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc + DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 + DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 + DatadogSDKReactNative: 8e0f39de38621d4d7ed961a74d8a216fd3a38321 + DatadogSDKReactNativeSessionReplay: f9288c8e981dcc65d1f727b01421ee9a7601e75f + DatadogSDKReactNativeWebView: 993527f6c5d38e0fcc4804a6a60c334dd199dc5b + DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 + DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 + DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: e32d34492c519a2194ec9d7f5e7a79d11b73f91c diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index 6f4d1150c..0bccb77fe 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,10 +1,11 @@ PODS: - boost (1.84.0) - - DatadogCore (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogCrashReporting (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogCore (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogCrashReporting (3.1.0): + - DatadogInternal (= 3.1.0) - PLCrashReporter (~> 1.12.0) +<<<<<<< HEAD <<<<<<< HEAD - DatadogInternal (2.30.2) - DatadogLogs (2.30.2): @@ -32,6 +33,20 @@ PODS: - DatadogTrace (= 3.0.0) - DatadogWebViewTracking (= 3.0.0) >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) +======= + - DatadogInternal (3.1.0) + - DatadogLogs (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogRUM (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogSDKReactNative (2.12.1): + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) +>>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) - DoubleConversion - glog - hermes-engine @@ -62,6 +77,7 @@ PODS: - DatadogWebViewTracking (= 2.30.2) ======= - DatadogSDKReactNative/Tests (2.12.1): +<<<<<<< HEAD - DatadogCore (= 3.0.0) - DatadogCrashReporting (= 3.0.0) - DatadogLogs (= 3.0.0) @@ -69,6 +85,14 @@ PODS: - DatadogTrace (= 3.0.0) - DatadogWebViewTracking (= 3.0.0) >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) +======= + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) +>>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) - DoubleConversion - glog - hermes-engine @@ -89,11 +113,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogTrace (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogTrace (3.1.0): + - DatadogInternal (= 3.1.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogWebViewTracking (3.1.0): + - DatadogInternal (= 3.1.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1875,6 +1899,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 +<<<<<<< HEAD <<<<<<< HEAD DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b @@ -1894,6 +1919,16 @@ SPEC CHECKSUMS: DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) +======= + DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d + DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d + DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc + DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 + DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 + DatadogSDKReactNative: 069ea9876220b2d09b0f4b180ce571b1b6ecbb35 + DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 + DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 +>>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 238b7d8c9..a1cca0994 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,34 +1,34 @@ PODS: - boost (1.84.0) - - DatadogCore (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogCrashReporting (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogCore (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogCrashReporting (3.1.0): + - DatadogInternal (= 3.1.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.0.0) - - DatadogLogs (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogRUM (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogInternal (3.1.0) + - DatadogLogs (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogRUM (3.1.0): + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative (2.12.1): - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - DatadogSDKReactNative/Tests (2.12.1): - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.0.0) + - DatadogSessionReplay (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -51,7 +51,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay/Tests (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.0.0) + - DatadogSessionReplay (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -74,24 +74,24 @@ PODS: - ReactCommon/turbomodule/core - Yoga - DatadogSDKReactNativeWebView (2.12.1): - - DatadogInternal (= 3.0.0) + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.0.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - DatadogSDKReactNativeWebView/Tests (2.12.1): - - DatadogInternal (= 3.0.0) + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.0.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - react-native-webview - React-RCTText - - DatadogSessionReplay (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogTrace (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogSessionReplay (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogTrace (3.1.0): + - DatadogInternal (= 3.1.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogWebViewTracking (3.1.0): + - DatadogInternal (= 3.1.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1988,17 +1988,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 - DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e - DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 - DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd - DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f - DatadogSDKReactNative: e430b3f4d7adb0fac07b61e2fb65ceacdaf82b3e - DatadogSDKReactNativeSessionReplay: 4b2a3d166a79581f18522795b40141c34cf3685d - DatadogSDKReactNativeWebView: 35dc2b9736e1aaa82b366bf6b8a8a959a9b088c5 - DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 - DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 - DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 + DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d + DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d + DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc + DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 + DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 + DatadogSDKReactNative: af351a4e1ce08124c290c52de94b0062a166cc67 + DatadogSDKReactNativeSessionReplay: dcbd55d9d0f2b86026996a8b7ec9654922d5dfe1 + DatadogSDKReactNativeWebView: 096ac87eb753b6a217b93441983264b9837c3b7e + DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 + DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 + DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/packages/core/DatadogSDKReactNative.podspec b/packages/core/DatadogSDKReactNative.podspec index a2f41ddd1..c0d235304 100644 --- a/packages/core/DatadogSDKReactNative.podspec +++ b/packages/core/DatadogSDKReactNative.podspec @@ -19,14 +19,14 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the versions in sync with DatadogSDKReactNativeSessionReplay.podspec - s.dependency 'DatadogCore', '3.0.0' - s.dependency 'DatadogLogs', '3.0.0' - s.dependency 'DatadogTrace', '3.0.0' - s.dependency 'DatadogRUM', '3.0.0' - s.dependency 'DatadogCrashReporting', '3.0.0' + s.dependency 'DatadogCore', '3.1.0' + s.dependency 'DatadogLogs', '3.1.0' + s.dependency 'DatadogTrace', '3.1.0' + s.dependency 'DatadogRUM', '3.1.0' + s.dependency 'DatadogCrashReporting', '3.1.0' # DatadogWebViewTracking is not available for tvOS - s.ios.dependency 'DatadogWebViewTracking', '3.0.0' + s.ios.dependency 'DatadogWebViewTracking', '3.1.0' s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'ios/Tests/**/*.{swift,json}' diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index a8d09d4d1..59bb75cc3 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -201,16 +201,16 @@ dependencies { // This breaks builds if the React Native target is below 0.76.0. as it relies on Gradle 8.5.0. // To avoid this, we enforce 1.0.0-beta01 on RN < 0.76.0 if (reactNativeMinorVersion < 76) { - implementation("com.datadoghq:dd-sdk-android-rum:3.0.0") { + implementation("com.datadoghq:dd-sdk-android-rum:3.1.0") { exclude group: "androidx.metrics", module: "metrics-performance" } implementation "androidx.metrics:metrics-performance:1.0.0-beta01" } else { - implementation "com.datadoghq:dd-sdk-android-rum:3.0.0" + implementation "com.datadoghq:dd-sdk-android-rum:3.1.0" } - implementation "com.datadoghq:dd-sdk-android-logs:3.0.0" - implementation "com.datadoghq:dd-sdk-android-trace:3.0.0" - implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" + implementation "com.datadoghq:dd-sdk-android-logs:3.1.0" + implementation "com.datadoghq:dd-sdk-android-trace:3.1.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" implementation "com.google.code.gson:gson:2.10.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt index 7c5585edd..702cc2533 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt @@ -13,6 +13,7 @@ import com.datadog.android.rum.RumMonitor import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum._RumInternalProxy +import com.datadog.android.rum.featureoperations.FailureReason class MockRumMonitor : RumMonitor { override var debug = false @@ -123,4 +124,26 @@ class MockRumMonitor : RumMonitor { key: Any, attributes: Map ) {} + + @ExperimentalRumApi + override fun startFeatureOperation( + name: String, + operationKey: String?, + attributes: Map + ) {} + + @ExperimentalRumApi + override fun succeedFeatureOperation( + name: String, + operationKey: String?, + attributes: Map + ) {} + + @ExperimentalRumApi + override fun failFeatureOperation( + name: String, + operationKey: String?, + failureReason: FailureReason, + attributes: Map + ) {} } diff --git a/packages/core/ios/Tests/DdLogsTests.swift b/packages/core/ios/Tests/DdLogsTests.swift index 2d9fdebac..60640e807 100644 --- a/packages/core/ios/Tests/DdLogsTests.swift +++ b/packages/core/ios/Tests/DdLogsTests.swift @@ -463,6 +463,8 @@ private class MockNativeLogger: LoggerProtocol { } extension MockNativeLogger: InternalLoggerProtocol { + func critical(message: String, error: (any Error)?, attributes: [String : any Encodable]?, completionHandler: @escaping DatadogInternal.CompletionHandler) {} + func log(level: DatadogLogs.LogLevel, message: String, errorKind: String?, errorMessage: String?, stackTrace: String?, attributes: [String : Encodable]?) { receivedMethodCalls.append(MethodCall( kind: MockNativeLogger.MethodCall.Kind(from: level), diff --git a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec index 3a2d5ff48..6a5d0b78f 100644 --- a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec +++ b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec - s.dependency 'DatadogSessionReplay', '3.0.0' + s.dependency 'DatadogSessionReplay', '3.1.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle index 6d6fe20b1..a0d77f2ff 100644 --- a/packages/react-native-session-replay/android/build.gradle +++ b/packages/react-native-session-replay/android/build.gradle @@ -216,8 +216,8 @@ dependencies { api "com.facebook.react:react-android:$reactNativeVersion" } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.datadoghq:dd-sdk-android-session-replay:3.0.0" - implementation "com.datadoghq:dd-sdk-android-internal:3.0.0" + implementation "com.datadoghq:dd-sdk-android-session-replay:3.1.0" + implementation "com.datadoghq:dd-sdk-android-internal:3.1.0" implementation project(path: ':datadog_mobile-react-native') testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" diff --git a/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec b/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec index 26e160bbc..080a853d8 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.0.0' - s.dependency 'DatadogInternal', '3.0.0' + s.dependency 'DatadogWebViewTracking', '3.1.0' + s.dependency 'DatadogInternal', '3.1.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-webview/android/build.gradle b/packages/react-native-webview/android/build.gradle index 8eeaea88e..e1fb081f0 100644 --- a/packages/react-native-webview/android/build.gradle +++ b/packages/react-native-webview/android/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "com.facebook.react:react-android:$reactNativeVersion" } - implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(path: ':datadog_mobile-react-native') From d60c26e76f53eab918a5a04343794af47a2df655 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 14:33:51 +0200 Subject: [PATCH 026/114] Fix internaltTestingTools tests --- .../DdInternalTestingImplementation.kt | 45 ++++++++------- .../DdInternalTestingImplementationTest.kt | 55 ++++++++++++------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt b/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt index b33bef6de..0d548aa72 100644 --- a/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt +++ b/packages/internal-testing-tools/android/src/main/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementation.kt @@ -6,11 +6,13 @@ package com.datadog.reactnative.internaltesting +import androidx.annotation.WorkerThread import com.datadog.android.api.InternalLogger import com.datadog.android.Datadog import com.datadog.android.api.context.DatadogContext import com.datadog.android.api.context.NetworkInfo import com.datadog.android.api.context.TimeInfo +import com.datadog.android.api.feature.EventWriteScope import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.storage.EventBatchWriter @@ -112,53 +114,54 @@ internal class FeatureScopeInterceptor( private val featureScope: FeatureScope, private val core: InternalSdkCore, ) : FeatureScope by featureScope { - private val eventsBatchInterceptor = EventBatchInterceptor() + private val eventWriteScopeInterceptor = EventWriteScopeInterceptor() fun eventsWritten(): List { - return eventsBatchInterceptor.events + return eventWriteScopeInterceptor.events } fun clearData() { - eventsBatchInterceptor.clearData() + eventWriteScopeInterceptor.clearData() } // region FeatureScope override fun withWriteContext( - forceNewBatch: Boolean, - callback: (DatadogContext, EventBatchWriter) -> Unit + withFeatureContexts: Set, + callback: (datadogContext: DatadogContext, write: EventWriteScope) -> Unit ) { - featureScope.withWriteContext(forceNewBatch, callback) + featureScope.withWriteContext(withFeatureContexts, callback) core.getDatadogContext()?.let { - callback(it, eventsBatchInterceptor) + callback(it, eventWriteScopeInterceptor) } } // endregion } - -internal class EventBatchInterceptor: EventBatchWriter { +internal class EventWriteScopeInterceptor : EventWriteScope { internal val events = mutableListOf() - override fun currentMetadata(): ByteArray? { - return null - } - fun clearData() { events.clear() } - override fun write( - event: RawBatchEvent, - batchMetadata: ByteArray?, - eventType: EventType - ): Boolean { - val eventContent = String(event.data) + private val writer = object : EventBatchWriter { + override fun currentMetadata(): ByteArray? = null - events += eventContent + override fun write( + event: RawBatchEvent, + batchMetadata: ByteArray?, + eventType: EventType + ): Boolean { + events += String(event.data) + return true + } + } - return true + override fun invoke(p1: (EventBatchWriter) -> Unit) { + p1(writer) } } + diff --git a/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt b/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt index 4a6938f9b..d25db9274 100644 --- a/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt +++ b/packages/internal-testing-tools/android/src/test/kotlin/com/datadog/reactnative/internaltesting/DdInternalTestingImplementationTest.kt @@ -10,9 +10,9 @@ import android.content.Context import com.datadog.android.Datadog import com.datadog.android.api.SdkCore import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.feature.EventWriteScope import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureScope -import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType import com.datadog.android.api.storage.RawBatchEvent import com.datadog.android.api.storage.datastore.DataStoreHandler @@ -85,23 +85,27 @@ internal class DdInternalTestingImplementationTest { wrappedCore.registerFeature(mockFeature) requireNotNull(wrappedCore.getFeature(mockFeature.name)) - .withWriteContext { _, eventBatchWriter -> - eventBatchWriter.write( - RawBatchEvent(data = "mock event for test".toByteArray()), - batchMetadata = null, - eventType = EventType.DEFAULT + .withWriteContext { _, writeScope -> + writeScope { + val rawBatchEvent = + RawBatchEvent(data = "mock event for test".toByteArray()) + it.write( + rawBatchEvent, + batchMetadata = null, + eventType = EventType.DEFAULT + ) + } + + // Then + assertThat( + wrappedCore.featureScopes[mockFeature.name] + ?.eventsWritten() + ?.first() ) + .isEqualTo( + "mock event for test" + ) } - - // Then - assertThat( - wrappedCore.featureScopes[mockFeature.name] - ?.eventsWritten() - ?.first() - ) - .isEqualTo( - "mock event for test" - ) } } } @@ -116,10 +120,23 @@ internal class MockFeatureScope(private val feature: Feature) : FeatureScope { return feature as T } + override fun withContext( + withFeatureContexts: Set, + callback: (datadogContext: DatadogContext) -> Unit + ) { + } + override fun withWriteContext( - forceNewBatch: Boolean, - callback: (DatadogContext, EventBatchWriter) -> Unit - ) {} + withFeatureContexts: Set, + callback: (datadogContext: DatadogContext, write: EventWriteScope) -> Unit + ) { + } + + override fun getWriteContextSync( + withFeatureContexts: Set + ): Pair? { + return TODO("Provide the return value") + } } internal class MockFeature(override val name: String) : Feature { From 97167021e444545729b5d397435882e47da3522c Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Sep 2025 17:47:29 +0200 Subject: [PATCH 027/114] Use native sdk's core instance instead of the one inside RN SDK wrapper --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 ++ .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +----- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 +------ 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index 4388ad5f6..ee55d08fe 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,7 +65,9 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) + Logs.enable(logsConfiguration, Datadog.getInstance()) + Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 97acb2ebf..3c86e28b4 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -21,11 +21,7 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation( - reactContext, - datadog = datadogWrapper, - ddTelemetry - ) + private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 8797bcc64..c9f91738f 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -163,12 +163,7 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation( - mockReactContext, - mockDatadog, - mockDdTelemetry, - TestUiThreadExecutor() - ) + testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From 06e6af587208ad0c299a45f8b2376fda4f5ac040 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Mon, 22 Sep 2025 16:20:27 +0200 Subject: [PATCH 028/114] Fixed internal testing tools and unit tests --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 -- .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +++++- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 ++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index ee55d08fe..4388ad5f6 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,9 +65,7 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) - Logs.enable(logsConfiguration, Datadog.getInstance()) - Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 3c86e28b4..97acb2ebf 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -21,7 +21,11 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) + private val implementation = DdSdkImplementation( + reactContext, + datadog = datadogWrapper, + ddTelemetry + ) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index c9f91738f..8797bcc64 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -163,7 +163,12 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) + testedBridgeSdk = DdSdkImplementation( + mockReactContext, + mockDatadog, + mockDdTelemetry, + TestUiThreadExecutor() + ) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From de1f9d4ed0596ef0f4e886248aa60687327ac254 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 17:37:31 +0200 Subject: [PATCH 029/114] Expose clearUserInfo API --- packages/core/__mocks__/react-native.ts | 3 + .../datadog/reactnative/DatadogSDKWrapper.kt | 4 ++ .../com/datadog/reactnative/DatadogWrapper.kt | 5 ++ .../reactnative/DdSdkImplementation.kt | 10 ++- .../kotlin/com/datadog/reactnative/DdSdk.kt | 8 +++ .../kotlin/com/datadog/reactnative/DdSdk.kt | 8 +++ .../com/datadog/reactnative/DdSdkTest.kt | 11 +++ packages/core/ios/Sources/DdSdk.mm | 12 +++- .../ios/Sources/DdSdkImplementation.swift | 8 ++- packages/core/ios/Tests/DdSdkTests.swift | 67 +++++++++++++++++++ packages/core/jest/mock.js | 3 + packages/core/src/DdSdkReactNative.tsx | 10 +++ .../src/__tests__/DdSdkReactNative.test.tsx | 26 +++++++ .../UserInfoSingleton/UserInfoSingleton.ts | 4 ++ .../__tests__/UserInfoSingleton.test.ts | 55 ++++++++++++--- packages/core/src/specs/NativeDdSdk.ts | 5 ++ packages/core/src/types.tsx | 5 ++ 17 files changed, 230 insertions(+), 14 deletions(-) diff --git a/packages/core/__mocks__/react-native.ts b/packages/core/__mocks__/react-native.ts index 260fe68a7..0e85e65ae 100644 --- a/packages/core/__mocks__/react-native.ts +++ b/packages/core/__mocks__/react-native.ts @@ -24,6 +24,9 @@ actualRN.NativeModules.DdSdk = { addUserExtraInfo: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, + clearUserInfo: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, setAttributes: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index 198061d14..f781687eb 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -86,6 +86,10 @@ internal class DatadogSDKWrapper : DatadogWrapper { Datadog.addUserProperties(extraInfo) } + override fun clearUserInfo() { + Datadog.clearUserInfo() + } + override fun addRumGlobalAttributes(attributes: Map) { val rumMonitor = this.getRumMonitor() for (attribute in attributes) { diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index 3ae3e6266..49d606b35 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -86,6 +86,11 @@ interface DatadogWrapper { extraInfo: Map ) + /** + * Clears the user information. + */ + fun clearUserInfo() + /** * Adds global attributes. * diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index 7a5c6848c..7adcf7438 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -102,7 +102,7 @@ class DdSdkImplementation( } /** - * Sets the user information. + * Sets the user extra information. * @param userExtraInfo: The additional information. (To set the id, name or email please user setUserInfo). */ fun addUserExtraInfo( @@ -114,6 +114,14 @@ class DdSdkImplementation( promise.resolve(null) } + /** + * Clears the user information. + */ + fun clearUserInfo(promise: Promise) { + datadog.clearUserInfo() + promise.resolve(null) + } + /** * Set the tracking consent regarding the data collection. * @param trackingConsent Consent, which can take one of the following values: 'pending', diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt index cfafffffe..4e4668a3e 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -67,6 +67,14 @@ class DdSdk( implementation.addUserExtraInfo(extraInfo, promise) } + /** + * Clears the user information. + */ + @ReactMethod + override fun clearUserInfo(promise: Promise) { + implementation.clearUserInfo(promise) + } + /** * Set the tracking consent regarding the data collection. * @param trackingConsent Consent, which can take one of the following values: 'pending', diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 97acb2ebf..0ebdd37fb 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -93,6 +93,14 @@ class DdSdk( implementation.addUserExtraInfo(extraInfo, promise) } + /** + * Clears the user information. + */ + @ReactMethod + fun clearUserInfo(promise: Promise) { + implementation.clearUserInfo(promise) + } + /** * Set the tracking consent regarding the data collection. * @param trackingConsent Consent, which can take one of the following values: 'pending', diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 8797bcc64..f917bb847 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -2954,6 +2954,17 @@ internal class DdSdkTest { } } + @Test + fun `𝕄 clear user info 𝕎 clearUserInfo()`() { + // When + testedBridgeSdk.clearUserInfo(mockPromise) + + // Then + argumentCaptor> { + verify(mockDatadog).clearUserInfo() + } + } + @Test fun `𝕄 set RUM attributes 𝕎 setAttributes`( @MapForgery( diff --git a/packages/core/ios/Sources/DdSdk.mm b/packages/core/ios/Sources/DdSdk.mm index 918a8db03..674cd0fbc 100644 --- a/packages/core/ios/Sources/DdSdk.mm +++ b/packages/core/ios/Sources/DdSdk.mm @@ -51,6 +51,12 @@ + (void)initFromNative { [self addUserExtraInfo:extraInfo resolve:resolve reject:reject]; } +RCT_EXPORT_METHOD(clearUserInfo:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self clearUserInfo:resolve reject:reject]; +} + RCT_REMAP_METHOD(setTrackingConsent, withTrackingConsent:(NSString*)trackingConsent withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) @@ -81,7 +87,7 @@ + (void)initFromNative { [self consumeWebviewEvent:message resolve:resolve reject:reject]; } -RCT_REMAP_METHOD(clearAllData, withResolver:(RCTPromiseResolveBlock)resolve +RCT_EXPORT_METHOD(clearAllData:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) { [self clearAllData:resolve reject:reject]; @@ -143,6 +149,10 @@ - (void)setUserInfo:(NSDictionary *)userInfo resolve:(RCTPromiseResolveBlock)res [self.ddSdkImplementation setUserInfoWithUserInfo:userInfo resolve:resolve reject:reject]; } +- (void)clearUserInfo:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation clearUserInfoWithResolve:resolve reject:reject]; +} + -(void)addUserExtraInfo:(NSDictionary *)extraInfo resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self.ddSdkImplementation addUserExtraInfoWithExtraInfo:extraInfo resolve:resolve reject:reject]; } diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index b2e610635..03c630b70 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -108,7 +108,7 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - + @objc public func addUserExtraInfo(extraInfo: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { let castedExtraInfo = castAttributesToSwift(extraInfo) @@ -117,6 +117,12 @@ public class DdSdkImplementation: NSObject { resolve(nil) } + @objc + public func clearUserInfo(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + Datadog.clearUserInfo() + resolve(nil) + } + @objc public func setTrackingConsent(trackingConsent: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { Datadog.set(trackingConsent: (trackingConsent as NSString?).asTrackingConsent()) diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index 555ce4549..efbf57b96 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -651,6 +651,73 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } + + func testClearUserInfo() throws { + let bridge = DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: JSRefreshRateMonitor(), + RUMMonitorProvider: { MockRUMMonitor() }, + RUMMonitorInternalProvider: { nil } + ) + bridge.initialize( + configuration: .mockAny(), + resolve: mockResolve, + reject: mockReject + ) + + bridge.setUserInfo( + userInfo: NSDictionary( + dictionary: [ + "id": "id_123", + "name": "John Doe", + "email": "john@doe.com", + "extraInfo": [ + "extra-info-1": 123, + "extra-info-2": "abc", + "extra-info-3": true, + "extra-info-4": [ + "nested-extra-info-1": 456 + ], + ], + ] + ), + resolve: mockResolve, + reject: mockReject + ) + + var ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + var userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, "id_123") + XCTAssertEqual(userInfo.name, "John Doe") + XCTAssertEqual(userInfo.email, "john@doe.com") + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) + + if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] + as? DatadogSDKReactNative.AnyEncodable, + let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] + { + XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) + } else { + XCTFail("extra-info-4 is not of expected type or value") + } + + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) + + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, nil) + XCTAssertEqual(userInfo.name, nil) + XCTAssertEqual(userInfo.email, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) + } func testSettingAttributes() { let rumMonitorMock = MockRUMMonitor() diff --git a/packages/core/jest/mock.js b/packages/core/jest/mock.js index a8161295a..8e154c4cd 100644 --- a/packages/core/jest/mock.js +++ b/packages/core/jest/mock.js @@ -33,6 +33,9 @@ module.exports = { addUserExtraInfo: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), + clearUserInfo: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), setAttributes: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index 668ae09f3..1838542df 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -214,6 +214,16 @@ export class DdSdkReactNative { UserInfoSingleton.getInstance().setUserInfo(userInfo); }; + /** + * Clears the user information. + * @returns a Promise. + */ + static clearUserInfo = async (): Promise => { + InternalLog.log('Clearing user info', SdkVerbosity.DEBUG); + await DdSdk.clearUserInfo(); + UserInfoSingleton.getInstance().clearUserInfo(); + }; + /** * Set the user information. * @param extraUserInfo: The additional information. (To set the id, name or email please user setUserInfo). diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index f9405aa51..57ff5d0ed 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -1112,6 +1112,32 @@ describe('DdSdkReactNative', () => { }); }); + describe('clearUserInfo', () => { + it('calls SDK method when clearUserInfo, and clears the user in UserProvider', async () => { + // GIVEN + const userInfo = { + id: 'id', + name: 'name', + email: 'email', + extraInfo: { + foo: 'bar' + } + }; + + await DdSdkReactNative.setUserInfo(userInfo); + + // WHEN + await DdSdkReactNative.clearUserInfo(); + + // THEN + expect(DdSdk.clearUserInfo).toHaveBeenCalledTimes(1); + expect(DdSdk.setUserInfo).toHaveBeenCalled(); + expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual( + undefined + ); + }); + }); + describe('setTrackingConsent', () => { it('calls SDK method when setTrackingConsent', async () => { // GIVEN diff --git a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts index 26392d794..3ce23614b 100644 --- a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts +++ b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts @@ -16,6 +16,10 @@ class UserInfoProvider { getUserInfo = (): UserInfo | undefined => { return this.userInfo; }; + + clearUserInfo = () => { + this.userInfo = undefined; + }; } export class UserInfoSingleton { diff --git a/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts b/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts index 1f7ae84e7..f8e7276d6 100644 --- a/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts +++ b/packages/core/src/sdk/UserInfoSingleton/__tests__/UserInfoSingleton.test.ts @@ -7,27 +7,60 @@ import { UserInfoSingleton } from '../UserInfoSingleton'; describe('UserInfoSingleton', () => { - it('sets, returns and resets the user info', () => { + beforeEach(() => { + UserInfoSingleton.reset(); + }); + + it('returns undefined by default', () => { + expect(UserInfoSingleton.getInstance().getUserInfo()).toBeUndefined(); + }); + + it('stores and returns user info after setUserInfo', () => { + const info = { + id: 'test', + email: 'user@mail.com', + extraInfo: { loggedIn: true } + }; + + UserInfoSingleton.getInstance().setUserInfo(info); + + expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual(info); + }); + + it('clears user info with clearUserInfo', () => { UserInfoSingleton.getInstance().setUserInfo({ id: 'test', email: 'user@mail.com', - extraInfo: { - loggedIn: true - } + extraInfo: { loggedIn: true } }); - expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({ + UserInfoSingleton.getInstance().clearUserInfo(); + + expect(UserInfoSingleton.getInstance().getUserInfo()).toBeUndefined(); + }); + + it('reset() replaces the provider and clears stored user info', () => { + const instanceBefore = UserInfoSingleton.getInstance(); + + UserInfoSingleton.getInstance().setUserInfo({ id: 'test', email: 'user@mail.com', - extraInfo: { - loggedIn: true - } + extraInfo: { loggedIn: true } }); UserInfoSingleton.reset(); - expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual( - undefined - ); + const instanceAfter = UserInfoSingleton.getInstance(); + + expect(instanceAfter).not.toBe(instanceBefore); + + expect(instanceAfter.getUserInfo()).toBeUndefined(); + }); + + it('getInstance returns the same provider between calls (singleton behavior)', () => { + const a = UserInfoSingleton.getInstance(); + const b = UserInfoSingleton.getInstance(); + + expect(a).toBe(b); }); }); diff --git a/packages/core/src/specs/NativeDdSdk.ts b/packages/core/src/specs/NativeDdSdk.ts index bbf2572ee..a2ce1120e 100644 --- a/packages/core/src/specs/NativeDdSdk.ts +++ b/packages/core/src/specs/NativeDdSdk.ts @@ -37,6 +37,11 @@ export interface Spec extends TurboModule { */ setUserInfo(user: Object): Promise; + /** + * Clears the user information. + */ + clearUserInfo(): Promise; + /** * Add custom attributes to the current user information * @param extraInfo: The extraInfo object containing additionall custom attributes diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index fe1a5895c..bad19d429 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -98,6 +98,11 @@ export type DdSdkType = { */ setUserInfo(userInfo: UserInfo): Promise; + /** + * Clears the user information. + */ + clearUserInfo(): Promise; + /** * Add additional user information. * @param extraUserInfo: The additional information. (To set the id, name or email please user setUserInfo). From 7fd923a1ccf8e211ebdea6f1b76c3c1ee6e1c191 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 29 Sep 2025 12:20:44 +0200 Subject: [PATCH 030/114] Update attribute API --- benchmarks/src/testSetup/monitor.ts | 5 +- example/src/ddUtils.tsx | 4 +- packages/codepush/__mocks__/react-native.ts | 13 +- packages/core/__mocks__/react-native.ts | 13 +- .../datadog/reactnative/DatadogSDKWrapper.kt | 17 ++- .../com/datadog/reactnative/DatadogWrapper.kt | 22 ++++ .../reactnative/DdSdkImplementation.kt | 51 ++++++- .../kotlin/com/datadog/reactnative/DdSdk.kt | 39 +++++- .../kotlin/com/datadog/reactnative/DdSdk.kt | 37 +++++- .../com/datadog/reactnative/DdSdkTest.kt | 118 ++++++++++++++++- packages/core/ios/Sources/AnyEncodable.swift | 21 ++- packages/core/ios/Sources/DdSdk.mm | 42 +++++- .../ios/Sources/DdSdkImplementation.swift | 31 ++++- packages/core/ios/Sources/GlobalState.swift | 2 +- packages/core/ios/Tests/DdSdkTests.swift | 124 +++++++++++++++++- packages/core/ios/Tests/MockRUMMonitor.swift | 12 +- packages/core/jest/mock.js | 11 +- packages/core/src/DdSdkReactNative.tsx | 53 +++++++- .../src/__tests__/DdSdkReactNative.test.tsx | 68 +++++++++- .../AttributesSingleton.ts | 26 +++- .../__tests__/AttributesSingleton.test.ts | 60 +++++++-- packages/core/src/specs/NativeDdSdk.ts | 23 +++- packages/core/src/types.tsx | 21 ++- .../__mocks__/react-native.ts | 4 +- 24 files changed, 742 insertions(+), 75 deletions(-) diff --git a/benchmarks/src/testSetup/monitor.ts b/benchmarks/src/testSetup/monitor.ts index c7cc23473..93ea0fccd 100644 --- a/benchmarks/src/testSetup/monitor.ts +++ b/benchmarks/src/testSetup/monitor.ts @@ -4,8 +4,7 @@ * Copyright 2016-Present Datadog, Inc. */ -import { DefaultTimeProvider, RumActionType } from "@datadog/mobile-react-native"; -import { ErrorSource } from "@datadog/mobile-react-native/lib/typescript/rum/types"; +import { DefaultTimeProvider, ErrorSource, RumActionType } from "@datadog/mobile-react-native"; import type { DdRumType, ResourceKind } from "@datadog/mobile-react-native/lib/typescript/rum/types"; import type { GestureResponderEvent } from "react-native/types"; @@ -72,4 +71,4 @@ export const Monitor: Pick { DdLogs.info('The RN Sdk was properly initialized') DdSdkReactNative.setUserInfo({id: "1337", name: "Xavier", email: "xg@example.com", extraInfo: { type: "premium" } }) - DdSdkReactNative.setAttributes({campaign: "ad-network"}) + DdSdkReactNative.addAttributes({campaign: "ad-network"}) }); } diff --git a/packages/codepush/__mocks__/react-native.ts b/packages/codepush/__mocks__/react-native.ts index 046ced2f6..0c8189840 100644 --- a/packages/codepush/__mocks__/react-native.ts +++ b/packages/codepush/__mocks__/react-native.ts @@ -18,9 +18,18 @@ actualRN.NativeModules.DdSdk = { initialize: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - setAttributes: jest.fn().mockImplementation( + addAttribute: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, + ) as jest.MockedFunction, + removeAttribute: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + addAttributes: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + removeAttributes: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, setTrackingConsent: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, diff --git a/packages/core/__mocks__/react-native.ts b/packages/core/__mocks__/react-native.ts index 0e85e65ae..24e3f80c7 100644 --- a/packages/core/__mocks__/react-native.ts +++ b/packages/core/__mocks__/react-native.ts @@ -27,9 +27,18 @@ actualRN.NativeModules.DdSdk = { clearUserInfo: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - setAttributes: jest.fn().mockImplementation( + addAttribute: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, + ) as jest.MockedFunction, + removeAttribute: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + addAttributes: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + removeAttributes: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, setTrackingConsent: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index f781687eb..06151d834 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -89,11 +89,24 @@ internal class DatadogSDKWrapper : DatadogWrapper { override fun clearUserInfo() { Datadog.clearUserInfo() } + + override fun addRumGlobalAttribute(key: String, value: Any?) { + this.getRumMonitor().addAttribute(key, value) + } + + override fun removeRumGlobalAttribute(key: String) { + this.getRumMonitor().removeAttribute(key) + } override fun addRumGlobalAttributes(attributes: Map) { - val rumMonitor = this.getRumMonitor() for (attribute in attributes) { - rumMonitor.addAttribute(attribute.key, attribute.value) + this.addRumGlobalAttribute(attribute.key, attribute.value) + } + } + + override fun removeRumGlobalAttributes(keys: Array) { + for (key in keys) { + this.removeRumGlobalAttribute(key) } } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index 49d606b35..d6395b18b 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -91,6 +91,21 @@ interface DatadogWrapper { */ fun clearUserInfo() + + /** Adds a global attribute. + * + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + fun addRumGlobalAttribute(key: String, value: Any?) + + /** + * Removes a global attribute. + * + * @param key: Key that identifies the attribute. + */ + fun removeRumGlobalAttribute(key: String) + /** * Adds global attributes. * @@ -98,6 +113,13 @@ interface DatadogWrapper { */ fun addRumGlobalAttributes(attributes: Map) + /** + * Removes global attributes. + * + * @param keys Keys linked to the attributes to be removed + */ + fun removeRumGlobalAttributes(keys: Array) + /** * Sets tracking consent. */ diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index 7adcf7438..ed545d9e1 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -14,6 +14,7 @@ import com.datadog.android.rum.configuration.VitalsUpdateFrequency import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import java.util.Locale import java.util.concurrent.TimeUnit @@ -66,11 +67,35 @@ class DdSdkImplementation( } /** - * Sets the global context (set of attributes) attached with all future Logs, Spans and RUM + * Sets a specific attribute in the global context attached with all future Logs, Spans and RUM. + * + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + fun addAttribute(key: String, value: ReadableMap, promise: Promise) { + val attributeValue = value.toMap()["value"] + datadog.addRumGlobalAttribute(key, attributeValue) + GlobalState.addAttribute(key, attributeValue) + promise.resolve(null) + } + + /** + * Removes an attribute from the global context attached with all future Logs, Spans and RUM events. + * @param key: They key associated with the attribute to be removed. + */ + fun removeAttribute(key: String, promise: Promise) { + datadog.removeRumGlobalAttribute(key) + GlobalState.removeAttribute(key) + promise.resolve(null) + } + + + /** + * Adds a set of attributes to the global context that is attached with all future Logs, Spans and RUM * events. - * @param attributes The global context attributes. + * @param attributes: The global context attributes. */ - fun setAttributes(attributes: ReadableMap, promise: Promise) { + fun addAttributes(attributes: ReadableMap, promise: Promise) { datadog.addRumGlobalAttributes(attributes.toHashMap()) for ((k,v) in attributes.toHashMap()) { GlobalState.addAttribute(k, v) @@ -78,6 +103,26 @@ class DdSdkImplementation( promise.resolve(null) } + /** + * Removes a set of attributes from the global context that is attached with all future Logs, Spans and RUM + * events. + * @param keys: They keys associated with the attributes to be removed. + */ + fun removeAttributes(keys: ReadableArray, promise: Promise) { + val keysArray = mutableListOf() + for (i in 0 until keys.size()) { + val key: String = keys.getString(i) + keysArray.add(key) + } + val keysStringArray = keysArray.toTypedArray() + + datadog.removeRumGlobalAttributes(keysStringArray) + for (key in keysStringArray) { + GlobalState.removeAttribute(key) + } + promise.resolve(null) + } + /** * Set the user information. * @param userInfo The user object (use builtin attributes: 'id', 'email', 'name', and any custom diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt index 4e4668a3e..a9d430081 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -12,13 +12,14 @@ import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.modules.core.DeviceEventManagerModule /** The entry point to initialize Datadog's features. */ class DdSdk( reactContext: ReactApplicationContext, - datadogWrapper: DatadogWrapper = DatadogSDKWrapper() + datadogWrapper: DatadogWrapper = DatadogSDKWrapper(), ddTelemetry: DdTelemetry = DdTelemetry() ) : NativeDdSdkSpec(reactContext) { @@ -40,13 +41,43 @@ class DdSdk( } /** - * Sets the global context (set of attributes) attached with all future Logs, Spans and RUM + * Sets a specific attribute in the global context attached with all future Logs, Spans and RUM + * + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + @ReactMethod + override fun addAttribute(key: String, value: ReadableMap, promise: Promise) { + implementation.addAttribute(key, value, promise) + } + + /** + * Removes an attribute from the context attached with all future Logs, Spans and RUM events. + * @param key: They key associated with the attribute to be removed. + */ + @ReactMethod + override fun removeAttribute(key: String, promise: Promise) { + implementation.removeAttribute(key, promise) + } + + /** + * Adds a set of attributes to the global context that is attached with all future Logs, Spans and RUM * events. * @param attributes The global context attributes. */ @ReactMethod - override fun setAttributes(attributes: ReadableMap, promise: Promise) { - implementation.setAttributes(attributes, promise) + override fun addAttributes(attributes: ReadableMap, promise: Promise) { + implementation.addAttributes(attributes, promise) + } + + /** + * Removes a set of attributes from the global context that is attached with all future Logs, Spans and RUM + * events. + * @param keys: They keys associated with the attributes to be removed. + */ + @ReactMethod + override fun removeAttributes(keys: ReadableArray, promise: Promise) { + implementation.removeAttributes(keys, promise) } /** diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 0ebdd37fb..958ba521b 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -12,6 +12,7 @@ import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap /** The entry point to initialize Datadog's features. */ @@ -66,13 +67,43 @@ class DdSdk( } /** - * Sets the global context (set of attributes) attached with all future Logs, Spans and RUM + * Sets a specific attribute in the global context attached with all future Logs, Spans and RUM + * + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + @ReactMethod + fun addAttribute(key: String, value: ReadableMap, promise: Promise) { + implementation.addAttribute(key, value, promise) + } + + /** + * Removes an attribute from the context attached with all future Logs, Spans and RUM events. + * @param key: They key associated with the attribute to be removed. + */ + @ReactMethod + fun removeAttribute(key: String, promise: Promise) { + implementation.removeAttribute(key, promise) + } + + /** + * Adds a set of attributes to the global context that is attached with all future Logs, Spans and RUM * events. * @param attributes The global context attributes. */ @ReactMethod - fun setAttributes(attributes: ReadableMap, promise: Promise) { - implementation.setAttributes(attributes, promise) + fun addAttributes(attributes: ReadableMap, promise: Promise) { + implementation.addAttributes(attributes, promise) + } + + /** + * Removes a set of attributes from the global context that is attached with all future Logs, Spans and RUM + * events. + * @param keys: They keys associated with the attributes to be removed. + */ + @ReactMethod + fun removeAttributes(keys: ReadableArray, promise: Promise) { + implementation.removeAttributes(keys, promise) } /** diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index f917bb847..ae6ded89c 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -40,6 +40,7 @@ import com.datadog.tools.unit.setStaticValue import com.datadog.tools.unit.toReadableArray import com.datadog.tools.unit.toReadableJavaOnlyMap import com.datadog.tools.unit.toReadableMap +import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap @@ -78,7 +79,6 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.doThrow import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder -import org.mockito.kotlin.isNotNull import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -2966,28 +2966,96 @@ internal class DdSdkTest { } @Test - fun `𝕄 set RUM attributes 𝕎 setAttributes`( + fun `M set Rum attribute W addAttribute`( + @StringForgery(type = StringForgeryType.NUMERICAL) key: String, + @StringForgery(type = StringForgeryType.ASCII) value: String + ) { + // When + val attributeMap = JavaOnlyMap().apply { + putString("value", value) + } + testedBridgeSdk.addAttribute(key, attributeMap, mockPromise) + + // Then + verify(mockDatadog).addRumGlobalAttribute(key, value) + } + + @Test + fun `M set GlobalState attribute W addAttribute`( + @StringForgery(type = StringForgeryType.NUMERICAL) key: String, + @StringForgery(type = StringForgeryType.ASCII) value: String + ) { + // When + val attributeMap = JavaOnlyMap().apply { + putString("value", value) + } + testedBridgeSdk.addAttribute(key, attributeMap, mockPromise) + + // Then + assertThat(GlobalState.globalAttributes).containsEntry(key, value) + } + + @Test + fun `M remove Rum attribute W removeAttribute`( + @StringForgery(type = StringForgeryType.NUMERICAL) key: String, + @StringForgery(type = StringForgeryType.ASCII) value: String + ) { + // Given + val attributeMap = JavaOnlyMap().apply { + putString("value", value) + } + testedBridgeSdk.addAttribute(key, attributeMap, mockPromise) + assertThat(GlobalState.globalAttributes).containsEntry(key, value) + + // When + testedBridgeSdk.removeAttribute(key, mockPromise) + + // Then + verify(mockDatadog).removeRumGlobalAttribute(key) + } + + @Test + fun `M remove GlobalState attribute W removeAttribute`( + @StringForgery(type = StringForgeryType.NUMERICAL) key: String, + @StringForgery(type = StringForgeryType.ASCII) value: String + ) { + // Given + val attributeMap = JavaOnlyMap().apply { + putString("value", value) + } + testedBridgeSdk.addAttribute(key, attributeMap, mockPromise) + assertThat(GlobalState.globalAttributes).containsEntry(key, value) + + // When + testedBridgeSdk.removeAttribute(key, mockPromise) + + // Then + assertThat(GlobalState.globalAttributes).doesNotContainEntry(key, value) + } + + @Test + fun `𝕄 set RUM attributes 𝕎 addAttributes`( @MapForgery( key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) ) customAttributes: Map ) { // When - testedBridgeSdk.setAttributes(customAttributes.toReadableMap(), mockPromise) + testedBridgeSdk.addAttributes(customAttributes.toReadableMap(), mockPromise) // Then verify(mockDatadog).addRumGlobalAttributes(customAttributes) } @Test - fun `𝕄 set GlobalState attributes 𝕎 setAttributes`( + fun `𝕄 set GlobalState attributes 𝕎 addAttributes`( @MapForgery( key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) ) customAttributes: Map ) { // When - testedBridgeSdk.setAttributes(customAttributes.toReadableMap(), mockPromise) + testedBridgeSdk.addAttributes(customAttributes.toReadableMap(), mockPromise) // Then customAttributes.forEach { (k, v) -> @@ -2995,6 +3063,46 @@ internal class DdSdkTest { } } + @Test + fun `𝕄 remove RUM attributes 𝕎 removeAttributes`( + @MapForgery( + key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), + value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) + ) customAttributes: Map + ) { + // Given + testedBridgeSdk.addAttributes(customAttributes.toReadableMap(), mockPromise) + verify(mockDatadog).addRumGlobalAttributes(customAttributes) + + // When + val keys = customAttributes.keys.toReadableArray() + testedBridgeSdk.removeAttributes(keys, mockPromise) + + // Then + verify(mockDatadog).removeRumGlobalAttributes(customAttributes.keys.toTypedArray()) + } + + @Test + fun `𝕄 remve GlobalState attributes 𝕎 removeAttributes`( + @MapForgery( + key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), + value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) + ) customAttributes: Map + ) { + // Given + testedBridgeSdk.addAttributes(customAttributes.toReadableMap(), mockPromise) + verify(mockDatadog).addRumGlobalAttributes(customAttributes) + + // When + val keys = customAttributes.keys.toReadableArray() + testedBridgeSdk.removeAttributes(keys, mockPromise) + + // Then + customAttributes.forEach { (k, v) -> + assertThat(GlobalState.globalAttributes).doesNotContainEntry(k, v) + } + } + @Test fun `𝕄 build Granted consent 𝕎 buildTrackingConsent {granted}`(forge: Forge) { // When diff --git a/packages/core/ios/Sources/AnyEncodable.swift b/packages/core/ios/Sources/AnyEncodable.swift index 39821af87..7fac7bb3b 100644 --- a/packages/core/ios/Sources/AnyEncodable.swift +++ b/packages/core/ios/Sources/AnyEncodable.swift @@ -14,18 +14,25 @@ internal func castAttributesToSwift(_ attributes: [String: Any]) -> [String: Enc var casted: [String: Encodable] = [:] attributes.forEach { key, value in - if let castedValue = castByPreservingTypeInformation(attributeValue: value) { - // If possible, cast attribute by preserving its type information - casted[key] = castedValue - } else { - // Otherwise, cast by preserving its encoded value (and loosing type information) - casted[key] = castByPreservingEncodedValue(attributeValue: value) - } + casted[key] = castValueToSwift(value) } return casted } +internal func castValueToSwift(_ value: Any) -> Encodable { + var casted: Encodable + if let castedValue = castByPreservingTypeInformation(attributeValue: value) { + // If possible, cast attribute by preserving its type information + casted = castedValue + } else { + // Otherwise, cast by preserving its encoded value (and loosing type information) + casted = castByPreservingEncodedValue(attributeValue: value) + } + + return casted +} + /// Casts `Any` value to `Encodable` by preserving its type information. private func castByPreservingTypeInformation(attributeValue: Any) -> Encodable? { switch attributeValue { diff --git a/packages/core/ios/Sources/DdSdk.mm b/packages/core/ios/Sources/DdSdk.mm index 674cd0fbc..06736a69e 100644 --- a/packages/core/ios/Sources/DdSdk.mm +++ b/packages/core/ios/Sources/DdSdk.mm @@ -30,11 +30,33 @@ + (void)initFromNative { [self initialize:configuration resolve:resolve reject:reject]; } -RCT_REMAP_METHOD(setAttributes, withAttributes:(NSDictionary*)attributes +RCT_EXPORT_METHOD(addAttribute:(NSString*) key + withValue:(NSDictionary*) value + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self addAttribute:key value:value resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(removeAttribute:(NSString*) key + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self removeAttribute:key resolve:resolve reject:reject]; +} + +RCT_REMAP_METHOD(addAttributes, withAttributes:(NSDictionary*)attributes withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) { - [self setAttributes:attributes resolve:resolve reject:reject]; + [self addAttributes:attributes resolve:resolve reject:reject]; +} + +RCT_REMAP_METHOD(removeAttributes, withKeys:(NSArray *)keys + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self removeAttributes:keys resolve:resolve reject:reject]; } RCT_REMAP_METHOD(setUserInfo, withUserInfo:(NSDictionary*)userInfo @@ -137,8 +159,20 @@ - (void)initialize:(NSDictionary *)configuration resolve:(RCTPromiseResolveBlock [self.ddSdkImplementation initializeWithConfiguration:configuration resolve:resolve reject:reject]; } -- (void)setAttributes:(NSDictionary *)attributes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { - [self.ddSdkImplementation setAttributesWithAttributes:attributes resolve:resolve reject:reject]; +- (void)addAttribute:(NSString *)key value:(NSDictionary *)value resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation addAttributeWithKey:key value:value resolve:resolve reject:reject]; +} + +- (void)removeAttribute:(NSString *)key resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation removeAttributeWithKey:key resolve:resolve reject:reject]; +} + +- (void)addAttributes:(NSDictionary *)attributes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation addAttributesWithAttributes:attributes resolve:resolve reject:reject]; +} + +- (void)removeAttributes:(NSArray *)keys resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation removeAttributesWithKeys:keys resolve:resolve reject:reject]; } - (void)setTrackingConsent:(NSString *)trackingConsent resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 03c630b70..8aae5e10d 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -75,14 +75,43 @@ public class DdSdkImplementation: NSObject { resolve(nil) } + + @objc + public func addAttribute(key: AttributeKey, value: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + if let attributeValue = value.object(forKey: "value") { + let castedValue = castValueToSwift(attributeValue) + RUMMonitorProvider().addAttribute(forKey: key, value: castedValue) + GlobalState.addAttribute(forKey: key, value: castedValue) + } + + resolve(nil) + } + + @objc + public func removeAttribute(key: AttributeKey, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + RUMMonitorProvider().removeAttribute(forKey: key) + GlobalState.removeAttribute(key: key) + + resolve(nil) + } @objc - public func setAttributes(attributes: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func addAttributes(attributes: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { let castedAttributes = castAttributesToSwift(attributes) for (key, value) in castedAttributes { RUMMonitorProvider().addAttribute(forKey: key, value: value) GlobalState.addAttribute(forKey: key, value: value) } + + resolve(nil) + } + + @objc + public func removeAttributes(keys: [AttributeKey], resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + RUMMonitorProvider().removeAttributes(forKeys: keys) + for (key) in keys { + GlobalState.removeAttribute(key: key) + } resolve(nil) } diff --git a/packages/core/ios/Sources/GlobalState.swift b/packages/core/ios/Sources/GlobalState.swift index b932803a1..a758bf0ef 100644 --- a/packages/core/ios/Sources/GlobalState.swift +++ b/packages/core/ios/Sources/GlobalState.swift @@ -15,7 +15,7 @@ internal struct GlobalState { } internal static func removeAttribute(key: String) { - GlobalState.globalAttributes.removeValue(forKey: key) + GlobalState.globalAttributes[key] = nil } } diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index efbf57b96..adbb57da9 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -651,7 +651,7 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } - + func testClearUserInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -704,12 +704,12 @@ class DdSdkTests: XCTestCase { } else { XCTFail("extra-info-4 is not of expected type or value") } - + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) - + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() userInfo = try XCTUnwrap(ddContext.userInfo) - + XCTAssertEqual(userInfo.id, nil) XCTAssertEqual(userInfo.name, nil) XCTAssertEqual(userInfo.email, nil) @@ -719,7 +719,59 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) } - func testSettingAttributes() { + func testRemovingAttribute() { + let rumMonitorMock = MockRUMMonitor() + let bridge = DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: JSRefreshRateMonitor(), + RUMMonitorProvider: { rumMonitorMock }, + RUMMonitorInternalProvider: { nil } + ) + + bridge.initialize( + configuration: .mockAny(), + resolve: mockResolve, + reject: mockReject + ) + + bridge.addAttributes( + attributes: NSDictionary( + dictionary: [ + "attribute-1": 123, + "attribute-2": "abc", + ] + ), + resolve: mockResolve, + reject: mockReject + ) + + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, 123) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, "abc") + + XCTAssertEqual(GlobalState.globalAttributes["attribute-1"] as? Int64, 123) + XCTAssertEqual(GlobalState.globalAttributes["attribute-2"] as? String, "abc") + + bridge.removeAttribute(key: "attribute-1", resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, "abc") + + XCTAssertEqual(GlobalState.globalAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(GlobalState.globalAttributes["attribute-2"] as? String, "abc") + + bridge.removeAttribute(key: "attribute-2", resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, nil) + + XCTAssertEqual(GlobalState.globalAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(GlobalState.globalAttributes["attribute-2"] as? String, nil) + + GlobalState.globalAttributes.removeAll() + } + + func testAddingAttributes() { let rumMonitorMock = MockRUMMonitor() let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -734,7 +786,7 @@ class DdSdkTests: XCTestCase { reject: mockReject ) - bridge.setAttributes( + bridge.addAttributes( attributes: NSDictionary( dictionary: [ "attribute-1": 123, @@ -757,6 +809,66 @@ class DdSdkTests: XCTestCase { GlobalState.globalAttributes.removeAll() } + func testRemovingAttributes() { + let rumMonitorMock = MockRUMMonitor() + let bridge = DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: JSRefreshRateMonitor(), + RUMMonitorProvider: { rumMonitorMock }, + RUMMonitorInternalProvider: { nil } + ) + bridge.initialize( + configuration: .mockAny(), + resolve: mockResolve, + reject: mockReject + ) + + bridge.addAttributes( + attributes: NSDictionary( + dictionary: [ + "attribute-1": 123, + "attribute-2": "abc", + "attribute-3": true, + ] + ), + resolve: mockResolve, + reject: mockReject + ) + + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, 123) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, "abc") + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-3"] as? Bool, true) + + XCTAssertEqual(GlobalState.globalAttributes["attribute-1"] as? Int64, 123) + XCTAssertEqual(GlobalState.globalAttributes["attribute-2"] as? String, "abc") + XCTAssertEqual(GlobalState.globalAttributes["attribute-3"] as? Bool, true) + + bridge.removeAttributes( + keys: ["attribute-1", "attribute-2"], resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, nil) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-3"] as? Bool, true) + + XCTAssertEqual(GlobalState.globalAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(GlobalState.globalAttributes["attribute-2"] as? String, nil) + XCTAssertEqual(GlobalState.globalAttributes["attribute-3"] as? Bool, true) + + bridge.removeAttributes(keys: ["attribute-3"], resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, nil) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-3"] as? Bool, nil) + + XCTAssertEqual(GlobalState.globalAttributes["attribute-1"] as? Int64, nil) + XCTAssertEqual(GlobalState.globalAttributes["attribute-2"] as? String, nil) + XCTAssertEqual(GlobalState.globalAttributes["attribute-3"] as? Bool, nil) + + GlobalState.globalAttributes.removeAll() + + } + func testBuildLongTaskThreshold() { let configuration: DdSdkConfiguration = .mockAny(nativeLongTaskThresholdMs: 2500) diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index f0fa03364..3a882ed47 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -38,14 +38,20 @@ internal class MockRUMMonitor: RUMMonitorProtocol { addedAttributes[key] = value } - func removeAttribute(forKey key: DatadogInternal.AttributeKey) {} + func removeAttribute(forKey key: DatadogInternal.AttributeKey) { + addedAttributes.removeValue(forKey: key) + } func addAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { - // Not implemented + for (key, value) in attributes { + addAttribute(forKey: key, value: value) + } } func removeAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { - // Not implemented + for key in keys { + removeAttribute(forKey: key) + } } var debug: Bool diff --git a/packages/core/jest/mock.js b/packages/core/jest/mock.js index 8e154c4cd..c49d13f48 100644 --- a/packages/core/jest/mock.js +++ b/packages/core/jest/mock.js @@ -36,7 +36,16 @@ module.exports = { clearUserInfo: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), - setAttributes: jest + addAttribute: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + removeAttribute: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + addAttributes: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + removeAttributes: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), setTrackingConsent: jest diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index 1838542df..8360a695b 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -175,20 +175,61 @@ export class DdSdkReactNative { ); }; + /** + * Adds a specific attribute to the global context attached with all future Logs, Spans and RUM. + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + static addAttribute = async ( + key: string, + value: unknown + ): Promise => { + InternalLog.log( + `Adding attribute ${JSON.stringify(value)} for key ${key}`, + SdkVerbosity.DEBUG + ); + await DdSdk.addAttribute(key, { value }); + AttributesSingleton.getInstance().addAttribute(key, value); + }; + + /** + * Removes an attribute from the context attached with all future Logs, Spans and RUM events. + * @param key: They key associated with the attribute to be removed. + */ + static removeAttribute = async (key: string): Promise => { + InternalLog.log( + `Removing attribute for key ${key}`, + SdkVerbosity.DEBUG + ); + await DdSdk.removeAttribute(key); + AttributesSingleton.getInstance().removeAttribute(key); + }; + /** * Adds a set of attributes to the global context attached with all future Logs, Spans and RUM events. - * To remove an attribute, set it to `undefined` in a call to `setAttributes`. * @param attributes: The global context attributes. * @returns a Promise. */ - // eslint-disable-next-line @typescript-eslint/ban-types - static setAttributes = async (attributes: Attributes): Promise => { + static addAttributes = async (attributes: Attributes): Promise => { + InternalLog.log( + `Adding attributes ${JSON.stringify(attributes)}`, + SdkVerbosity.DEBUG + ); + await DdSdk.addAttributes(attributes); + AttributesSingleton.getInstance().addAttributes(attributes); + }; + + /** + * Removes a set of attributes from the context attached with all future Logs, Spans and RUM events. + * @param keys: They keys associated with the attributes to be removed. + */ + static removeAttributes = async (keys: string[]): Promise => { InternalLog.log( - `Setting attributes ${JSON.stringify(attributes)}`, + `Removing attributes for keys ${JSON.stringify(keys)}`, SdkVerbosity.DEBUG ); - await DdSdk.setAttributes(attributes); - AttributesSingleton.getInstance().setAttributes(attributes); + await DdSdk.removeAttributes(keys); + AttributesSingleton.getInstance().removeAttributes(keys); }; /** diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 57ff5d0ed..5e6f8c447 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -62,7 +62,7 @@ beforeEach(async () => { GlobalState.instance.isInitialized = false; DdSdkReactNative['wasAutoInstrumented'] = false; NativeModules.DdSdk.initialize.mockClear(); - NativeModules.DdSdk.setAttributes.mockClear(); + NativeModules.DdSdk.addAttributes.mockClear(); NativeModules.DdSdk.setTrackingConsent.mockClear(); NativeModules.DdSdk.onRUMSessionStarted.mockClear(); @@ -1045,24 +1045,80 @@ describe('DdSdkReactNative', () => { }); }); - describe('setAttributes', () => { - it('calls SDK method when setAttributes', async () => { + describe('addAttribute', () => { + it('calls SDK method when addAttribute', async () => { + // GIVEN + const key = 'foo'; + const value = 'bar'; + + // WHEN + + await DdSdkReactNative.addAttribute(key, value); + + // THEN + expect(DdSdk.addAttribute).toHaveBeenCalledTimes(1); + expect(DdSdk.addAttribute).toHaveBeenCalledWith(key, { value }); + expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual( + value + ); + }); + }); + + describe('removeAttribute', () => { + it('calls SDK method when removeAttribute', async () => { + // GIVEN + const key = 'foo'; + const value = 'bar'; + await DdSdkReactNative.addAttribute(key, value); + + // WHEN + await DdSdkReactNative.removeAttribute(key); + + // THEN + expect(DdSdk.removeAttribute).toHaveBeenCalledTimes(1); + expect(DdSdk.removeAttribute).toHaveBeenCalledWith(key); + expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual( + undefined + ); + }); + }); + + describe('addAttributes', () => { + it('calls SDK method when addAttributes', async () => { // GIVEN const attributes = { foo: 'bar' }; // WHEN - await DdSdkReactNative.setAttributes(attributes); + await DdSdkReactNative.addAttributes(attributes); // THEN - expect(DdSdk.setAttributes).toHaveBeenCalledTimes(1); - expect(DdSdk.setAttributes).toHaveBeenCalledWith(attributes); + expect(DdSdk.addAttributes).toHaveBeenCalledTimes(1); + expect(DdSdk.addAttributes).toHaveBeenCalledWith(attributes); expect(AttributesSingleton.getInstance().getAttributes()).toEqual({ foo: 'bar' }); }); }); + describe('removeAttributes', () => { + it('calls SDK method when removeAttributes', async () => { + // GIVEN + const attributes = { foo: 'bar', baz: 'quux' }; + await DdSdkReactNative.addAttributes(attributes); + + // WHEN + await DdSdkReactNative.removeAttributes(['foo', 'baz']); + + // THEN + expect(DdSdk.removeAttributes).toHaveBeenCalledTimes(1); + expect(DdSdk.removeAttributes).toHaveBeenCalledWith(['foo', 'baz']); + expect(AttributesSingleton.getInstance().getAttributes()).toEqual( + {} + ); + }); + }); + describe('setUserInfo', () => { it('calls SDK method when setUserInfo, and sets the user in UserProvider', async () => { // GIVEN diff --git a/packages/core/src/sdk/AttributesSingleton/AttributesSingleton.ts b/packages/core/src/sdk/AttributesSingleton/AttributesSingleton.ts index a51bb6c99..ac92c2d32 100644 --- a/packages/core/src/sdk/AttributesSingleton/AttributesSingleton.ts +++ b/packages/core/src/sdk/AttributesSingleton/AttributesSingleton.ts @@ -9,13 +9,37 @@ import type { Attributes } from './types'; class AttributesProvider { private attributes: Attributes = {}; - setAttributes = (attributes: Attributes) => { + addAttribute = (key: string, value: unknown) => { + const newAttributes = { ...this.attributes }; + newAttributes[key] = value; + this.attributes = newAttributes; + }; + + removeAttribute = (key: string) => { + const updatedAttributes = { ...this.attributes }; + delete updatedAttributes[key]; + this.attributes = updatedAttributes; + }; + + addAttributes = (attributes: Attributes) => { this.attributes = { ...this.attributes, ...attributes }; }; + removeAttributes = (keys: string[]) => { + const updated = { ...this.attributes }; + for (const k of keys) { + delete updated[k]; + } + this.attributes = updated; + }; + + getAttribute = (key: string): unknown | undefined => { + return this.attributes[key]; + }; + getAttributes = (): Attributes => { return this.attributes; }; diff --git a/packages/core/src/sdk/AttributesSingleton/__tests__/AttributesSingleton.test.ts b/packages/core/src/sdk/AttributesSingleton/__tests__/AttributesSingleton.test.ts index 23fbe5ad7..90d1133b4 100644 --- a/packages/core/src/sdk/AttributesSingleton/__tests__/AttributesSingleton.test.ts +++ b/packages/core/src/sdk/AttributesSingleton/__tests__/AttributesSingleton.test.ts @@ -7,9 +7,12 @@ import { AttributesSingleton } from '../AttributesSingleton'; describe('AttributesSingleton', () => { - it('adds, returns and resets the user info', () => { - // Adding first attributes - AttributesSingleton.getInstance().setAttributes({ + beforeEach(() => { + AttributesSingleton.reset(); + }); + + it('adds, returns and resets the attributes', () => { + AttributesSingleton.getInstance().addAttributes({ appType: 'student', extraInfo: { loggedIn: true @@ -23,11 +26,8 @@ describe('AttributesSingleton', () => { } }); - // Removing and adding new attributes - AttributesSingleton.getInstance().setAttributes({ - appType: undefined, - newAttribute: false - }); + AttributesSingleton.getInstance().removeAttribute('appType'); + AttributesSingleton.getInstance().addAttribute('newAttribute', false); expect(AttributesSingleton.getInstance().getAttributes()).toEqual({ newAttribute: false, @@ -41,4 +41,48 @@ describe('AttributesSingleton', () => { expect(AttributesSingleton.getInstance().getAttributes()).toEqual({}); }); + + it('addAttribute sets a single key and getAttribute returns it', () => { + AttributesSingleton.getInstance().addAttribute('userId', '123'); + expect(AttributesSingleton.getInstance().getAttribute('userId')).toBe( + '123' + ); + expect(AttributesSingleton.getInstance().getAttributes()).toEqual({ + userId: '123' + }); + }); + + it('removeAttribute removes a single key and leaves others intact', () => { + AttributesSingleton.getInstance().addAttributes({ + a: 1, + b: 2 + }); + + AttributesSingleton.getInstance().removeAttribute('a'); + + expect( + AttributesSingleton.getInstance().getAttribute('a') + ).toBeUndefined(); + expect(AttributesSingleton.getInstance().getAttributes()).toEqual({ + b: 2 + }); + }); + + it('removeAttributes removes multiple keys (missing keys are ignored)', () => { + AttributesSingleton.getInstance().addAttributes({ + keyToKeep: 'yes', + keyToRemove1: true, + keyToRemove2: false + }); + + AttributesSingleton.getInstance().removeAttributes([ + 'keyToRemove1', + 'keyToRemove2', + 'keyToIgnore' + ]); + + expect(AttributesSingleton.getInstance().getAttributes()).toEqual({ + keyToKeep: 'yes' + }); + }); }); diff --git a/packages/core/src/specs/NativeDdSdk.ts b/packages/core/src/specs/NativeDdSdk.ts index a2ce1120e..70401fe3c 100644 --- a/packages/core/src/specs/NativeDdSdk.ts +++ b/packages/core/src/specs/NativeDdSdk.ts @@ -26,10 +26,29 @@ export interface Spec extends TurboModule { initialize(configuration: Object): Promise; /** - * Sets the global context (set of attributes) attached with all future Logs, Spans and RUM events. + * Adds a specific attribute to the global context attached with all future Logs, Spans and RUM. + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + addAttribute(key: string, value: Object): Promise; + + /** + * Removes an attribute from the context attached with all future Logs, Spans and RUM events. + * @param key: They key associated with the attribute to be removed. + */ + removeAttribute(key: string): Promise; + + /** + * Adds the global context (set of attributes) attached with all future Logs, Spans and RUM events. * @param attributes: The global context attributes. */ - setAttributes(attributes: Object): Promise; + addAttributes(attributes: Object): Promise; + + /** + * Removes a set of attributes from the context attached with all future Logs, Spans and RUM events. + * @param keys: They keys associated with the attributes to be removed. + */ + removeAttributes(keys: string[]): Promise; /** * Set the user information. diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index bad19d429..c8d9821cc 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -83,11 +83,30 @@ export type DdSdkType = { */ initialize(configuration: DdSdkConfiguration): Promise; + /** + * Sets a specific attribute in the global context attached with all future Logs, Spans and RUM + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + addAttribute(key: string, value: object): Promise; + + /** + * Removes an attribute from the context attached with all future Logs, Spans and RUM events. + * @param key: They key associated with the attribute to be removed. + */ + removeAttribute(key: string): Promise; + /** * Sets the global context (set of attributes) attached with all future Logs, Spans and RUM events. * @param attributes: The global context attributes. */ - setAttributes(attributes: object): Promise; + addAttributes(attributes: object): Promise; + + /** + * Removes a set of attributes from the context attached with all future Logs, Spans and RUM events. + * @param keys: They keys associated with the attributes to be removed. + */ + removeAttributes(keys: string[]): Promise; /** * Sets the user information. diff --git a/packages/react-native-apollo-client/__mocks__/react-native.ts b/packages/react-native-apollo-client/__mocks__/react-native.ts index 046ced2f6..bbac607d3 100644 --- a/packages/react-native-apollo-client/__mocks__/react-native.ts +++ b/packages/react-native-apollo-client/__mocks__/react-native.ts @@ -18,9 +18,9 @@ actualRN.NativeModules.DdSdk = { initialize: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - setAttributes: jest.fn().mockImplementation( + addAttributes: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, + ) as jest.MockedFunction, setTrackingConsent: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, From 892ba0bc083ce4ba9c4be1792fa25a80a989a202 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 16 Oct 2025 11:01:21 +0200 Subject: [PATCH 031/114] JS refresh rate normalization --- .../reactnative/DdSdkImplementation.kt | 51 ++++++- .../com/datadog/reactnative/DdSdkTest.kt | 127 ++++++++++++++++++ .../ios/Sources/DdSdkImplementation.swift | 21 ++- packages/core/ios/Tests/DdSdkTests.swift | 108 +++++++++++++++ 4 files changed, 305 insertions(+), 2 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index ed545d9e1..574394a36 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -7,7 +7,10 @@ package com.datadog.reactnative import android.content.Context +import android.hardware.display.DisplayManager +import android.os.Build import android.util.Log +import android.view.Display import com.datadog.android.privacy.TrackingConsent import com.datadog.android.rum.RumPerformanceMetric import com.datadog.android.rum.configuration.VitalsUpdateFrequency @@ -19,6 +22,7 @@ import com.facebook.react.bridge.ReadableMap import java.util.Locale import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean +import kotlin.math.max /** The entry point to initialize Datadog's features. */ @Suppress("TooManyFunctions") @@ -293,9 +297,10 @@ class DdSdkImplementation( return { if (jsRefreshRateMonitoringEnabled && it > 0.0) { + val normalizedFrameTimeSeconds = normalizeFrameTime(it, appContext) datadog.getRumMonitor() ._getInternal() - ?.updatePerformanceMetric(RumPerformanceMetric.JS_FRAME_TIME, it) + ?.updatePerformanceMetric(RumPerformanceMetric.JS_FRAME_TIME, normalizedFrameTimeSeconds) } if (jsLongTasksMonitoringEnabled && it > @@ -308,6 +313,49 @@ class DdSdkImplementation( } } + /** + * Normalizes frameTime values so when are turned into FPS metrics they are normalized on a range of zero to 60fps. + * @param frameTimeSeconds: the frame time to normalize. In seconds. + * @param context: The current app context + * @param fpsBudget: The maximum fps under which the frame Time will be normalized [0-fpsBudget]. Defaults to 60Hz. + * @param deviceDisplayFps: The maximum fps supported by the device. If not provided it will be set from the value obtained from the app context. + */ + @Suppress("CyclomaticComplexMethod") + fun normalizeFrameTime( + frameTimeSeconds: Double, + context: Context, + fpsBudget: Double? = null, + deviceDisplayFps: Double? = null, + ) : Double { + val frameTimeMs = frameTimeSeconds * 1000.0 + val frameBudgetHz = fpsBudget ?: DEFAULT_REFRESH_HZ + val maxDeviceDisplayHz = deviceDisplayFps ?: getMaxDisplayRefreshRate(context) + ?: 60.0 + + val maxDeviceFrameTimeMs = 1000.0 / maxDeviceDisplayHz + val budgetFrameTimeMs = 1000.0 / frameBudgetHz + + if (listOf( + maxDeviceDisplayHz, frameTimeMs, frameBudgetHz, budgetFrameTimeMs, maxDeviceFrameTimeMs + ).any { !it.isFinite() || it <= 0.0 } + ) return 1.0 / DEFAULT_REFRESH_HZ + + + var normalizedFrameTimeMs = frameTimeMs / (maxDeviceFrameTimeMs / budgetFrameTimeMs) + + normalizedFrameTimeMs = max(normalizedFrameTimeMs, maxDeviceFrameTimeMs) + + return normalizedFrameTimeMs / 1000.0 // in seconds + } + + @Suppress("CyclomaticComplexMethod") + private fun getMaxDisplayRefreshRate(context: Context?): Double { + val dm = context?.getSystemService(Context.DISPLAY_SERVICE) as? DisplayManager ?: return 60.0 + val display: Display = dm.getDisplay(Display.DEFAULT_DISPLAY) ?: return DEFAULT_REFRESH_HZ + + return display.supportedModes.maxOf { it.refreshRate.toDouble() } + } + // endregion internal companion object { internal const val DEFAULT_APP_VERSION = "?" @@ -317,6 +365,7 @@ class DdSdkImplementation( internal const val DD_DROP_ACTION = "_dd.action.drop_action" internal const val MONITOR_JS_ERROR_MESSAGE = "Error monitoring JS refresh rate" internal const val PACKAGE_INFO_NOT_FOUND_ERROR_MESSAGE = "Error getting package info" + internal const val DEFAULT_REFRESH_HZ = 60.0 internal const val NAME = "DdSdk" } } diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index ae6ded89c..327d8ffc0 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -60,6 +60,7 @@ import java.util.Locale import java.util.stream.Stream import kotlin.time.Duration.Companion.seconds import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -3238,6 +3239,132 @@ internal class DdSdkTest { } } + @Test + fun `𝕄 normalize frameTime according to the device's refresh rate`() { + // 10 fps, 60Hz device, 60 fps budget -> 10 fps + var frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.1, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 60.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.1) + + // 30 fps, 60Hz device, 60 fps budget -> 30 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.03, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 60.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.03) + + // 60 fps, 60Hz device, 60 fps budget -> 60 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.016, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 60.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.016, Offset.offset(0.005)) + + // 60 fps, 120Hz device, 60 fps budget -> 30 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.016, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 120.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.032) + + // 120 fps, 120Hz device, 60 fps budget -> 60 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.0083, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 120.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.016, Offset.offset(0.005)) + + // 90 fps, 120Hz device, 60 fps budget -> 45 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.0111, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 120.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.0222, Offset.offset(0.001)) + + // 100 fps, 120Hz device, 60 fps budget -> 50 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.01, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 120.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.02, Offset.offset(0.001)) + + // 120 fps, 120Hz device, 120 fps budget -> 120 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.0083, + context = mockContext, + fpsBudget = 120.0, + deviceDisplayFps = 120.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.0083, Offset.offset(0.001)) + + // 80 fps, 160Hz device, 60 fps budget -> 30 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.0125, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 160.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.033, Offset.offset(0.001)) + + // 160 fps, 160Hz device, 60 fps budget -> 60 fps + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.00625, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 160.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.016, Offset.offset(0.001)) + + // Edge cases + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.0, + context = mockContext, + fpsBudget = 0.0, + deviceDisplayFps = 0.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.016, Offset.offset(0.001)) + + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.016, + context = mockContext, + fpsBudget = 0.0, + deviceDisplayFps = 0.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.016, Offset.offset(0.001)) + + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.016, + context = mockContext, + fpsBudget = 60.0, + deviceDisplayFps = 0.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.016, Offset.offset(0.001)) + + frameTimeSeconds = testedBridgeSdk.normalizeFrameTime( + frameTimeSeconds = 0.016, + context = mockContext, + fpsBudget = 0.0, + deviceDisplayFps = 60.0 + ) + assertThat(frameTimeSeconds).isEqualTo(0.016, Offset.offset(0.001)) + } + // endregion // region Internal diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 8aae5e10d..437b5ee39 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -244,7 +244,8 @@ public class DdSdkImplementation: NSObject { // Leave JS thread ASAP to give as much time to JS engine work. sharedQueue.async { if (shouldRecordFrameTime) { - rumMonitorInternal.updatePerformanceMetric(at: now, metric: .jsFrameTimeSeconds, value: frameTime, attributes: [:]) + let normalizedFrameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(frameTime) + rumMonitorInternal.updatePerformanceMetric(at: now, metric: .jsFrameTimeSeconds, value: normalizedFrameTimeSeconds, attributes: [:]) } if (shouldRecordLongTask) { rumMonitorInternal.addLongTask(at: now, duration: frameTime, attributes: ["long_task.target": "javascript"]) @@ -254,5 +255,23 @@ public class DdSdkImplementation: NSObject { return frameTimeCallback } + + // Normalizes frameTime values so when they are turned into FPS metrics they are normalized on a range between 0 and fpsBudget. If fpsBudget is not provided it will default to 60hz. + public static func normalizeFrameTimeForDeviceRefreshRate(_ frameTime: Double, fpsBudget: Double? = nil, deviceDisplayFps: Double? = nil) -> Double { + let DEFAULT_REFRESH_HZ = 60.0 + let frameTimeMs: Double = frameTime * 1000.0 + let frameBudgetHz: Double = fpsBudget ?? DEFAULT_REFRESH_HZ + let maxDeviceDisplayHz = deviceDisplayFps ?? Double(UIScreen.main.maximumFramesPerSecond) + let maxDeviceFrameTimeMs = 1000.0 / maxDeviceDisplayHz + let budgetFrameTimeMs = 1000.0 / frameBudgetHz + + guard maxDeviceDisplayHz > 0, frameTimeMs.isFinite, frameTimeMs > 0, frameBudgetHz > 0, budgetFrameTimeMs.isFinite, budgetFrameTimeMs > 0, maxDeviceFrameTimeMs.isFinite, maxDeviceFrameTimeMs > 0 else { + return 1.0 / DEFAULT_REFRESH_HZ + } + + var normalizedFrameTimeMs = frameTimeMs / (maxDeviceFrameTimeMs / budgetFrameTimeMs) + normalizedFrameTimeMs = max(normalizedFrameTimeMs, maxDeviceFrameTimeMs) + return normalizedFrameTimeMs / 1000.0 // in seconds + } } diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index adbb57da9..4a5d13f2e 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -1181,6 +1181,114 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(rumMonitorMock.receivedLongTasks.first?.value, 0.25) XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.25) } + + func testFrameTimeNormalizationFromCallback() { + let mockRefreshRateMonitor = MockJSRefreshRateMonitor() + let rumMonitorMock = MockRUMMonitor() + + DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: mockRefreshRateMonitor, + RUMMonitorProvider: { rumMonitorMock }, + RUMMonitorInternalProvider: { rumMonitorMock._internalMock } + ).initialize( + configuration: .mockAny( + longTaskThresholdMs: 200, + vitalsUpdateFrequency: "average" + ), + resolve: mockResolve, + reject: mockReject + ) + + XCTAssertTrue(mockRefreshRateMonitor.isStarted) + + // 10 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.1) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.1) + + // 30 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.03) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.03) + + // 45 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.02) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.02) + + // 60 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.016) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) + + // 90 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.011) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) + + // 120 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.008) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) + } + + func testFrameTimeNormalizationUtilityFunction() { + + // 10 fps, 60fps capable device, 60 fps budget -> Normalized to 10fps + var frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.1, fpsBudget: 60.0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.1, accuracy: 0.01) + + // 30 fps, 60fps capable device, 60 fps budget -> Normalized to 30fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.03, fpsBudget: 60.0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.03, accuracy: 0.01) + + // 60 fps, 60fps capable device, 60 fps budget-> Normalized to 60fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.01) + + // 60 fps, 120fps capable device, 60 fps budget -> Normalized to 30fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.03, accuracy: 0.01) + + // 120 fps, 120fps capable device, 60 fps budget -> Normalized to 60fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0083, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + // 90 fps, 120fps capable device, 60 fps budget -> Normalized to 45fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0111, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.0222, accuracy: 0.001) + + // 100 fps, 120fps capable device, 60 fps budget -> Normalized to 50fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.01, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.02, accuracy: 0.001) + + // 120 fps, 120fps capable device, 120 fps budget -> Normalized to 120fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0083, fpsBudget: 120.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.0083, accuracy: 0.001) + + // 80 fps, 160fps capable device, 60 fps budget -> Normalized to 30fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0125, fpsBudget: 60.0, deviceDisplayFps: 160.0) + XCTAssertEqual(frameTimeSeconds, 0.033, accuracy: 0.001) + + // 160 fps, 160fps capable device, 60 fps budget -> Normalized to 60fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.00625, fpsBudget: 60.0, deviceDisplayFps: 160.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + // Edge cases + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0, fpsBudget: 0, deviceDisplayFps: 0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 0, deviceDisplayFps: 0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + } func testSDKInitializationWithCustomEndpoints() throws { let mockRefreshRateMonitor = MockJSRefreshRateMonitor() From c5e8d013946d37c0dc08127eadf2e9a46bfd3b43 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 10 Oct 2025 14:52:21 +0200 Subject: [PATCH 032/114] Expose view Attributes API --- packages/core/__mocks__/react-native.ts | 12 ++ .../reactnative/DdRumImplementation.kt | 44 ++++++ .../reactnative/DdSdkImplementation.kt | 2 - .../kotlin/com/datadog/reactnative/DdRum.kt | 38 ++++++ .../kotlin/com/datadog/reactnative/DdRum.kt | 38 ++++++ .../com/datadog/reactnative/DdRumTest.kt | 58 ++++++++ .../com/datadog/tools/unit/MockRumMonitor.kt | 17 +-- packages/core/ios/Sources/DdRum.mm | 47 ++++++- .../ios/Sources/DdRumImplementation.swift | 28 ++++ .../ios/Sources/DdSdkImplementation.swift | 129 ++++++++++++------ packages/core/ios/Tests/DdRumTests.swift | 59 ++++++++ packages/core/ios/Tests/MockRUMMonitor.swift | 38 +++--- packages/core/jest/mock.js | 12 ++ packages/core/src/rum/DdRum.ts | 45 ++++++ packages/core/src/rum/__tests__/DdRum.test.ts | 92 +++++++++++++ packages/core/src/rum/types.ts | 26 ++++ packages/core/src/specs/NativeDdRum.ts | 25 ++++ 17 files changed, 638 insertions(+), 72 deletions(-) diff --git a/packages/core/__mocks__/react-native.ts b/packages/core/__mocks__/react-native.ts index 24e3f80c7..73308f711 100644 --- a/packages/core/__mocks__/react-native.ts +++ b/packages/core/__mocks__/react-native.ts @@ -132,6 +132,18 @@ actualRN.NativeModules.DdRum = { addTiming: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, + addViewAttribute: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + removeViewAttribute: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + addViewAttributes: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + removeViewAttributes: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, addViewLoadingTime: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt index 013872c08..4e3cd416f 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt @@ -13,6 +13,7 @@ import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.RumResourceMethod import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import java.util.Locale @@ -248,6 +249,49 @@ class DdRumImplementation(private val datadog: DatadogWrapper = DatadogSDKWrappe promise.resolve(null) } + /** + * Adds a custom attribute to the active RUM View. It will be propagated to all future RUM events associated with the active View. + * @param key: key for this view attribute. + * @param value: value for this attribute. + */ + fun addViewAttribute(key: String, value: ReadableMap, promise: Promise) { + val attributeValue = value.toMap()["value"] + val attributes = mutableMapOf() + attributes[key] = attributeValue + datadog.getRumMonitor().addViewAttributes(attributes) + promise.resolve(null) + } + + /** + * Removes an attribute from the active RUM View. + * @param key: key for the attribute to be removed from the view. + */ + fun removeViewAttribute(key: String, promise: Promise) { + val keysToDelete: Collection = listOf(key) + datadog.getRumMonitor().removeViewAttributes(keysToDelete) + promise.resolve(null) + } + + /** + * Adds multiple attributes to the active RUM View. They will be propagated to all future RUM events associated with the active View. + * @param attributes: key/value object containing all attributes to be added to the view. + */ + fun addViewAttributes(attributes: ReadableMap, promise: Promise) { + datadog.getRumMonitor().addViewAttributes(attributes.toMap()) + promise.resolve(null) + } + + /** + * Removes multiple attributes from the active RUM View. + * @param keys: keys for the attributes to be removed from the view. + */ + fun removeViewAttributes(keys: ReadableArray, promise: Promise) { + val keysToDelete = (0 until keys.size()) + .mapNotNull { keys.getString(it) } + datadog.getRumMonitor().removeViewAttributes(keysToDelete) + promise.resolve(null) + } + /** * Adds the loading time of the view to the active view. * It is calculated as the difference between the current time and the start time of the view. diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index 574394a36..6741e971e 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -143,8 +143,6 @@ class DdSdkImplementation( if (id != null) { datadog.setUserInfo(id, name, email, extraInfo) - } else { - // TO DO - Log warning? } promise.resolve(null) diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt index ce8104685..6cb2b385b 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt @@ -9,6 +9,7 @@ package com.datadog.reactnative import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap /** @@ -201,6 +202,43 @@ class DdRum( implementation.addTiming(name, promise) } + /** + * Adds a custom attribute to the active RUM View. It will be propagated to all future RUM events associated with the active View. + * @param key: key for this view attribute. + * @param value: value for this attribute. + */ + @ReactMethod + override fun addViewAttribute(key: String, value: ReadableMap, promise: Promise) { + implementation.addViewAttribute(key, value, promise) + } + + /** + * Removes an attribute from the active RUM View. + * @param key: key for the attribute to be removed from the view. + */ + @ReactMethod + override fun removeViewAttribute(key: String, promise: Promise) { + implementation.removeViewAttribute(key, promise) + } + + /** + * Adds multiple attributes to the active RUM View. They will be propagated to all future RUM events associated with the active View. + * @param attributes: key/value object containing all attributes to be added to the view. + */ + @ReactMethod + override fun addViewAttributes(attributes: ReadableMap, promise: Promise) { + implementation.addViewAttributes(attributes, promise) + } + + /** + * Removes multiple attributes from the active RUM View. + * @param keys: keys for the attributes to be removed from the view. + */ + @ReactMethod + override fun removeViewAttributes(keys: ReadableArray, promise: Promise) { + implementation.removeViewAttributes(keys, promise) + } + /** * Adds the loading time of the view to the active view. * It is calculated as the difference between the current time and the start time of the view. diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt index 79742e854..a6c4965ea 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt @@ -10,6 +10,7 @@ import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap /** @@ -192,6 +193,43 @@ class DdRum( implementation.addTiming(name, promise) } + /** + * Adds a custom attribute to the active RUM View. It will be propagated to all future RUM events associated with the active View. + * @param key: key for this view attribute. + * @param value: value for this attribute. + */ + @ReactMethod + fun addViewAttribute(key: String, value: ReadableMap, promise: Promise) { + implementation.addViewAttribute(key, value, promise) + } + + /** + * Removes an attribute from the active RUM View. + * @param key: key for the attribute to be removed from the view. + */ + @ReactMethod + fun removeViewAttribute(key: String, promise: Promise) { + implementation.removeViewAttribute(key, promise) + } + + /** + * Adds multiple attributes to the active RUM View. They will be propagated to all future RUM events associated with the active View. + * @param attributes: key/value object containing all attributes to be added to the view. + */ + @ReactMethod + fun addViewAttributes(attributes: ReadableMap, promise: Promise) { + implementation.addViewAttributes(attributes, promise) + } + + /** + * Removes multiple attributes from the active RUM View. + * @param keys: keys for the attributes to be removed from the view. + */ + @ReactMethod + fun removeViewAttributes(keys: ReadableArray, promise: Promise) { + implementation.removeViewAttributes(keys, promise) + } + /** * Adds the loading time of the view to the active view. * It is calculated as the difference between the current time and the start time of the view. diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt index be1c57b3a..9619794df 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt @@ -13,13 +13,16 @@ import com.datadog.android.rum.RumMonitor import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.RumResourceMethod import com.datadog.tools.unit.forge.BaseConfigurator +import com.datadog.tools.unit.toReadableArray import com.datadog.tools.unit.toReadableMap import com.facebook.react.bridge.Promise import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.AdvancedForgery import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.DoubleForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.MapForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.annotation.StringForgeryType import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -456,6 +459,61 @@ internal class DdRumTest { verify(mockRumMonitor).addTiming(timing) } + @Test + fun `M call addViewAttribute W addViewAttribute()`( + @StringForgery key: String, + @StringForgery value: String + ) { + var attributeMap = mutableMapOf() + attributeMap.put("value", value) + + var attributes = mutableMapOf() + attributes.put(key, value) + + // When + testedDdRum.addViewAttribute(key, attributeMap.toReadableMap(), mockPromise) + + // Then + verify(mockRumMonitor).addViewAttributes(attributes) + } + + @Test + fun `M call removeViewAttribute W removeViewAttribute()`(@StringForgery key: String) { + // When + testedDdRum.removeViewAttribute(key, mockPromise) + + // Then + verify(mockRumMonitor).removeViewAttributes(listOf(key)) + } + + @Test + fun `M call addViewAttributes W addViewAttributes()`( + @MapForgery( + key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), + value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) + ) customAttributes: Map + ) { + // When + testedDdRum.addViewAttributes(customAttributes.toReadableMap(), mockPromise) + + // Then + verify(mockRumMonitor).addViewAttributes(customAttributes) + } + + @Test + fun `𝕄 call removeViewAttributes 𝕎 removeViewAttributes`( + @MapForgery( + key = AdvancedForgery(string = [StringForgery(StringForgeryType.NUMERICAL)]), + value = AdvancedForgery(string = [StringForgery(StringForgeryType.ASCII)]) + ) customAttributes: Map + ) { + // When + testedDdRum.removeViewAttributes(customAttributes.keys.toReadableArray(), mockPromise) + + // Then + verify(mockRumMonitor).removeViewAttributes(customAttributes.keys.toList()) + } + @Test fun `M call addViewLoadingTime w addViewLoadingTime()`(@BoolForgery overwrite: Boolean) { // When diff --git a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt index 702cc2533..13f73d94a 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt @@ -30,7 +30,13 @@ class MockRumMonitor : RumMonitor { override fun addAttribute(key: String, value: Any?) {} - override fun addViewAttributes(attributes: Map) {} + override fun removeAttribute(key: String) {} + + override fun clearAttributes() {} + + override fun getAttributes(): Map { + return mapOf() + } override fun addError( message: String, @@ -55,15 +61,10 @@ class MockRumMonitor : RumMonitor { @ExperimentalRumApi override fun addViewLoadingTime(overwrite: Boolean) {} - override fun clearAttributes() {} - - override fun getAttributes(): Map { - return mapOf() - } - override fun getCurrentSessionId(callback: (String?) -> Unit) {} - override fun removeAttribute(key: String) {} + override fun addViewAttributes(attributes: Map) {} + override fun removeViewAttributes(attributes: Collection) {} override fun startAction( diff --git a/packages/core/ios/Sources/DdRum.mm b/packages/core/ios/Sources/DdRum.mm index 5d831942a..f5c324ce8 100644 --- a/packages/core/ios/Sources/DdRum.mm +++ b/packages/core/ios/Sources/DdRum.mm @@ -107,6 +107,35 @@ @implementation DdRum [self addTiming:name resolve:resolve reject:reject]; } +RCT_EXPORT_METHOD(addViewAttribute:(NSString*) key + withValue:(NSDictionary*) value + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self addViewAttribute:key value:value resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(removeViewAttribute:(NSString*) key + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self removeViewAttribute:key resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(addViewAttributes:(NSDictionary*) attributes + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self addViewAttributes:attributes resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(removeViewAttributes:(NSArray *)keys + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self removeViewAttributes:keys resolve:resolve reject:reject]; +} + RCT_REMAP_METHOD(addViewLoadingTime, withOverwrite:(BOOL)overwrite withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) @@ -138,7 +167,7 @@ @implementation DdRum // Thanks to this guard, we won't compile this code when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: - (const facebook::react::ObjCTurboModule::InitParams &)params +(const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } @@ -180,6 +209,22 @@ - (void)addTiming:(NSString *)name resolve:(RCTPromiseResolveBlock)resolve rejec [self.ddRumImplementation addTimingWithName:name resolve:resolve reject:reject]; } +- (void)addViewAttribute:(NSString *)key value:(NSDictionary *)value resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation addViewAttributeWithKey:key value:value resolve:resolve reject:reject]; +} + +- (void)removeViewAttribute:(NSString *)key resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation removeViewAttributeWithKey:key resolve:resolve reject:reject]; +} + +- (void)addViewAttributes:(NSDictionary *)attributes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation addViewAttributesWithAttributes:attributes resolve:resolve reject:reject]; +} + +- (void)removeViewAttributes:(NSArray *)keys resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation removeViewAttributesWithKeys:keys resolve:resolve reject:reject]; +} + - (void)addViewLoadingTime:(BOOL)overwrite resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {\ [self.ddRumImplementation addViewLoadingTimeWithOverwrite:overwrite resolve:resolve reject:reject]; } diff --git a/packages/core/ios/Sources/DdRumImplementation.swift b/packages/core/ios/Sources/DdRumImplementation.swift index 9f8da4c7f..6fac21f82 100644 --- a/packages/core/ios/Sources/DdRumImplementation.swift +++ b/packages/core/ios/Sources/DdRumImplementation.swift @@ -181,6 +181,34 @@ public class DdRumImplementation: NSObject { resolve(nil) } + @objc + public func addViewAttribute(key: AttributeKey, value: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + if let attributeValue = value.object(forKey: "value") { + let castedAttribute = castValueToSwift(attributeValue) + nativeRUM.addViewAttribute(forKey: key, value: castedAttribute) + } + resolve(nil) + } + + @objc + public func removeViewAttribute(key: AttributeKey, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + nativeRUM.removeViewAttribute(forKey: key) + resolve(nil) + } + + @objc + public func addViewAttributes(attributes: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + let castedAttributes = castAttributesToSwift(attributes) + nativeRUM.addViewAttributes(castedAttributes) + resolve(nil) + } + + @objc + public func removeViewAttributes(keys: [AttributeKey], resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + nativeRUM.removeViewAttributes(forKeys: keys) + resolve(nil) + } + @objc public func addViewLoadingTime(overwrite: Bool, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { nativeRUM.addViewLoadingTime(overwrite: overwrite) diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 437b5ee39..9c3fe980f 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -4,13 +4,14 @@ * Copyright 2016-Present Datadog, Inc. */ -import Foundation import DatadogCore -import DatadogRUM -import DatadogLogs -import DatadogTrace import DatadogCrashReporting import DatadogInternal +import DatadogLogs +import DatadogRUM +import DatadogTrace +import DatadogWebViewTracking +import Foundation import React #if os(iOS) @@ -18,7 +19,8 @@ import DatadogWebViewTracking #endif func getDefaultAppVersion() -> String { - let bundleShortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + let bundleShortVersion = + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String let bundleVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String return bundleShortVersion ?? bundleVersion ?? "0.0.0" } @@ -35,7 +37,7 @@ public class DdSdkImplementation: NSObject { var webviewMessageEmitter: InternalExtension.AbstractMessageEmitter? #endif - private let jsLongTaskThresholdInSeconds: TimeInterval = 0.1; + private let jsLongTaskThresholdInSeconds: TimeInterval = 0.1 @objc public convenience init(bridge: RCTBridge) { @@ -47,7 +49,7 @@ public class DdSdkImplementation: NSObject { RUMMonitorInternalProvider: { RUMMonitor.shared()._internal } ) } - + init( mainDispatchQueue: DispatchQueueType, jsDispatchQueue: DispatchQueueType, @@ -62,10 +64,13 @@ public class DdSdkImplementation: NSObject { self.RUMMonitorInternalProvider = RUMMonitorInternalProvider super.init() } - + // Using @escaping RCTPromiseResolveBlock type will result in an issue when compiling the Swift header file. @objc - public func initialize(configuration: NSDictionary, resolve:@escaping ((Any?) -> Void), reject:RCTPromiseRejectBlock) -> Void { + public func initialize( + configuration: NSDictionary, resolve: @escaping ((Any?) -> Void), + reject: RCTPromiseRejectBlock + ) { let sdkConfiguration = configuration.asDdSdkConfiguration() let nativeInitialization = DdSdkNativeInitialization() @@ -117,7 +122,9 @@ public class DdSdkImplementation: NSObject { } @objc - public func setUserInfo(userInfo: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func setUserInfo( + userInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { let castedUserInfo = castAttributesToSwift(userInfo) let id = castedUserInfo["id"] as? String let name = castedUserInfo["name"] as? String @@ -125,21 +132,22 @@ public class DdSdkImplementation: NSObject { var extraInfo: [AttributeKey: AttributeValue] = [:] if let extraInfoEncodable = castedUserInfo["extraInfo"] as? AnyEncodable, - let extraInfoDict = extraInfoEncodable.value as? [String: Any] { + let extraInfoDict = extraInfoEncodable.value as? [String: Any] + { extraInfo = castAttributesToSwift(extraInfoDict) } if let validId = id { Datadog.setUserInfo(id: validId, name: name, email: email, extraInfo: extraInfo) - } else { - // TO DO - log warning message? } resolve(nil) } - + @objc - public func addUserExtraInfo(extraInfo: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func addUserExtraInfo( + extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { let castedExtraInfo = castAttributesToSwift(extraInfo) Datadog.addUserExtraInfo(castedExtraInfo) @@ -147,35 +155,37 @@ public class DdSdkImplementation: NSObject { } @objc - public func clearUserInfo(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func clearUserInfo(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { Datadog.clearUserInfo() resolve(nil) } @objc - public func setTrackingConsent(trackingConsent: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func setTrackingConsent( + trackingConsent: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { Datadog.set(trackingConsent: (trackingConsent as NSString?).asTrackingConsent()) resolve(nil) } - - + @objc - public func sendTelemetryLog(message: NSString, attributes: NSDictionary, config: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func sendTelemetryLog( + message: NSString, attributes: NSDictionary, config: NSDictionary, + resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { let castedAttributes = castAttributesToSwift(attributes) let castedConfig = castAttributesToSwift(config) - DdTelemetry.sendTelemetryLog(message: message as String, attributes: castedAttributes, config: castedConfig) + DdTelemetry.sendTelemetryLog( + message: message as String, attributes: castedAttributes, config: castedConfig) resolve(nil) } @objc - public func telemetryDebug(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - DdTelemetry.telemetryDebug(id: "datadog_react_native:\(message)", message: message as String) - resolve(nil) - } - - @objc - public func telemetryError(message: NSString, stack: NSString, kind: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - DdTelemetry.telemetryError(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as String, stack: stack as String) + public func telemetryDebug( + message: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { + DdTelemetry.telemetryDebug( + id: "datadog_react_native:\(message)", message: message as String) resolve(nil) } <<<<<<< HEAD @@ -185,27 +195,50 @@ public class DdSdkImplementation: NSObject { >>>>>>> 0443e0ff (iOS: Always use SDK default core instance) @objc - public func consumeWebviewEvent(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - do{ + public func telemetryError( + message: NSString, stack: NSString, kind: NSString, resolve: RCTPromiseResolveBlock, + reject: RCTPromiseRejectBlock + ) { + DdTelemetry.telemetryError( + id: "datadog_react_native:\(String(describing: kind)):\(message)", + message: message as String, kind: kind as String, stack: stack as String) + resolve(nil) + } + + @objc + public func consumeWebviewEvent( + message: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { + do { try DatadogSDKWrapper.shared.sendWebviewMessage(body: message) } catch { - DdTelemetry.telemetryError(id: "datadog_react_native:\(error.localizedDescription)", message: "The message being sent was:\(message)" as String, kind: "WebViewEventBridgeError" as String, stack: String(describing: error) as String) + DdTelemetry.telemetryError( + id: "datadog_react_native:\(error.localizedDescription)", + message: "The message being sent was:\(message)" as String, + kind: "WebViewEventBridgeError" as String, + stack: String(describing: error) as String) } resolve(nil) } +<<<<<<< HEAD #endif +======= + +>>>>>>> 1f781a51 (Expose view Attributes API) @objc - public func clearAllData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func clearAllData(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { Datadog.clearAllData() resolve(nil) } - func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) -> Void { + func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) { DdTelemetry.overrideTelemetryConfiguration( - initializationType: rnConfiguration.configurationForTelemetry?.initializationType as? String, - reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion as? String, + initializationType: rnConfiguration.configurationForTelemetry?.initializationType + as? String, + reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion + as? String, reactVersion: rnConfiguration.configurationForTelemetry?.reactVersion as? String, trackCrossPlatformLongTasks: rnConfiguration.longTaskThresholdMs != 0, trackErrors: rnConfiguration.configurationForTelemetry?.trackErrors, @@ -220,24 +253,28 @@ public class DdSdkImplementation: NSObject { func startJSRefreshRateMonitoring(sdkConfiguration: DdSdkConfiguration) { if let frameTimeCallback = buildFrameTimeCallback(sdkConfiguration: sdkConfiguration) { // Falling back to mainDispatchQueue if bridge is nil is only useful for tests - self.jsRefreshRateMonitor.startMonitoring(jsQueue: jsDispatchQueue, frameTimeCallback: frameTimeCallback) + self.jsRefreshRateMonitor.startMonitoring( + jsQueue: jsDispatchQueue, frameTimeCallback: frameTimeCallback) } } - func buildFrameTimeCallback(sdkConfiguration: DdSdkConfiguration)-> ((Double) -> ())? { + func buildFrameTimeCallback(sdkConfiguration: DdSdkConfiguration) -> ((Double) -> Void)? { let jsRefreshRateMonitoringEnabled = sdkConfiguration.vitalsUpdateFrequency != nil let jsLongTaskMonitoringEnabled = sdkConfiguration.longTaskThresholdMs != 0 - - if (!jsRefreshRateMonitoringEnabled && !jsLongTaskMonitoringEnabled) { + + if !jsRefreshRateMonitoringEnabled && !jsLongTaskMonitoringEnabled { return nil } func frameTimeCallback(frameTime: Double) { // These checks happen before dispatching because they are quick and less overhead than the dispatch itself. let shouldRecordFrameTime = jsRefreshRateMonitoringEnabled && frameTime > 0 - let shouldRecordLongTask = jsLongTaskMonitoringEnabled && frameTime > sdkConfiguration.longTaskThresholdMs / 1_000 + let shouldRecordLongTask = + jsLongTaskMonitoringEnabled + && frameTime > sdkConfiguration.longTaskThresholdMs / 1_000 guard shouldRecordFrameTime || shouldRecordLongTask, - let rumMonitorInternal = RUMMonitorInternalProvider() else { return } + let rumMonitorInternal = RUMMonitorInternalProvider() + else { return } // Record current timestamp, it may change slightly before event is created on background thread. let now = Date() @@ -247,12 +284,14 @@ public class DdSdkImplementation: NSObject { let normalizedFrameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(frameTime) rumMonitorInternal.updatePerformanceMetric(at: now, metric: .jsFrameTimeSeconds, value: normalizedFrameTimeSeconds, attributes: [:]) } - if (shouldRecordLongTask) { - rumMonitorInternal.addLongTask(at: now, duration: frameTime, attributes: ["long_task.target": "javascript"]) + if shouldRecordLongTask { + rumMonitorInternal.addLongTask( + at: now, duration: frameTime, attributes: ["long_task.target": "javascript"] + ) } } } - + return frameTimeCallback } diff --git a/packages/core/ios/Tests/DdRumTests.swift b/packages/core/ios/Tests/DdRumTests.swift index 1102b7b6b..5f6adc016 100644 --- a/packages/core/ios/Tests/DdRumTests.swift +++ b/packages/core/ios/Tests/DdRumTests.swift @@ -253,6 +253,65 @@ internal class DdRumTests: XCTestCase { XCTAssertEqual(mockNativeRUM.receivedAttributes.count, 0) } + func testAddViewAttribute() throws { + let viewAttributeKey = "attributeKey" + let viewAttributes = NSDictionary( + dictionary: [ + "value": 123, + ] + ) + + rum.addViewAttribute(key: viewAttributeKey, value: viewAttributes, resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(mockNativeRUM.calledMethods.count, 1) + XCTAssertEqual(mockNativeRUM.calledMethods.last, .addViewAttribute(key: viewAttributeKey)) + XCTAssertEqual(mockNativeRUM.receivedAttributes.count, 1) + let lastAttributes = try XCTUnwrap(mockNativeRUM.receivedAttributes.last) + XCTAssertEqual(lastAttributes.count, 1) + XCTAssertEqual(lastAttributes["attributeKey"] as? Int64, 123) + } + + func testRemoveViewAttribute() throws { + let viewAttributeKey = "attributeKey" + + rum.removeViewAttribute(key: viewAttributeKey, resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(mockNativeRUM.calledMethods.count, 1) + XCTAssertEqual(mockNativeRUM.calledMethods.last, .removeViewAttribute(key: viewAttributeKey)) + } + + func testAddViewAttributes() throws { + let viewAttributes = NSDictionary( + dictionary: [ + "attribute-1": 123, + "attribute-2": "abc", + "attribute-3": true, + ] + ) + + rum.addViewAttributes(attributes: viewAttributes, resolve: mockResolve, reject: mockReject) + + + XCTAssertEqual(mockNativeRUM.calledMethods.count, 1) + XCTAssertEqual(mockNativeRUM.calledMethods.last, .addViewAttributes()) + XCTAssertEqual(mockNativeRUM.receivedAttributes.count, 1) + let lastAttributes = try XCTUnwrap(mockNativeRUM.receivedAttributes.last) + XCTAssertEqual(lastAttributes.count, 3) + XCTAssertEqual(lastAttributes["attribute-1"] as? Int64, 123) + XCTAssertEqual(lastAttributes["attribute-2"] as? String, "abc") + XCTAssertEqual(lastAttributes["attribute-3"] as? Bool, true) + } + + + func testRemoveViewAttributes() throws { + let viewAttributeKeys = ["attributeKey1", "attributeKey2", "attributeKey3"] + + rum.removeViewAttributes(keys: viewAttributeKeys, resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(mockNativeRUM.calledMethods.count, 1) + XCTAssertEqual(mockNativeRUM.calledMethods.last, .removeViewAttributes(keys: viewAttributeKeys)) + } + func testAddViewLoadingTime() throws { rum.addViewLoadingTime(overwrite: true, resolve: mockResolve, reject: mockReject) diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index 3a882ed47..4a1d0cd92 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,22 +10,6 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { - func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { - // not implemented - } - - func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { - // not implemented - } - - func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { - // not implemented - } - - func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { - // not implemented - } - func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } @@ -71,6 +55,10 @@ internal class MockRUMMonitor: RUMMonitorProtocol { case stopUserAction(type: RUMActionType, name: String?) case addUserAction(type: RUMActionType, name: String) case addTiming(name: String) + case addViewAttribute(key: String) + case removeViewAttribute(key: String) + case addViewAttributes(_: Int? = nil) // We need an attribute for the case to be Equatable + case removeViewAttributes(keys: [String]) case addViewLoadingTime(overwrite: Bool) case stopSession(_: Int? = nil) // We need an attribute for the case to be Equatable case addResourceMetrics(resourceKey: String, @@ -131,6 +119,24 @@ internal class MockRUMMonitor: RUMMonitorProtocol { func addTiming(name: String) { calledMethods.append(.addTiming(name: name)) } + func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { + calledMethods.append(.addViewAttribute(key: key)) + receivedAttributes.append([key :value]) + } + + func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { + calledMethods.append(.removeViewAttribute(key: key)) + } + + func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { + calledMethods.append(.addViewAttributes()) + receivedAttributes.append(attributes) + } + + func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { + calledMethods.append(.removeViewAttributes(keys: keys)) + } + func addViewLoadingTime(overwrite: Bool) { calledMethods.append(.addViewLoadingTime(overwrite: overwrite)) } diff --git a/packages/core/jest/mock.js b/packages/core/jest/mock.js index c49d13f48..0dc9ec138 100644 --- a/packages/core/jest/mock.js +++ b/packages/core/jest/mock.js @@ -119,6 +119,18 @@ module.exports = { addTiming: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), + addViewAttribute: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + removeViewAttribute: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + addViewAttributes: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + removeViewAttributes: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), addViewLoadingTime: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index b4a8632d7..555b144de 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -10,6 +10,7 @@ import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; import { debugId } from '../metro/debugIdResolver'; import type { DdNativeRumType } from '../nativeModulesTypes'; +import type { Attributes } from '../sdk/AttributesSingleton/types'; import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; import { DdSdk } from '../sdk/DdSdk'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; @@ -298,6 +299,50 @@ class DdRumWrapper implements DdRumType { return bufferVoidNativeCall(() => this.nativeRum.addTiming(name)); }; + addViewAttribute = (key: string, value: unknown): Promise => { + InternalLog.log( + `Adding view attribute “${key}" with value “${JSON.stringify( + value + )}” to RUM View`, + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.addViewAttribute(key, { value }) + ); + }; + + removeViewAttribute = (key: string): Promise => { + InternalLog.log( + `Removing view attribute “${key}" from RUM View`, + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.removeViewAttribute(key) + ); + }; + + addViewAttributes = (attributes: Attributes): Promise => { + InternalLog.log( + `Adding view attributes "${JSON.stringify( + attributes + )}” to RUM View`, + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.addViewAttributes(attributes) + ); + }; + + removeViewAttributes = (keys: string[]): Promise => { + InternalLog.log( + `Removing view attributes “${keys}" from RUM View`, + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.removeViewAttributes(keys) + ); + }; + addViewLoadingTime = (overwrite: boolean): Promise => { InternalLog.log( overwrite diff --git a/packages/core/src/rum/__tests__/DdRum.test.ts b/packages/core/src/rum/__tests__/DdRum.test.ts index ec765ed5d..af1f4fbfa 100644 --- a/packages/core/src/rum/__tests__/DdRum.test.ts +++ b/packages/core/src/rum/__tests__/DdRum.test.ts @@ -1091,6 +1091,98 @@ describe('DdRum', () => { }); }); + describe('DdRum.addTiming', () => { + it('calls the native SDK when setting a timing', async () => { + // GIVEN + const timingName = 'testTiming'; + + // WHEN + await DdRum.addTiming(timingName); + + // THEN + expect(NativeModules.DdRum.addTiming).toHaveBeenCalledTimes(1); + expect(NativeModules.DdRum.addTiming).toHaveBeenCalledWith( + timingName + ); + }); + }); + + describe('DdRum.addViewAttribute', () => { + it('calls the native SDK when setting a view attribute', async () => { + // GIVEN + const key = 'testAttribute'; + const value = { test: 'attribute' }; + + // WHEN + + await DdRum.addViewAttribute(key, value); + + // THEN + expect( + NativeModules.DdRum.addViewAttribute + ).toHaveBeenCalledTimes(1); + expect( + NativeModules.DdRum.addViewAttribute + ).toHaveBeenCalledWith(key, { value }); + }); + }); + + describe('DdRum.removViewAttribute', () => { + it('calls the native SDK when removing a view attribute', async () => { + // GIVEN + const key = 'testAttribute'; + + // WHEN + await DdRum.removeViewAttribute(key); + + // THEN + expect( + NativeModules.DdRum.removeViewAttribute + ).toHaveBeenCalledTimes(1); + expect( + NativeModules.DdRum.removeViewAttribute + ).toHaveBeenCalledWith(key); + }); + }); + + describe('DdRum.addViewAttributes', () => { + it('calls the native SDK when setting view attributes', async () => { + // GIVEN + const attributes = { + test: 'attribute' + }; + + // WHEN + await DdRum.addViewAttributes(attributes); + + // THEN + expect( + NativeModules.DdRum.addViewAttributes + ).toHaveBeenCalledTimes(1); + expect( + NativeModules.DdRum.addViewAttributes + ).toHaveBeenCalledWith(attributes); + }); + }); + + describe('DdRum.removViewAttributes', () => { + it('calls the native SDK when removing view attributes', async () => { + // GIVEN + const keysToDelete = ['test1', 'test2']; + + // WHEN + await DdRum.removeViewAttributes(keysToDelete); + + // THEN + expect( + NativeModules.DdRum.removeViewAttributes + ).toHaveBeenCalledTimes(1); + expect( + NativeModules.DdRum.removeViewAttributes + ).toHaveBeenCalledWith(keysToDelete); + }); + }); + describe('DdRum.addAction', () => { test('uses given context when context is valid', async () => { const context = { diff --git a/packages/core/src/rum/types.ts b/packages/core/src/rum/types.ts index fc8d07c02..3def7f0e6 100644 --- a/packages/core/src/rum/types.ts +++ b/packages/core/src/rum/types.ts @@ -4,6 +4,7 @@ * Copyright 2016-Present Datadog, Inc. */ +import type { Attributes } from '../sdk/AttributesSingleton/types'; import type { ErrorSource } from '../types'; import type { DatadogTracingContext } from './instrumentation/resourceTracking/distributedTracing/DatadogTracingContext'; @@ -148,6 +149,31 @@ export type DdRumType = { */ addTiming(name: string): Promise; + /** + * Adds a custom attribute to the active RUM View. It will be propagated to all future RUM events associated with the active View. + * @param key: key for this view attribute. + * @param value: value for this attribute. + */ + addViewAttribute(key: string, value: unknown): Promise; + + /** + * Removes an attribute from the active RUM View. + * @param key: key for the attribute to be removed from the view. + */ + removeViewAttribute(key: string): Promise; + + /** + * Adds multiple attributes to the active RUM View. They will be propagated to all future RUM events associated with the active View. + * @param attributes: key/value object containing all attributes to be added to the view. + */ + addViewAttributes(attributes: Attributes): Promise; + + /** + * Removes multiple attributes from the active RUM View. + * @param keys: keys for the attributes to be removed from the view. + */ + removeViewAttributes(keys: string[]): Promise; + /** * Adds the loading time of the view to the active view. * It is calculated as the difference between the current time and the start time of the view. diff --git a/packages/core/src/specs/NativeDdRum.ts b/packages/core/src/specs/NativeDdRum.ts index f6f7b3daa..e31f5b925 100644 --- a/packages/core/src/specs/NativeDdRum.ts +++ b/packages/core/src/specs/NativeDdRum.ts @@ -136,6 +136,31 @@ export interface Spec extends TurboModule { */ addTiming(name: string): Promise; + /** + * Adds a custom attribute to the active RUM View. It will be propagated to all future RUM events associated with the active View. + * @param key: key for this view attribute. + * @param value: value for this attribute. + */ + addViewAttribute(key: string, value: Object): Promise; + + /** + * Removes an attribute from the active RUM View. + * @param key: key for the attribute to be removed from the view. + */ + removeViewAttribute(key: string): Promise; + + /** + * Adds multiple attributes to the active RUM View. They will be propagated to all future RUM events associated with the active View. + * @param attributes: key/value object containing all attributes to be added to the view. + */ + addViewAttributes(attributes: Object): Promise; + + /** + * Removes multiple attributes from the active RUM View. + * @param keys: keys for the attributes to be removed from the view. + */ + removeViewAttributes(keys: string[]): Promise; + /** * Adds the loading time of the view to the active view. * It is calculated as the difference between the current time and the start time of the view. From fea5fec82350d965b13bcf2e06093a8625845416 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 22 Aug 2025 15:43:01 +0200 Subject: [PATCH 033/114] Fix FileBasedConfiguration related issues --- example/datadog-configuration.json | 20 ++ example/src/App.tsx | 20 +- .../codepush/src/__tests__/index.test.tsx | 4 +- .../FileBasedConfiguration.ts | 54 ++--- .../__tests__/FileBasedConfiguration.test.ts | 192 ++++++++++-------- .../__fixtures__/malformed-configuration.json | 1 - 6 files changed, 157 insertions(+), 134 deletions(-) create mode 100644 example/datadog-configuration.json diff --git a/example/datadog-configuration.json b/example/datadog-configuration.json new file mode 100644 index 000000000..684e60304 --- /dev/null +++ b/example/datadog-configuration.json @@ -0,0 +1,20 @@ +{ + "$schema": "./node_modules/@datadog/mobile-react-native/datadog-configuration.schema.json", + "configuration": { + "applicationId": "APP_ID", + "batchSize": "SMALL", + "clientToken": "CLIENT_TOKEN", + "env": "ENVIRONMENT", + "longTaskThresholdMs": 1000, + "nativeCrashReportEnabled": true, + "sessionSamplingRate": 100, + "site": "US1", + "telemetrySampleRate": 20, + "trackBackgroundEvents": false, + "trackErrors": true, + "trackInteractions": true, + "trackResources": true, + "trackingConsent": "GRANTED", + "verbosity": "DEBUG" + } +} diff --git a/example/src/App.tsx b/example/src/App.tsx index cefcafffe..c29982a97 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -7,7 +7,7 @@ import AboutScreen from './screens/AboutScreen'; import style from './screens/styles'; import { navigationRef } from './NavigationRoot'; import { DdRumReactNavigationTracking, ViewNamePredicate } from '@datadog/mobile-react-navigation'; -import {DatadogProvider} from '@datadog/mobile-react-native' +import {DatadogProvider, FileBasedConfiguration} from '@datadog/mobile-react-native' import { Route } from "@react-navigation/native"; import { NestedNavigator } from './screens/NestedNavigator/NestedNavigator'; import { getDatadogConfig, onDatadogInitialization } from './ddUtils'; @@ -19,9 +19,25 @@ const viewPredicate: ViewNamePredicate = function customViewNamePredicate(route: return "Custom RN " + trackedName; } +// === Datadog Provider Configuration schemes === + +// 1.- Direct configuration +const configuration = getDatadogConfig(TrackingConsent.GRANTED) + +// 2.- File based configuration from .json +// const configuration = new FileBasedConfiguration(require("../datadog-configuration.json")); + +// 3.- File based configuration from .json and custom mapper setup +// const configuration = new FileBasedConfiguration( { +// configuration: require("../datadog-configuration.json").configuration, +// errorEventMapper: (event) => event, +// resourceEventMapper: (event) => event, +// actionEventMapper: (event) => event}); + + export default function App() { return ( - + { DdRumReactNavigationTracking.startTrackingViews(navigationRef.current, viewPredicate) }}> diff --git a/packages/codepush/src/__tests__/index.test.tsx b/packages/codepush/src/__tests__/index.test.tsx index 5c94c16ef..63e1af7d6 100644 --- a/packages/codepush/src/__tests__/index.test.tsx +++ b/packages/codepush/src/__tests__/index.test.tsx @@ -279,7 +279,7 @@ describe('AppCenter Codepush integration', () => { }; const configuration = new FileBasedConfiguration({ - configuration: { configuration: autoInstrumentationConfig } + configuration: autoInstrumentationConfig }); render(); @@ -346,7 +346,7 @@ describe('AppCenter Codepush integration', () => { }; const configuration = new FileBasedConfiguration({ - configuration: { configuration: autoInstrumentationConfig } + configuration: autoInstrumentationConfig }); render(); diff --git a/packages/core/src/sdk/FileBasedConfiguration/FileBasedConfiguration.ts b/packages/core/src/sdk/FileBasedConfiguration/FileBasedConfiguration.ts index ddd1943aa..3fc69f1f3 100644 --- a/packages/core/src/sdk/FileBasedConfiguration/FileBasedConfiguration.ts +++ b/packages/core/src/sdk/FileBasedConfiguration/FileBasedConfiguration.ts @@ -56,44 +56,7 @@ export class FileBasedConfiguration extends DatadogProviderConfiguration { const resolveJSONConfiguration = ( userSpecifiedConfiguration: unknown ): Record => { - if ( - userSpecifiedConfiguration === undefined || - userSpecifiedConfiguration === null - ) { - try { - // This corresponds to a file located at the root of a RN project. - // /!\ We have to write the require this way as dynamic requires are not supported by Hermes. - // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires - const jsonContent = require('../../../../../../datadog-configuration.json'); - - if ( - typeof jsonContent !== 'object' || - !jsonContent['configuration'] - ) { - console.error(`Failed to parse the Datadog configuration file located at the root of the project. -Your configuration must validate the node_modules/@datadog/mobile-react-native/datadog-configuration.schema.json JSON schema. -You can use VSCode to check your configuration by adding the following line to your JSON file: -{ - "$schema": "./node_modules/@datadog/mobile-react-native/datadog-configuration.schema.json", -}`); - - return {}; - } - - return jsonContent.configuration as Record; - } catch (error) { - console.error(`Failed to read Datadog configuration file at the root of the project. -If you don't have a datadog-configuration.json file at the same level as your node_modules directory,\ -please use the following syntax:\n -new FileBasedConfiguration({configuration: require('./file/to/configuration-file.json')}) -`); - return {}; - } - } - if ( - typeof userSpecifiedConfiguration !== 'object' || - !(userSpecifiedConfiguration as any)['configuration'] - ) { + if (typeof userSpecifiedConfiguration !== 'object') { console.error(`Failed to parse the Datadog configuration file you provided. Your configuration must validate the node_modules/@datadog/mobile-react-native/datadog-configuration.schema.json JSON schema. You can use VSCode to check your configuration by adding the following line to your JSON file: @@ -104,10 +67,7 @@ You can use VSCode to check your configuration by adding the following line to y return {}; } - return (userSpecifiedConfiguration as any)['configuration'] as Record< - string, - any - >; + return (userSpecifiedConfiguration as any) as Record; }; export const getJSONConfiguration = ( @@ -130,6 +90,16 @@ export const getJSONConfiguration = ( } => { const configuration = resolveJSONConfiguration(userSpecifiedConfiguration); + if ( + configuration.clientToken === undefined || + configuration.env === undefined || + configuration.applicationId === undefined + ) { + console.warn( + 'DATADOG: Warning: Malformed json configuration file - clientToken, applicationId and env are mandatory properties.' + ); + } + return { clientToken: configuration.clientToken, env: configuration.env, diff --git a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts index 716243e86..6d3ee2e44 100644 --- a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts +++ b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts @@ -16,35 +16,99 @@ import malformedConfiguration from './__fixtures__/malformed-configuration.json' describe('FileBasedConfiguration', () => { describe('with user-specified configuration', () => { + it('resolves configuration fields', () => { + const configuration = new FileBasedConfiguration( + configurationAllFields + ); + + expect(configuration).toMatchInlineSnapshot(` + FileBasedConfiguration { + "actionEventMapper": null, + "actionNameAttribute": "action-name-attr", + "additionalConfiguration": {}, + "applicationId": "fake-app-id", + "batchProcessingLevel": "MEDIUM", + "batchSize": "MEDIUM", + "bundleLogsWithRum": true, + "bundleLogsWithTraces": true, + "clientToken": "fake-client-token", + "customEndpoints": {}, + "env": "fake-env", + "errorEventMapper": null, + "firstPartyHosts": [ + { + "match": "example.com", + "propagatorTypes": [ + "b3multi", + "tracecontext", + ], + }, + ], + "initializationMode": "SYNC", + "logEventMapper": null, + "longTaskThresholdMs": 44, + "nativeCrashReportEnabled": false, + "nativeInteractionTracking": false, + "nativeLongTaskThresholdMs": 200, + "nativeViewTracking": false, + "proxyConfig": undefined, + "resourceEventMapper": null, + "resourceTracingSamplingRate": 33, + "serviceName": undefined, + "sessionSamplingRate": 100, + "site": "US5", + "telemetrySampleRate": 20, + "trackBackgroundEvents": false, + "trackErrors": true, + "trackFrustrations": true, + "trackInteractions": true, + "trackResources": true, + "trackWatchdogTerminations": false, + "trackingConsent": "not_granted", + "uploadFrequency": "AVERAGE", + "useAccessibilityLabel": false, + "verbosity": "warn", + "vitalsUpdateFrequency": "AVERAGE", + } + `); + }); + + it('prints a warning message when the configuration file cannot be parsed correctly', () => { + const warnSpy = jest.spyOn(console, 'warn'); + getJSONConfiguration(malformedConfiguration); + + expect(warnSpy).toHaveBeenCalledWith( + 'DATADOG: Warning: Malformed json configuration file - clientToken, applicationId and env are mandatory properties.' + ); + }); + it('resolves all properties from a given file path', () => { const config = new FileBasedConfiguration({ configuration: { - configuration: { - applicationId: 'fake-app-id', - env: 'fake-env', - clientToken: 'fake-client-token', - trackInteractions: true, - trackResources: true, - trackErrors: true, - trackingConsent: 'NOT_GRANTED', - longTaskThresholdMs: 44, - site: 'US5', - verbosity: 'WARN', - actionNameAttribute: 'action-name-attr', - useAccessibilityLabel: false, - resourceTracingSamplingRate: 33, - firstPartyHosts: [ - { - match: 'example.com', - propagatorTypes: [ - 'B3MULTI', - 'TRACECONTEXT', - 'B3', - 'DATADOG' - ] - } - ] - } + applicationId: 'fake-app-id', + env: 'fake-env', + clientToken: 'fake-client-token', + trackInteractions: true, + trackResources: true, + trackErrors: true, + trackingConsent: 'NOT_GRANTED', + longTaskThresholdMs: 44, + site: 'US5', + verbosity: 'WARN', + actionNameAttribute: 'action-name-attr', + useAccessibilityLabel: false, + resourceTracingSamplingRate: 33, + firstPartyHosts: [ + { + match: 'example.com', + propagatorTypes: [ + 'B3MULTI', + 'TRACECONTEXT', + 'B3', + 'DATADOG' + ] + } + ] } }); expect(config).toMatchInlineSnapshot(` @@ -103,11 +167,9 @@ describe('FileBasedConfiguration', () => { it('applies default values to configuration from a given file path', () => { const config = new FileBasedConfiguration({ configuration: { - configuration: { - applicationId: 'fake-app-id', - env: 'fake-env', - clientToken: 'fake-client-token' - } + applicationId: 'fake-app-id', + env: 'fake-env', + clientToken: 'fake-client-token' } }); expect(config).toMatchInlineSnapshot(` @@ -159,11 +221,9 @@ describe('FileBasedConfiguration', () => { const resourceEventMapper = () => null; const config = new FileBasedConfiguration({ configuration: { - configuration: { - applicationId: 'fake-app-id', - env: 'fake-env', - clientToken: 'fake-client-token' - } + applicationId: 'fake-app-id', + env: 'fake-env', + clientToken: 'fake-client-token' }, actionEventMapper, errorEventMapper, @@ -188,62 +248,20 @@ describe('FileBasedConfiguration', () => { it('prints a warning message when the first party hosts contain unknown propagator types', () => { const config = new FileBasedConfiguration({ configuration: { - configuration: { - applicationId: 'fake-app-id', - env: 'fake-env', - clientToken: 'fake-client-token', - firstPartyHosts: [ - { - match: 'example.com', - propagatorTypes: ['UNKNOWN'] - } - ] - } + applicationId: 'fake-app-id', + env: 'fake-env', + clientToken: 'fake-client-token', + firstPartyHosts: [ + { + match: 'example.com', + propagatorTypes: ['UNKNOWN'] + } + ] } }); expect(config.firstPartyHosts).toHaveLength(0); }); }); - describe('with resolved file configuration', () => { - it('resolves configuration fields', () => { - const configuration = getJSONConfiguration(configurationAllFields); - - expect(configuration).toMatchInlineSnapshot(` - { - "actionNameAttribute": "action-name-attr", - "applicationId": "fake-app-id", - "clientToken": "fake-client-token", - "env": "fake-env", - "firstPartyHosts": [ - { - "match": "example.com", - "propagatorTypes": [ - "b3multi", - "tracecontext", - ], - }, - ], - "longTaskThresholdMs": 44, - "resourceTracingSamplingRate": 33, - "site": "US5", - "trackErrors": true, - "trackInteractions": true, - "trackResources": true, - "trackingConsent": "not_granted", - "useAccessibilityLabel": false, - "verbosity": "warn", - } - `); - }); - it('prints a warning message when the configuration file is not found', () => { - expect(() => getJSONConfiguration(undefined)).not.toThrow(); - }); - it('prints a warning message when the configuration file cannot be parsed correctly', () => { - expect(() => - getJSONConfiguration(malformedConfiguration) - ).not.toThrow(); - }); - }); describe('formatPropagatorType', () => { it('formats all propagatorTypes correctly', () => { diff --git a/packages/core/src/sdk/FileBasedConfiguration/__tests__/__fixtures__/malformed-configuration.json b/packages/core/src/sdk/FileBasedConfiguration/__tests__/__fixtures__/malformed-configuration.json index 28423084d..0e1b26639 100644 --- a/packages/core/src/sdk/FileBasedConfiguration/__tests__/__fixtures__/malformed-configuration.json +++ b/packages/core/src/sdk/FileBasedConfiguration/__tests__/__fixtures__/malformed-configuration.json @@ -1,5 +1,4 @@ { "clientToken": "clientToken", - "env": "env", "applicationId": "applicationId" } From 06990673aaec7db7de67091c98e2a9f73759cf6c Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 27 Oct 2025 14:45:54 +0100 Subject: [PATCH 034/114] Bump Android Native SDK to 3.2.0 and regenerate app podfiles --- benchmarks/android/app/build.gradle | 2 +- benchmarks/ios/Podfile.lock | 10 +++++----- example-new-architecture/ios/Podfile.lock | 8 ++++++-- example/ios/Podfile.lock | 14 +++++++------- packages/core/android/build.gradle | 10 +++++----- .../android/build.gradle | 4 ++-- packages/react-native-webview/android/build.gradle | 2 +- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/benchmarks/android/app/build.gradle b/benchmarks/android/app/build.gradle index 04c240bd0..6d9f28764 100644 --- a/benchmarks/android/app/build.gradle +++ b/benchmarks/android/app/build.gradle @@ -129,5 +129,5 @@ dependencies { // Benchmark tools from dd-sdk-android are used for vitals recording // Remember to bump thid alongside the main dd-sdk-android dependencies - implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.1.0") + implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.2.0") } diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index 4bfda721d..258195f94 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -10,7 +10,7 @@ PODS: - DatadogInternal (= 3.1.0) - DatadogRUM (3.1.0): - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.12.1): + - DatadogSDKReactNative (2.13.0): - DatadogCore (= 3.1.0) - DatadogCrashReporting (= 3.1.0) - DatadogLogs (= 3.1.0) @@ -60,7 +60,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.12.1): + - DatadogSDKReactNativeWebView (2.13.0): - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - DatadogWebViewTracking (= 3.1.0) @@ -2075,9 +2075,9 @@ SPEC CHECKSUMS: DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 - DatadogSDKReactNative: 8e0f39de38621d4d7ed961a74d8a216fd3a38321 - DatadogSDKReactNativeSessionReplay: f9288c8e981dcc65d1f727b01421ee9a7601e75f - DatadogSDKReactNativeWebView: 993527f6c5d38e0fcc4804a6a60c334dd199dc5b + DatadogSDKReactNative: 620018df2896abcfad6b338c633cc8eccd5de406 + DatadogSDKReactNativeSessionReplay: b2ef22431dd0816adea8d65df13180cf40533f9d + DatadogSDKReactNativeWebView: 299629cf348a5e8f1dabb8289920a00eee625d6a DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index 0bccb77fe..46710d627 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -39,7 +39,7 @@ PODS: - DatadogInternal (= 3.1.0) - DatadogRUM (3.1.0): - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.12.1): + - DatadogSDKReactNative (2.13.0): - DatadogCore (= 3.1.0) - DatadogCrashReporting (= 3.1.0) - DatadogLogs (= 3.1.0) @@ -67,6 +67,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga +<<<<<<< HEAD <<<<<<< HEAD - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) @@ -86,6 +87,9 @@ PODS: - DatadogWebViewTracking (= 3.0.0) >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= +======= + - DatadogSDKReactNative/Tests (2.13.0): +>>>>>>> 71638bf0 (Bump Android Native SDK to 3.2.0 and regenerate app podfiles) - DatadogCore (= 3.1.0) - DatadogCrashReporting (= 3.1.0) - DatadogLogs (= 3.1.0) @@ -1925,7 +1929,7 @@ SPEC CHECKSUMS: DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 - DatadogSDKReactNative: 069ea9876220b2d09b0f4b180ce571b1b6ecbb35 + DatadogSDKReactNative: 2f11191b56e18680f633bfb125ab1832b327d9b4 DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 >>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a1cca0994..60d0b1b71 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -10,7 +10,7 @@ PODS: - DatadogInternal (= 3.1.0) - DatadogRUM (3.1.0): - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.12.1): + - DatadogSDKReactNative (2.13.0): - DatadogCore (= 3.1.0) - DatadogCrashReporting (= 3.1.0) - DatadogLogs (= 3.1.0) @@ -18,7 +18,7 @@ PODS: - DatadogTrace (= 3.1.0) - DatadogWebViewTracking (= 3.1.0) - React-Core - - DatadogSDKReactNative/Tests (2.12.1): + - DatadogSDKReactNative/Tests (2.13.0): - DatadogCore (= 3.1.0) - DatadogCrashReporting (= 3.1.0) - DatadogLogs (= 3.1.0) @@ -73,12 +73,12 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.12.1): + - DatadogSDKReactNativeWebView (2.13.0): - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - DatadogWebViewTracking (= 3.1.0) - React-Core - - DatadogSDKReactNativeWebView/Tests (2.12.1): + - DatadogSDKReactNativeWebView/Tests (2.13.0): - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - DatadogWebViewTracking (= 3.1.0) @@ -1993,9 +1993,9 @@ SPEC CHECKSUMS: DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 - DatadogSDKReactNative: af351a4e1ce08124c290c52de94b0062a166cc67 - DatadogSDKReactNativeSessionReplay: dcbd55d9d0f2b86026996a8b7ec9654922d5dfe1 - DatadogSDKReactNativeWebView: 096ac87eb753b6a217b93441983264b9837c3b7e + DatadogSDKReactNative: 822ff8092666172584d4d5e56f79c3799887d408 + DatadogSDKReactNativeSessionReplay: afc4e2b1db34ba8af3a442b0691359faaf5e586e + DatadogSDKReactNativeWebView: 00affefdaca0cf2375e669fa03925d8fa75263d0 DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 59bb75cc3..080185d41 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -201,16 +201,16 @@ dependencies { // This breaks builds if the React Native target is below 0.76.0. as it relies on Gradle 8.5.0. // To avoid this, we enforce 1.0.0-beta01 on RN < 0.76.0 if (reactNativeMinorVersion < 76) { - implementation("com.datadoghq:dd-sdk-android-rum:3.1.0") { + implementation("com.datadoghq:dd-sdk-android-rum:3.2.0") { exclude group: "androidx.metrics", module: "metrics-performance" } implementation "androidx.metrics:metrics-performance:1.0.0-beta01" } else { - implementation "com.datadoghq:dd-sdk-android-rum:3.1.0" + implementation "com.datadoghq:dd-sdk-android-rum:3.2.0" } - implementation "com.datadoghq:dd-sdk-android-logs:3.1.0" - implementation "com.datadoghq:dd-sdk-android-trace:3.1.0" - implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" + implementation "com.datadoghq:dd-sdk-android-logs:3.2.0" + implementation "com.datadoghq:dd-sdk-android-trace:3.2.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.2.0" implementation "com.google.code.gson:gson:2.10.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle index a0d77f2ff..bea305684 100644 --- a/packages/react-native-session-replay/android/build.gradle +++ b/packages/react-native-session-replay/android/build.gradle @@ -216,8 +216,8 @@ dependencies { api "com.facebook.react:react-android:$reactNativeVersion" } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.datadoghq:dd-sdk-android-session-replay:3.1.0" - implementation "com.datadoghq:dd-sdk-android-internal:3.1.0" + implementation "com.datadoghq:dd-sdk-android-session-replay:3.2.0" + implementation "com.datadoghq:dd-sdk-android-internal:3.2.0" implementation project(path: ':datadog_mobile-react-native') testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" diff --git a/packages/react-native-webview/android/build.gradle b/packages/react-native-webview/android/build.gradle index e1fb081f0..95e1e42e0 100644 --- a/packages/react-native-webview/android/build.gradle +++ b/packages/react-native-webview/android/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "com.facebook.react:react-android:$reactNativeVersion" } - implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.2.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(path: ':datadog_mobile-react-native') From ed8d2e62fd3390d9866bdb7b5c02dfd1e4fa7f67 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 9 Sep 2025 12:08:35 +0200 Subject: [PATCH 035/114] iOS: Always use SDK default core instance --- .../core/ios/Sources/DatadogSDKWrapper.swift | 113 +----------------- .../ios/Sources/DdSdkImplementation.swift | 37 ++---- .../Sources/DdSdkNativeInitialization.swift | 3 + 3 files changed, 17 insertions(+), 136 deletions(-) diff --git a/packages/core/ios/Sources/DatadogSDKWrapper.swift b/packages/core/ios/Sources/DatadogSDKWrapper.swift index 3c56688b0..894f0a09f 100644 --- a/packages/core/ios/Sources/DatadogSDKWrapper.swift +++ b/packages/core/ios/Sources/DatadogSDKWrapper.swift @@ -13,15 +13,12 @@ import DatadogCrashReporting import DatadogInternal import Foundation -<<<<<<< HEAD + #if os(iOS) import DatadogWebViewTracking #endif -public typealias OnCoreInitializedListener = (DatadogCoreProtocol) -> Void -======= -public typealias OnSdkInitializedListener = () -> Void ->>>>>>> 0443e0ff (iOS: Always use SDK default core instance) +public typealias OnSdkInitializedListener = (DatadogCoreProtocol) -> Void /// Wrapper around the Datadog SDK. Use DatadogSDKWrapper.shared to access the instance. public class DatadogSDKWrapper { @@ -45,118 +42,16 @@ public class DatadogSDKWrapper { loggerConfiguration: DatadogLogs.Logger.Configuration, trackingConsent: TrackingConsent ) -> Void { - Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent) + let core = Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent) for listener in onSdkInitializedListeners { - listener() + listener(core) } self.loggerConfiguration = loggerConfiguration } -<<<<<<< HEAD - internal func isInitialized() -> Bool { - return Datadog.isInitialized() - } - - internal func clearAllData() -> Void { - if let core = coreInstance { - Datadog.clearAllData(in: core) - } else { - Datadog.clearAllData() - } - } - - // Features - internal func enableRUM(with configuration: RUM.Configuration) { - if let core = coreInstance { - RUM.enable(with: configuration, in: core) - } else { - consolePrint("Core instance was not found when initializing RUM.", .critical) - } - } - - internal func enableLogs(with configuration: Logs.Configuration) { - if let core = coreInstance { - Logs.enable(with: configuration, in: core) - } else { - consolePrint("Core instance was not found when initializing Logs.", .critical) - } - } - - internal func enableTrace(with configuration: Trace.Configuration) { - if let core = coreInstance { - Trace.enable(with: configuration, in: core) - } else { - consolePrint("Core instance was not found when initializing Trace.", .critical) - } - } - - internal func enableCrashReporting() { - if let core = coreInstance { - CrashReporting.enable(in: core) - } else { - consolePrint("Core instance was not found when initializing CrashReporting.", .critical) - } - } - - internal func createLogger() -> LoggerProtocol { - let core = coreInstance ?? { - consolePrint("Core instance was not found when creating Logger.", .critical) - return CoreRegistry.default - }() - - return DatadogLogs.Logger.create(with: loggerConfiguration, in: core) - } - - // Telemetry - internal func sendTelemetryLog(message: String, attributes: [String: any Encodable], config: [String: any Encodable]) { - if let core = coreInstance { - let id = (config["onlyOnce"] as? Bool) == true ? message : UUID().uuidString - core.telemetry.debug(id: id, message: message, attributes: attributes) - } else { - consolePrint("Core instance was not found when calling sendTelemetryLog.", .warn) - } - } - - internal func telemetryDebug(id: String, message: String) { - return Datadog._internal.telemetry.debug(id: id, message: message) - } - - internal func telemetryError(id: String, message: String, kind: String?, stack: String?) { - return Datadog._internal.telemetry.error(id: id, message: message, kind: kind, stack: stack) - } - - internal func overrideTelemetryConfiguration( - initializationType: String? = nil, - reactNativeVersion: String? = nil, - reactVersion: String? = nil, - trackCrossPlatformLongTasks: Bool? = nil, - trackErrors: Bool? = nil, - trackInteractions: Bool? = nil, - trackLongTask: Bool? = nil, - trackNativeErrors: Bool? = nil, - trackNativeLongTasks: Bool? = nil, - trackNetworkRequests: Bool? = nil - ) { - coreInstance?.telemetry.configuration( - initializationType: initializationType, - reactNativeVersion: reactNativeVersion, - reactVersion: reactVersion, - trackCrossPlatformLongTasks: trackCrossPlatformLongTasks, - trackErrors: trackErrors, - trackLongTask: trackLongTask, - trackNativeErrors: trackNativeErrors, - trackNativeLongTasks: trackNativeLongTasks, - trackNetworkRequests: trackNetworkRequests, - trackUserInteractions: trackInteractions - ) - } - - #if os(iOS) -======= ->>>>>>> 0443e0ff (iOS: Always use SDK default core instance) // Webview private var webviewMessageEmitter: InternalExtension.AbstractMessageEmitter? diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 9c3fe980f..997068ddf 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -175,33 +175,21 @@ public class DdSdkImplementation: NSObject { ) { let castedAttributes = castAttributesToSwift(attributes) let castedConfig = castAttributesToSwift(config) - DdTelemetry.sendTelemetryLog( - message: message as String, attributes: castedAttributes, config: castedConfig) + DdTelemetry.sendTelemetryLog(message: message as String, attributes: castedAttributes, config: castedConfig) resolve(nil) } @objc - public func telemetryDebug( - message: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock - ) { - DdTelemetry.telemetryDebug( - id: "datadog_react_native:\(message)", message: message as String) + + public func telemetryDebug(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + DdTelemetry.telemetryDebug(id: "datadog_react_native:\(message)", message: message as String) resolve(nil) } -<<<<<<< HEAD #if os(iOS) -======= - ->>>>>>> 0443e0ff (iOS: Always use SDK default core instance) @objc - public func telemetryError( - message: NSString, stack: NSString, kind: NSString, resolve: RCTPromiseResolveBlock, - reject: RCTPromiseRejectBlock - ) { - DdTelemetry.telemetryError( - id: "datadog_react_native:\(String(describing: kind)):\(message)", - message: message as String, kind: kind as String, stack: stack as String) + public func telemetryError(message: NSString, stack: NSString, kind: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + DdTelemetry.telemetryError(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as String, stack: stack as String) resolve(nil) } @@ -221,24 +209,19 @@ public class DdSdkImplementation: NSObject { resolve(nil) } -<<<<<<< HEAD #endif -======= ->>>>>>> 1f781a51 (Expose view Attributes API) @objc - public func clearAllData(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + public func clearAllData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { Datadog.clearAllData() resolve(nil) } - func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) { + func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) -> Void { DdTelemetry.overrideTelemetryConfiguration( - initializationType: rnConfiguration.configurationForTelemetry?.initializationType - as? String, - reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion - as? String, + initializationType: rnConfiguration.configurationForTelemetry?.initializationType as? String, + reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion as? String, reactVersion: rnConfiguration.configurationForTelemetry?.reactVersion as? String, trackCrossPlatformLongTasks: rnConfiguration.longTaskThresholdMs != 0, trackErrors: rnConfiguration.configurationForTelemetry?.trackErrors, diff --git a/packages/core/ios/Sources/DdSdkNativeInitialization.swift b/packages/core/ios/Sources/DdSdkNativeInitialization.swift index 4ea229871..b55f90561 100644 --- a/packages/core/ios/Sources/DdSdkNativeInitialization.swift +++ b/packages/core/ios/Sources/DdSdkNativeInitialization.swift @@ -94,12 +94,15 @@ public class DdSdkNativeInitialization: NSObject { CrashReporting.enable() } <<<<<<< HEAD +<<<<<<< HEAD #if os(iOS) DatadogSDKWrapper.shared.enableWebviewTracking() #endif ======= >>>>>>> 0443e0ff (iOS: Always use SDK default core instance) +======= +>>>>>>> 93aa6125 (iOS: Always use SDK default core instance) } func buildSDKConfiguration(configuration: DdSdkConfiguration, defaultAppVersion: String = getDefaultAppVersion()) -> Datadog.Configuration { From 9ee2ff834db44416689d0a107fd9ae94a22207b5 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 8 Sep 2025 15:27:17 +0200 Subject: [PATCH 036/114] Bump native SDK dependencies to 3.0.0 --- benchmarks/android/app/build.gradle | 2 +- benchmarks/ios/Podfile.lock | 178 ++++++++-------- example-new-architecture/ios/Podfile.lock | 121 ++++++----- example/ios/Podfile.lock | 196 +++++++++--------- packages/core/DatadogSDKReactNative.podspec | 12 +- packages/core/android/build.gradle | 10 +- ...DatadogSDKReactNativeSessionReplay.podspec | 2 +- .../android/build.gradle | 4 +- .../DatadogSDKReactNativeWebView.podspec | 4 +- .../react-native-webview/android/build.gradle | 2 +- 10 files changed, 275 insertions(+), 256 deletions(-) diff --git a/benchmarks/android/app/build.gradle b/benchmarks/android/app/build.gradle index 6d9f28764..7dc4b3707 100644 --- a/benchmarks/android/app/build.gradle +++ b/benchmarks/android/app/build.gradle @@ -129,5 +129,5 @@ dependencies { // Benchmark tools from dd-sdk-android are used for vitals recording // Remember to bump thid alongside the main dd-sdk-android dependencies - implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.2.0") + implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.0.0") } diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index 258195f94..51319ec2c 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -1,22 +1,22 @@ PODS: - boost (1.84.0) - - DatadogCore (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogCrashReporting (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogCore (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogCrashReporting (3.0.0): + - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.1.0) - - DatadogLogs (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogRUM (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.13.0): - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) + - DatadogInternal (3.0.0) + - DatadogLogs (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogRUM (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogSDKReactNative (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -39,7 +39,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.1.0) + - DatadogSessionReplay (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -60,10 +60,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.13.0): - - DatadogInternal (= 3.1.0) + - DatadogSDKReactNativeWebView (2.12.1): + - DatadogInternal (= 3.0.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.1.0) + - DatadogWebViewTracking (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -84,13 +84,13 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSessionReplay (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogTrace (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogSessionReplay (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogTrace (3.0.0): + - DatadogInternal (= 3.0.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogWebViewTracking (3.0.0): + - DatadogInternal (= 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.78.2) @@ -2070,17 +2070,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d - DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d - DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc - DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 - DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 - DatadogSDKReactNative: 620018df2896abcfad6b338c633cc8eccd5de406 - DatadogSDKReactNativeSessionReplay: b2ef22431dd0816adea8d65df13180cf40533f9d - DatadogSDKReactNativeWebView: 299629cf348a5e8f1dabb8289920a00eee625d6a - DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 - DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 - DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 + DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 + DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e + DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 + DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd + DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f + DatadogSDKReactNative: 241bf982c16ceff03d94a58e6d005e55c47b99c6 + DatadogSDKReactNativeSessionReplay: bba36092686e3183e97c1a0c7f4ca8142582ae28 + DatadogSDKReactNativeWebView: 6da060df20e235abac533e582d9fc2b3a5070840 + DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 + DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 + DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: e32d34492c519a2194ec9d7f5e7a79d11b73f91c @@ -2089,70 +2089,70 @@ SPEC CHECKSUMS: hermes-engine: 2771b98fb813fdc6f92edd7c9c0035ecabf9fee7 OpenTelemetrySwiftApi: aaee576ed961e0c348af78df58b61300e95bd104 PLCrashReporter: db59ef96fa3d25f3650040d02ec2798cffee75f2 - RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: be794de7dc6ed8f9f7fbf525f86e7651b8b68746 RCTRequired: a83787b092ec554c2eb6019ff3f5b8d125472b3b RCTTypeSafety: 48ad3c858926b1c46f46a81a58822b476e178e2c React: 3b5754191f1b65f1dbc52fbea7959c3d2d9e39c9 React-callinvoker: 6beeaf4c7db11b6cc953fac45f2c76e3fb125013 - React-Core: 88e817c42de035378cc71e009193b9a044d3f595 - React-CoreModules: dcf764d71efb4f75d38fcae8d4513b6729f49360 - React-cxxreact: 8cdcc937c5fbc406fe843a381102fd69440ca78a + React-Core: 8a10ac9de53373a3ecb5dfcbcf56df1d3dad0861 + React-CoreModules: af6999b35c7c01b0e12b59d27f3e054e13da43b1 + React-cxxreact: 833f00155ce8c2fda17f6d286f8eaeff2ececc69 React-debug: 440175830c448e7e53e61ebb8d8468c3256b645e - React-defaultsnativemodule: 4824bcd7b96ee2d75c28b1ca21f58976867f5535 - React-domnativemodule: a421118b475618961cf282e8ea85347cc9bb453c - React-Fabric: 6ac7de06009eb96b609a770b17abba6e460b5f45 - React-FabricComponents: e3bc2680a5a9a4917ff0c8d7f390688c30ef753c - React-FabricImage: 8bad558dec7478077974caa96acc79692d6b71f5 + React-defaultsnativemodule: a970effe18fe50bdbbb7115c3297f873b666d0d4 + React-domnativemodule: 45f886342a724e61531b18fba1859bb6782e5d62 + React-Fabric: 69f1881f2177a8512304a64157943548ab6df0cf + React-FabricComponents: f54111c8e2439fc273ab07483e3a7054ca1e75af + React-FabricImage: 9ad2619dfe8c386d79e8aaa87da6e8f018ab9592 React-featureflags: b9cf9b35baca1c7f20c06a104ffc325a02752faa - React-featureflagsnativemodule: dc93d81da9f41f7132e24455ec8b4b60802fd5b0 - React-graphics: aaa5a38bea15d7b895b210d95d554af45a07002a - React-hermes: 08ad9fb832d1b9faef391be17309aa6a69fad23b - React-idlecallbacksnativemodule: aacea33ef6c511a9781f9286cc7cdf93f39bba14 - React-ImageManager: c596c3b658c9c14607f9183ed0f635c8dd77987c - React-jserrorhandler: 987609b2f16b7d79d63fcd621bf0110dd7400b35 - React-jsi: afa286d7e0c102c2478dc420d4f8935e13c973fc - React-jsiexecutor: 08f5b512b4db9e2f147416d60a0a797576b9cfef - React-jsinspector: 5a94bcae66e3637711c4d96a00038ab9ec935bf5 - React-jsinspectortracing: a12589a0adbb2703cbc4380dabe9a58800810923 - React-jsitracing: 0b1a403d7757cec66b7dd8b308d04db85eef75f3 - React-logger: 304814ae37503c8eb54359851cc55bd4f936b39c - React-Mapbuffer: b588d1ca18d2ce626f868f04ab12d8b1f004f12c - React-microtasksnativemodule: 11831d070aa47755bb5739069eb04ec621fec548 - react-native-config: 3367df9c1f25bb96197007ec531c7087ed4554c3 - react-native-safe-area-context: 9b169299f9dc95f1d7fe1dd266fde53bd899cd0c - react-native-slider: 27263d134d55db948a4706f1e47d0ec88fb354dd - react-native-webview: be9957759cb73cb64f2ed5359e32a85f1f5bdff8 - React-NativeModulesApple: 79a4404ac301b40bec3b367879c5e9a9ce81683c - React-perflogger: 0ea25c109dba33d47dec36b2634bf7ea67c1a555 - React-performancetimeline: f74480de6efbcd8541c34317c0baedb433f27296 + React-featureflagsnativemodule: 7f1bc76d1d2c5bede5e753b8d188dbde7c59b12f + React-graphics: 069e0d0b31ed1e80feb023ad4f7e97f00e84f7b9 + React-hermes: 63df5ac5a944889c8758a6213b39ed825863adb7 + React-idlecallbacksnativemodule: 4c700bd7c0012adf904929075a79418b828b5ffc + React-ImageManager: 5d1ba8a7bae44ebba43fc93da64937c713d42941 + React-jserrorhandler: 0defd58f8bb797cdd0a820f733bf42d8bee708ce + React-jsi: 99d6207ec802ad73473a0dad3c9ad48cd98463f6 + React-jsiexecutor: 8c8097b4ba7e7f480582d6e6238b01be5dcc01c0 + React-jsinspector: ea148ec45bc7ff830e443383ea715f9780c15934 + React-jsinspectortracing: 46bb2841982f01e7b63eaab98140fa1de5b2a1db + React-jsitracing: c1063fc2233960d1c8322291e74bca51d25c10d7 + React-logger: 763728cf4eebc9c5dc9bfc3649e22295784f69f3 + React-Mapbuffer: 63278529b5cf531a7eaf8fc71244fabb062ca90c + React-microtasksnativemodule: 6a39463c32ce831c4c2aa8469273114d894b6be9 + react-native-config: 644074ab88db883fcfaa584f03520ec29589d7df + react-native-safe-area-context: afcc2e2b3e78ae8ef90d81e658aacee34ebc27ea + react-native-slider: 310d3f89edd6ca8344a974bfe83a29a3fbb60e5a + react-native-webview: 80ef603d1df42e24fdde765686fbb9b8a6ecd554 + React-NativeModulesApple: fd0545efbb7f936f78edd15a6564a72d2c34bb32 + React-perflogger: 5f8fa36a8e168fb355efe72099efe77213bc2ac6 + React-performancetimeline: 8c0ecfa1ae459cc5678a65f95ac3bf85644d6feb React-RCTActionSheet: 2ef95837e89b9b154f13cd8401f9054fc3076aff - React-RCTAnimation: 33d960d7f58a81779eea6dea47ad0364c67e1517 - React-RCTAppDelegate: 85c13403fd6f6b6cc630428d52bd8bd76a670dc9 - React-RCTBlob: 74c986a02d951931d2f6ed0e07ed5a7eb385bfc0 - React-RCTFabric: 384a8fea4f22fc0f21299d771971862883ba630a - React-RCTFBReactNativeSpec: eb1c3ec5149f76133593a516ff9d5efe32ebcecd - React-RCTImage: 2c58b5ddeb3c65e52f942bbe13ff9c59bd649b09 - React-RCTLinking: b6b14f8a3e62c02fc627ac4f3fb0c7bd941f907c - React-RCTNetwork: 1d050f2466c1541b339587d46f78d5eee218d626 - React-RCTSettings: 8148f6be0ccc0cfe6e313417ebf8a479caaa2146 - React-RCTText: 64114531ad1359e4e02a4a8af60df606dbbabc25 - React-RCTVibration: f4859417a7dd859b6bf18b1aba897e52beb72ef6 + React-RCTAnimation: 46abefd5acfda7e6629f9e153646deecc70babd2 + React-RCTAppDelegate: 7e58e0299e304cceee3f7019fa77bc6990f66b22 + React-RCTBlob: f68c63a801ef1d27e83c4011e3b083cc86a200d7 + React-RCTFabric: c59f41d0c4edbaac8baa232731ca09925ae4dda7 + React-RCTFBReactNativeSpec: 3240b9b8d792aa4be0fb85c9898fc183125ba8de + React-RCTImage: 34e0bba1507e55f1c614bd759eb91d9be48c8c5b + React-RCTLinking: a0b6c9f4871c18b0b81ea952f43e752718bd5f1d + React-RCTNetwork: bdafd661ac2b20d23b779e45bf7ac3e4c8bd1b60 + React-RCTSettings: 98aa5163796f43789314787b584a84eba47787a9 + React-RCTText: 424a274fc9015b29de89cf3cbcdf4dd85dd69f83 + React-RCTVibration: 92d9875a955b0adb34b4b773528fdbbbc5addd6c React-rendererconsistency: 5ac4164ec18cfdd76ed5f864dbfdc56a5a948bc9 - React-rendererdebug: 3dc1d97bbee0c0c13191e501a96ed9325bbd920e + React-rendererdebug: 710dbd7990e355852c786aa6bc7753f6028f357a React-rncore: 0bace3b991d8843bb5b57c5f2301ec6e9c94718b - React-RuntimeApple: 1e1e0a0c6086bc8c3b07e8f1a2f6ca99b50419a0 - React-RuntimeCore: d39322c59bef2a4b343fda663d20649f29f57fcc + React-RuntimeApple: 701ec44a8b5d863ee9b6a2b2447b6a26bb6805a1 + React-RuntimeCore: a82767065b9a936b05e209dc6987bc1ea9eb5d2d React-runtimeexecutor: 876dfc1d8daa819dfd039c40f78f277c5a3e66a6 - React-RuntimeHermes: 44f5f2baf039f249b31ea4f3e224484fd1731e0e - React-runtimescheduler: 3b3c5b50743bb8743ca49b9e5a70c2c385f156e1 + React-RuntimeHermes: e7a051fd91cab8849df56ac917022ef6064ad621 + React-runtimescheduler: c544141f2124ee3d5f3d5bf0d69f4029a61a68b0 React-timing: 1ee3572c398f5579c9df5bf76aacddf5683ff74e - React-utils: 0cfb7c7fb37d4e5f31cc18ffc7426be0ae6bf907 - ReactAppDependencyProvider: b48473fe434569ff8f6cb6ed4421217ebcbda878 - ReactCodegen: 653a0d8532d8c7dab50c391392044d98e20c9f79 - ReactCommon: 547db015202a80a5b3e7e041586ea54c4a087180 - RNCPicker: ffbd7b9fc7c1341929e61dbef6219f7860f57418 - RNScreens: 0f01bbed9bd8045a8d58e4b46993c28c7f498f3c + React-utils: 18703928768cb37e70cf2efff09def12d74a399e + ReactAppDependencyProvider: 4893bde33952f997a323eb1a1ee87a72764018ff + ReactCodegen: da30aff1cea9b5993dcbc33bf1ef47a463c55194 + ReactCommon: 865ebe76504a95e115b6229dd00a31e56d2d4bfe + RNCPicker: cfb51a08c6e10357d9a65832e791825b0747b483 + RNScreens: 790123c4a28783d80a342ce42e8c7381bed62db1 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: e14bad835e12b6c7e2260fc320bd00e0f4b45add diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index 46710d627..4b2839d0d 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,11 +1,12 @@ PODS: - boost (1.84.0) - - DatadogCore (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogCrashReporting (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogCore (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogCrashReporting (3.0.0): + - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD - DatadogInternal (2.30.2) - DatadogLogs (2.30.2): @@ -20,6 +21,8 @@ PODS: - DatadogTrace (= 2.30.2) - DatadogWebViewTracking (= 2.30.2) ======= +======= +>>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) - DatadogInternal (3.0.0) - DatadogLogs (3.0.0): - DatadogInternal (= 3.0.0) @@ -32,6 +35,7 @@ PODS: - DatadogRUM (= 3.0.0) - DatadogTrace (= 3.0.0) - DatadogWebViewTracking (= 3.0.0) +<<<<<<< HEAD >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= - DatadogInternal (3.1.0) @@ -47,6 +51,8 @@ PODS: - DatadogTrace (= 3.1.0) - DatadogWebViewTracking (= 3.1.0) >>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) +======= +>>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -68,6 +74,7 @@ PODS: - ReactCommon/turbomodule/core - Yoga <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) @@ -79,12 +86,16 @@ PODS: ======= - DatadogSDKReactNative/Tests (2.12.1): <<<<<<< HEAD +======= + - DatadogSDKReactNative/Tests (2.12.1): +>>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) - DatadogCore (= 3.0.0) - DatadogCrashReporting (= 3.0.0) - DatadogLogs (= 3.0.0) - DatadogRUM (= 3.0.0) - DatadogTrace (= 3.0.0) - DatadogWebViewTracking (= 3.0.0) +<<<<<<< HEAD >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= ======= @@ -97,6 +108,8 @@ PODS: - DatadogTrace (= 3.1.0) - DatadogWebViewTracking (= 3.1.0) >>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) +======= +>>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -117,11 +130,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogTrace (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogTrace (3.0.0): + - DatadogInternal (= 3.0.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogWebViewTracking (3.0.0): + - DatadogInternal (= 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1904,6 +1917,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b @@ -1914,6 +1928,8 @@ SPEC CHECKSUMS: DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be ======= +======= +>>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 @@ -1922,6 +1938,7 @@ SPEC CHECKSUMS: DatadogSDKReactNative: 0a80aa75958d595a99be54d2838db53eda7a08af DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 +<<<<<<< HEAD >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d @@ -1933,6 +1950,8 @@ SPEC CHECKSUMS: DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 >>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) +======= +>>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 @@ -1941,62 +1960,62 @@ SPEC CHECKSUMS: hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 OpenTelemetrySwiftApi: aaee576ed961e0c348af78df58b61300e95bd104 PLCrashReporter: db59ef96fa3d25f3650040d02ec2798cffee75f2 - RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 + RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f React: 4641770499c39f45d4e7cde1eba30e081f9d8a3d React-callinvoker: 4bef67b5c7f3f68db5929ab6a4d44b8a002998ea - React-Core: 0a06707a0b34982efc4a556aff5dae4b22863455 - React-CoreModules: 907334e94314189c2e5eed4877f3efe7b26d85b0 - React-cxxreact: 3a1d5e8f4faa5e09be26614e9c8bbcae8d11b73d + React-Core: a68cea3e762814e60ecc3fa521c7f14c36c99245 + React-CoreModules: d81b1eaf8066add66299bab9d23c9f00c9484c7c + React-cxxreact: 984f8b1feeca37181d4e95301fcd6f5f6501c6ab React-debug: 817160c07dc8d24d020fbd1eac7b3558ffc08964 - React-defaultsnativemodule: 814830ccbc3fb08d67d0190e63b179ee4098c67b - React-domnativemodule: 270acf94bd0960b026bc3bfb327e703665d27fb4 - React-Fabric: 64586dc191fc1c170372a638b8e722e4f1d0a09b - React-FabricComponents: b0ebd032387468ea700574c581b139f57a7497fb - React-FabricImage: 81f0e0794caf25ad1224fa406d288fbc1986607f + React-defaultsnativemodule: 18a684542f82ce1897552a1c4b847be414c9566e + React-domnativemodule: 90bdd4ec3ab38c47cfc3461c1e9283a8507d613f + React-Fabric: f6dade7007533daeb785ba5925039d83f343be4b + React-FabricComponents: b0655cc3e1b5ae12a4a1119aa7d8308f0ad33520 + React-FabricImage: 9b157c4c01ac2bf433f834f0e1e5fe234113a576 React-featureflags: f2792b067a351d86fdc7bec23db3b9a2f2c8d26c - React-featureflagsnativemodule: 0d7091ae344d6160c0557048e127897654a5c00f - React-graphics: cbebe910e4a15b65b0bff94a4d3ed278894d6386 - React-hermes: ec18c10f5a69d49fb9b5e17ae95494e9ea13d4d3 - React-idlecallbacksnativemodule: 6b84add48971da9c40403bd1860d4896462590f2 - React-ImageManager: f2a4c01c2ccb2193e60a20c135da74c7ca4d36f2 - React-jserrorhandler: 61d205b5a7cbc57fed3371dd7eed48c97f49fc64 - React-jsi: 95f7676103137861b79b0f319467627bcfa629ee - React-jsiexecutor: 41e0fe87cda9ea3970ffb872ef10f1ff8dbd1932 - React-jsinspector: 15578208796723e5c6f39069b6e8bf36863ef6e2 - React-jsitracing: 3758cdb155ea7711f0e77952572ea62d90c69f0b - React-logger: dbca7bdfd4aa5ef69431362bde6b36d49403cb20 - React-Mapbuffer: 6efad4a606c1fae7e4a93385ee096681ef0300dc - React-microtasksnativemodule: a645237a841d733861c70b69908ab4a1707b52ad + React-featureflagsnativemodule: 742a8325b3c821d2a1ca13a6d2a0fc72d04555e0 + React-graphics: 68969e4e49d73f89da7abef4116c9b5f466aa121 + React-hermes: ac0bcba26a5d288ebc99b500e1097da2d0297ddf + React-idlecallbacksnativemodule: d61d9c9816131bf70d3d80cd04889fc625ee523f + React-ImageManager: e906eec93a9eb6102a06576b89d48d80a4683020 + React-jserrorhandler: ac5dde01104ff444e043cad8f574ca02756e20d6 + React-jsi: 496fa2b9d63b726aeb07d0ac800064617d71211d + React-jsiexecutor: dd22ab48371b80f37a0a30d0e8915b6d0f43a893 + React-jsinspector: 4629ac376f5765e684d19064f2093e55c97fd086 + React-jsitracing: 7a1c9cd484248870cf660733cd3b8114d54c035f + React-logger: c4052eb941cca9a097ef01b59543a656dc088559 + React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de + React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678 - React-NativeModulesApple: 958d4f6c5c2ace4c0f427cf7ef82e28ae6538a22 - React-perflogger: 9b4f13c0afe56bc7b4a0e93ec74b1150421ee22d - React-performancetimeline: 359db1cb889aa0282fafc5838331b0987c4915a9 + React-NativeModulesApple: cebca2e5320a3d66e123cade23bd90a167ffce5e + React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358 + React-performancetimeline: cd6a9374a72001165995d2ab632f672df04076dc React-RCTActionSheet: aacf2375084dea6e7c221f4a727e579f732ff342 - React-RCTAnimation: d8c82deebebe3aaf7a843affac1b57cb2dc073d4 - React-RCTAppDelegate: 1774aa421a29a41a704ecaf789811ef73c4634b6 - React-RCTBlob: 70a58c11a6a3500d1a12f2e51ca4f6c99babcff8 - React-RCTFabric: 731cda82aed592aacce2d32ead69d78cde5d9274 - React-RCTImage: 5e9d655ba6a790c31e3176016f9b47fd0978fbf0 - React-RCTLinking: 2a48338252805091f7521eaf92687206401bdf2a - React-RCTNetwork: 0c1282b377257f6b1c81934f72d8a1d0c010e4c3 - React-RCTSettings: f757b679a74e5962be64ea08d7865a7debd67b40 - React-RCTText: e7d20c490b407d3b4a2daa48db4bcd8ec1032af2 - React-RCTVibration: 8228e37144ca3122a91f1de16ba8e0707159cfec + React-RCTAnimation: 395ab53fd064dff81507c15efb781c8684d9a585 + React-RCTAppDelegate: 345a6f1b82abc578437df0ce7e9c48740eca827c + React-RCTBlob: 13311e554c1a367de063c10ee7c5e6573b2dd1d6 + React-RCTFabric: 007b1a98201cc49b5bc6e1417d7fe3f6fc6e2b78 + React-RCTImage: 1b1f914bcc12187c49ba5d949dac38c2eb9f5cc8 + React-RCTLinking: 4ac7c42beb65e36fba0376f3498f3cd8dd0be7fa + React-RCTNetwork: 938902773add4381e84426a7aa17a2414f5f94f7 + React-RCTSettings: e848f1ba17a7a18479cf5a31d28145f567da8223 + React-RCTText: 7e98fafdde7d29e888b80f0b35544e0cb07913cf + React-RCTVibration: cd7d80affd97dc7afa62f9acd491419558b64b78 React-rendererconsistency: b4917053ecbaa91469c67a4319701c9dc0d40be6 - React-rendererdebug: 81becbc8852b38d9b1b68672aa504556481330d5 + React-rendererdebug: aa181c36dd6cf5b35511d1ed875d6638fd38f0ec React-rncore: 120d21715c9b4ba8f798bffe986cb769b988dd74 - React-RuntimeApple: 52ed0e9e84a7c2607a901149fb13599a3c057655 - React-RuntimeCore: ca6189d2e53d86db826e2673fe8af6571b8be157 + React-RuntimeApple: d033becbbd1eba6f9f6e3af6f1893030ce203edd + React-RuntimeCore: 38af280bb678e66ba000a3c3d42920b2a138eebb React-runtimeexecutor: 877596f82f5632d073e121cba2d2084b76a76899 - React-RuntimeHermes: 3b752dc5d8a1661c9d1687391d6d96acfa385549 - React-runtimescheduler: 8321bb09175ace2a4f0b3e3834637eb85bf42ebe + React-RuntimeHermes: 37aad735ff21ca6de2d8450a96de1afe9f86c385 + React-runtimescheduler: 8ec34cc885281a34696ea16c4fd86892d631f38d React-timing: 331cbf9f2668c67faddfd2e46bb7f41cbd9320b9 - React-utils: 54df9ada708578c8ad40d92895d6fed03e0e8a9e - ReactCodegen: 21a52ccddc6479448fc91903a437dd23ddc7366c - ReactCommon: bfd3600989d79bc3acbe7704161b171a1480b9fd + React-utils: ed818f19ab445000d6b5c4efa9d462449326cc9f + ReactCodegen: f853a20cc9125c5521c8766b4b49375fec20648b + ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 60d0b1b71..cacae2dff 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,34 +1,34 @@ PODS: - boost (1.84.0) - - DatadogCore (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogCrashReporting (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogCore (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogCrashReporting (3.0.0): + - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.1.0) - - DatadogLogs (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogRUM (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.13.0): - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) + - DatadogInternal (3.0.0) + - DatadogLogs (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogRUM (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogSDKReactNative (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) - React-Core - - DatadogSDKReactNative/Tests (2.13.0): - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) + - DatadogSDKReactNative/Tests (2.12.1): + - DatadogCore (= 3.0.0) + - DatadogCrashReporting (= 3.0.0) + - DatadogLogs (= 3.0.0) + - DatadogRUM (= 3.0.0) + - DatadogTrace (= 3.0.0) + - DatadogWebViewTracking (= 3.0.0) - React-Core - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.1.0) + - DatadogSessionReplay (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -51,7 +51,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay/Tests (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.1.0) + - DatadogSessionReplay (= 3.0.0) - DoubleConversion - glog - hermes-engine @@ -73,25 +73,25 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.13.0): - - DatadogInternal (= 3.1.0) + - DatadogSDKReactNativeWebView (2.12.1): + - DatadogInternal (= 3.0.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.1.0) + - DatadogWebViewTracking (= 3.0.0) - React-Core - - DatadogSDKReactNativeWebView/Tests (2.13.0): - - DatadogInternal (= 3.1.0) + - DatadogSDKReactNativeWebView/Tests (2.12.1): + - DatadogInternal (= 3.0.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.1.0) + - DatadogWebViewTracking (= 3.0.0) - React-Core - react-native-webview - React-RCTText - - DatadogSessionReplay (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogTrace (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogSessionReplay (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogTrace (3.0.0): + - DatadogInternal (= 3.0.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogWebViewTracking (3.0.0): + - DatadogInternal (= 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1988,17 +1988,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d - DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d - DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc - DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 - DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 - DatadogSDKReactNative: 822ff8092666172584d4d5e56f79c3799887d408 - DatadogSDKReactNativeSessionReplay: afc4e2b1db34ba8af3a442b0691359faaf5e586e - DatadogSDKReactNativeWebView: 00affefdaca0cf2375e669fa03925d8fa75263d0 - DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 - DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 - DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 + DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 + DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e + DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 + DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd + DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f + DatadogSDKReactNative: e430b3f4d7adb0fac07b61e2fb65ceacdaf82b3e + DatadogSDKReactNativeSessionReplay: 4b2a3d166a79581f18522795b40141c34cf3685d + DatadogSDKReactNativeWebView: 35dc2b9736e1aaa82b366bf6b8a8a959a9b088c5 + DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 + DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 + DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 @@ -2008,69 +2008,69 @@ SPEC CHECKSUMS: HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352 OpenTelemetrySwiftApi: aaee576ed961e0c348af78df58b61300e95bd104 PLCrashReporter: db59ef96fa3d25f3650040d02ec2798cffee75f2 - RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 + RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f React: 4641770499c39f45d4e7cde1eba30e081f9d8a3d React-callinvoker: 4bef67b5c7f3f68db5929ab6a4d44b8a002998ea - React-Core: 0a06707a0b34982efc4a556aff5dae4b22863455 - React-CoreModules: 907334e94314189c2e5eed4877f3efe7b26d85b0 - React-cxxreact: 3a1d5e8f4faa5e09be26614e9c8bbcae8d11b73d + React-Core: a68cea3e762814e60ecc3fa521c7f14c36c99245 + React-CoreModules: d81b1eaf8066add66299bab9d23c9f00c9484c7c + React-cxxreact: 984f8b1feeca37181d4e95301fcd6f5f6501c6ab React-debug: 817160c07dc8d24d020fbd1eac7b3558ffc08964 - React-defaultsnativemodule: a965cb39fb0a79276ab611793d39f52e59a9a851 - React-domnativemodule: d647f94e503c62c44f54291334b1aa22a30fa08b - React-Fabric: 64586dc191fc1c170372a638b8e722e4f1d0a09b - React-FabricComponents: b0ebd032387468ea700574c581b139f57a7497fb - React-FabricImage: 81f0e0794caf25ad1224fa406d288fbc1986607f + React-defaultsnativemodule: 21f216e8db975897eb32b5f13247f5bbfaa97f41 + React-domnativemodule: 19270ad4b8d33312838d257f24731a0026809d49 + React-Fabric: f6dade7007533daeb785ba5925039d83f343be4b + React-FabricComponents: b0655cc3e1b5ae12a4a1119aa7d8308f0ad33520 + React-FabricImage: 9b157c4c01ac2bf433f834f0e1e5fe234113a576 React-featureflags: f2792b067a351d86fdc7bec23db3b9a2f2c8d26c - React-featureflagsnativemodule: 95a02d895475de8ace78fedd76143866838bb720 - React-graphics: cbebe910e4a15b65b0bff94a4d3ed278894d6386 - React-hermes: ec18c10f5a69d49fb9b5e17ae95494e9ea13d4d3 - React-idlecallbacksnativemodule: 0c1ae840cc5587197cd926a3cb76828ad059d116 - React-ImageManager: f2a4c01c2ccb2193e60a20c135da74c7ca4d36f2 - React-jserrorhandler: 61d205b5a7cbc57fed3371dd7eed48c97f49fc64 - React-jsi: 95f7676103137861b79b0f319467627bcfa629ee - React-jsiexecutor: 41e0fe87cda9ea3970ffb872ef10f1ff8dbd1932 - React-jsinspector: 15578208796723e5c6f39069b6e8bf36863ef6e2 - React-jsitracing: 3758cdb155ea7711f0e77952572ea62d90c69f0b - React-logger: dbca7bdfd4aa5ef69431362bde6b36d49403cb20 - React-Mapbuffer: 6efad4a606c1fae7e4a93385ee096681ef0300dc - React-microtasksnativemodule: 8732b71aa66045da4bb341ddee1bb539f71e5f38 - react-native-crash-tester: 3ffaa64141427ca362079cb53559fe9a532487ae - react-native-safe-area-context: 04803a01f39f31cc6605a5531280b477b48f8a88 - react-native-webview: 1e12de2fad74c17b4f8b1b53ebd1e3baa0148d71 + React-featureflagsnativemodule: 3a8731d8fd9f755be57e00d9fa8a7f92aa77e87d + React-graphics: 68969e4e49d73f89da7abef4116c9b5f466aa121 + React-hermes: ac0bcba26a5d288ebc99b500e1097da2d0297ddf + React-idlecallbacksnativemodule: 9a2c5b5c174c0c476f039bedc1b9497a8272133e + React-ImageManager: e906eec93a9eb6102a06576b89d48d80a4683020 + React-jserrorhandler: ac5dde01104ff444e043cad8f574ca02756e20d6 + React-jsi: 496fa2b9d63b726aeb07d0ac800064617d71211d + React-jsiexecutor: dd22ab48371b80f37a0a30d0e8915b6d0f43a893 + React-jsinspector: 4629ac376f5765e684d19064f2093e55c97fd086 + React-jsitracing: 7a1c9cd484248870cf660733cd3b8114d54c035f + React-logger: c4052eb941cca9a097ef01b59543a656dc088559 + React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de + React-microtasksnativemodule: 5c3d795318c22ab8df55100e50b151384a4a60b3 + react-native-crash-tester: 48bde9d6f5256c61ef2e0c52dfc74256b26e55eb + react-native-safe-area-context: e134b241010ebe2aacdcea013565963d13826faa + react-native-webview: 2ea635bc43fd8a4b89de61133e8cc0607084e9f8 React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678 - React-NativeModulesApple: 958d4f6c5c2ace4c0f427cf7ef82e28ae6538a22 - React-perflogger: 9b4f13c0afe56bc7b4a0e93ec74b1150421ee22d - React-performancetimeline: 359db1cb889aa0282fafc5838331b0987c4915a9 + React-NativeModulesApple: cebca2e5320a3d66e123cade23bd90a167ffce5e + React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358 + React-performancetimeline: cd6a9374a72001165995d2ab632f672df04076dc React-RCTActionSheet: aacf2375084dea6e7c221f4a727e579f732ff342 - React-RCTAnimation: d8c82deebebe3aaf7a843affac1b57cb2dc073d4 - React-RCTAppDelegate: 6c0377d9c4058773ea7073bb34bb9ebd6ddf5a84 - React-RCTBlob: 70a58c11a6a3500d1a12f2e51ca4f6c99babcff8 - React-RCTFabric: 7eb6dd2c8fda98cb860a572e3f4e4eb60d62c89e - React-RCTImage: 5e9d655ba6a790c31e3176016f9b47fd0978fbf0 - React-RCTLinking: 2a48338252805091f7521eaf92687206401bdf2a - React-RCTNetwork: 0c1282b377257f6b1c81934f72d8a1d0c010e4c3 - React-RCTSettings: f757b679a74e5962be64ea08d7865a7debd67b40 - React-RCTText: e7d20c490b407d3b4a2daa48db4bcd8ec1032af2 - React-RCTVibration: 8228e37144ca3122a91f1de16ba8e0707159cfec + React-RCTAnimation: 395ab53fd064dff81507c15efb781c8684d9a585 + React-RCTAppDelegate: 1e5b43833e3e36e9fa34eec20be98174bc0e14a2 + React-RCTBlob: 13311e554c1a367de063c10ee7c5e6573b2dd1d6 + React-RCTFabric: bd906861a4e971e21d8df496c2d8f3ca6956f840 + React-RCTImage: 1b1f914bcc12187c49ba5d949dac38c2eb9f5cc8 + React-RCTLinking: 4ac7c42beb65e36fba0376f3498f3cd8dd0be7fa + React-RCTNetwork: 938902773add4381e84426a7aa17a2414f5f94f7 + React-RCTSettings: e848f1ba17a7a18479cf5a31d28145f567da8223 + React-RCTText: 7e98fafdde7d29e888b80f0b35544e0cb07913cf + React-RCTVibration: cd7d80affd97dc7afa62f9acd491419558b64b78 React-rendererconsistency: b4917053ecbaa91469c67a4319701c9dc0d40be6 - React-rendererdebug: 81becbc8852b38d9b1b68672aa504556481330d5 + React-rendererdebug: aa181c36dd6cf5b35511d1ed875d6638fd38f0ec React-rncore: 120d21715c9b4ba8f798bffe986cb769b988dd74 - React-RuntimeApple: 52ed0e9e84a7c2607a901149fb13599a3c057655 - React-RuntimeCore: ca6189d2e53d86db826e2673fe8af6571b8be157 + React-RuntimeApple: d033becbbd1eba6f9f6e3af6f1893030ce203edd + React-RuntimeCore: 38af280bb678e66ba000a3c3d42920b2a138eebb React-runtimeexecutor: 877596f82f5632d073e121cba2d2084b76a76899 - React-RuntimeHermes: 3b752dc5d8a1661c9d1687391d6d96acfa385549 - React-runtimescheduler: 8321bb09175ace2a4f0b3e3834637eb85bf42ebe + React-RuntimeHermes: 37aad735ff21ca6de2d8450a96de1afe9f86c385 + React-runtimescheduler: 8ec34cc885281a34696ea16c4fd86892d631f38d React-timing: 331cbf9f2668c67faddfd2e46bb7f41cbd9320b9 - React-utils: 54df9ada708578c8ad40d92895d6fed03e0e8a9e - ReactCodegen: 21a52ccddc6479448fc91903a437dd23ddc7366c - ReactCommon: bfd3600989d79bc3acbe7704161b171a1480b9fd - ReactNativeNavigation: 50c1eef68b821e7265eff3a391d27ed18fdce459 - RNCAsyncStorage: 23e56519cc41d3bade3c8d4479f7760cb1c11996 - RNGestureHandler: 950dfa674dbf481460ca389c65b9036ac4ab8ada - RNScreens: 606ab1cf68162f7ba0d049a31f2a84089a6fffb4 + React-utils: ed818f19ab445000d6b5c4efa9d462449326cc9f + ReactCodegen: f853a20cc9125c5521c8766b4b49375fec20648b + ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9 + ReactNativeNavigation: 445f86273eb245d15b14023ee4ef9d6e4f891ad6 + RNCAsyncStorage: b44e8a4e798c3e1f56bffccd0f591f674fb9198f + RNGestureHandler: cb711d56ee3b03a5adea1d38324d4459ab55653f + RNScreens: f75b26fd4777848c216e27b0a09e1bf9c9f4760a SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a diff --git a/packages/core/DatadogSDKReactNative.podspec b/packages/core/DatadogSDKReactNative.podspec index c0d235304..a2f41ddd1 100644 --- a/packages/core/DatadogSDKReactNative.podspec +++ b/packages/core/DatadogSDKReactNative.podspec @@ -19,14 +19,14 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the versions in sync with DatadogSDKReactNativeSessionReplay.podspec - s.dependency 'DatadogCore', '3.1.0' - s.dependency 'DatadogLogs', '3.1.0' - s.dependency 'DatadogTrace', '3.1.0' - s.dependency 'DatadogRUM', '3.1.0' - s.dependency 'DatadogCrashReporting', '3.1.0' + s.dependency 'DatadogCore', '3.0.0' + s.dependency 'DatadogLogs', '3.0.0' + s.dependency 'DatadogTrace', '3.0.0' + s.dependency 'DatadogRUM', '3.0.0' + s.dependency 'DatadogCrashReporting', '3.0.0' # DatadogWebViewTracking is not available for tvOS - s.ios.dependency 'DatadogWebViewTracking', '3.1.0' + s.ios.dependency 'DatadogWebViewTracking', '3.0.0' s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'ios/Tests/**/*.{swift,json}' diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 080185d41..a8d09d4d1 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -201,16 +201,16 @@ dependencies { // This breaks builds if the React Native target is below 0.76.0. as it relies on Gradle 8.5.0. // To avoid this, we enforce 1.0.0-beta01 on RN < 0.76.0 if (reactNativeMinorVersion < 76) { - implementation("com.datadoghq:dd-sdk-android-rum:3.2.0") { + implementation("com.datadoghq:dd-sdk-android-rum:3.0.0") { exclude group: "androidx.metrics", module: "metrics-performance" } implementation "androidx.metrics:metrics-performance:1.0.0-beta01" } else { - implementation "com.datadoghq:dd-sdk-android-rum:3.2.0" + implementation "com.datadoghq:dd-sdk-android-rum:3.0.0" } - implementation "com.datadoghq:dd-sdk-android-logs:3.2.0" - implementation "com.datadoghq:dd-sdk-android-trace:3.2.0" - implementation "com.datadoghq:dd-sdk-android-webview:3.2.0" + implementation "com.datadoghq:dd-sdk-android-logs:3.0.0" + implementation "com.datadoghq:dd-sdk-android-trace:3.0.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" implementation "com.google.code.gson:gson:2.10.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec index 6a5d0b78f..3a2d5ff48 100644 --- a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec +++ b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec - s.dependency 'DatadogSessionReplay', '3.1.0' + s.dependency 'DatadogSessionReplay', '3.0.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle index bea305684..6d6fe20b1 100644 --- a/packages/react-native-session-replay/android/build.gradle +++ b/packages/react-native-session-replay/android/build.gradle @@ -216,8 +216,8 @@ dependencies { api "com.facebook.react:react-android:$reactNativeVersion" } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.datadoghq:dd-sdk-android-session-replay:3.2.0" - implementation "com.datadoghq:dd-sdk-android-internal:3.2.0" + implementation "com.datadoghq:dd-sdk-android-session-replay:3.0.0" + implementation "com.datadoghq:dd-sdk-android-internal:3.0.0" implementation project(path: ':datadog_mobile-react-native') testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" diff --git a/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec b/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec index 080a853d8..26e160bbc 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.1.0' - s.dependency 'DatadogInternal', '3.1.0' + s.dependency 'DatadogWebViewTracking', '3.0.0' + s.dependency 'DatadogInternal', '3.0.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-webview/android/build.gradle b/packages/react-native-webview/android/build.gradle index 95e1e42e0..8eeaea88e 100644 --- a/packages/react-native-webview/android/build.gradle +++ b/packages/react-native-webview/android/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "com.facebook.react:react-android:$reactNativeVersion" } - implementation "com.datadoghq:dd-sdk-android-webview:3.2.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(path: ':datadog_mobile-react-native') From 1d9c8be8c905dc39120aeaaae2865dba3dfa09c5 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 12 Sep 2025 14:43:25 +0200 Subject: [PATCH 037/114] Remove setUser --- packages/core/ios/Tests/MockRUMMonitor.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index 4a1d0cd92..c692f6e7c 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,6 +10,22 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { + func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { + // not implemented + } + + func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { + // not implemented + } + + func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { + // not implemented + } + + func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { + // not implemented + } + func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } From b1d78d48afe812c3bb871710f2c67739323f7dc5 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 11:45:00 +0200 Subject: [PATCH 038/114] Bump Native SDKs to 3.1.0 --- benchmarks/android/app/build.gradle | 2 +- benchmarks/ios/Podfile.lock | 70 +++++++-------- example-new-architecture/ios/Podfile.lock | 43 +++++++-- example/ios/Podfile.lock | 88 +++++++++---------- packages/core/DatadogSDKReactNative.podspec | 12 +-- packages/core/android/build.gradle | 10 +-- ...DatadogSDKReactNativeSessionReplay.podspec | 2 +- .../android/build.gradle | 4 +- .../DatadogSDKReactNativeWebView.podspec | 4 +- .../react-native-webview/android/build.gradle | 2 +- 10 files changed, 132 insertions(+), 105 deletions(-) diff --git a/benchmarks/android/app/build.gradle b/benchmarks/android/app/build.gradle index 7dc4b3707..04c240bd0 100644 --- a/benchmarks/android/app/build.gradle +++ b/benchmarks/android/app/build.gradle @@ -129,5 +129,5 @@ dependencies { // Benchmark tools from dd-sdk-android are used for vitals recording // Remember to bump thid alongside the main dd-sdk-android dependencies - implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.0.0") + implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.1.0") } diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index 51319ec2c..2da943f5e 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -1,22 +1,22 @@ PODS: - boost (1.84.0) - - DatadogCore (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogCrashReporting (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogCore (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogCrashReporting (3.1.0): + - DatadogInternal (= 3.1.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.0.0) - - DatadogLogs (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogRUM (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogInternal (3.1.0) + - DatadogLogs (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogRUM (3.1.0): + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative (2.12.1): - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -39,7 +39,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.0.0) + - DatadogSessionReplay (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -61,9 +61,9 @@ PODS: - ReactCommon/turbomodule/core - Yoga - DatadogSDKReactNativeWebView (2.12.1): - - DatadogInternal (= 3.0.0) + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.0.0) + - DatadogWebViewTracking (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -84,13 +84,13 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSessionReplay (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogTrace (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogSessionReplay (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogTrace (3.1.0): + - DatadogInternal (= 3.1.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogWebViewTracking (3.1.0): + - DatadogInternal (= 3.1.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.78.2) @@ -2070,17 +2070,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 - DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e - DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 - DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd - DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f - DatadogSDKReactNative: 241bf982c16ceff03d94a58e6d005e55c47b99c6 - DatadogSDKReactNativeSessionReplay: bba36092686e3183e97c1a0c7f4ca8142582ae28 - DatadogSDKReactNativeWebView: 6da060df20e235abac533e582d9fc2b3a5070840 - DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 - DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 - DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 + DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d + DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d + DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc + DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 + DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 + DatadogSDKReactNative: 8e0f39de38621d4d7ed961a74d8a216fd3a38321 + DatadogSDKReactNativeSessionReplay: f9288c8e981dcc65d1f727b01421ee9a7601e75f + DatadogSDKReactNativeWebView: 993527f6c5d38e0fcc4804a6a60c334dd199dc5b + DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 + DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 + DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: e32d34492c519a2194ec9d7f5e7a79d11b73f91c diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index 4b2839d0d..d0e64cb43 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,12 +1,13 @@ PODS: - boost (1.84.0) - - DatadogCore (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogCrashReporting (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogCore (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogCrashReporting (3.1.0): + - DatadogInternal (= 3.1.0) - PLCrashReporter (~> 1.12.0) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD - DatadogInternal (2.30.2) - DatadogLogs (2.30.2): @@ -44,15 +45,26 @@ PODS: - DatadogRUM (3.1.0): - DatadogInternal (= 3.1.0) - DatadogSDKReactNative (2.13.0): +======= + - DatadogInternal (3.1.0) + - DatadogLogs (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogRUM (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogSDKReactNative (2.12.1): +>>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) - DatadogCore (= 3.1.0) - DatadogCrashReporting (= 3.1.0) - DatadogLogs (= 3.1.0) - DatadogRUM (= 3.1.0) - DatadogTrace (= 3.1.0) - DatadogWebViewTracking (= 3.1.0) +<<<<<<< HEAD >>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) ======= >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) +======= +>>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) - DoubleConversion - glog - hermes-engine @@ -86,6 +98,7 @@ PODS: ======= - DatadogSDKReactNative/Tests (2.12.1): <<<<<<< HEAD +<<<<<<< HEAD ======= - DatadogSDKReactNative/Tests (2.12.1): >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) @@ -101,15 +114,20 @@ PODS: ======= - DatadogSDKReactNative/Tests (2.13.0): >>>>>>> 71638bf0 (Bump Android Native SDK to 3.2.0 and regenerate app podfiles) +======= +>>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) - DatadogCore (= 3.1.0) - DatadogCrashReporting (= 3.1.0) - DatadogLogs (= 3.1.0) - DatadogRUM (= 3.1.0) - DatadogTrace (= 3.1.0) - DatadogWebViewTracking (= 3.1.0) +<<<<<<< HEAD >>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) ======= >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) +======= +>>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) - DoubleConversion - glog - hermes-engine @@ -130,11 +148,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogTrace (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogTrace (3.1.0): + - DatadogInternal (= 3.1.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogWebViewTracking (3.1.0): + - DatadogInternal (= 3.1.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1918,6 +1936,7 @@ SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b @@ -1941,17 +1960,25 @@ SPEC CHECKSUMS: <<<<<<< HEAD >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= +======= +>>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 +<<<<<<< HEAD DatadogSDKReactNative: 2f11191b56e18680f633bfb125ab1832b327d9b4 DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 >>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) ======= >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) +======= + DatadogSDKReactNative: 069ea9876220b2d09b0f4b180ce571b1b6ecbb35 + DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 + DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 +>>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index cacae2dff..190e83448 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,34 +1,34 @@ PODS: - boost (1.84.0) - - DatadogCore (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogCrashReporting (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogCore (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogCrashReporting (3.1.0): + - DatadogInternal (= 3.1.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.0.0) - - DatadogLogs (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogRUM (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogInternal (3.1.0) + - DatadogLogs (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogRUM (3.1.0): + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative (2.12.1): - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - DatadogSDKReactNative/Tests (2.12.1): - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.0.0) + - DatadogSessionReplay (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -51,7 +51,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay/Tests (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.0.0) + - DatadogSessionReplay (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -74,24 +74,24 @@ PODS: - ReactCommon/turbomodule/core - Yoga - DatadogSDKReactNativeWebView (2.12.1): - - DatadogInternal (= 3.0.0) + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.0.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - DatadogSDKReactNativeWebView/Tests (2.12.1): - - DatadogInternal (= 3.0.0) + - DatadogInternal (= 3.1.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.0.0) + - DatadogWebViewTracking (= 3.1.0) - React-Core - react-native-webview - React-RCTText - - DatadogSessionReplay (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogTrace (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogSessionReplay (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogTrace (3.1.0): + - DatadogInternal (= 3.1.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogWebViewTracking (3.1.0): + - DatadogInternal (= 3.1.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1988,17 +1988,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 - DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e - DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 - DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd - DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f - DatadogSDKReactNative: e430b3f4d7adb0fac07b61e2fb65ceacdaf82b3e - DatadogSDKReactNativeSessionReplay: 4b2a3d166a79581f18522795b40141c34cf3685d - DatadogSDKReactNativeWebView: 35dc2b9736e1aaa82b366bf6b8a8a959a9b088c5 - DatadogSessionReplay: 0357b911c6ab42c77c93b29761ecc20d9282e971 - DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 - DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 + DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d + DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d + DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc + DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 + DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 + DatadogSDKReactNative: af351a4e1ce08124c290c52de94b0062a166cc67 + DatadogSDKReactNativeSessionReplay: dcbd55d9d0f2b86026996a8b7ec9654922d5dfe1 + DatadogSDKReactNativeWebView: 096ac87eb753b6a217b93441983264b9837c3b7e + DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 + DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 + DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/packages/core/DatadogSDKReactNative.podspec b/packages/core/DatadogSDKReactNative.podspec index a2f41ddd1..c0d235304 100644 --- a/packages/core/DatadogSDKReactNative.podspec +++ b/packages/core/DatadogSDKReactNative.podspec @@ -19,14 +19,14 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the versions in sync with DatadogSDKReactNativeSessionReplay.podspec - s.dependency 'DatadogCore', '3.0.0' - s.dependency 'DatadogLogs', '3.0.0' - s.dependency 'DatadogTrace', '3.0.0' - s.dependency 'DatadogRUM', '3.0.0' - s.dependency 'DatadogCrashReporting', '3.0.0' + s.dependency 'DatadogCore', '3.1.0' + s.dependency 'DatadogLogs', '3.1.0' + s.dependency 'DatadogTrace', '3.1.0' + s.dependency 'DatadogRUM', '3.1.0' + s.dependency 'DatadogCrashReporting', '3.1.0' # DatadogWebViewTracking is not available for tvOS - s.ios.dependency 'DatadogWebViewTracking', '3.0.0' + s.ios.dependency 'DatadogWebViewTracking', '3.1.0' s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'ios/Tests/**/*.{swift,json}' diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index a8d09d4d1..59bb75cc3 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -201,16 +201,16 @@ dependencies { // This breaks builds if the React Native target is below 0.76.0. as it relies on Gradle 8.5.0. // To avoid this, we enforce 1.0.0-beta01 on RN < 0.76.0 if (reactNativeMinorVersion < 76) { - implementation("com.datadoghq:dd-sdk-android-rum:3.0.0") { + implementation("com.datadoghq:dd-sdk-android-rum:3.1.0") { exclude group: "androidx.metrics", module: "metrics-performance" } implementation "androidx.metrics:metrics-performance:1.0.0-beta01" } else { - implementation "com.datadoghq:dd-sdk-android-rum:3.0.0" + implementation "com.datadoghq:dd-sdk-android-rum:3.1.0" } - implementation "com.datadoghq:dd-sdk-android-logs:3.0.0" - implementation "com.datadoghq:dd-sdk-android-trace:3.0.0" - implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" + implementation "com.datadoghq:dd-sdk-android-logs:3.1.0" + implementation "com.datadoghq:dd-sdk-android-trace:3.1.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" implementation "com.google.code.gson:gson:2.10.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec index 3a2d5ff48..6a5d0b78f 100644 --- a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec +++ b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec - s.dependency 'DatadogSessionReplay', '3.0.0' + s.dependency 'DatadogSessionReplay', '3.1.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle index 6d6fe20b1..a0d77f2ff 100644 --- a/packages/react-native-session-replay/android/build.gradle +++ b/packages/react-native-session-replay/android/build.gradle @@ -216,8 +216,8 @@ dependencies { api "com.facebook.react:react-android:$reactNativeVersion" } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.datadoghq:dd-sdk-android-session-replay:3.0.0" - implementation "com.datadoghq:dd-sdk-android-internal:3.0.0" + implementation "com.datadoghq:dd-sdk-android-session-replay:3.1.0" + implementation "com.datadoghq:dd-sdk-android-internal:3.1.0" implementation project(path: ':datadog_mobile-react-native') testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" diff --git a/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec b/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec index 26e160bbc..080a853d8 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.0.0' - s.dependency 'DatadogInternal', '3.0.0' + s.dependency 'DatadogWebViewTracking', '3.1.0' + s.dependency 'DatadogInternal', '3.1.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-webview/android/build.gradle b/packages/react-native-webview/android/build.gradle index 8eeaea88e..e1fb081f0 100644 --- a/packages/react-native-webview/android/build.gradle +++ b/packages/react-native-webview/android/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "com.facebook.react:react-android:$reactNativeVersion" } - implementation "com.datadoghq:dd-sdk-android-webview:3.0.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(path: ':datadog_mobile-react-native') From 8e49c27ee414cb2622f2617bf0f02bbc27a453a6 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Sep 2025 17:47:29 +0200 Subject: [PATCH 039/114] Use native sdk's core instance instead of the one inside RN SDK wrapper --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 ++ .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +----- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 +------ 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index 4388ad5f6..ee55d08fe 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,7 +65,9 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) + Logs.enable(logsConfiguration, Datadog.getInstance()) + Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 958ba521b..df44a6b5f 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -22,11 +22,7 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation( - reactContext, - datadog = datadogWrapper, - ddTelemetry - ) + private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 327d8ffc0..f034b292c 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -164,12 +164,7 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation( - mockReactContext, - mockDatadog, - mockDdTelemetry, - TestUiThreadExecutor() - ) + testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From 4ecc84e75d3d67c37a35a1a885fa79e19442d24d Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Mon, 22 Sep 2025 16:20:27 +0200 Subject: [PATCH 040/114] Fixed internal testing tools and unit tests --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 -- .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +++++- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 ++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index ee55d08fe..4388ad5f6 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,9 +65,7 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) - Logs.enable(logsConfiguration, Datadog.getInstance()) - Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index df44a6b5f..958ba521b 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -22,7 +22,11 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) + private val implementation = DdSdkImplementation( + reactContext, + datadog = datadogWrapper, + ddTelemetry + ) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index f034b292c..327d8ffc0 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -164,7 +164,12 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) + testedBridgeSdk = DdSdkImplementation( + mockReactContext, + mockDatadog, + mockDdTelemetry, + TestUiThreadExecutor() + ) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From d9e53a37371dd8a5597ed2504ecd30bae13d2f0c Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 17:37:31 +0200 Subject: [PATCH 041/114] Expose clearUserInfo API --- .../ios/Sources/DdSdkImplementation.swift | 8 +-- packages/core/ios/Tests/DdSdkTests.swift | 67 +++++++++++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 997068ddf..2c5d4cdf3 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -143,7 +143,7 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - + @objc public func addUserExtraInfo( extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock @@ -155,15 +155,13 @@ public class DdSdkImplementation: NSObject { } @objc - public func clearUserInfo(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + public func clearUserInfo(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { Datadog.clearUserInfo() resolve(nil) } @objc - public func setTrackingConsent( - trackingConsent: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock - ) { + public func setTrackingConsent(trackingConsent: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { Datadog.set(trackingConsent: (trackingConsent as NSString?).asTrackingConsent()) resolve(nil) } diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index 4a5d13f2e..5d0237530 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -651,6 +651,73 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } + + func testClearUserInfo() throws { + let bridge = DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: JSRefreshRateMonitor(), + RUMMonitorProvider: { MockRUMMonitor() }, + RUMMonitorInternalProvider: { nil } + ) + bridge.initialize( + configuration: .mockAny(), + resolve: mockResolve, + reject: mockReject + ) + + bridge.setUserInfo( + userInfo: NSDictionary( + dictionary: [ + "id": "id_123", + "name": "John Doe", + "email": "john@doe.com", + "extraInfo": [ + "extra-info-1": 123, + "extra-info-2": "abc", + "extra-info-3": true, + "extra-info-4": [ + "nested-extra-info-1": 456 + ], + ], + ] + ), + resolve: mockResolve, + reject: mockReject + ) + + var ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + var userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, "id_123") + XCTAssertEqual(userInfo.name, "John Doe") + XCTAssertEqual(userInfo.email, "john@doe.com") + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) + + if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] + as? DatadogSDKReactNative.AnyEncodable, + let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] + { + XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) + } else { + XCTFail("extra-info-4 is not of expected type or value") + } + + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) + + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, nil) + XCTAssertEqual(userInfo.name, nil) + XCTAssertEqual(userInfo.email, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) + } func testClearUserInfo() throws { let bridge = DdSdkImplementation( From c232936f037f96494953e09a52e140c5fa98305e Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 29 Sep 2025 12:20:44 +0200 Subject: [PATCH 042/114] Update attribute API --- packages/core/ios/Tests/DdSdkTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index 5d0237530..cba49f84f 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -651,7 +651,7 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } - + func testClearUserInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -704,12 +704,12 @@ class DdSdkTests: XCTestCase { } else { XCTFail("extra-info-4 is not of expected type or value") } - + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) - + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() userInfo = try XCTUnwrap(ddContext.userInfo) - + XCTAssertEqual(userInfo.id, nil) XCTAssertEqual(userInfo.name, nil) XCTAssertEqual(userInfo.email, nil) From ea59f120fd4144f3636033f8ffad21c4c929f8bb Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 16 Oct 2025 11:01:21 +0200 Subject: [PATCH 043/114] JS refresh rate normalization --- .../reactnative/DdSdkImplementation.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index 6741e971e..29fdcdce0 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -354,6 +354,32 @@ class DdSdkImplementation( return display.supportedModes.maxOf { it.refreshRate.toDouble() } } + // endregion + + val maxDeviceFrameTimeMs = 1000.0 / maxDeviceDisplayHz + val budgetFrameTimeMs = 1000.0 / frameBudgetHz + + if (listOf( + maxDeviceDisplayHz, frameTimeMs, frameBudgetHz, budgetFrameTimeMs, maxDeviceFrameTimeMs + ).any { !it.isFinite() || it <= 0.0 } + ) return 1.0 / DEFAULT_REFRESH_HZ + + + var normalizedFrameTimeMs = frameTimeMs / (maxDeviceFrameTimeMs / budgetFrameTimeMs) + + normalizedFrameTimeMs = max(normalizedFrameTimeMs, maxDeviceFrameTimeMs) + + return normalizedFrameTimeMs / 1000.0 // in seconds + } + + @Suppress("CyclomaticComplexMethod") + private fun getMaxDisplayRefreshRate(context: Context?): Double { + val dm = context?.getSystemService(Context.DISPLAY_SERVICE) as? DisplayManager ?: return 60.0 + val display: Display = dm.getDisplay(Display.DEFAULT_DISPLAY) ?: return DEFAULT_REFRESH_HZ + + return display.supportedModes.maxOf { it.refreshRate.toDouble() } + } + // endregion internal companion object { internal const val DEFAULT_APP_VERSION = "?" From 70211b2fc9e65cbf7f2743212cf571ce4f54c225 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 10 Oct 2025 14:52:21 +0200 Subject: [PATCH 044/114] Expose view Attributes API --- .../ios/Sources/DdSdkImplementation.swift | 25 +++++++++++-------- packages/core/ios/Tests/MockRUMMonitor.swift | 16 ------------ 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 2c5d4cdf3..5b91039b3 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -143,7 +143,7 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - + @objc public func addUserExtraInfo( extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock @@ -155,13 +155,15 @@ public class DdSdkImplementation: NSObject { } @objc - public func clearUserInfo(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func clearUserInfo(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { Datadog.clearUserInfo() resolve(nil) } @objc - public func setTrackingConsent(trackingConsent: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func setTrackingConsent( + trackingConsent: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { Datadog.set(trackingConsent: (trackingConsent as NSString?).asTrackingConsent()) resolve(nil) } @@ -173,7 +175,8 @@ public class DdSdkImplementation: NSObject { ) { let castedAttributes = castAttributesToSwift(attributes) let castedConfig = castAttributesToSwift(config) - DdTelemetry.sendTelemetryLog(message: message as String, attributes: castedAttributes, config: castedConfig) + DdTelemetry.sendTelemetryLog( + message: message as String, attributes: castedAttributes, config: castedConfig) resolve(nil) } @@ -184,13 +187,13 @@ public class DdSdkImplementation: NSObject { resolve(nil) } -#if os(iOS) @objc public func telemetryError(message: NSString, stack: NSString, kind: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { DdTelemetry.telemetryError(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as String, stack: stack as String) resolve(nil) } +#if os(iOS) @objc public func consumeWebviewEvent( message: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock @@ -207,19 +210,21 @@ public class DdSdkImplementation: NSObject { resolve(nil) } + #endif - @objc - public func clearAllData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + public func clearAllData(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { Datadog.clearAllData() resolve(nil) } - func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) -> Void { + func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) { DdTelemetry.overrideTelemetryConfiguration( - initializationType: rnConfiguration.configurationForTelemetry?.initializationType as? String, - reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion as? String, + initializationType: rnConfiguration.configurationForTelemetry?.initializationType + as? String, + reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion + as? String, reactVersion: rnConfiguration.configurationForTelemetry?.reactVersion as? String, trackCrossPlatformLongTasks: rnConfiguration.longTaskThresholdMs != 0, trackErrors: rnConfiguration.configurationForTelemetry?.trackErrors, diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index c692f6e7c..4a1d0cd92 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,22 +10,6 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { - func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { - // not implemented - } - - func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { - // not implemented - } - - func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { - // not implemented - } - - func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { - // not implemented - } - func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } From b906feee77feefe0d475b6811fa4be111126207e Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 27 Oct 2025 16:28:37 +0100 Subject: [PATCH 045/114] Handle optional String on removeAttributes --- .../main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index 29fdcdce0..f02395ed8 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -115,8 +115,7 @@ class DdSdkImplementation( fun removeAttributes(keys: ReadableArray, promise: Promise) { val keysArray = mutableListOf() for (i in 0 until keys.size()) { - val key: String = keys.getString(i) - keysArray.add(key) + keys.getString(i)?.let { if (it.isNotBlank()) keysArray.add(it) } } val keysStringArray = keysArray.toTypedArray() From 78b650b22c28d04b1d2ce1bb63c6eb1549e484d6 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 27 Oct 2025 11:37:44 +0100 Subject: [PATCH 046/114] Bump minSdkVersion to 23 --- packages/core/android/build.gradle | 20 ++++--------------- packages/core/android/gradle.properties | 2 +- .../android/gradle.properties | 2 +- .../android/gradle.properties | 2 +- .../android/gradle.properties | 2 +- 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 59bb75cc3..1344b2531 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -195,22 +195,10 @@ dependencies { } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compileOnly "com.squareup.okhttp3:okhttp:3.12.13" - - // dd-sdk-android-rum requires androidx.metrics:metrics-performance. - // From 2.21.0, it uses 1.0.0-beta02, which requires Gradle 8.6.0. - // This breaks builds if the React Native target is below 0.76.0. as it relies on Gradle 8.5.0. - // To avoid this, we enforce 1.0.0-beta01 on RN < 0.76.0 - if (reactNativeMinorVersion < 76) { - implementation("com.datadoghq:dd-sdk-android-rum:3.1.0") { - exclude group: "androidx.metrics", module: "metrics-performance" - } - implementation "androidx.metrics:metrics-performance:1.0.0-beta01" - } else { - implementation "com.datadoghq:dd-sdk-android-rum:3.1.0" - } - implementation "com.datadoghq:dd-sdk-android-logs:3.1.0" - implementation "com.datadoghq:dd-sdk-android-trace:3.1.0" - implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" + implementation "com.datadoghq:dd-sdk-android-rum:3.2.0" + implementation "com.datadoghq:dd-sdk-android-logs:3.2.0" + implementation "com.datadoghq:dd-sdk-android-trace:3.2.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.2.0" implementation "com.google.code.gson:gson:2.10.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/core/android/gradle.properties b/packages/core/android/gradle.properties index c9f7a205e..65f975c3e 100644 --- a/packages/core/android/gradle.properties +++ b/packages/core/android/gradle.properties @@ -1,5 +1,5 @@ DdSdkReactNative_kotlinVersion=1.8.21 -DdSdkReactNative_minSdkVersion=21 +DdSdkReactNative_minSdkVersion=23 DdSdkReactNative_compileSdkVersion=33 DdSdkReactNative_buildToolsVersion=33.0.0 DdSdkReactNative_targetSdkVersion=33 diff --git a/packages/internal-testing-tools/android/gradle.properties b/packages/internal-testing-tools/android/gradle.properties index 2a3186ab3..7a783c97d 100644 --- a/packages/internal-testing-tools/android/gradle.properties +++ b/packages/internal-testing-tools/android/gradle.properties @@ -1,5 +1,5 @@ DatadogInternalTesting_kotlinVersion=1.8.21 -DatadogInternalTesting_minSdkVersion=21 +DatadogInternalTesting_minSdkVersion=23 DatadogInternalTesting_compileSdkVersion=33 DatadogInternalTesting_buildToolsVersion=33.0.0 DatadogInternalTesting_targetSdkVersion=33 diff --git a/packages/react-native-session-replay/android/gradle.properties b/packages/react-native-session-replay/android/gradle.properties index 9f1573be5..d072f557e 100644 --- a/packages/react-native-session-replay/android/gradle.properties +++ b/packages/react-native-session-replay/android/gradle.properties @@ -1,6 +1,6 @@ DatadogSDKReactNativeSessionReplay_kotlinVersion=1.8.21 DatadogSDKReactNativeSessionReplay_compileSdkVersion=33 -DatadogSDKReactNativeSessionReplay_minSdkVersion=21 +DatadogSDKReactNativeSessionReplay_minSdkVersion=23 DatadogSDKReactNativeSessionReplay_buildToolsVersion=33.0.0 DatadogSDKReactNativeSessionReplay_targetSdkVersion=33 android.useAndroidX=true diff --git a/packages/react-native-webview/android/gradle.properties b/packages/react-native-webview/android/gradle.properties index 622c7b6b9..25112d024 100644 --- a/packages/react-native-webview/android/gradle.properties +++ b/packages/react-native-webview/android/gradle.properties @@ -1,5 +1,5 @@ DatadogSDKReactNativeWebView_kotlinVersion=1.7.21 -DatadogSDKReactNativeWebView_minSdkVersion=21 +DatadogSDKReactNativeWebView_minSdkVersion=23 DatadogSDKReactNativeWebView_compileSdkVersion=33 DatadogSDKReactNativeWebView_buildToolsVersion=33.0.0 DatadogSDKReactNativeWebView_targetSdkVersion=33 From cb78569c9fe7a56660b7a3915980a25ca4b2c372 Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Wed, 5 Nov 2025 15:58:05 +0000 Subject: [PATCH 047/114] Expose sdk iOS config option `trackMemoryWarnings` --- packages/core/ios/Sources/DdSdkConfiguration.swift | 5 ++++- .../ios/Sources/DdSdkNativeInitialization.swift | 1 + .../core/ios/Sources/RNDdSdkConfiguration.swift | 9 +++++++-- packages/core/src/DdSdkReactNative.tsx | 3 ++- .../core/src/DdSdkReactNativeConfiguration.tsx | 14 +++++++++++++- .../DdSdkReactNativeConfiguration.test.ts | 3 +++ .../__tests__/initialization.test.tsx | 1 + .../__tests__/FileBasedConfiguration.test.ts | 3 +++ packages/core/src/types.tsx | 3 ++- 9 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/core/ios/Sources/DdSdkConfiguration.swift b/packages/core/ios/Sources/DdSdkConfiguration.swift index 7a76cf39d..288e2a539 100644 --- a/packages/core/ios/Sources/DdSdkConfiguration.swift +++ b/packages/core/ios/Sources/DdSdkConfiguration.swift @@ -76,6 +76,7 @@ public class DdSdkConfiguration: NSObject { public var trackWatchdogTerminations: Bool public var batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel public var initialResourceThreshold: Double? = nil + public var trackMemoryWarnings: Bool public init( clientToken: String, @@ -108,7 +109,8 @@ public class DdSdkConfiguration: NSObject { appHangThreshold: Double?, trackWatchdogTerminations: Bool, batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel, - initialResourceThreshold: Double? + initialResourceThreshold: Double?, + trackMemoryWarnings: Bool = true ) { self.clientToken = clientToken self.env = env @@ -141,6 +143,7 @@ public class DdSdkConfiguration: NSObject { self.trackWatchdogTerminations = trackWatchdogTerminations self.batchProcessingLevel = batchProcessingLevel self.initialResourceThreshold = initialResourceThreshold + self.trackMemoryWarnings = trackMemoryWarnings } } diff --git a/packages/core/ios/Sources/DdSdkNativeInitialization.swift b/packages/core/ios/Sources/DdSdkNativeInitialization.swift index b55f90561..969f72abf 100644 --- a/packages/core/ios/Sources/DdSdkNativeInitialization.swift +++ b/packages/core/ios/Sources/DdSdkNativeInitialization.swift @@ -201,6 +201,7 @@ public class DdSdkNativeInitialization: NSObject { }, onSessionStart: DdSdkSessionStartedListener.instance.rumSessionListener, customEndpoint: customRUMEndpointURL, + trackMemoryWarnings: configuration.trackMemoryWarnings, telemetrySampleRate: (configuration.telemetrySampleRate as? NSNumber)?.floatValue ?? Float(DefaultConfiguration.telemetrySampleRate) ) } diff --git a/packages/core/ios/Sources/RNDdSdkConfiguration.swift b/packages/core/ios/Sources/RNDdSdkConfiguration.swift index 66437c481..6869d1711 100644 --- a/packages/core/ios/Sources/RNDdSdkConfiguration.swift +++ b/packages/core/ios/Sources/RNDdSdkConfiguration.swift @@ -43,6 +43,7 @@ extension NSDictionary { let trackWatchdogTerminations = object(forKey: "trackWatchdogTerminations") as? Bool let batchProcessingLevel = object(forKey: "batchProcessingLevel") as? NSString let initialResourceThreshold = object(forKey: "initialResourceThreshold") as? Double + let trackMemoryWarnings = object(forKey: "trackMemoryWarnings") as? Bool return DdSdkConfiguration( clientToken: (clientToken != nil) ? clientToken! : String(), @@ -75,7 +76,8 @@ extension NSDictionary { appHangThreshold: appHangThreshold, trackWatchdogTerminations: trackWatchdogTerminations ?? DefaultConfiguration.trackWatchdogTerminations, batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel(), - initialResourceThreshold: initialResourceThreshold + initialResourceThreshold: initialResourceThreshold, + trackMemoryWarnings: trackMemoryWarnings ?? DefaultConfiguration.trackMemoryWarnings ) } @@ -206,6 +208,7 @@ internal struct DefaultConfiguration { static let bundleLogsWithRum = true static let bundleLogsWithTraces = true static let trackWatchdogTerminations = false + static let trackMemoryWarnings = true } extension Dictionary where Key == String, Value == AnyObject { @@ -244,6 +247,7 @@ extension Dictionary where Key == String, Value == AnyObject { let trackWatchdogTerminations = configuration["trackWatchdogTerminations"] as? Bool let batchProcessingLevel = configuration["batchProcessingLevel"] as? NSString let initialResourceThreshold = configuration["initialResourceThreshold"] as? Double + let trackMemoryWarnings = configuration["trackMemoryWarnings"] as? Bool return DdSdkConfiguration( clientToken: clientToken ?? String(), @@ -279,7 +283,8 @@ extension Dictionary where Key == String, Value == AnyObject { appHangThreshold: appHangThreshold, trackWatchdogTerminations: trackWatchdogTerminations ?? DefaultConfiguration.trackWatchdogTerminations, batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel(), - initialResourceThreshold: initialResourceThreshold + initialResourceThreshold: initialResourceThreshold, + trackMemoryWarnings: trackMemoryWarnings ?? DefaultConfiguration.trackMemoryWarnings ) } } diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index 8360a695b..b1ea4f4c1 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -397,7 +397,8 @@ export class DdSdkReactNative { configuration.resourceTracingSamplingRate, configuration.trackWatchdogTerminations, configuration.batchProcessingLevel, - configuration.initialResourceThreshold + configuration.initialResourceThreshold, + configuration.trackMemoryWarnings ); }; diff --git a/packages/core/src/DdSdkReactNativeConfiguration.tsx b/packages/core/src/DdSdkReactNativeConfiguration.tsx index 44debb2d2..4ec1ef883 100644 --- a/packages/core/src/DdSdkReactNativeConfiguration.tsx +++ b/packages/core/src/DdSdkReactNativeConfiguration.tsx @@ -133,7 +133,8 @@ export const DEFAULTS = { bundleLogsWithTraces: true, useAccessibilityLabel: true, trackWatchdogTerminations: false, - batchProcessingLevel: BatchProcessingLevel.MEDIUM + batchProcessingLevel: BatchProcessingLevel.MEDIUM, + trackMemoryWarnings: true }; /** @@ -328,6 +329,16 @@ export class DdSdkReactNativeConfiguration { public trackWatchdogTerminations: boolean = DEFAULTS.trackWatchdogTerminations; + /** + * Enables tracking of memory warnings as RUM events. + * + * When enabled, the SDK will automatically record a RUM event each time the app + * receives a memory warning from the operating system. + * + * **Note:** This setting is only supported on **iOS**. It has no effect on other platforms. + */ + public trackMemoryWarnings: boolean = DEFAULTS.trackMemoryWarnings; + /** * Specifies a custom prop to name RUM actions on elements having an `onPress` prop. * @@ -469,6 +480,7 @@ export type PartialInitializationConfiguration = { readonly bundleLogsWithTraces?: boolean; readonly batchProcessingLevel?: BatchProcessingLevel; readonly initialResourceThreshold?: number; + readonly trackMemoryWarnings?: boolean; }; const setConfigurationAttribute = < diff --git a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts index b1522ef7f..60a9d13ff 100644 --- a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts +++ b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts @@ -60,6 +60,7 @@ describe('DdSdkReactNativeConfiguration', () => { "trackErrors": false, "trackFrustrations": true, "trackInteractions": false, + "trackMemoryWarnings": true, "trackResources": false, "trackWatchdogTerminations": false, "trackingConsent": "granted", @@ -170,6 +171,7 @@ describe('DdSdkReactNativeConfiguration', () => { "trackErrors": true, "trackFrustrations": true, "trackInteractions": true, + "trackMemoryWarnings": true, "trackResources": true, "trackWatchdogTerminations": false, "trackingConsent": "pending", @@ -246,6 +248,7 @@ describe('DdSdkReactNativeConfiguration', () => { "trackErrors": false, "trackFrustrations": false, "trackInteractions": false, + "trackMemoryWarnings": true, "trackResources": false, "trackWatchdogTerminations": false, "trackingConsent": "granted", diff --git a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx index c654cd24b..8ace4b731 100644 --- a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx +++ b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx @@ -100,6 +100,7 @@ describe('DatadogProvider', () => { "telemetrySampleRate": 20, "trackBackgroundEvents": false, "trackFrustrations": true, + "trackMemoryWarnings": true, "trackNonFatalAnrs": undefined, "trackWatchdogTerminations": false, "trackingConsent": "granted", diff --git a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts index 6d3ee2e44..1670e5fe2 100644 --- a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts +++ b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts @@ -62,6 +62,7 @@ describe('FileBasedConfiguration', () => { "trackErrors": true, "trackFrustrations": true, "trackInteractions": true, + "trackMemoryWarnings": true, "trackResources": true, "trackWatchdogTerminations": false, "trackingConsent": "not_granted", @@ -154,6 +155,7 @@ describe('FileBasedConfiguration', () => { "trackErrors": true, "trackFrustrations": true, "trackInteractions": true, + "trackMemoryWarnings": true, "trackResources": true, "trackWatchdogTerminations": false, "trackingConsent": "not_granted", @@ -205,6 +207,7 @@ describe('FileBasedConfiguration', () => { "trackErrors": false, "trackFrustrations": true, "trackInteractions": false, + "trackMemoryWarnings": true, "trackResources": false, "trackWatchdogTerminations": false, "trackingConsent": "granted", diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index c8d9821cc..5c7d64cec 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -69,7 +69,8 @@ export class DdSdkConfiguration { readonly resourceTracingSamplingRate: number, readonly trackWatchdogTerminations: boolean | undefined, readonly batchProcessingLevel: BatchProcessingLevel, // eslint-disable-next-line no-empty-function - readonly initialResourceThreshold: number | undefined + readonly initialResourceThreshold: number | undefined, + readonly trackMemoryWarnings: boolean ) {} } From 3984bccdc47d78628cbe81e877b0f2ac8a13b753 Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Mon, 3 Nov 2025 18:05:04 +0000 Subject: [PATCH 048/114] Expose `setAccountInfo` API to JS layer add account info to `applyEventMapper` --- .../datadog/reactnative/DatadogSDKWrapper.kt | 20 ++++- .../com/datadog/reactnative/DatadogWrapper.kt | 27 ++++++ .../reactnative/DdSdkImplementation.kt | 41 +++++++++ .../kotlin/com/datadog/reactnative/DdSdk.kt | 26 ++++++ .../kotlin/com/datadog/reactnative/DdSdk.kt | 26 ++++++ packages/core/ios/Sources/DdSdk.mm | 32 +++++++ .../ios/Sources/DdSdkImplementation.swift | 38 ++++++++ packages/core/jest/mock.js | 9 ++ packages/core/src/DdSdkReactNative.tsx | 66 ++++++++++++++ .../AccountInfoSingleton.ts | 46 ++++++++++ .../__tests__/AccountInfoSingleton.test.ts | 90 +++++++++++++++++++ .../src/sdk/AccountInfoSingleton/types.ts | 11 +++ .../core/src/sdk/EventMappers/EventMapper.ts | 5 ++ packages/core/src/specs/NativeDdSdk.ts | 17 ++++ packages/core/src/types.tsx | 27 ++++++ 15 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts create mode 100644 packages/core/src/sdk/AccountInfoSingleton/__tests__/AccountInfoSingleton.test.ts create mode 100644 packages/core/src/sdk/AccountInfoSingleton/types.ts diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index 06151d834..56a15373c 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -89,7 +89,25 @@ internal class DatadogSDKWrapper : DatadogWrapper { override fun clearUserInfo() { Datadog.clearUserInfo() } - + + override fun setAccountInfo( + id: String, + name: String?, + extraInfo: Map + ) { + Datadog.setAccountInfo(id, name, extraInfo) + } + + override fun addAccountExtraInfo( + extraInfo: Map + ) { + Datadog.addAccountExtraInfo(extraInfo) + } + + override fun clearAccountInfo() { + Datadog.clearAccountInfo() + } + override fun addRumGlobalAttribute(key: String, value: Any?) { this.getRumMonitor().addAttribute(key, value) } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index d6395b18b..c72f2faef 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -91,6 +91,33 @@ interface DatadogWrapper { */ fun clearUserInfo() + /** + * Sets the account information. + * + * @param id a unique account identifier (relevant to your business domain) + * @param name (nullable) the account name + * @param extraInfo additional information. An extra information can be + * nested up to 8 levels deep. Keys using more than 8 levels will be sanitized by SDK. + */ + fun setAccountInfo( + id: String, + name: String?, + extraInfo: Map + ) + + /** + * Sets the account information. + * @param extraInfo: The additional information. (To set the id or name please use setAccountInfo). + */ + fun addAccountExtraInfo( + extraInfo: Map + ) + + /** + * Clears the account information. + */ + fun clearAccountInfo() + /** Adds a global attribute. * diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index f02395ed8..02123ee8f 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -168,6 +168,47 @@ class DdSdkImplementation( promise.resolve(null) } + /** + * Set the account information. + * @param accountInfo The account object (use builtin attributes: 'id', 'name', and any custom + * attribute inside 'extraInfo'). + */ + fun setAccountInfo(accountInfo: ReadableMap, promise: Promise) { + val accountInfoMap = accountInfo.toHashMap().toMutableMap() + val id = accountInfoMap["id"] as? String + val name = accountInfoMap["name"] as? String + val extraInfo = (accountInfoMap["extraInfo"] as? Map<*, *>)?.filterKeys { it is String } + ?.mapKeys { it.key as String } + ?.mapValues { it.value } ?: emptyMap() + + if (id != null) { + datadog.setAccountInfo(id, name, extraInfo) + } + + promise.resolve(null) + } + + /** + * Sets the account extra information. + * @param accountExtraInfo: The additional information. (To set the id or name please use setAccountInfo). + */ + fun addAccountExtraInfo( + accountExtraInfo: ReadableMap, promise: Promise + ) { + val extraInfoMap = accountExtraInfo.toHashMap().toMutableMap() + + datadog.addAccountExtraInfo(extraInfoMap) + promise.resolve(null) + } + + /** + * Clears the account information. + */ + fun clearAccountInfo(promise: Promise) { + datadog.clearAccountInfo() + promise.resolve(null) + } + /** * Set the tracking consent regarding the data collection. * @param trackingConsent Consent, which can take one of the following values: 'pending', diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt index a9d430081..421812545 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -106,6 +106,32 @@ class DdSdk( implementation.clearUserInfo(promise) } + /** + * Set the account information. + * @param account The account object (use builtin attributes: 'id', 'name', and any custom * attribute inside 'extraInfo'). + */ + @ReactMethod + override fun setAccountInfo(account: ReadableMap, promise: Promise) { + implementation.setAccountInfo(account, promise) + } + + /** + * Sets the account information. + * @param extraAccountInfo: The additional information. (To set the id or name please use setAccountInfo). + */ + @ReactMethod + override fun addAccountExtraInfo(extraInfo: ReadableMap, promise: Promise) { + implementation.addAccountExtraInfo(extraInfo, promise) + } + + /** + * Clears the account information. + */ + @ReactMethod + override fun clearAccountInfo(promise: Promise) { + implementation.clearAccountInfo(promise) + } + /** * Set the tracking consent regarding the data collection. * @param trackingConsent Consent, which can take one of the following values: 'pending', diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 958ba521b..ef91ca549 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -132,6 +132,32 @@ class DdSdk( implementation.clearUserInfo(promise) } + /** + * Set the account information. + * @param account The account object (use builtin attributes: 'id', 'name', and any custom * attribute inside 'extraInfo'). + */ + @ReactMethod + fun setAccountInfo(account: ReadableMap, promise: Promise) { + implementation.setAccountInfo(account, promise) + } + + /** + * Sets the account information. + * @param extraAccountInfo: The additional information. (To set the id or name please use setAccountInfo). + */ + @ReactMethod + fun addAccountExtraInfo(extraInfo: ReadableMap, promise: Promise) { + implementation.addAccountExtraInfo(extraInfo, promise) + } + + /** + * Clears the account information. + */ + @ReactMethod + fun clearAccountInfo(promise: Promise) { + implementation.clearAccountInfo(promise) + } + /** * Set the tracking consent regarding the data collection. * @param trackingConsent Consent, which can take one of the following values: 'pending', diff --git a/packages/core/ios/Sources/DdSdk.mm b/packages/core/ios/Sources/DdSdk.mm index 06736a69e..c05ac6a7c 100644 --- a/packages/core/ios/Sources/DdSdk.mm +++ b/packages/core/ios/Sources/DdSdk.mm @@ -79,6 +79,26 @@ + (void)initFromNative { [self clearUserInfo:resolve reject:reject]; } +RCT_REMAP_METHOD(setAccountInfo, withAccountInfo:(NSDictionary*)accountInfo + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self setAccountInfo:accountInfo resolve:resolve reject:reject]; +} + +RCT_REMAP_METHOD(addAccountExtraInfo, withAccountExtraInfo:(NSDictionary*)extraInfo + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self addAccountExtraInfo:extraInfo resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(clearAccountInfo:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self clearAccountInfo:resolve reject:reject]; +} + RCT_REMAP_METHOD(setTrackingConsent, withTrackingConsent:(NSString*)trackingConsent withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) @@ -191,6 +211,18 @@ -(void)addUserExtraInfo:(NSDictionary *)extraInfo resolve:(RCTPromiseResolveBloc [self.ddSdkImplementation addUserExtraInfoWithExtraInfo:extraInfo resolve:resolve reject:reject]; } +- (void)setAccountInfo:(NSDictionary *)accountInfo resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation setAccountInfoWithAccountInfo:accountInfo resolve:resolve reject:reject]; +} + +- (void)clearAccountInfo:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation clearAccountInfoWithResolve:resolve reject:reject]; +} + +-(void)addAccountExtraInfo:(NSDictionary *)extraInfo resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation addAccountExtraInfoWithExtraInfo:extraInfo resolve:resolve reject:reject]; +} + - (void)sendTelemetryLog:(NSString *)message attributes:(NSDictionary *)attributes config:(NSDictionary *)config resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self.ddSdkImplementation sendTelemetryLogWithMessage:message attributes:attributes config:config resolve:resolve reject:reject]; } diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 5b91039b3..db8c4e938 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -160,6 +160,44 @@ public class DdSdkImplementation: NSObject { resolve(nil) } + @objc + public func setAccountInfo( + accountInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { + let castedAccountInfo = castAttributesToSwift(accountInfo) + let id = castedAccountInfo["id"] as? String + let name = castedAccountInfo["name"] as? String + var extraInfo: [AttributeKey: AttributeValue] = [:] + + if let extraInfoEncodable = castedAccountInfo["extraInfo"] as? AnyEncodable, + let extraInfoDict = extraInfoEncodable.value as? [String: Any] + { + extraInfo = castAttributesToSwift(extraInfoDict) + } + + if let validId = id { + Datadog.setAccountInfo(id: validId, name: name, extraInfo: extraInfo) + } + + resolve(nil) + } + + @objc + public func addAccountExtraInfo( + extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock + ) { + let castedExtraInfo = castAttributesToSwift(extraInfo) + + Datadog.addAccountExtraInfo(castedExtraInfo) + resolve(nil) + } + + @objc + public func clearAccountInfo(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + Datadog.clearAccountInfo() + resolve(nil) + } + @objc public func setTrackingConsent( trackingConsent: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock diff --git a/packages/core/jest/mock.js b/packages/core/jest/mock.js index 0dc9ec138..9f5ee2c41 100644 --- a/packages/core/jest/mock.js +++ b/packages/core/jest/mock.js @@ -36,6 +36,15 @@ module.exports = { clearUserInfo: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), + setAccountInfo: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + addAccountExtraInfo: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + clearAccountInfo: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), addAttribute: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index b1ea4f4c1..e07ba4e34 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -30,6 +30,7 @@ import { DdRumErrorTracking } from './rum/instrumentation/DdRumErrorTracking'; import { DdBabelInteractionTracking } from './rum/instrumentation/interactionTracking/DdBabelInteractionTracking'; import { DdRumUserInteractionTracking } from './rum/instrumentation/interactionTracking/DdRumUserInteractionTracking'; import { DdRumResourceTracking } from './rum/instrumentation/resourceTracking/DdRumResourceTracking'; +import { AccountInfoSingleton } from './sdk/AccountInfoSingleton/AccountInfoSingleton'; import { AttributesSingleton } from './sdk/AttributesSingleton/AttributesSingleton'; import type { Attributes } from './sdk/AttributesSingleton/types'; import { registerNativeBridge } from './sdk/DatadogInternalBridge/DdSdkInternalNativeBridge'; @@ -299,6 +300,71 @@ export class DdSdkReactNative { UserInfoSingleton.getInstance().setUserInfo(updatedUserInfo); }; + /** + * Sets the account information. + * @param id: A mandatory unique account identifier (relevant to your business domain). + * @param name: The account name. + * @param extraInfo: Additional information. + * @returns a Promise. + */ + static setAccountInfo = async (accountInfo: { + id: string; + name?: string; + extraInfo?: Record; + }): Promise => { + InternalLog.log( + `Setting account ${JSON.stringify(accountInfo)}`, + SdkVerbosity.DEBUG + ); + + await DdSdk.setAccountInfo(accountInfo); + AccountInfoSingleton.getInstance().setAccountInfo(accountInfo); + }; + + /** + * Clears the account information. + * @returns a Promise. + */ + static clearAccountInfo = async (): Promise => { + InternalLog.log('Clearing account info', SdkVerbosity.DEBUG); + await DdSdk.clearAccountInfo(); + AccountInfoSingleton.getInstance().clearAccountInfo(); + }; + + /** + * Set the account information. + * @param extraAccountInfo: The additional information. (To set the id or name please use setAccountInfo). + * @returns a Promise. + */ + static addAccountExtraInfo = async ( + extraAccountInfo: Record + ): Promise => { + InternalLog.log( + `Adding extra account info ${JSON.stringify(extraAccountInfo)}`, + SdkVerbosity.DEBUG + ); + + const accountInfo = AccountInfoSingleton.getInstance().getAccountInfo(); + if (!accountInfo) { + InternalLog.log( + 'Skipped adding Account Extra Info: Account Info is currently undefined. An account ID must be set before adding extra info. Please call setAccountInfo() first.', + SdkVerbosity.WARN + ); + + return; + } + + const extraInfo = { + ...accountInfo.extraInfo, + ...extraAccountInfo + }; + + await DdSdk.addAccountExtraInfo(extraInfo); + AccountInfoSingleton.getInstance().addAccountExtraInfo( + extraAccountInfo + ); + }; + /** * Set the tracking consent regarding the data collection. * @param trackingConsent: One of TrackingConsent values. diff --git a/packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts b/packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts new file mode 100644 index 000000000..5f2be8dea --- /dev/null +++ b/packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts @@ -0,0 +1,46 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import type { AccountInfo } from './types'; + +class AccountInfoProvider { + private accountInfo: AccountInfo | undefined = undefined; + + setAccountInfo = (accountInfo: AccountInfo) => { + this.accountInfo = accountInfo; + }; + + addAccountExtraInfo = (extraInfo: AccountInfo['extraInfo']) => { + if (!this.accountInfo) { + return; + } + + this.accountInfo.extraInfo = { + ...this.accountInfo.extraInfo, + ...extraInfo + }; + }; + + getAccountInfo = (): AccountInfo | undefined => { + return this.accountInfo; + }; + + clearAccountInfo = () => { + this.accountInfo = undefined; + }; +} + +export class AccountInfoSingleton { + private static accountInfoProvider = new AccountInfoProvider(); + + static getInstance = (): AccountInfoProvider => { + return AccountInfoSingleton.accountInfoProvider; + }; + + static reset = () => { + AccountInfoSingleton.accountInfoProvider = new AccountInfoProvider(); + }; +} diff --git a/packages/core/src/sdk/AccountInfoSingleton/__tests__/AccountInfoSingleton.test.ts b/packages/core/src/sdk/AccountInfoSingleton/__tests__/AccountInfoSingleton.test.ts new file mode 100644 index 000000000..9af387619 --- /dev/null +++ b/packages/core/src/sdk/AccountInfoSingleton/__tests__/AccountInfoSingleton.test.ts @@ -0,0 +1,90 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import { AccountInfoSingleton } from '../AccountInfoSingleton'; + +describe('AccountInfoSingleton', () => { + beforeEach(() => { + AccountInfoSingleton.reset(); + }); + + it('returns undefined by default', () => { + expect( + AccountInfoSingleton.getInstance().getAccountInfo() + ).toBeUndefined(); + }); + + it('stores and returns account info after `setAccountInfo`', () => { + const info = { + id: 'test', + name: 'test user', + extraInfo: { premium: true } + }; + + AccountInfoSingleton.getInstance().setAccountInfo(info); + + expect(AccountInfoSingleton.getInstance().getAccountInfo()).toEqual( + info + ); + }); + + it('adds extra account info with `addAccountExtraInfo`', () => { + const info = { + id: 'test', + name: 'test user', + extraInfo: { premium: true } + }; + + AccountInfoSingleton.getInstance().setAccountInfo(info); + AccountInfoSingleton.getInstance().addAccountExtraInfo({ + testGroup: 'A' + }); + + expect(AccountInfoSingleton.getInstance().getAccountInfo()).toEqual({ + ...info, + extraInfo: { ...info.extraInfo, testGroup: 'A' } + }); + }); + + it('clears account info with `clearAccountInfo`', () => { + AccountInfoSingleton.getInstance().setAccountInfo({ + id: 'test', + name: 'test user', + extraInfo: { premium: true } + }); + + AccountInfoSingleton.getInstance().clearAccountInfo(); + + expect( + AccountInfoSingleton.getInstance().getAccountInfo() + ).toBeUndefined(); + }); + + it('`reset()` replaces the provider and clears stored account info', () => { + const instanceBefore = AccountInfoSingleton.getInstance(); + + AccountInfoSingleton.getInstance().setAccountInfo({ + id: 'test', + name: 'test user', + extraInfo: { premium: true } + }); + + AccountInfoSingleton.reset(); + + const instanceAfter = AccountInfoSingleton.getInstance(); + + expect(instanceAfter).not.toBe(instanceBefore); + + expect(instanceAfter.getAccountInfo()).toBeUndefined(); + }); + + it('getInstance returns the same provider between calls (singleton behavior)', () => { + const a = AccountInfoSingleton.getInstance(); + const b = AccountInfoSingleton.getInstance(); + + expect(a).toBe(b); + }); +}); diff --git a/packages/core/src/sdk/AccountInfoSingleton/types.ts b/packages/core/src/sdk/AccountInfoSingleton/types.ts new file mode 100644 index 000000000..1dceb0958 --- /dev/null +++ b/packages/core/src/sdk/AccountInfoSingleton/types.ts @@ -0,0 +1,11 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +export type AccountInfo = { + readonly id: string; + readonly name?: string; + extraInfo?: Record; +}; diff --git a/packages/core/src/sdk/EventMappers/EventMapper.ts b/packages/core/src/sdk/EventMappers/EventMapper.ts index 9ca252d72..e1cbaae19 100644 --- a/packages/core/src/sdk/EventMappers/EventMapper.ts +++ b/packages/core/src/sdk/EventMappers/EventMapper.ts @@ -7,6 +7,8 @@ import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; import { DdSdk } from '../../sdk/DdSdk'; +import { AccountInfoSingleton } from '../AccountInfoSingleton/AccountInfoSingleton'; +import type { AccountInfo } from '../AccountInfoSingleton/types'; import { AttributesSingleton } from '../AttributesSingleton/AttributesSingleton'; import type { Attributes } from '../AttributesSingleton/types'; import { UserInfoSingleton } from '../UserInfoSingleton/UserInfoSingleton'; @@ -16,6 +18,7 @@ import { deepClone } from './utils/deepClone'; export type AdditionalEventDataForMapper = { userInfo?: UserInfo; + accountInfo?: AccountInfo; attributes: Attributes; }; @@ -66,9 +69,11 @@ export class EventMapper { // formatting const userInfo = UserInfoSingleton.getInstance().getUserInfo(); + const accountInfo = AccountInfoSingleton.getInstance().getAccountInfo(); const attributes = AttributesSingleton.getInstance().getAttributes(); const initialEvent = this.formatRawEventForMapper(rawEvent, { userInfo, + accountInfo, attributes }); diff --git a/packages/core/src/specs/NativeDdSdk.ts b/packages/core/src/specs/NativeDdSdk.ts index 70401fe3c..68c9c4711 100644 --- a/packages/core/src/specs/NativeDdSdk.ts +++ b/packages/core/src/specs/NativeDdSdk.ts @@ -67,6 +67,23 @@ export interface Spec extends TurboModule { */ addUserExtraInfo(extraInfo: Object): Promise; + /** + * Set the account information. + * @param account: The account object (use builtin attributes: 'id', 'name', and any custom attribute under extraInfo). + */ + setAccountInfo(account: Object): Promise; + + /** + * Clears the account information. + */ + clearAccountInfo(): Promise; + + /** + * Add custom attributes to the current account information + * @param extraInfo: The extraInfo object containing additional custom attributes + */ + addAccountExtraInfo(extraInfo: Object): Promise; + /** * Set the tracking consent regarding the data collection. * @param trackingConsent: Consent, which can take one of the following values: 'pending', 'granted', 'not_granted'. diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 5c7d64cec..7f97779ee 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -129,6 +129,27 @@ export type DdSdkType = { */ addUserExtraInfo(extraUserInfo: Record): Promise; + /** + * Sets the account information. + * @param id: A unique account identifier (relevant to your business domain) + * @param name: The account name. + * @param extraInfo: Additional information. + */ + setAccountInfo(accountInfo: AccountInfo): Promise; + + /** + * Clears the account information. + */ + clearAccountInfo(): Promise; + + /** + * Add additional account information. + * @param extraAccountInfo: The additional information. (To set the id or name please use setAccountInfo). + */ + addAccountExtraInfo( + extraAccountInfo: Record + ): Promise; + /** * Set the tracking consent regarding the data collection. * @param trackingConsent: Consent, which can take one of the following values: 'pending', 'granted', 'not_granted'. @@ -176,6 +197,12 @@ export type UserInfo = { extraInfo?: object; }; +export type AccountInfo = { + id: string; + name?: string; + extraInfo?: object; +}; + // DdLogs export type LogStatus = 'debug' | 'info' | 'warn' | 'error'; From 6a42fca99f4647293c95230df7efdf652d0a0b51 Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Fri, 7 Nov 2025 14:49:40 +0000 Subject: [PATCH 049/114] Add `userId` and `accountId`to baggage headers Preserve user baggage header when setting session ID Enforced W3 specification for 'baggage' header Only inject Session ID header if propagator=Datadog|W3C Additional test for baggage header and minor warn message improvement --- packages/core/src/rum/DdRum.ts | 32 ++++++++------ packages/core/src/rum/__tests__/DdRum.test.ts | 2 +- packages/core/src/rum/helper.ts | 36 +++++++++++++++ .../distributedTracing/distributedTracing.tsx | 21 +++++++-- .../distributedTracingHeaders.ts | 44 ++++++++++++++++--- .../requestProxy/XHRProxy/XHRProxy.ts | 10 ++++- .../XHRProxy/__tests__/XHRProxy.test.ts | 2 +- .../XHRProxy/baggageHeaderUtils.ts | 36 +-------------- .../DdSdkInternalNativeBridge.tsx | 2 +- 9 files changed, 122 insertions(+), 63 deletions(-) create mode 100644 packages/core/src/rum/helper.ts diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index 555b144de..1e231a197 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -20,12 +20,19 @@ import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; import type { TimeProvider } from '../utils/time-provider/TimeProvider'; -import { generateActionEventMapper } from './eventMappers/actionEventMapper'; import type { ActionEventMapper } from './eventMappers/actionEventMapper'; -import { generateErrorEventMapper } from './eventMappers/errorEventMapper'; +import { generateActionEventMapper } from './eventMappers/actionEventMapper'; import type { ErrorEventMapper } from './eventMappers/errorEventMapper'; -import { generateResourceEventMapper } from './eventMappers/resourceEventMapper'; +import { generateErrorEventMapper } from './eventMappers/errorEventMapper'; import type { ResourceEventMapper } from './eventMappers/resourceEventMapper'; +import { generateResourceEventMapper } from './eventMappers/resourceEventMapper'; +import { + clearCachedSessionId, + getCachedAccountId, + getCachedSessionId, + getCachedUserId, + setCachedSessionId +} from './helper'; import type { DatadogTracingContext } from './instrumentation/resourceTracking/distributedTracing/DatadogTracingContext'; import { DatadogTracingIdentifier } from './instrumentation/resourceTracking/distributedTracing/DatadogTracingIdentifier'; import { TracingIdentifier } from './instrumentation/resourceTracking/distributedTracing/TracingIdentifier'; @@ -33,17 +40,12 @@ import { getTracingContext, getTracingContextForPropagators } from './instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders'; -import { - clearCachedSessionId, - getCachedSessionId, - setCachedSessionId -} from './sessionId/sessionIdHelper'; import type { DdRumType, - RumActionType, - ResourceKind, FirstPartyHost, - PropagatorType + PropagatorType, + ResourceKind, + RumActionType } from './types'; const RUM_MODULE = 'com.datadog.reactnative.rum'; @@ -396,7 +398,9 @@ class DdRumWrapper implements DdRumType { url, tracingSamplingRate, firstPartyHosts, - getCachedSessionId() + getCachedSessionId(), + getCachedUserId(), + getCachedAccountId() ); }; @@ -407,7 +411,9 @@ class DdRumWrapper implements DdRumType { return getTracingContextForPropagators( propagators, tracingSamplingRate, - getCachedSessionId() + getCachedSessionId(), + getCachedUserId(), + getCachedAccountId() ); }; diff --git a/packages/core/src/rum/__tests__/DdRum.test.ts b/packages/core/src/rum/__tests__/DdRum.test.ts index af1f4fbfa..8bdc86aac 100644 --- a/packages/core/src/rum/__tests__/DdRum.test.ts +++ b/packages/core/src/rum/__tests__/DdRum.test.ts @@ -17,11 +17,11 @@ import { DdRum } from '../DdRum'; import type { ActionEventMapper } from '../eventMappers/actionEventMapper'; import type { ErrorEventMapper } from '../eventMappers/errorEventMapper'; import type { ResourceEventMapper } from '../eventMappers/resourceEventMapper'; +import { setCachedSessionId } from '../helper'; import { DatadogTracingContext } from '../instrumentation/resourceTracking/distributedTracing/DatadogTracingContext'; import { DatadogTracingIdentifier } from '../instrumentation/resourceTracking/distributedTracing/DatadogTracingIdentifier'; import { TracingIdFormat } from '../instrumentation/resourceTracking/distributedTracing/TracingIdentifier'; import { TracingIdentifierUtils } from '../instrumentation/resourceTracking/distributedTracing/__tests__/__utils__/TracingIdentifierUtils'; -import { setCachedSessionId } from '../sessionId/sessionIdHelper'; import type { FirstPartyHost } from '../types'; import { PropagatorType, RumActionType } from '../types'; diff --git a/packages/core/src/rum/helper.ts b/packages/core/src/rum/helper.ts new file mode 100644 index 000000000..a153b79b5 --- /dev/null +++ b/packages/core/src/rum/helper.ts @@ -0,0 +1,36 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ +let _cachedSessionId: string | undefined; +let _cachedUserId: string | undefined; +let _cachedAccountId: string | undefined; + +export const getCachedSessionId = () => { + return _cachedSessionId; +}; + +export const setCachedSessionId = (sessionId: string) => { + _cachedSessionId = sessionId; +}; + +export const clearCachedSessionId = () => { + _cachedSessionId = undefined; +}; + +export const getCachedUserId = () => { + return _cachedUserId; +}; + +export const setCachedUserId = (userId: string) => { + _cachedUserId = userId; +}; + +export const getCachedAccountId = () => { + return _cachedAccountId; +}; + +export const setCachedAccountId = (accountId: string) => { + _cachedAccountId = accountId; +}; diff --git a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx index 9c4fcbff7..2ebb98233 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx +++ b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracing.tsx @@ -26,6 +26,9 @@ export type DdRumResourceTracingAttributes = rulePsr: number; propagatorTypes: PropagatorType[]; rumSessionId?: string; + userId?: string; + accountId?: string; + baggageHeaders?: Set; } | { tracingStrategy: 'DISCARD'; @@ -43,12 +46,16 @@ export const getTracingAttributes = ({ hostname, firstPartyHostsRegexMap, tracingSamplingRate, - rumSessionId + rumSessionId, + userId, + accountId }: { hostname: Hostname | null; firstPartyHostsRegexMap: RegexMap; tracingSamplingRate: number; rumSessionId?: string; + userId?: string; + accountId?: string; }): DdRumResourceTracingAttributes => { if (hostname === null) { return DISCARDED_TRACE_ATTRIBUTES; @@ -61,7 +68,9 @@ export const getTracingAttributes = ({ return generateTracingAttributesWithSampling( tracingSamplingRate, propagatorsForHost, - rumSessionId + rumSessionId, + userId, + accountId ); } return DISCARDED_TRACE_ATTRIBUTES; @@ -70,7 +79,9 @@ export const getTracingAttributes = ({ export const generateTracingAttributesWithSampling = ( tracingSamplingRate: number, propagatorTypes: PropagatorType[], - rumSessionId?: string + rumSessionId?: string, + userId?: string, + accountId?: string ): DdRumResourceTracingAttributes => { if (!propagatorTypes || propagatorTypes.length === 0) { return DISCARDED_TRACE_ATTRIBUTES; @@ -93,7 +104,9 @@ export const generateTracingAttributesWithSampling = ( tracingStrategy: 'KEEP', rulePsr: tracingSamplingRate / 100, propagatorTypes, - rumSessionId + rumSessionId, + userId, + accountId }; return tracingAttributes; diff --git a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts index 8bfaec669..8ad9137c7 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts @@ -1,6 +1,5 @@ /* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ @@ -29,6 +28,8 @@ export const PARENT_ID_HEADER_KEY = 'x-datadog-parent-id'; export const TAGS_HEADER_KEY = 'x-datadog-tags'; export const DD_TRACE_ID_TAG = '_dd.p.tid'; export const DD_RUM_SESSION_ID_TAG = 'session.id'; +export const DD_RUM_USER_ID_TAG = 'user.id'; +export const DD_RUM_ACCOUNT_ID_TAG = 'account.id'; /** * OTel headers @@ -143,6 +144,29 @@ export const getTracingHeadersFromAttributes = ( } }); + if (hasDatadogOrW3CPropagator) { + if (tracingAttributes.rumSessionId) { + headers.push({ + header: BAGGAGE_HEADER_KEY, + value: `${DD_RUM_SESSION_ID_TAG}=${tracingAttributes.rumSessionId}` + }); + } + + if (tracingAttributes.userId) { + headers.push({ + header: BAGGAGE_HEADER_KEY, + value: `${DD_RUM_USER_ID_TAG}=${tracingAttributes.userId}` + }); + } + + if (tracingAttributes.accountId) { + headers.push({ + header: BAGGAGE_HEADER_KEY, + value: `${DD_RUM_ACCOUNT_ID_TAG}=${tracingAttributes.accountId}` + }); + } + } + if (hasDatadogOrW3CPropagator && tracingAttributes.rumSessionId) { headers.push({ header: BAGGAGE_HEADER_KEY, @@ -157,7 +181,9 @@ export const getTracingContext = ( url: string, tracingSamplingRate: number, firstPartyHosts: FirstPartyHost[], - rumSessionId?: string + rumSessionId?: string, + userId?: string, + accountId?: string ): DatadogTracingContext => { const hostname = URLHostParser(url); const firstPartyHostsRegexMap = firstPartyHostsRegexMapBuilder( @@ -167,7 +193,9 @@ export const getTracingContext = ( hostname, firstPartyHostsRegexMap, tracingSamplingRate, - rumSessionId + rumSessionId, + userId, + accountId }); return getTracingContextForAttributes( @@ -179,13 +207,17 @@ export const getTracingContext = ( export const getTracingContextForPropagators = ( propagators: PropagatorType[], tracingSamplingRate: number, - rumSessionId?: string + rumSessionId?: string, + userId?: string, + accountId?: string ): DatadogTracingContext => { return getTracingContextForAttributes( generateTracingAttributesWithSampling( tracingSamplingRate, propagators, - rumSessionId + rumSessionId, + userId, + accountId ), tracingSamplingRate ); diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/XHRProxy.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/XHRProxy.ts index d48c4f01a..723ace5ed 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/XHRProxy.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/XHRProxy.ts @@ -5,7 +5,11 @@ */ import { Timer } from '../../../../../utils/Timer'; -import { getCachedSessionId } from '../../../../sessionId/sessionIdHelper'; +import { + getCachedAccountId, + getCachedSessionId, + getCachedUserId +} from '../../../../helper'; import { BAGGAGE_HEADER_KEY, getTracingHeadersFromAttributes @@ -115,7 +119,9 @@ const proxyOpen = ( hostname, firstPartyHostsRegexMap, tracingSamplingRate, - rumSessionId: getCachedSessionId() + rumSessionId: getCachedSessionId(), + userId: getCachedUserId(), + accountId: getCachedAccountId() }), baggageHeaderEntries: new Set() }; diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts index cfc5e0178..048dbadec 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts @@ -11,7 +11,7 @@ import { InternalLog } from '../../../../../../InternalLog'; import { SdkVerbosity } from '../../../../../../SdkVerbosity'; import { BufferSingleton } from '../../../../../../sdk/DatadogProvider/Buffer/BufferSingleton'; import { DdRum } from '../../../../../DdRum'; -import { setCachedSessionId } from '../../../../../sessionId/sessionIdHelper'; +import { setCachedSessionId } from '../../../../../helper'; import { PropagatorType } from '../../../../../types'; import { XMLHttpRequestMock } from '../../../__tests__/__utils__/XMLHttpRequestMock'; import { TracingIdentifierUtils } from '../../../distributedTracing/__tests__/__utils__/TracingIdentifierUtils'; diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/baggageHeaderUtils.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/baggageHeaderUtils.ts index dccae9ddf..7094ec4e8 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/baggageHeaderUtils.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/baggageHeaderUtils.ts @@ -110,7 +110,7 @@ export function formatBaggageHeader(entries: Set): string | null { } const headerValue = formattedParts.join(','); - const byteLength = utf8ByteLength(headerValue); + const byteLength = Buffer.byteLength(headerValue, 'utf8'); if (byteLength > MAX_BYTES) { InternalLog.log( @@ -122,40 +122,6 @@ export function formatBaggageHeader(entries: Set): string | null { return headerValue; } -/** - * Returns the number of bytes needed to encode a string in UTF-8. - * - * Useful as a lightweight alternative to Node.js `Buffer.byteLength()` - * for older environments that do not support it. - * - * @param text - The input string. - * @returns The UTF-8 byte length of the string. - */ -function utf8ByteLength(text: string): number { - let byteLength = text.length; - for (let i = text.length - 1; i >= 0; i--) { - const code = text.charCodeAt(i); - - // 2-byte characters (U+0080 to U+07FF) - if (code > 0x7f && code <= 0x7ff) { - byteLength++; - } - // 3-byte characters (U+0800 to U+FFFF) - else if (code > 0x7ff && code <= 0xffff) { - byteLength += 2; - } - - // Handle surrogate pairs (4-byte characters, e.g. emoji) - // These characters already count as 2 in the initial length - // Encountering the low surrogate already accounts for the full 4 bytes - // (2 from the initial length + 2 for the 3-byte characters logic above) - if (code >= 0xdc00 && code <= 0xdfff) { - i--; // prevents double counting the same character by skipping high surrogate - } - } - return byteLength; -} - /** * Returns a set of valid baggage header characters. */ diff --git a/packages/core/src/sdk/DatadogInternalBridge/DdSdkInternalNativeBridge.tsx b/packages/core/src/sdk/DatadogInternalBridge/DdSdkInternalNativeBridge.tsx index bbae2cd12..34a1e623a 100644 --- a/packages/core/src/sdk/DatadogInternalBridge/DdSdkInternalNativeBridge.tsx +++ b/packages/core/src/sdk/DatadogInternalBridge/DdSdkInternalNativeBridge.tsx @@ -5,7 +5,7 @@ */ import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; -import { setCachedSessionId } from '../../rum/sessionId/sessionIdHelper'; +import { setCachedSessionId } from '../../rum/helper'; import { DatadogDefaultEventEmitter } from '../DatadogEventEmitter/DatadogDefaultEventEmitter'; import type { DatadogEventEmitter } from '../DatadogEventEmitter/DatadogEventEmitter'; From 85ebed2b55c694d520221435ff7b35dbe405e034 Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Thu, 13 Nov 2025 17:24:34 +0000 Subject: [PATCH 050/114] Cache `userId` and `accountId` when first set --- .../requestProxy/XHRProxy/__tests__/XHRProxy.test.ts | 10 +++++++++- .../sdk/AccountInfoSingleton/AccountInfoSingleton.ts | 3 +++ .../src/sdk/UserInfoSingleton/UserInfoSingleton.ts | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts index 048dbadec..a18825771 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts @@ -11,7 +11,11 @@ import { InternalLog } from '../../../../../../InternalLog'; import { SdkVerbosity } from '../../../../../../SdkVerbosity'; import { BufferSingleton } from '../../../../../../sdk/DatadogProvider/Buffer/BufferSingleton'; import { DdRum } from '../../../../../DdRum'; -import { setCachedSessionId } from '../../../../../helper'; +import { + setCachedSessionId, + setCachedUserId, + setCachedAccountId +} from '../../../../../helper'; import { PropagatorType } from '../../../../../types'; import { XMLHttpRequestMock } from '../../../__tests__/__utils__/XMLHttpRequestMock'; import { TracingIdentifierUtils } from '../../../distributedTracing/__tests__/__utils__/TracingIdentifierUtils'; @@ -90,6 +94,10 @@ afterEach(() => { (Date.now as jest.MockedFunction).mockClear(); jest.spyOn(global.Math, 'random').mockRestore(); DdRum.unregisterResourceEventMapper(); + + setCachedSessionId(undefined as any); + setCachedUserId(undefined as any); + setCachedAccountId(undefined as any); }); describe('XHRProxy', () => { diff --git a/packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts b/packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts index 5f2be8dea..439c1493a 100644 --- a/packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts +++ b/packages/core/src/sdk/AccountInfoSingleton/AccountInfoSingleton.ts @@ -4,6 +4,8 @@ * Copyright 2016-Present Datadog, Inc. */ +import { setCachedAccountId } from '../../rum/helper'; + import type { AccountInfo } from './types'; class AccountInfoProvider { @@ -11,6 +13,7 @@ class AccountInfoProvider { setAccountInfo = (accountInfo: AccountInfo) => { this.accountInfo = accountInfo; + setCachedAccountId(this.accountInfo.id); }; addAccountExtraInfo = (extraInfo: AccountInfo['extraInfo']) => { diff --git a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts index 3ce23614b..2408fdbf2 100644 --- a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts +++ b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts @@ -4,6 +4,8 @@ * Copyright 2016-Present Datadog, Inc. */ +import { setCachedUserId } from '../../rum/helper'; + import type { UserInfo } from './types'; class UserInfoProvider { @@ -11,6 +13,7 @@ class UserInfoProvider { setUserInfo = (userInfo: UserInfo) => { this.userInfo = userInfo; + setCachedUserId(this.userInfo.id); }; getUserInfo = (): UserInfo | undefined => { From c04b071a07591a33c3462fca6d29ca7393467124 Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Fri, 21 Nov 2025 11:20:51 +0000 Subject: [PATCH 051/114] Integrate Feature Operations into `core` SDK --- packages/codepush/__mocks__/react-native.ts | 11 +++- packages/core/__mocks__/react-native.ts | 11 +++- .../reactnative/DdRumImplementation.kt | 54 ++++++++++++++++ .../kotlin/com/datadog/reactnative/DdRum.kt | 63 ++++++++++++++++++- .../kotlin/com/datadog/reactnative/DdRum.kt | 56 +++++++++++++++++ packages/core/ios/Sources/DdRum.mm | 43 +++++++++++++ .../ios/Sources/DdRumImplementation.swift | 51 +++++++++++++++ packages/core/jest/mock.js | 9 +++ packages/core/src/index.tsx | 3 +- packages/core/src/rum/DdRum.ts | 54 +++++++++++++++- packages/core/src/rum/types.ts | 42 ++++++++++++- packages/core/src/specs/NativeDdRum.ts | 40 ++++++++++++ packages/core/src/types.tsx | 6 ++ .../__mocks__/react-native.ts | 11 +++- 14 files changed, 447 insertions(+), 7 deletions(-) diff --git a/packages/codepush/__mocks__/react-native.ts b/packages/codepush/__mocks__/react-native.ts index 0c8189840..a87659170 100644 --- a/packages/codepush/__mocks__/react-native.ts +++ b/packages/codepush/__mocks__/react-native.ts @@ -119,7 +119,16 @@ actualRN.NativeModules.DdRum = { new Promise(resolve => resolve('test-session-id') ) - ) as jest.MockedFunction + ) as jest.MockedFunction, + startFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + succeedFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + failFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction }; module.exports = actualRN; diff --git a/packages/core/__mocks__/react-native.ts b/packages/core/__mocks__/react-native.ts index 73308f711..a4622b6b9 100644 --- a/packages/core/__mocks__/react-native.ts +++ b/packages/core/__mocks__/react-native.ts @@ -155,7 +155,16 @@ actualRN.NativeModules.DdRum = { new Promise(resolve => resolve('test-session-id') ) - ) as jest.MockedFunction + ) as jest.MockedFunction, + startFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + succeedFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + failFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction }; module.exports = actualRN; diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt index 4e3cd416f..67a299f36 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt @@ -12,6 +12,7 @@ import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.RumResourceMethod +import com.datadog.android.rum.featureoperations.FailureReason import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap @@ -333,6 +334,59 @@ class DdRumImplementation(private val datadog: DatadogWrapper = DatadogSDKWrappe } } + /** + * Starts a Feature Operation. + * + * @param name Human-readable operation name (e.g., "login_flow"). + * @param operationKey Optional key that uniquely identifies this operation instance. + * @param attributes Additional attributes to attach to the operation. + * @param promise Resolved with `null` when the call completes. + */ + fun startFeatureOperation(name: String, operationKey: String? = null, attributes: ReadableMap, promise: Promise) { + val attributesMap = attributes.toHashMap().toMutableMap() + datadog.getRumMonitor().startFeatureOperation(name, operationKey, attributesMap); + promise.resolve(null) + } + + /** + * Marks a Feature Operation as successfully completed. + * + * @param name The name of the feature operation (for example, `"login_flow"`). + * @param operationKey The key of the operation instance to complete, if one was provided when starting it. + * @param attributes A map of custom attributes to attach to this completion event. + */ + fun succeedFeatureOperation(name: String, operationKey: String? = null, attributes: ReadableMap, promise: Promise) { + val attributesMap = attributes.toHashMap().toMutableMap() + datadog.getRumMonitor().succeedFeatureOperation(name, operationKey, attributesMap) + promise.resolve(null) + } + + + /** + * Marks a Feature Operation as failed. + * + * @param name The name of the feature operation (for example, `"login_flow"`). + * @param operationKey The key of the operation instance to fail, if one was provided when starting it. + * @param failureReason The reason for the failure. Possible values are defined in [FailureReason] + * (e.g., `FailureReason.ERROR`, `FailureReason.ABANDONED`, `FailureReason.OTHER`). + * @param attributes A map of custom attributes to attach to this failure event. + */ + fun failFeatureOperation( + name: String, + operationKey: String? = null, + failureReason: String, + attributes: ReadableMap, + promise: Promise + ) { + val attributesMap = attributes.toHashMap().toMutableMap() + val reason = runCatching { + enumValueOf(failureReason.uppercase()) + }.getOrDefault(FailureReason.OTHER) + + datadog.getRumMonitor().failFeatureOperation(name, operationKey, reason, attributesMap) + promise.resolve(null) + } + // region Internal private fun String.asRumActionType(): RumActionType { diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt index 6cb2b385b..30788acff 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt @@ -6,6 +6,7 @@ package com.datadog.reactnative +import com.datadog.android.rum.featureoperations.FailureReason import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactMethod @@ -52,7 +53,12 @@ class DdRum( * If not provided, current timestamp will be used. */ @ReactMethod - override fun stopView(key: String, context: ReadableMap, timestampMs: Double, promise: Promise) { + override fun stopView( + key: String, + context: ReadableMap, + timestampMs: Double, + promise: Promise + ) { implementation.stopView(key, context, timestampMs, promise) } @@ -276,4 +282,59 @@ class DdRum( override fun getCurrentSessionId(promise: Promise) { implementation.getCurrentSessionId(promise) } + + /** + * Starts a RUM Feature Operation. + * + * @param name Human-readable operation name (e.g., "login_flow"). + * @param operationKey Optional key that uniquely identifies this operation instance. + * @param attributes Additional attributes to attach to the operation. + * @param promise Resolved with `null` when the call completes. + */ + @ReactMethod + override fun startFeatureOperation( + name: String, + operationKey: String?, + attributes: ReadableMap, + promise: Promise + ) { + implementation.startFeatureOperation(name, operationKey, attributes, promise) + } + + /** + * Marks a Feature Operation as successfully completed. + * + * @param name The name of the feature operation (for example, `"login_flow"`). + * @param operationKey The key of the operation instance to complete, if one was provided when starting it. + * @param attributes A map of custom attributes to attach to this completion event. + */ + @ReactMethod + override fun succeedFeatureOperation( + name: String, + operationKey: String?, + attributes: ReadableMap, + promise: Promise + ) { + implementation.succeedFeatureOperation(name, operationKey, attributes, promise) + } + + /** + * Marks a Feature Operation as failed. + * + * @param name The name of the feature operation (for example, `"login_flow"`). + * @param operationKey The key of the operation instance to fail, if one was provided when starting it. + * @param failureReason The reason for the failure. Possible values are defined in [FailureReason] + * (e.g., `FailureReason.ERROR`, `FailureReason.ABANDONED`, `FailureReason.OTHER`). + * @param attributes A map of custom attributes to attach to this failure event. + */ + @ReactMethod + override fun failFeatureOperation( + name: String, + operationKey: String?, + failureReason: String, + attributes: ReadableMap, + promise: Promise + ) { + implementation.failFeatureOperation(name, operationKey, failureReason, attributes, promise) + } } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt index a6c4965ea..4ef8409b2 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt @@ -6,6 +6,7 @@ package com.datadog.reactnative +import com.datadog.android.rum.featureoperations.FailureReason import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule @@ -266,4 +267,59 @@ class DdRum( fun getCurrentSessionId(promise: Promise) { implementation.getCurrentSessionId(promise) } + + /** + * Starts a RUM Feature Operation. + * + * @param name Human-readable operation name (e.g., "login_flow"). + * @param operationKey Optional key that uniquely identifies this operation instance. + * @param attributes Additional attributes to attach to the operation. + * @param promise Resolved with `null` when the call completes. + */ + @ReactMethod + fun startFeatureOperation( + name: String, + operationKey: String? = null, + attributes: ReadableMap, + promise: Promise + ) { + implementation.startFeatureOperation(name, operationKey, attributes, promise) + } + + /** + * Marks a Feature Operation as successfully completed. + * + * @param name The name of the feature operation (for example, "login_flow"). + * @param operationKey The key of the operation instance to complete, if one was provided. + * @param attributes A map of custom attributes to attach to this completion event. + */ + @ReactMethod + fun succeedFeatureOperation( + name: String, + operationKey: String? = null, + attributes: ReadableMap, + promise: Promise + ) { + implementation.succeedFeatureOperation(name, operationKey, attributes, promise) + } + + /** + * Marks a Feature Operation as failed. + * + * @param name The name of the feature operation (for example, "login_flow"). + * @param operationKey The key of the operation instance to fail, if one was provided. + * @param failureReason The reason for the failure. Values are defined in [FailureReason] + * (e.g., `FailureReason.ERROR`, `FailureReason.ABANDONED`, `FailureReason.OTHER`). + * @param attributes A map of custom attributes to attach to this failure event. + */ + @ReactMethod + fun failFeatureOperation( + name: String, + operationKey: String? = null, + failureReason: String, + attributes: ReadableMap, + promise: Promise + ) { + implementation.failFeatureOperation(name, operationKey, failureReason, attributes, promise) + } } diff --git a/packages/core/ios/Sources/DdRum.mm b/packages/core/ios/Sources/DdRum.mm index f5c324ce8..c891537f3 100644 --- a/packages/core/ios/Sources/DdRum.mm +++ b/packages/core/ios/Sources/DdRum.mm @@ -164,6 +164,37 @@ @implementation DdRum [self getCurrentSessionId:resolve reject:reject]; } +RCT_REMAP_METHOD(startFeatureOperation, + startWithName:(NSString*)name + withOperationKey:(NSString*)operationKey + withAttributes:(NSDictionary*)attributes + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self startFeatureOperation:name operationKey:operationKey attributes:attributes resolve:resolve reject:reject]; +} + +RCT_REMAP_METHOD(succeedFeatureOperation, + succeedWithName:(NSString*)name + withOperationKey:(NSString*)operationKey + withAttributes:(NSDictionary*)attributes + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self succeedFeatureOperation:name operationKey:operationKey attributes:attributes resolve:resolve reject:reject]; +} + +RCT_REMAP_METHOD(failFeatureOperation, + failWithName:(NSString*)name + withOperationKey:(NSString*)operationKey + withReason:(NSString*)reason + withAttributes:(NSDictionary*)attributes + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self failFeatureOperation:name operationKey:operationKey reason:reason attributes:attributes resolve:resolve reject:reject]; +} + // Thanks to this guard, we won't compile this code when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: @@ -257,4 +288,16 @@ - (void)stopView:(NSString *)key context:(NSDictionary *)context timestampMs:(do [self.ddRumImplementation stopViewWithKey:key context:context timestampMs:timestampMs resolve:resolve reject:reject]; } +- (void) startFeatureOperation:(NSString *)name operationKey:(NSString *)operationKey attributes:(NSDictionary *)attributes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation startFeatureOperationWithName:name operationKey:operationKey attributes:attributes resolve:resolve reject:reject]; +} + +- (void) succeedFeatureOperation:(NSString *)name operationKey:(NSString *)operationKey attributes:(NSDictionary *)attributes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation succeedFeatureOperationWithName:name operationKey:operationKey attributes:attributes resolve:resolve reject:reject]; +} + +- (void) failFeatureOperation:(NSString *)name operationKey:(NSString *)operationKey reason:(NSString *)reason attributes:(NSDictionary *)attributes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation failFeatureOperationWithName:name operationKey:operationKey reason:reason attributes:attributes resolve:resolve reject:reject]; +} + @end diff --git a/packages/core/ios/Sources/DdRumImplementation.swift b/packages/core/ios/Sources/DdRumImplementation.swift index 6fac21f82..0ac8a19bf 100644 --- a/packages/core/ios/Sources/DdRumImplementation.swift +++ b/packages/core/ios/Sources/DdRumImplementation.swift @@ -63,6 +63,16 @@ private extension RUMMethod { } } +internal extension RUMFeatureOperationFailureReason { + init(from string: String) { + switch string.lowercased() { + case "error": self = .error + case "abandoned": self = .abandoned + default: self = .other + } + } +} + @objc public class DdRumImplementation: NSObject { internal static let timestampKey = "_dd.timestamp" @@ -236,6 +246,47 @@ public class DdRumImplementation: NSObject { resolve(sessionId) } } + + @objc + public func startFeatureOperation( + name: String, + operationKey: String?, + attributes: NSDictionary, + resolve: @escaping (Any?) -> Void, + reject: RCTPromiseRejectBlock + ){ + let castedAttributes = castAttributesToSwift(attributes) + nativeRUM.startFeatureOperation(name: name, operationKey: operationKey, attributes: castedAttributes) + resolve(nil) + } + + @objc + public func succeedFeatureOperation( + name: String, + operationKey: String?, + attributes: NSDictionary, + resolve: @escaping (Any?) -> Void, + reject: RCTPromiseRejectBlock + ){ + let castedAttributes = castAttributesToSwift(attributes) + nativeRUM.succeedFeatureOperation(name: name, operationKey: operationKey, attributes: castedAttributes) + resolve(nil) + } + + @objc + public func failFeatureOperation( + name: String, + operationKey: String?, + reason: String, + attributes: NSDictionary, + resolve: @escaping (Any?) -> Void, + reject: RCTPromiseRejectBlock + ){ + let castedAttributes = castAttributesToSwift(attributes) + nativeRUM.failFeatureOperation(name: name, operationKey: operationKey, + reason: RUMFeatureOperationFailureReason(from: reason), attributes: castedAttributes) + resolve(nil) + } // MARK: - Private methods diff --git a/packages/core/jest/mock.js b/packages/core/jest/mock.js index 9f5ee2c41..aadc79d27 100644 --- a/packages/core/jest/mock.js +++ b/packages/core/jest/mock.js @@ -154,6 +154,15 @@ module.exports = { .mockImplementation( () => new Promise(resolve => resolve('test-session-id')) ), + startFeatureOperation: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + succeedFeatureOperation: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), + failFeatureOperation: jest + .fn() + .mockImplementation(() => new Promise() < (resolve => resolve())), setTimeProvider: jest.fn().mockImplementation(() => {}), timeProvider: jest.fn().mockReturnValue(undefined), getTracingContext: jest.fn().mockReturnValue(undefined), diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index 062fecc90..ce39c9dfa 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -42,7 +42,7 @@ import { DatadogProvider } from './sdk/DatadogProvider/DatadogProvider'; import { DdSdk } from './sdk/DdSdk'; import { FileBasedConfiguration } from './sdk/FileBasedConfiguration/FileBasedConfiguration'; import { DdTrace } from './trace/DdTrace'; -import { ErrorSource } from './types'; +import { ErrorSource, FeatureOperationFailure } from './types'; import { DefaultTimeProvider } from './utils/time-provider/DefaultTimeProvider'; import type { Timestamp } from './utils/time-provider/TimeProvider'; import { TimeProvider } from './utils/time-provider/TimeProvider'; @@ -57,6 +57,7 @@ export { DdRum, RumActionType, ErrorSource, + FeatureOperationFailure, DdSdkReactNativeConfiguration, DdSdkReactNative, DdSdk, diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index 1e231a197..6f4a63fc8 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -14,7 +14,7 @@ import type { Attributes } from '../sdk/AttributesSingleton/types'; import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; import { DdSdk } from '../sdk/DdSdk'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; -import type { ErrorSource } from '../types'; +import type { ErrorSource, FeatureOperationFailure } from '../types'; import { validateContext } from '../utils/argsUtils'; import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; @@ -133,6 +133,58 @@ class DdRumWrapper implements DdRumType { return this.callNativeStopAction(...nativeCallArgs); }; + startFeatureOperation( + name: string, + operationKey: string | null, + attributes: object + ): Promise { + InternalLog.log( + `Starting feature operation “${name}” (${operationKey})`, + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.startFeatureOperation(name, operationKey, attributes) + ); + } + + succeedFeatureOperation( + name: string, + operationKey: string | null, + attributes: object + ): Promise { + InternalLog.log( + `Succeding feature operation “${name}” (${operationKey})`, + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.succeedFeatureOperation( + name, + operationKey, + attributes + ) + ); + } + + failFeatureOperation( + name: string, + operationKey: string | null, + reason: FeatureOperationFailure, + attributes: object + ): Promise { + InternalLog.log( + `Failing feature operation “${name}” (${operationKey})`, + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.failFeatureOperation( + name, + operationKey, + reason, + attributes + ) + ); + } + setTimeProvider = (timeProvider: TimeProvider): void => { this.timeProvider = timeProvider; }; diff --git a/packages/core/src/rum/types.ts b/packages/core/src/rum/types.ts index 3def7f0e6..b879010f7 100644 --- a/packages/core/src/rum/types.ts +++ b/packages/core/src/rum/types.ts @@ -5,7 +5,7 @@ */ import type { Attributes } from '../sdk/AttributesSingleton/types'; -import type { ErrorSource } from '../types'; +import type { ErrorSource, FeatureOperationFailure } from '../types'; import type { DatadogTracingContext } from './instrumentation/resourceTracking/distributedTracing/DatadogTracingContext'; import type { DatadogTracingIdentifier } from './instrumentation/resourceTracking/distributedTracing/DatadogTracingIdentifier'; @@ -230,6 +230,46 @@ export type DdRumType = { * Generates a unique 128bit Span ID. */ generateSpanId(): DatadogTracingIdentifier; + + /** + * Starts a Feature Operation, representing a high-level logical flow within your application (e.g., `login_flow`). + * @param name - The name of the feature operation (for example, `"login_flow"`). + * @param operationKey - An optional key to uniquely identify a specific instance of this operation when multiple are running concurrently. + * @param attributes - Custom attributes to attach to this operation. + */ + startFeatureOperation( + name: string, + operationKey: string | null, + attributes: object + ): Promise; + + /** + * Marks a Feature Operation as successfully completed. + * Should be called when a previously started operation (via `startFeatureOperation`) finishes without error. + * @param name - The name of the feature operation (for example, `"login_flow"`). + * @param operationKey - The key for the operation instance to complete, if it was specified when starting it. + * @param attributes - Custom attributes to attach to this operation’s completion event. + */ + succeedFeatureOperation( + name: string, + operationKey: string | null, + attributes: object + ): Promise; + + /** + * Marks a Feature Operation as failed. + * Should be called when a previously started operation (via `startFeatureOperation`) ends with an error. + * @param name - The name of the feature operation (for example, `"login_flow"`). + * @param operationKey - The key for the operation instance to fail, if it was specified when starting it. + * @param reason - The reason for the failure. + * @param attributes - Custom attributes to attach to this operation’s failure event. + */ + failFeatureOperation( + name: string, + operationKey: string | null, + reason: FeatureOperationFailure, + attributes: object + ): Promise; }; /** diff --git a/packages/core/src/specs/NativeDdRum.ts b/packages/core/src/specs/NativeDdRum.ts index e31f5b925..9c0c459b3 100644 --- a/packages/core/src/specs/NativeDdRum.ts +++ b/packages/core/src/specs/NativeDdRum.ts @@ -185,6 +185,46 @@ export interface Spec extends TurboModule { * Get current Session ID, or `undefined` if not available. */ getCurrentSessionId(): Promise; + + /** + * Starts a Feature Operation, representing a high-level logical flow within your application (e.g., `login_flow`). + * @param name - The name of the feature operation (for example, `"login_flow"`). + * @param operationKey - An optional key to uniquely identify a specific instance of this operation when multiple are running concurrently. + * @param attributes - Custom attributes to attach to this operation. + */ + startFeatureOperation( + name: string, + operationKey: string | null, + attributes: Object + ): Promise; + + /** + * Marks a Feature Operation as successfully completed. + * Should be called when a previously started operation (via `startFeatureOperation`) finishes without error. + * @param name - The name of the feature operation (for example, `"login_flow"`). + * @param operationKey - The key for the operation instance to complete, if it was specified when starting it. + * @param attributes - Custom attributes to attach to this operation’s completion event. + */ + succeedFeatureOperation( + name: string, + operationKey: string | null, + attributes: Object + ): Promise; + + /** + * Marks a Feature Operation as failed. + * Should be called when a previously started operation (via `startFeatureOperation`) ends with an error. + * @param name - The name of the feature operation (for example, `"login_flow"`). + * @param operationKey - The key for the operation instance to fail, if it was specified when starting it. + * @param reason - The reason for the failure. + * @param attributes - Custom attributes to attach to this operation’s failure event. + */ + failFeatureOperation( + name: string, + operationKey: string | null, + reason: string, + attributes: Object + ): Promise; } // eslint-disable-next-line import/no-default-export diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 7f97779ee..cd697c04b 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -232,3 +232,9 @@ export enum ErrorSource { WEBVIEW = 'WEBVIEW', CUSTOM = 'CUSTOM' } + +export enum FeatureOperationFailure { + ERROR = 'ERROR', + ABANDONED = 'ABANDONED', + OTHER = 'OTHER' +} diff --git a/packages/react-native-apollo-client/__mocks__/react-native.ts b/packages/react-native-apollo-client/__mocks__/react-native.ts index bbac607d3..ded32499f 100644 --- a/packages/react-native-apollo-client/__mocks__/react-native.ts +++ b/packages/react-native-apollo-client/__mocks__/react-native.ts @@ -110,7 +110,16 @@ actualRN.NativeModules.DdRum = { new Promise(resolve => resolve('test-session-id') ) - ) as jest.MockedFunction + ) as jest.MockedFunction, + startFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + succeedFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + failFeatureOperation: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction }; module.exports = actualRN; From ee2972a2d579b7d88332251ae69c3267dc812a5e Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Tue, 18 Nov 2025 09:57:58 +0100 Subject: [PATCH 052/114] Remove defaultPrivacyLevel from Session Replay --- ...lemetryConfigurationEventForgeryFactory.kt | 1 - .../src/SessionReplay.ts | 41 +------------------ .../src/__tests__/SessionReplay.test.ts | 38 +---------------- 3 files changed, 2 insertions(+), 78 deletions(-) diff --git a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/forge/TelemetryConfigurationEventForgeryFactory.kt b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/forge/TelemetryConfigurationEventForgeryFactory.kt index 10e894e6e..684d92c21 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/forge/TelemetryConfigurationEventForgeryFactory.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/forge/TelemetryConfigurationEventForgeryFactory.kt @@ -86,7 +86,6 @@ internal class TelemetryConfigurationEventForgeryFactory : ) } }, - defaultPrivacyLevel = forge.aNullable { aString() }, enablePrivacyForActionName = forge.aNullable { aBool() }, useExcludedActivityUrls = forge.aNullable { aBool() }, useWorkerUrl = forge.aNullable { aBool() }, diff --git a/packages/react-native-session-replay/src/SessionReplay.ts b/packages/react-native-session-replay/src/SessionReplay.ts index 60d4e8ff8..516b8ffb4 100644 --- a/packages/react-native-session-replay/src/SessionReplay.ts +++ b/packages/react-native-session-replay/src/SessionReplay.ts @@ -95,15 +95,6 @@ export interface SessionReplayConfiguration { * Default: `true`. */ startRecordingImmediately?: boolean; - - /** - * Defines the way sensitive content (e.g. text) should be masked. - * - * Default `SessionReplayPrivacy.MASK`. - * @deprecated Use {@link imagePrivacyLevel}, {@link touchPrivacyLevel} and {@link textAndInputPrivacyLevel} instead. - * Note: setting this property (`defaultPrivacyLevel`) will override the individual privacy levels. - */ - defaultPrivacyLevel?: SessionReplayPrivacy; } type InternalBaseSessionReplayConfiguration = { @@ -121,11 +112,8 @@ type InternalPrivacySessionReplayConfiguration = { type InternalSessionReplayConfiguration = InternalBaseSessionReplayConfiguration & InternalPrivacySessionReplayConfiguration; -const DEFAULTS: InternalSessionReplayConfiguration & { - defaultPrivacyLevel: SessionReplayPrivacy; -} = { +const DEFAULTS: InternalSessionReplayConfiguration = { replaySampleRate: 100, - defaultPrivacyLevel: SessionReplayPrivacy.MASK, customEndpoint: '', imagePrivacyLevel: ImagePrivacyLevel.MASK_ALL, touchPrivacyLevel: TouchPrivacyLevel.HIDE, @@ -175,33 +163,6 @@ export class SessionReplayWrapper { DEFAULTS.textAndInputPrivacyLevel }; - // Legacy Default Privacy Level property handling - if (configuration.defaultPrivacyLevel) { - switch (configuration.defaultPrivacyLevel) { - case SessionReplayPrivacy.MASK: - privacyConfig.imagePrivacyLevel = - ImagePrivacyLevel.MASK_ALL; - privacyConfig.touchPrivacyLevel = TouchPrivacyLevel.HIDE; - privacyConfig.textAndInputPrivacyLevel = - TextAndInputPrivacyLevel.MASK_ALL; - break; - case SessionReplayPrivacy.MASK_USER_INPUT: - privacyConfig.imagePrivacyLevel = - ImagePrivacyLevel.MASK_NONE; - privacyConfig.touchPrivacyLevel = TouchPrivacyLevel.HIDE; - privacyConfig.textAndInputPrivacyLevel = - TextAndInputPrivacyLevel.MASK_ALL_INPUTS; - break; - case SessionReplayPrivacy.ALLOW: - privacyConfig.imagePrivacyLevel = - ImagePrivacyLevel.MASK_NONE; - privacyConfig.touchPrivacyLevel = TouchPrivacyLevel.SHOW; - privacyConfig.textAndInputPrivacyLevel = - TextAndInputPrivacyLevel.MASK_SENSITIVE_INPUTS; - break; - } - } - return { ...baseConfig, ...privacyConfig }; }; diff --git a/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts b/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts index c755fc6e2..43f449cf7 100644 --- a/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts +++ b/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts @@ -9,7 +9,6 @@ import { NativeModules } from 'react-native'; import { ImagePrivacyLevel, SessionReplay, - SessionReplayPrivacy, TextAndInputPrivacyLevel, TouchPrivacyLevel } from '../SessionReplay'; @@ -41,27 +40,9 @@ describe('SessionReplay', () => { ); }); - it('calls native session replay with provided configuration { w defaultPrivacyLevel = ALLOW }', () => { + it('calls native session replay with provided configuration { w custom endpoint }', () => { SessionReplay.enable({ replaySampleRate: 100, - defaultPrivacyLevel: SessionReplayPrivacy.ALLOW, - customEndpoint: 'https://session-replay.example.com' - }); - - expect(NativeModules.DdSessionReplay.enable).toHaveBeenCalledWith( - 100, - 'https://session-replay.example.com', - 'MASK_NONE', - 'SHOW', - 'MASK_SENSITIVE_INPUTS', - true - ); - }); - - it('calls native session replay with provided configuration { w defaultPrivacyLevel = MASK }', () => { - SessionReplay.enable({ - replaySampleRate: 100, - defaultPrivacyLevel: SessionReplayPrivacy.MASK, customEndpoint: 'https://session-replay.example.com' }); @@ -75,23 +56,6 @@ describe('SessionReplay', () => { ); }); - it('calls native session replay with provided configuration { w defaultPrivacyLevel = MASK_USER_INPUT }', () => { - SessionReplay.enable({ - replaySampleRate: 100, - defaultPrivacyLevel: SessionReplayPrivacy.MASK_USER_INPUT, - customEndpoint: 'https://session-replay.example.com' - }); - - expect(NativeModules.DdSessionReplay.enable).toHaveBeenCalledWith( - 100, - 'https://session-replay.example.com', - 'MASK_NONE', - 'HIDE', - 'MASK_ALL_INPUTS', - true - ); - }); - it('calls native session replay with provided configuration { w random privacy levels }', () => { const TIMES = 20; From a7067aeb81cdcf14507068e5736dafef49d32e24 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 28 Nov 2025 11:49:29 +0100 Subject: [PATCH 053/114] Bump Native SDKs to 3.3.0 --- benchmarks/android/app/build.gradle | 2 +- benchmarks/ios/Podfile.lock | 74 +++++++------- example-new-architecture/ios/Podfile.lock | 52 ++++++++-- example/ios/Podfile.lock | 96 +++++++++---------- packages/core/DatadogSDKReactNative.podspec | 12 +-- packages/core/android/build.gradle | 8 +- ...DatadogSDKReactNativeSessionReplay.podspec | 2 +- .../android/build.gradle | 4 +- .../DatadogSDKReactNativeWebView.podspec | 4 +- .../react-native-webview/android/build.gradle | 2 +- 10 files changed, 146 insertions(+), 110 deletions(-) diff --git a/benchmarks/android/app/build.gradle b/benchmarks/android/app/build.gradle index 04c240bd0..ba6ae7a53 100644 --- a/benchmarks/android/app/build.gradle +++ b/benchmarks/android/app/build.gradle @@ -129,5 +129,5 @@ dependencies { // Benchmark tools from dd-sdk-android are used for vitals recording // Remember to bump thid alongside the main dd-sdk-android dependencies - implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.1.0") + implementation("com.datadoghq:dd-sdk-android-benchmark-internal:3.3.0") } diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index 2da943f5e..723e72e67 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -1,22 +1,22 @@ PODS: - boost (1.84.0) - - DatadogCore (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogCrashReporting (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogCore (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogCrashReporting (3.3.0): + - DatadogInternal (= 3.3.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.1.0) - - DatadogLogs (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogRUM (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.12.1): - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) + - DatadogInternal (3.3.0) + - DatadogLogs (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogRUM (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogSDKReactNative (2.13.2): + - DatadogCore (= 3.3.0) + - DatadogCrashReporting (= 3.3.0) + - DatadogLogs (= 3.3.0) + - DatadogRUM (= 3.3.0) + - DatadogTrace (= 3.3.0) + - DatadogWebViewTracking (= 3.3.0) - DoubleConversion - glog - hermes-engine @@ -39,7 +39,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.1.0) + - DatadogSessionReplay (= 3.3.0) - DoubleConversion - glog - hermes-engine @@ -60,10 +60,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.12.1): - - DatadogInternal (= 3.1.0) + - DatadogSDKReactNativeWebView (2.13.2): + - DatadogInternal (= 3.3.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.1.0) + - DatadogWebViewTracking (= 3.3.0) - DoubleConversion - glog - hermes-engine @@ -84,13 +84,13 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSessionReplay (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogTrace (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogSessionReplay (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogTrace (3.3.0): + - DatadogInternal (= 3.3.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogWebViewTracking (3.3.0): + - DatadogInternal (= 3.3.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.78.2) @@ -2070,17 +2070,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d - DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d - DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc - DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 - DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 - DatadogSDKReactNative: 8e0f39de38621d4d7ed961a74d8a216fd3a38321 - DatadogSDKReactNativeSessionReplay: f9288c8e981dcc65d1f727b01421ee9a7601e75f - DatadogSDKReactNativeWebView: 993527f6c5d38e0fcc4804a6a60c334dd199dc5b - DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 - DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 - DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 + DatadogCore: 9b1256ac9c27a07087d6214c8546acf756e40be7 + DatadogCrashReporting: 89a00886ef40808bffb8ccb4b6531e472f52e213 + DatadogInternal: 21dac5a7db548da6368a096d0714bdbec66deb6c + DatadogLogs: 355a4ac6bce3f0cb8231819e475c03dbbdd7957c + DatadogRUM: 1b3a47a9b9a5a25890f7fb3aa1f2bd86009d1086 + DatadogSDKReactNative: b364ddf133b4d774f3f7bfb5fc98232f960f5331 + DatadogSDKReactNativeSessionReplay: 73b5b7d46abe2ea8ffcaccb0c6232e49c0e27591 + DatadogSDKReactNativeWebView: 0310cc142fb39e185112e79f196f99f856a96c31 + DatadogSessionReplay: 85e63d3c5e5618c3029726d00595750a73c0920a + DatadogTrace: f13e8c09981787d6cb0a4b7fd1991351fab6d64b + DatadogWebViewTracking: 08fe084b5f57da05c1610fab49ce7bc84226141e DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: e32d34492c519a2194ec9d7f5e7a79d11b73f91c diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index d0e64cb43..f00b8a6f3 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,13 +1,14 @@ PODS: - boost (1.84.0) - - DatadogCore (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogCrashReporting (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogCore (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogCrashReporting (3.3.0): + - DatadogInternal (= 3.3.0) - PLCrashReporter (~> 1.12.0) <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD - DatadogInternal (2.30.2) - DatadogLogs (2.30.2): @@ -65,6 +66,20 @@ PODS: >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) ======= >>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) +======= + - DatadogInternal (3.3.0) + - DatadogLogs (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogRUM (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogSDKReactNative (2.13.2): + - DatadogCore (= 3.3.0) + - DatadogCrashReporting (= 3.3.0) + - DatadogLogs (= 3.3.0) + - DatadogRUM (= 3.3.0) + - DatadogTrace (= 3.3.0) + - DatadogWebViewTracking (= 3.3.0) +>>>>>>> e8623221 (Bump Native SDKs to 3.3.0) - DoubleConversion - glog - hermes-engine @@ -87,6 +102,7 @@ PODS: - Yoga <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) @@ -128,6 +144,15 @@ PODS: >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) ======= >>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) +======= + - DatadogSDKReactNative/Tests (2.13.2): + - DatadogCore (= 3.3.0) + - DatadogCrashReporting (= 3.3.0) + - DatadogLogs (= 3.3.0) + - DatadogRUM (= 3.3.0) + - DatadogTrace (= 3.3.0) + - DatadogWebViewTracking (= 3.3.0) +>>>>>>> e8623221 (Bump Native SDKs to 3.3.0) - DoubleConversion - glog - hermes-engine @@ -148,11 +173,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogTrace (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogTrace (3.3.0): + - DatadogInternal (= 3.3.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogWebViewTracking (3.3.0): + - DatadogInternal (= 3.3.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1937,6 +1962,7 @@ SPEC CHECKSUMS: <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b @@ -1979,6 +2005,16 @@ SPEC CHECKSUMS: DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 >>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) +======= + DatadogCore: 9b1256ac9c27a07087d6214c8546acf756e40be7 + DatadogCrashReporting: 89a00886ef40808bffb8ccb4b6531e472f52e213 + DatadogInternal: 21dac5a7db548da6368a096d0714bdbec66deb6c + DatadogLogs: 355a4ac6bce3f0cb8231819e475c03dbbdd7957c + DatadogRUM: 1b3a47a9b9a5a25890f7fb3aa1f2bd86009d1086 + DatadogSDKReactNative: 3c756b98ff379907842eb3769d44e6b8b570385f + DatadogTrace: f13e8c09981787d6cb0a4b7fd1991351fab6d64b + DatadogWebViewTracking: 08fe084b5f57da05c1610fab49ce7bc84226141e +>>>>>>> e8623221 (Bump Native SDKs to 3.3.0) DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 190e83448..553681f27 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,34 +1,34 @@ PODS: - boost (1.84.0) - - DatadogCore (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogCrashReporting (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogCore (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogCrashReporting (3.3.0): + - DatadogInternal (= 3.3.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.1.0) - - DatadogLogs (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogRUM (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.12.1): - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) + - DatadogInternal (3.3.0) + - DatadogLogs (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogRUM (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogSDKReactNative (2.13.2): + - DatadogCore (= 3.3.0) + - DatadogCrashReporting (= 3.3.0) + - DatadogLogs (= 3.3.0) + - DatadogRUM (= 3.3.0) + - DatadogTrace (= 3.3.0) + - DatadogWebViewTracking (= 3.3.0) - React-Core - - DatadogSDKReactNative/Tests (2.12.1): - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) + - DatadogSDKReactNative/Tests (2.13.2): + - DatadogCore (= 3.3.0) + - DatadogCrashReporting (= 3.3.0) + - DatadogLogs (= 3.3.0) + - DatadogRUM (= 3.3.0) + - DatadogTrace (= 3.3.0) + - DatadogWebViewTracking (= 3.3.0) - React-Core - DatadogSDKReactNativeSessionReplay (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.1.0) + - DatadogSessionReplay (= 3.3.0) - DoubleConversion - glog - hermes-engine @@ -51,7 +51,7 @@ PODS: - Yoga - DatadogSDKReactNativeSessionReplay/Tests (2.14.0): - DatadogSDKReactNative - - DatadogSessionReplay (= 3.1.0) + - DatadogSessionReplay (= 3.3.0) - DoubleConversion - glog - hermes-engine @@ -73,25 +73,25 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNativeWebView (2.12.1): - - DatadogInternal (= 3.1.0) + - DatadogSDKReactNativeWebView (2.13.2): + - DatadogInternal (= 3.3.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.1.0) + - DatadogWebViewTracking (= 3.3.0) - React-Core - - DatadogSDKReactNativeWebView/Tests (2.12.1): - - DatadogInternal (= 3.1.0) + - DatadogSDKReactNativeWebView/Tests (2.13.2): + - DatadogInternal (= 3.3.0) - DatadogSDKReactNative - - DatadogWebViewTracking (= 3.1.0) + - DatadogWebViewTracking (= 3.3.0) - React-Core - react-native-webview - React-RCTText - - DatadogSessionReplay (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogTrace (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogSessionReplay (3.3.0): + - DatadogInternal (= 3.3.0) + - DatadogTrace (3.3.0): + - DatadogInternal (= 3.3.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.1.0): - - DatadogInternal (= 3.1.0) + - DatadogWebViewTracking (3.3.0): + - DatadogInternal (= 3.3.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1988,17 +1988,17 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d - DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d - DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc - DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 - DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 - DatadogSDKReactNative: af351a4e1ce08124c290c52de94b0062a166cc67 - DatadogSDKReactNativeSessionReplay: dcbd55d9d0f2b86026996a8b7ec9654922d5dfe1 - DatadogSDKReactNativeWebView: 096ac87eb753b6a217b93441983264b9837c3b7e - DatadogSessionReplay: 6bc71888e2b41dd0de3325f06f0c0b3cee0e6df4 - DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 - DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 + DatadogCore: 9b1256ac9c27a07087d6214c8546acf756e40be7 + DatadogCrashReporting: 89a00886ef40808bffb8ccb4b6531e472f52e213 + DatadogInternal: 21dac5a7db548da6368a096d0714bdbec66deb6c + DatadogLogs: 355a4ac6bce3f0cb8231819e475c03dbbdd7957c + DatadogRUM: 1b3a47a9b9a5a25890f7fb3aa1f2bd86009d1086 + DatadogSDKReactNative: 96c64d4627096497594113ffb0c86ae72490b17c + DatadogSDKReactNativeSessionReplay: 02ea3eefd261341d2ae839882351be3d209376d0 + DatadogSDKReactNativeWebView: 83cd1a58da38a7a4bd554051d6742138e36e3589 + DatadogSessionReplay: 85e63d3c5e5618c3029726d00595750a73c0920a + DatadogTrace: f13e8c09981787d6cb0a4b7fd1991351fab6d64b + DatadogWebViewTracking: 08fe084b5f57da05c1610fab49ce7bc84226141e DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 diff --git a/packages/core/DatadogSDKReactNative.podspec b/packages/core/DatadogSDKReactNative.podspec index c0d235304..692dd23c8 100644 --- a/packages/core/DatadogSDKReactNative.podspec +++ b/packages/core/DatadogSDKReactNative.podspec @@ -19,14 +19,14 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the versions in sync with DatadogSDKReactNativeSessionReplay.podspec - s.dependency 'DatadogCore', '3.1.0' - s.dependency 'DatadogLogs', '3.1.0' - s.dependency 'DatadogTrace', '3.1.0' - s.dependency 'DatadogRUM', '3.1.0' - s.dependency 'DatadogCrashReporting', '3.1.0' + s.dependency 'DatadogCore', '3.3.0' + s.dependency 'DatadogLogs', '3.3.0' + s.dependency 'DatadogTrace', '3.3.0' + s.dependency 'DatadogRUM', '3.3.0' + s.dependency 'DatadogCrashReporting', '3.3.0' # DatadogWebViewTracking is not available for tvOS - s.ios.dependency 'DatadogWebViewTracking', '3.1.0' + s.ios.dependency 'DatadogWebViewTracking', '3.3.0' s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'ios/Tests/**/*.{swift,json}' diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 1344b2531..bf7a2ef9c 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -195,10 +195,10 @@ dependencies { } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compileOnly "com.squareup.okhttp3:okhttp:3.12.13" - implementation "com.datadoghq:dd-sdk-android-rum:3.2.0" - implementation "com.datadoghq:dd-sdk-android-logs:3.2.0" - implementation "com.datadoghq:dd-sdk-android-trace:3.2.0" - implementation "com.datadoghq:dd-sdk-android-webview:3.2.0" + implementation "com.datadoghq:dd-sdk-android-rum:3.3.0" + implementation "com.datadoghq:dd-sdk-android-logs:3.3.0" + implementation "com.datadoghq:dd-sdk-android-trace:3.3.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.3.0" implementation "com.google.code.gson:gson:2.10.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec index 6a5d0b78f..e0b032a9e 100644 --- a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec +++ b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| s.dependency "React-Core" # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec - s.dependency 'DatadogSessionReplay', '3.1.0' + s.dependency 'DatadogSessionReplay', '3.3.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle index a0d77f2ff..1b16713db 100644 --- a/packages/react-native-session-replay/android/build.gradle +++ b/packages/react-native-session-replay/android/build.gradle @@ -216,8 +216,8 @@ dependencies { api "com.facebook.react:react-android:$reactNativeVersion" } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.datadoghq:dd-sdk-android-session-replay:3.1.0" - implementation "com.datadoghq:dd-sdk-android-internal:3.1.0" + implementation "com.datadoghq:dd-sdk-android-session-replay:3.3.0" + implementation "com.datadoghq:dd-sdk-android-internal:3.3.0" implementation project(path: ':datadog_mobile-react-native') testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" diff --git a/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec b/packages/react-native-webview/DatadogSDKReactNativeWebView.podspec index 080a853d8..000b35477 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.1.0' - s.dependency 'DatadogInternal', '3.1.0' + s.dependency 'DatadogWebViewTracking', '3.3.0' + s.dependency 'DatadogInternal', '3.3.0' s.dependency 'DatadogSDKReactNative' s.test_spec 'Tests' do |test_spec| diff --git a/packages/react-native-webview/android/build.gradle b/packages/react-native-webview/android/build.gradle index e1fb081f0..50fa7fce3 100644 --- a/packages/react-native-webview/android/build.gradle +++ b/packages/react-native-webview/android/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "com.facebook.react:react-android:$reactNativeVersion" } - implementation "com.datadoghq:dd-sdk-android-webview:3.1.0" + implementation "com.datadoghq:dd-sdk-android-webview:3.3.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(path: ':datadog_mobile-react-native') From ef5f4a1b2e5180be6fa313d27d1e7bb3e460027e Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 28 Nov 2025 11:49:57 +0100 Subject: [PATCH 054/114] Solve merge issues and fix failing native tests --- .../reactnative/DdSdkImplementation.kt | 34 +------- .../Sources/DdSdkNativeInitialization.swift | 6 -- packages/core/ios/Tests/DdSdkTests.swift | 81 ++++++------------- .../Sources/RCTDatadogWebViewTracking.swift | 4 +- 4 files changed, 31 insertions(+), 94 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index 02123ee8f..8cb165b5f 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -351,6 +351,7 @@ class DdSdkImplementation( } } + /** * Normalizes frameTime values so when are turned into FPS metrics they are normalized on a range of zero to 60fps. * @param frameTimeSeconds: the frame time to normalize. In seconds. @@ -368,43 +369,16 @@ class DdSdkImplementation( val frameTimeMs = frameTimeSeconds * 1000.0 val frameBudgetHz = fpsBudget ?: DEFAULT_REFRESH_HZ val maxDeviceDisplayHz = deviceDisplayFps ?: getMaxDisplayRefreshRate(context) - ?: 60.0 - - val maxDeviceFrameTimeMs = 1000.0 / maxDeviceDisplayHz - val budgetFrameTimeMs = 1000.0 / frameBudgetHz - - if (listOf( - maxDeviceDisplayHz, frameTimeMs, frameBudgetHz, budgetFrameTimeMs, maxDeviceFrameTimeMs - ).any { !it.isFinite() || it <= 0.0 } - ) return 1.0 / DEFAULT_REFRESH_HZ - - - var normalizedFrameTimeMs = frameTimeMs / (maxDeviceFrameTimeMs / budgetFrameTimeMs) - - normalizedFrameTimeMs = max(normalizedFrameTimeMs, maxDeviceFrameTimeMs) - - return normalizedFrameTimeMs / 1000.0 // in seconds - } - - @Suppress("CyclomaticComplexMethod") - private fun getMaxDisplayRefreshRate(context: Context?): Double { - val dm = context?.getSystemService(Context.DISPLAY_SERVICE) as? DisplayManager ?: return 60.0 - val display: Display = dm.getDisplay(Display.DEFAULT_DISPLAY) ?: return DEFAULT_REFRESH_HZ - - return display.supportedModes.maxOf { it.refreshRate.toDouble() } - } - - // endregion + ?: 60.0 val maxDeviceFrameTimeMs = 1000.0 / maxDeviceDisplayHz val budgetFrameTimeMs = 1000.0 / frameBudgetHz if (listOf( - maxDeviceDisplayHz, frameTimeMs, frameBudgetHz, budgetFrameTimeMs, maxDeviceFrameTimeMs - ).any { !it.isFinite() || it <= 0.0 } + maxDeviceDisplayHz, frameTimeMs, frameBudgetHz, budgetFrameTimeMs, maxDeviceFrameTimeMs + ).any { !it.isFinite() || it <= 0.0 } ) return 1.0 / DEFAULT_REFRESH_HZ - var normalizedFrameTimeMs = frameTimeMs / (maxDeviceFrameTimeMs / budgetFrameTimeMs) normalizedFrameTimeMs = max(normalizedFrameTimeMs, maxDeviceFrameTimeMs) diff --git a/packages/core/ios/Sources/DdSdkNativeInitialization.swift b/packages/core/ios/Sources/DdSdkNativeInitialization.swift index 969f72abf..fc11cd80d 100644 --- a/packages/core/ios/Sources/DdSdkNativeInitialization.swift +++ b/packages/core/ios/Sources/DdSdkNativeInitialization.swift @@ -93,16 +93,10 @@ public class DdSdkNativeInitialization: NSObject { if sdkConfiguration.nativeCrashReportEnabled ?? false { CrashReporting.enable() } -<<<<<<< HEAD -<<<<<<< HEAD #if os(iOS) DatadogSDKWrapper.shared.enableWebviewTracking() #endif -======= ->>>>>>> 0443e0ff (iOS: Always use SDK default core instance) -======= ->>>>>>> 93aa6125 (iOS: Always use SDK default core instance) } func buildSDKConfiguration(configuration: DdSdkConfiguration, defaultAppVersion: String = getDefaultAppVersion()) -> Datadog.Configuration { diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index cba49f84f..3f5c0e960 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -82,7 +82,7 @@ class DdSdkTests: XCTestCase { func testResolvesPromiseAfterInitializationIsDone() throws { let bridge = DispatchQueueMock() let mockJSRefreshRateMonitor = MockJSRefreshRateMonitor() - let mockListener = MockOnCoreInitializedListener() + let mockListener = MockOnSdkInitializedListener() DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: mockListener.listener) let expectation = self.expectation(description: "Listener is called when promise resolves") @@ -275,7 +275,9 @@ class DdSdkTests: XCTestCase { func testSDKInitializationWithOnInitializedCallback() { var isInitialized = false + var coreFromCallback: DatadogCoreProtocol? = nil DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: { + core in coreFromCallback = core isInitialized = Datadog.isInitialized() }) @@ -718,13 +720,14 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, nil) XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) } - - func testClearUserInfo() throws { + + func testAddingAttribute() { + let rumMonitorMock = MockRUMMonitor() let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), jsDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor(), - RUMMonitorProvider: { MockRUMMonitor() }, + RUMMonitorProvider: { rumMonitorMock }, RUMMonitorInternalProvider: { nil } ) bridge.initialize( @@ -733,57 +736,19 @@ class DdSdkTests: XCTestCase { reject: mockReject ) - bridge.setUserInfo( - userInfo: NSDictionary( - dictionary: [ - "id": "id_123", - "name": "John Doe", - "email": "john@doe.com", - "extraInfo": [ - "extra-info-1": 123, - "extra-info-2": "abc", - "extra-info-3": true, - "extra-info-4": [ - "nested-extra-info-1": 456 - ], - ], - ] - ), - resolve: mockResolve, - reject: mockReject - ) - - var ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() - var userInfo = try XCTUnwrap(ddContext.userInfo) - - XCTAssertEqual(userInfo.id, "id_123") - XCTAssertEqual(userInfo.name, "John Doe") - XCTAssertEqual(userInfo.email, "john@doe.com") - XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) - XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") - XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) - - if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] - as? DatadogSDKReactNative.AnyEncodable, - let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] - { - XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) - } else { - XCTFail("extra-info-4 is not of expected type or value") - } - - bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) + bridge.addAttribute(key: "attribute-1", value: NSDictionary(dictionary: ["value": 123]), resolve: mockResolve, reject: mockReject) + bridge.addAttribute(key: "attribute-2", value: NSDictionary(dictionary: ["value": "abc"]), resolve: mockResolve, reject: mockReject) + bridge.addAttribute(key: "attribute-3", value: NSDictionary(dictionary: ["value": true]), resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, 123) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, "abc") + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-3"] as? Bool, true) - ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() - userInfo = try XCTUnwrap(ddContext.userInfo) + XCTAssertEqual(GlobalState.globalAttributes["attribute-1"] as? Int64, 123) + XCTAssertEqual(GlobalState.globalAttributes["attribute-2"] as? String, "abc") + XCTAssertEqual(GlobalState.globalAttributes["attribute-3"] as? Bool, true) - XCTAssertEqual(userInfo.id, nil) - XCTAssertEqual(userInfo.name, nil) - XCTAssertEqual(userInfo.email, nil) - XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, nil) - XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, nil) - XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, nil) - XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) + GlobalState.globalAttributes.removeAll() } func testRemovingAttribute() { @@ -1542,7 +1507,7 @@ class DdSdkTests: XCTestCase { func testCallsOnSdkInitializedListeners() throws { let bridge = DispatchQueueMock() let mockJSRefreshRateMonitor = MockJSRefreshRateMonitor() - let mockListener = MockOnCoreInitializedListener() + let mockListener = MockOnSdkInitializedListener() DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: mockListener.listener) @@ -1811,9 +1776,13 @@ extension DdSdkImplementation { } } -class MockOnCoreInitializedListener { +class MockOnSdkInitializedListener { var called = false - func listener() { + var receivedCore: DatadogCoreProtocol? + + lazy var listener: OnSdkInitializedListener = { core in self.called = true + self.receivedCore = core } } + diff --git a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift index 45f11e452..6d3bc3f8d 100644 --- a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift +++ b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift @@ -17,14 +17,14 @@ import DatadogInternal public override init() { super.init() - self.onSdkInitializedListener = { [weak self] in + self.onSdkInitializedListener = { [weak self] (core: DatadogCoreProtocol) in guard let strongSelf = self, let webView = strongSelf.webView else { return } strongSelf.enableWebViewTracking( webView: webView, allowedHosts: strongSelf.allowedHosts, - core: CoreRegistry.default + core: core ) } } From bf864c4d4a3b74ecc8b57ebf7df42b00670e738e Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Sep 2025 15:29:37 +0200 Subject: [PATCH 055/114] Remove type interdependencies between modules --- packages/core/src/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index cd697c04b..c3c7a4a96 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -5,6 +5,7 @@ */ import type { BatchProcessingLevel } from './DdSdkReactNativeConfiguration'; +import type { UserInfo as UserInfoSingleton } from './sdk/UserInfoSingleton/types'; declare global { // eslint-disable-next-line no-var, vars-on-top From ce647121583c0aa8b2750f9377589dd45d936798 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 9 Sep 2025 12:08:35 +0200 Subject: [PATCH 056/114] iOS: Always use SDK default core instance --- .../core/ios/Sources/DatadogSDKWrapper.swift | 40 +++++++++---------- .../Sources/RCTDatadogWebViewTracking.swift | 2 +- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/core/ios/Sources/DatadogSDKWrapper.swift b/packages/core/ios/Sources/DatadogSDKWrapper.swift index 894f0a09f..3c23b3b0b 100644 --- a/packages/core/ios/Sources/DatadogSDKWrapper.swift +++ b/packages/core/ios/Sources/DatadogSDKWrapper.swift @@ -4,18 +4,16 @@ * Copyright 2016-Present Datadog, Inc. */ - import DatadogCore -import DatadogRUM -import DatadogLogs -import DatadogTrace import DatadogCrashReporting import DatadogInternal +import DatadogLogs +import DatadogRUM +import DatadogTrace import Foundation - #if os(iOS) -import DatadogWebViewTracking + import DatadogWebViewTracking #endif public typealias OnSdkInitializedListener = (DatadogCoreProtocol) -> Void @@ -30,9 +28,9 @@ public class DatadogSDKWrapper { internal private(set) var loggerConfiguration = DatadogLogs.Logger.Configuration() - private init() { } + private init() {} - public func addOnSdkInitializedListener(listener:@escaping OnSdkInitializedListener) { + public func addOnSdkInitializedListener(listener: @escaping OnSdkInitializedListener) { onSdkInitializedListeners.append(listener) } @@ -41,7 +39,7 @@ public class DatadogSDKWrapper { coreConfiguration: Datadog.Configuration, loggerConfiguration: DatadogLogs.Logger.Configuration, trackingConsent: TrackingConsent - ) -> Void { + ) { let core = Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent) for listener in onSdkInitializedListeners { @@ -51,18 +49,18 @@ public class DatadogSDKWrapper { self.loggerConfiguration = loggerConfiguration } -#if os(iOS) - // Webview - private var webviewMessageEmitter: InternalExtension.AbstractMessageEmitter? + #if os(iOS) + // Webview + private var webviewMessageEmitter: + InternalExtension.AbstractMessageEmitter? - internal func enableWebviewTracking() { - webviewMessageEmitter = WebViewTracking._internal.messageEmitter(in: CoreRegistry.default) - } + internal func enableWebviewTracking() { + webviewMessageEmitter = WebViewTracking._internal.messageEmitter( + in: CoreRegistry.default) + } - internal func sendWebviewMessage(body: NSString) throws { - try self.webviewMessageEmitter?.send(body: body) - } -#endif + internal func sendWebviewMessage(body: NSString) throws { + try self.webviewMessageEmitter?.send(body: body) + } + #endif } - - diff --git a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift index 6d3bc3f8d..b2189037b 100644 --- a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift +++ b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewTracking.swift @@ -24,7 +24,7 @@ import DatadogInternal strongSelf.enableWebViewTracking( webView: webView, allowedHosts: strongSelf.allowedHosts, - core: core + core: CoreRegistry.default ) } } From 1158e3101bd8e5da48f51dc3fd58d257d25c53e8 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 8 Sep 2025 15:27:17 +0200 Subject: [PATCH 057/114] Bump native SDK dependencies to 3.0.0 --- example-new-architecture/ios/Podfile.lock | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index f00b8a6f3..930da529d 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,5 +1,6 @@ PODS: - boost (1.84.0) +<<<<<<< HEAD - DatadogCore (3.3.0): - DatadogInternal (= 3.3.0) - DatadogCrashReporting (3.3.0): @@ -25,6 +26,13 @@ PODS: ======= ======= >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) +======= + - DatadogCore (3.0.0): + - DatadogInternal (= 3.0.0) + - DatadogCrashReporting (3.0.0): + - DatadogInternal (= 3.0.0) + - PLCrashReporter (~> 1.12.0) +>>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DatadogInternal (3.0.0) - DatadogLogs (3.0.0): - DatadogInternal (= 3.0.0) @@ -38,6 +46,7 @@ PODS: - DatadogTrace (= 3.0.0) - DatadogWebViewTracking (= 3.0.0) <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= - DatadogInternal (3.1.0) @@ -80,6 +89,8 @@ PODS: - DatadogTrace (= 3.3.0) - DatadogWebViewTracking (= 3.3.0) >>>>>>> e8623221 (Bump Native SDKs to 3.3.0) +======= +>>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -103,6 +114,7 @@ PODS: <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) @@ -118,6 +130,9 @@ PODS: ======= - DatadogSDKReactNative/Tests (2.12.1): >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) +======= + - DatadogSDKReactNative/Tests (2.12.1): +>>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DatadogCore (= 3.0.0) - DatadogCrashReporting (= 3.0.0) - DatadogLogs (= 3.0.0) @@ -125,6 +140,7 @@ PODS: - DatadogTrace (= 3.0.0) - DatadogWebViewTracking (= 3.0.0) <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= ======= @@ -153,6 +169,8 @@ PODS: - DatadogTrace (= 3.3.0) - DatadogWebViewTracking (= 3.3.0) >>>>>>> e8623221 (Bump Native SDKs to 3.3.0) +======= +>>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -173,11 +191,19 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga +<<<<<<< HEAD - DatadogTrace (3.3.0): - DatadogInternal (= 3.3.0) - OpenTelemetrySwiftApi (= 1.13.1) - DatadogWebViewTracking (3.3.0): - DatadogInternal (= 3.3.0) +======= + - DatadogTrace (3.0.0): + - DatadogInternal (= 3.0.0) + - OpenTelemetrySwiftApi (= 1.13.1) + - DatadogWebViewTracking (3.0.0): + - DatadogInternal (= 3.0.0) +>>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1963,6 +1989,7 @@ SPEC CHECKSUMS: <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b @@ -1975,6 +2002,8 @@ SPEC CHECKSUMS: ======= ======= >>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) +======= +>>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 @@ -1984,6 +2013,7 @@ SPEC CHECKSUMS: DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) ======= ======= @@ -2015,6 +2045,8 @@ SPEC CHECKSUMS: DatadogTrace: f13e8c09981787d6cb0a4b7fd1991351fab6d64b DatadogWebViewTracking: 08fe084b5f57da05c1610fab49ce7bc84226141e >>>>>>> e8623221 (Bump Native SDKs to 3.3.0) +======= +>>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 From c3e310f87890f9633f911d861931d5e73ebc7748 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 12 Sep 2025 14:43:25 +0200 Subject: [PATCH 058/114] Remove setUser --- packages/core/ios/Tests/MockRUMMonitor.swift | 16 ++++++++++++++++ packages/core/src/types.tsx | 1 - 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index 4a1d0cd92..c692f6e7c 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,6 +10,22 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { + func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { + // not implemented + } + + func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { + // not implemented + } + + func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { + // not implemented + } + + func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { + // not implemented + } + func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index c3c7a4a96..cd697c04b 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -5,7 +5,6 @@ */ import type { BatchProcessingLevel } from './DdSdkReactNativeConfiguration'; -import type { UserInfo as UserInfoSingleton } from './sdk/UserInfoSingleton/types'; declare global { // eslint-disable-next-line no-var, vars-on-top From 01dd8170105384c2d7fa831e1ccf537c5678eba3 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 11:45:00 +0200 Subject: [PATCH 059/114] Bump Native SDKs to 3.1.0 --- example-new-architecture/ios/Podfile.lock | 275 ++++------------------ 1 file changed, 43 insertions(+), 232 deletions(-) diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index 930da529d..b1f128818 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,38 +1,10 @@ PODS: - boost (1.84.0) -<<<<<<< HEAD - - DatadogCore (3.3.0): - - DatadogInternal (= 3.3.0) - - DatadogCrashReporting (3.3.0): - - DatadogInternal (= 3.3.0) - - PLCrashReporter (~> 1.12.0) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - - DatadogInternal (2.30.2) - - DatadogLogs (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogRUM (2.30.2): - - DatadogInternal (= 2.30.2) - - DatadogSDKReactNative (2.14.0): - - DatadogCore (= 2.30.2) - - DatadogCrashReporting (= 2.30.2) - - DatadogLogs (= 2.30.2) - - DatadogRUM (= 2.30.2) - - DatadogTrace (= 2.30.2) - - DatadogWebViewTracking (= 2.30.2) -======= -======= ->>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) -======= - DatadogCore (3.0.0): - DatadogInternal (= 3.0.0) - DatadogCrashReporting (3.0.0): - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) ->>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DatadogInternal (3.0.0) - DatadogLogs (3.0.0): - DatadogInternal (= 3.0.0) @@ -45,52 +17,6 @@ PODS: - DatadogRUM (= 3.0.0) - DatadogTrace (= 3.0.0) - DatadogWebViewTracking (= 3.0.0) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) -======= - - DatadogInternal (3.1.0) - - DatadogLogs (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogRUM (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.13.0): -======= - - DatadogInternal (3.1.0) - - DatadogLogs (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogRUM (3.1.0): - - DatadogInternal (= 3.1.0) - - DatadogSDKReactNative (2.12.1): ->>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) -<<<<<<< HEAD ->>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) -======= ->>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) -======= ->>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) -======= - - DatadogInternal (3.3.0) - - DatadogLogs (3.3.0): - - DatadogInternal (= 3.3.0) - - DatadogRUM (3.3.0): - - DatadogInternal (= 3.3.0) - - DatadogSDKReactNative (2.13.2): - - DatadogCore (= 3.3.0) - - DatadogCrashReporting (= 3.3.0) - - DatadogLogs (= 3.3.0) - - DatadogRUM (= 3.3.0) - - DatadogTrace (= 3.3.0) - - DatadogWebViewTracking (= 3.3.0) ->>>>>>> e8623221 (Bump Native SDKs to 3.3.0) -======= ->>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -111,11 +37,6 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - DatadogSDKReactNative/Tests (2.14.0): - DatadogCore (= 2.30.2) - DatadogCrashReporting (= 2.30.2) @@ -123,54 +44,6 @@ PODS: - DatadogRUM (= 2.30.2) - DatadogTrace (= 2.30.2) - DatadogWebViewTracking (= 2.30.2) -======= - - DatadogSDKReactNative/Tests (2.12.1): -<<<<<<< HEAD -<<<<<<< HEAD -======= - - DatadogSDKReactNative/Tests (2.12.1): ->>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) -======= - - DatadogSDKReactNative/Tests (2.12.1): ->>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) -======= -======= - - DatadogSDKReactNative/Tests (2.13.0): ->>>>>>> 71638bf0 (Bump Android Native SDK to 3.2.0 and regenerate app podfiles) -======= ->>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) - - DatadogCore (= 3.1.0) - - DatadogCrashReporting (= 3.1.0) - - DatadogLogs (= 3.1.0) - - DatadogRUM (= 3.1.0) - - DatadogTrace (= 3.1.0) - - DatadogWebViewTracking (= 3.1.0) -<<<<<<< HEAD ->>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) -======= ->>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) -======= ->>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) -======= - - DatadogSDKReactNative/Tests (2.13.2): - - DatadogCore (= 3.3.0) - - DatadogCrashReporting (= 3.3.0) - - DatadogLogs (= 3.3.0) - - DatadogRUM (= 3.3.0) - - DatadogTrace (= 3.3.0) - - DatadogWebViewTracking (= 3.3.0) ->>>>>>> e8623221 (Bump Native SDKs to 3.3.0) -======= ->>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DoubleConversion - glog - hermes-engine @@ -191,19 +64,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga -<<<<<<< HEAD - - DatadogTrace (3.3.0): - - DatadogInternal (= 3.3.0) - - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.3.0): - - DatadogInternal (= 3.3.0) -======= - DatadogTrace (3.0.0): - DatadogInternal (= 3.0.0) - OpenTelemetrySwiftApi (= 1.13.1) - DatadogWebViewTracking (3.0.0): - DatadogInternal (= 3.0.0) ->>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) @@ -1985,12 +1850,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 @@ -1999,54 +1858,6 @@ SPEC CHECKSUMS: DatadogSDKReactNative: b641dbbbe1b80c9551f55d86cdddac8d71568263 DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be -======= -======= ->>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) -======= ->>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) - DatadogCore: 0c39ba4ef3dee02071f235f2fb56af7e29f4ceb0 - DatadogCrashReporting: 604e65e524cb2fc22cda39fd9c9ea5fd3bddf24e - DatadogInternal: 442673a7fb5329299b489b91ed705aa2085380f7 - DatadogLogs: 0146a0e3140ba62fd42729593c0910d692aea5fd - DatadogRUM: f97fbd0290ecec5bc952fb03a6a1ae068649243f - DatadogSDKReactNative: 0a80aa75958d595a99be54d2838db53eda7a08af - DatadogTrace: d714e7c456e612b7f171483d08086a59aa1c4213 - DatadogWebViewTracking: fb9591c09fca82b143f3843a7164a7e782175c53 -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 51280318 (Bump native SDK dependencies to 3.0.0) -======= -======= ->>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) - DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d - DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d - DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc - DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 - DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 -<<<<<<< HEAD - DatadogSDKReactNative: 2f11191b56e18680f633bfb125ab1832b327d9b4 - DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 - DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 ->>>>>>> a19a7316 (Bump Native SDKs to 3.1.0) -======= ->>>>>>> 846118cd (Bump native SDK dependencies to 3.0.0) -======= - DatadogSDKReactNative: 069ea9876220b2d09b0f4b180ce571b1b6ecbb35 - DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 - DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 ->>>>>>> 6a7b4267 (Bump Native SDKs to 3.1.0) -======= - DatadogCore: 9b1256ac9c27a07087d6214c8546acf756e40be7 - DatadogCrashReporting: 89a00886ef40808bffb8ccb4b6531e472f52e213 - DatadogInternal: 21dac5a7db548da6368a096d0714bdbec66deb6c - DatadogLogs: 355a4ac6bce3f0cb8231819e475c03dbbdd7957c - DatadogRUM: 1b3a47a9b9a5a25890f7fb3aa1f2bd86009d1086 - DatadogSDKReactNative: 3c756b98ff379907842eb3769d44e6b8b570385f - DatadogTrace: f13e8c09981787d6cb0a4b7fd1991351fab6d64b - DatadogWebViewTracking: 08fe084b5f57da05c1610fab49ce7bc84226141e ->>>>>>> e8623221 (Bump Native SDKs to 3.3.0) -======= ->>>>>>> 0317ded5 (Bump native SDK dependencies to 3.0.0) DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 @@ -2055,62 +1866,62 @@ SPEC CHECKSUMS: hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 OpenTelemetrySwiftApi: aaee576ed961e0c348af78df58b61300e95bd104 PLCrashReporter: db59ef96fa3d25f3650040d02ec2798cffee75f2 - RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 + RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f React: 4641770499c39f45d4e7cde1eba30e081f9d8a3d React-callinvoker: 4bef67b5c7f3f68db5929ab6a4d44b8a002998ea - React-Core: a68cea3e762814e60ecc3fa521c7f14c36c99245 - React-CoreModules: d81b1eaf8066add66299bab9d23c9f00c9484c7c - React-cxxreact: 984f8b1feeca37181d4e95301fcd6f5f6501c6ab + React-Core: 0a06707a0b34982efc4a556aff5dae4b22863455 + React-CoreModules: 907334e94314189c2e5eed4877f3efe7b26d85b0 + React-cxxreact: 3a1d5e8f4faa5e09be26614e9c8bbcae8d11b73d React-debug: 817160c07dc8d24d020fbd1eac7b3558ffc08964 - React-defaultsnativemodule: 18a684542f82ce1897552a1c4b847be414c9566e - React-domnativemodule: 90bdd4ec3ab38c47cfc3461c1e9283a8507d613f - React-Fabric: f6dade7007533daeb785ba5925039d83f343be4b - React-FabricComponents: b0655cc3e1b5ae12a4a1119aa7d8308f0ad33520 - React-FabricImage: 9b157c4c01ac2bf433f834f0e1e5fe234113a576 + React-defaultsnativemodule: 814830ccbc3fb08d67d0190e63b179ee4098c67b + React-domnativemodule: 270acf94bd0960b026bc3bfb327e703665d27fb4 + React-Fabric: 64586dc191fc1c170372a638b8e722e4f1d0a09b + React-FabricComponents: b0ebd032387468ea700574c581b139f57a7497fb + React-FabricImage: 81f0e0794caf25ad1224fa406d288fbc1986607f React-featureflags: f2792b067a351d86fdc7bec23db3b9a2f2c8d26c - React-featureflagsnativemodule: 742a8325b3c821d2a1ca13a6d2a0fc72d04555e0 - React-graphics: 68969e4e49d73f89da7abef4116c9b5f466aa121 - React-hermes: ac0bcba26a5d288ebc99b500e1097da2d0297ddf - React-idlecallbacksnativemodule: d61d9c9816131bf70d3d80cd04889fc625ee523f - React-ImageManager: e906eec93a9eb6102a06576b89d48d80a4683020 - React-jserrorhandler: ac5dde01104ff444e043cad8f574ca02756e20d6 - React-jsi: 496fa2b9d63b726aeb07d0ac800064617d71211d - React-jsiexecutor: dd22ab48371b80f37a0a30d0e8915b6d0f43a893 - React-jsinspector: 4629ac376f5765e684d19064f2093e55c97fd086 - React-jsitracing: 7a1c9cd484248870cf660733cd3b8114d54c035f - React-logger: c4052eb941cca9a097ef01b59543a656dc088559 - React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de - React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead + React-featureflagsnativemodule: 0d7091ae344d6160c0557048e127897654a5c00f + React-graphics: cbebe910e4a15b65b0bff94a4d3ed278894d6386 + React-hermes: ec18c10f5a69d49fb9b5e17ae95494e9ea13d4d3 + React-idlecallbacksnativemodule: 6b84add48971da9c40403bd1860d4896462590f2 + React-ImageManager: f2a4c01c2ccb2193e60a20c135da74c7ca4d36f2 + React-jserrorhandler: 61d205b5a7cbc57fed3371dd7eed48c97f49fc64 + React-jsi: 95f7676103137861b79b0f319467627bcfa629ee + React-jsiexecutor: 41e0fe87cda9ea3970ffb872ef10f1ff8dbd1932 + React-jsinspector: 15578208796723e5c6f39069b6e8bf36863ef6e2 + React-jsitracing: 3758cdb155ea7711f0e77952572ea62d90c69f0b + React-logger: dbca7bdfd4aa5ef69431362bde6b36d49403cb20 + React-Mapbuffer: 6efad4a606c1fae7e4a93385ee096681ef0300dc + React-microtasksnativemodule: a645237a841d733861c70b69908ab4a1707b52ad React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678 - React-NativeModulesApple: cebca2e5320a3d66e123cade23bd90a167ffce5e - React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358 - React-performancetimeline: cd6a9374a72001165995d2ab632f672df04076dc + React-NativeModulesApple: 958d4f6c5c2ace4c0f427cf7ef82e28ae6538a22 + React-perflogger: 9b4f13c0afe56bc7b4a0e93ec74b1150421ee22d + React-performancetimeline: 359db1cb889aa0282fafc5838331b0987c4915a9 React-RCTActionSheet: aacf2375084dea6e7c221f4a727e579f732ff342 - React-RCTAnimation: 395ab53fd064dff81507c15efb781c8684d9a585 - React-RCTAppDelegate: 345a6f1b82abc578437df0ce7e9c48740eca827c - React-RCTBlob: 13311e554c1a367de063c10ee7c5e6573b2dd1d6 - React-RCTFabric: 007b1a98201cc49b5bc6e1417d7fe3f6fc6e2b78 - React-RCTImage: 1b1f914bcc12187c49ba5d949dac38c2eb9f5cc8 - React-RCTLinking: 4ac7c42beb65e36fba0376f3498f3cd8dd0be7fa - React-RCTNetwork: 938902773add4381e84426a7aa17a2414f5f94f7 - React-RCTSettings: e848f1ba17a7a18479cf5a31d28145f567da8223 - React-RCTText: 7e98fafdde7d29e888b80f0b35544e0cb07913cf - React-RCTVibration: cd7d80affd97dc7afa62f9acd491419558b64b78 + React-RCTAnimation: d8c82deebebe3aaf7a843affac1b57cb2dc073d4 + React-RCTAppDelegate: 1774aa421a29a41a704ecaf789811ef73c4634b6 + React-RCTBlob: 70a58c11a6a3500d1a12f2e51ca4f6c99babcff8 + React-RCTFabric: 731cda82aed592aacce2d32ead69d78cde5d9274 + React-RCTImage: 5e9d655ba6a790c31e3176016f9b47fd0978fbf0 + React-RCTLinking: 2a48338252805091f7521eaf92687206401bdf2a + React-RCTNetwork: 0c1282b377257f6b1c81934f72d8a1d0c010e4c3 + React-RCTSettings: f757b679a74e5962be64ea08d7865a7debd67b40 + React-RCTText: e7d20c490b407d3b4a2daa48db4bcd8ec1032af2 + React-RCTVibration: 8228e37144ca3122a91f1de16ba8e0707159cfec React-rendererconsistency: b4917053ecbaa91469c67a4319701c9dc0d40be6 - React-rendererdebug: aa181c36dd6cf5b35511d1ed875d6638fd38f0ec + React-rendererdebug: 81becbc8852b38d9b1b68672aa504556481330d5 React-rncore: 120d21715c9b4ba8f798bffe986cb769b988dd74 - React-RuntimeApple: d033becbbd1eba6f9f6e3af6f1893030ce203edd - React-RuntimeCore: 38af280bb678e66ba000a3c3d42920b2a138eebb + React-RuntimeApple: 52ed0e9e84a7c2607a901149fb13599a3c057655 + React-RuntimeCore: ca6189d2e53d86db826e2673fe8af6571b8be157 React-runtimeexecutor: 877596f82f5632d073e121cba2d2084b76a76899 - React-RuntimeHermes: 37aad735ff21ca6de2d8450a96de1afe9f86c385 - React-runtimescheduler: 8ec34cc885281a34696ea16c4fd86892d631f38d + React-RuntimeHermes: 3b752dc5d8a1661c9d1687391d6d96acfa385549 + React-runtimescheduler: 8321bb09175ace2a4f0b3e3834637eb85bf42ebe React-timing: 331cbf9f2668c67faddfd2e46bb7f41cbd9320b9 - React-utils: ed818f19ab445000d6b5c4efa9d462449326cc9f - ReactCodegen: f853a20cc9125c5521c8766b4b49375fec20648b - ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9 + React-utils: 54df9ada708578c8ad40d92895d6fed03e0e8a9e + ReactCodegen: 21a52ccddc6479448fc91903a437dd23ddc7366c + ReactCommon: bfd3600989d79bc3acbe7704161b171a1480b9fd SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a From 1a7f360e1ffd2808ddca69a15c7b06d84eda9b02 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Sep 2025 17:47:29 +0200 Subject: [PATCH 060/114] Use native sdk's core instance instead of the one inside RN SDK wrapper --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 ++ .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +----- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 +------ 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index 4388ad5f6..ee55d08fe 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,7 +65,9 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) + Logs.enable(logsConfiguration, Datadog.getInstance()) + Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index ef91ca549..27e4c155b 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -22,11 +22,7 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation( - reactContext, - datadog = datadogWrapper, - ddTelemetry - ) + private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 327d8ffc0..f034b292c 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -164,12 +164,7 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation( - mockReactContext, - mockDatadog, - mockDdTelemetry, - TestUiThreadExecutor() - ) + testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From bd90e99e4903018bff2edd7564eedbd693899688 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Mon, 22 Sep 2025 16:20:27 +0200 Subject: [PATCH 061/114] Fixed internal testing tools and unit tests --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 -- .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +++++- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 ++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index ee55d08fe..4388ad5f6 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,9 +65,7 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) - Logs.enable(logsConfiguration, Datadog.getInstance()) - Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 27e4c155b..ef91ca549 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -22,7 +22,11 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) + private val implementation = DdSdkImplementation( + reactContext, + datadog = datadogWrapper, + ddTelemetry + ) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index f034b292c..327d8ffc0 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -164,7 +164,12 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) + testedBridgeSdk = DdSdkImplementation( + mockReactContext, + mockDatadog, + mockDdTelemetry, + TestUiThreadExecutor() + ) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From d5b43252dd76136bf6f98291b403dcdf29579208 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 17:37:31 +0200 Subject: [PATCH 062/114] Expose clearUserInfo API --- .../ios/Sources/DdSdkImplementation.swift | 2 +- packages/core/ios/Tests/DdSdkTests.swift | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index db8c4e938..b894a7998 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -143,7 +143,7 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - + @objc public func addUserExtraInfo( extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index 3f5c0e960..b2c6e8134 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -653,6 +653,73 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } + + func testClearUserInfo() throws { + let bridge = DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: JSRefreshRateMonitor(), + RUMMonitorProvider: { MockRUMMonitor() }, + RUMMonitorInternalProvider: { nil } + ) + bridge.initialize( + configuration: .mockAny(), + resolve: mockResolve, + reject: mockReject + ) + + bridge.setUserInfo( + userInfo: NSDictionary( + dictionary: [ + "id": "id_123", + "name": "John Doe", + "email": "john@doe.com", + "extraInfo": [ + "extra-info-1": 123, + "extra-info-2": "abc", + "extra-info-3": true, + "extra-info-4": [ + "nested-extra-info-1": 456 + ], + ], + ] + ), + resolve: mockResolve, + reject: mockReject + ) + + var ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + var userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, "id_123") + XCTAssertEqual(userInfo.name, "John Doe") + XCTAssertEqual(userInfo.email, "john@doe.com") + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) + + if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] + as? DatadogSDKReactNative.AnyEncodable, + let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] + { + XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) + } else { + XCTFail("extra-info-4 is not of expected type or value") + } + + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) + + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, nil) + XCTAssertEqual(userInfo.name, nil) + XCTAssertEqual(userInfo.email, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) + } func testClearUserInfo() throws { let bridge = DdSdkImplementation( From 283888b7021a84217313abbc21fd72f02df25fd1 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 29 Sep 2025 12:20:44 +0200 Subject: [PATCH 063/114] Update attribute API --- .../com/datadog/reactnative/DatadogSDKWrapper.kt | 8 ++++++++ .../com/datadog/reactnative/DatadogWrapper.kt | 15 +++++++++++++++ packages/core/ios/Tests/DdSdkTests.swift | 8 ++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index 56a15373c..46efaf37c 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -89,6 +89,14 @@ internal class DatadogSDKWrapper : DatadogWrapper { override fun clearUserInfo() { Datadog.clearUserInfo() } + + override fun addRumGlobalAttribute(key: String, value: Any?) { + this.getRumMonitor().addAttribute(key, value) + } + + override fun removeRumGlobalAttribute(key: String) { + this.getRumMonitor().removeAttribute(key) + } override fun setAccountInfo( id: String, diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index c72f2faef..ab48601b2 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -91,6 +91,21 @@ interface DatadogWrapper { */ fun clearUserInfo() + + /** Adds a global attribute. + * + * @param key: Key that identifies the attribute. + * @param value: Value linked to the attribute. + */ + fun addRumGlobalAttribute(key: String, value: Any?) + + /** + * Removes a global attribute. + * + * @param key: Key that identifies the attribute. + */ + fun removeRumGlobalAttribute(key: String) + /** * Sets the account information. * diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index b2c6e8134..b7914896d 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -653,7 +653,7 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } - + func testClearUserInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -706,12 +706,12 @@ class DdSdkTests: XCTestCase { } else { XCTFail("extra-info-4 is not of expected type or value") } - + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) - + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() userInfo = try XCTUnwrap(ddContext.userInfo) - + XCTAssertEqual(userInfo.id, nil) XCTAssertEqual(userInfo.name, nil) XCTAssertEqual(userInfo.email, nil) From 4a176b23001ba01facd31a25fc4c3f6c37322b26 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 16 Oct 2025 11:01:21 +0200 Subject: [PATCH 064/114] JS refresh rate normalization --- .../main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index 8cb165b5f..a926e4ac2 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -351,7 +351,6 @@ class DdSdkImplementation( } } - /** * Normalizes frameTime values so when are turned into FPS metrics they are normalized on a range of zero to 60fps. * @param frameTimeSeconds: the frame time to normalize. In seconds. From bc103f20bef067b1bc91b488d7fca98ef9e6d558 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 10 Oct 2025 14:52:21 +0200 Subject: [PATCH 065/114] Expose view Attributes API --- .../core/ios/Sources/DdSdkImplementation.swift | 2 +- packages/core/ios/Tests/MockRUMMonitor.swift | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index b894a7998..db8c4e938 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -143,7 +143,7 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - + @objc public func addUserExtraInfo( extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index c692f6e7c..4a1d0cd92 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,22 +10,6 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { - func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { - // not implemented - } - - func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { - // not implemented - } - - func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { - // not implemented - } - - func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { - // not implemented - } - func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } From 22c742259f1a2992233b08eccbb0e646e4f44a49 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 27 Oct 2025 14:45:54 +0100 Subject: [PATCH 066/114] Bump Android Native SDK to 3.2.0 and regenerate app podfiles --- example-new-architecture/ios/Podfile.lock | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index b1f128818..bc53ab102 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -5,18 +5,18 @@ PODS: - DatadogCrashReporting (3.0.0): - DatadogInternal (= 3.0.0) - PLCrashReporter (~> 1.12.0) - - DatadogInternal (3.0.0) - - DatadogLogs (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogRUM (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogSDKReactNative (2.12.1): - - DatadogCore (= 3.0.0) - - DatadogCrashReporting (= 3.0.0) - - DatadogLogs (= 3.0.0) - - DatadogRUM (= 3.0.0) - - DatadogTrace (= 3.0.0) - - DatadogWebViewTracking (= 3.0.0) + - DatadogInternal (3.1.0) + - DatadogLogs (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogRUM (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogSDKReactNative (2.13.0): + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -37,13 +37,13 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogSDKReactNative/Tests (2.14.0): - - DatadogCore (= 2.30.2) - - DatadogCrashReporting (= 2.30.2) - - DatadogLogs (= 2.30.2) - - DatadogRUM (= 2.30.2) - - DatadogTrace (= 2.30.2) - - DatadogWebViewTracking (= 2.30.2) + - DatadogSDKReactNative/Tests (2.13.0): + - DatadogCore (= 3.1.0) + - DatadogCrashReporting (= 3.1.0) + - DatadogLogs (= 3.1.0) + - DatadogRUM (= 3.1.0) + - DatadogTrace (= 3.1.0) + - DatadogWebViewTracking (= 3.1.0) - DoubleConversion - glog - hermes-engine @@ -1850,14 +1850,14 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DatadogCore: 8e50ad6cb68343f701707f7eeca16e0acb52ed8c - DatadogCrashReporting: 34763d4276d4fce286c7e8729cfcd2ac04dca43b - DatadogInternal: bd8672d506a7e67936ed5f7ca612169e49029b44 - DatadogLogs: d683aa9e0c9339f5ae679ead70bbdbe41cdc32f6 - DatadogRUM: 8b794aa458e6323ea9b1cef3f820fd3d092cbe27 - DatadogSDKReactNative: b641dbbbe1b80c9551f55d86cdddac8d71568263 - DatadogTrace: 3ba194791267efa09634234749111cac95abd3e5 - DatadogWebViewTracking: 8287d5ad06e992de5e46dd72a17e05c7513344be + DatadogCore: d2f51c7fb4308cf3c25e55e2e7242e5d558ee71d + DatadogCrashReporting: f636f1d1c534572c0b0abcdc59df244c884d825d + DatadogInternal: 7837b2ce3d525d429682532eeda697b181299fdc + DatadogLogs: 250894b5a99da5b924a019049c0d0326823cdbd6 + DatadogRUM: 0d2a60e1abb8aacfb8827ef84f6d5deb4d5026c8 + DatadogSDKReactNative: 2f11191b56e18680f633bfb125ab1832b327d9b4 + DatadogTrace: f59e933074cd285ad7e9f5af991f8fe04b095991 + DatadogWebViewTracking: 9bc92b4147aeed47eb1911451f651094aa6dd6c1 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 From e4a28286cc2ca3304b85702cfdbe70124c95b903 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 8 Sep 2025 15:27:17 +0200 Subject: [PATCH 067/114] Bump native SDK dependencies to 3.0.0 --- example-new-architecture/ios/Podfile.lock | 86 +++++++++++------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index bc53ab102..f7589f814 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1866,62 +1866,62 @@ SPEC CHECKSUMS: hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 OpenTelemetrySwiftApi: aaee576ed961e0c348af78df58b61300e95bd104 PLCrashReporter: db59ef96fa3d25f3650040d02ec2798cffee75f2 - RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 + RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f React: 4641770499c39f45d4e7cde1eba30e081f9d8a3d React-callinvoker: 4bef67b5c7f3f68db5929ab6a4d44b8a002998ea - React-Core: 0a06707a0b34982efc4a556aff5dae4b22863455 - React-CoreModules: 907334e94314189c2e5eed4877f3efe7b26d85b0 - React-cxxreact: 3a1d5e8f4faa5e09be26614e9c8bbcae8d11b73d + React-Core: a68cea3e762814e60ecc3fa521c7f14c36c99245 + React-CoreModules: d81b1eaf8066add66299bab9d23c9f00c9484c7c + React-cxxreact: 984f8b1feeca37181d4e95301fcd6f5f6501c6ab React-debug: 817160c07dc8d24d020fbd1eac7b3558ffc08964 - React-defaultsnativemodule: 814830ccbc3fb08d67d0190e63b179ee4098c67b - React-domnativemodule: 270acf94bd0960b026bc3bfb327e703665d27fb4 - React-Fabric: 64586dc191fc1c170372a638b8e722e4f1d0a09b - React-FabricComponents: b0ebd032387468ea700574c581b139f57a7497fb - React-FabricImage: 81f0e0794caf25ad1224fa406d288fbc1986607f + React-defaultsnativemodule: 18a684542f82ce1897552a1c4b847be414c9566e + React-domnativemodule: 90bdd4ec3ab38c47cfc3461c1e9283a8507d613f + React-Fabric: f6dade7007533daeb785ba5925039d83f343be4b + React-FabricComponents: b0655cc3e1b5ae12a4a1119aa7d8308f0ad33520 + React-FabricImage: 9b157c4c01ac2bf433f834f0e1e5fe234113a576 React-featureflags: f2792b067a351d86fdc7bec23db3b9a2f2c8d26c - React-featureflagsnativemodule: 0d7091ae344d6160c0557048e127897654a5c00f - React-graphics: cbebe910e4a15b65b0bff94a4d3ed278894d6386 - React-hermes: ec18c10f5a69d49fb9b5e17ae95494e9ea13d4d3 - React-idlecallbacksnativemodule: 6b84add48971da9c40403bd1860d4896462590f2 - React-ImageManager: f2a4c01c2ccb2193e60a20c135da74c7ca4d36f2 - React-jserrorhandler: 61d205b5a7cbc57fed3371dd7eed48c97f49fc64 - React-jsi: 95f7676103137861b79b0f319467627bcfa629ee - React-jsiexecutor: 41e0fe87cda9ea3970ffb872ef10f1ff8dbd1932 - React-jsinspector: 15578208796723e5c6f39069b6e8bf36863ef6e2 - React-jsitracing: 3758cdb155ea7711f0e77952572ea62d90c69f0b - React-logger: dbca7bdfd4aa5ef69431362bde6b36d49403cb20 - React-Mapbuffer: 6efad4a606c1fae7e4a93385ee096681ef0300dc - React-microtasksnativemodule: a645237a841d733861c70b69908ab4a1707b52ad + React-featureflagsnativemodule: 742a8325b3c821d2a1ca13a6d2a0fc72d04555e0 + React-graphics: 68969e4e49d73f89da7abef4116c9b5f466aa121 + React-hermes: ac0bcba26a5d288ebc99b500e1097da2d0297ddf + React-idlecallbacksnativemodule: d61d9c9816131bf70d3d80cd04889fc625ee523f + React-ImageManager: e906eec93a9eb6102a06576b89d48d80a4683020 + React-jserrorhandler: ac5dde01104ff444e043cad8f574ca02756e20d6 + React-jsi: 496fa2b9d63b726aeb07d0ac800064617d71211d + React-jsiexecutor: dd22ab48371b80f37a0a30d0e8915b6d0f43a893 + React-jsinspector: 4629ac376f5765e684d19064f2093e55c97fd086 + React-jsitracing: 7a1c9cd484248870cf660733cd3b8114d54c035f + React-logger: c4052eb941cca9a097ef01b59543a656dc088559 + React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de + React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678 - React-NativeModulesApple: 958d4f6c5c2ace4c0f427cf7ef82e28ae6538a22 - React-perflogger: 9b4f13c0afe56bc7b4a0e93ec74b1150421ee22d - React-performancetimeline: 359db1cb889aa0282fafc5838331b0987c4915a9 + React-NativeModulesApple: cebca2e5320a3d66e123cade23bd90a167ffce5e + React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358 + React-performancetimeline: cd6a9374a72001165995d2ab632f672df04076dc React-RCTActionSheet: aacf2375084dea6e7c221f4a727e579f732ff342 - React-RCTAnimation: d8c82deebebe3aaf7a843affac1b57cb2dc073d4 - React-RCTAppDelegate: 1774aa421a29a41a704ecaf789811ef73c4634b6 - React-RCTBlob: 70a58c11a6a3500d1a12f2e51ca4f6c99babcff8 - React-RCTFabric: 731cda82aed592aacce2d32ead69d78cde5d9274 - React-RCTImage: 5e9d655ba6a790c31e3176016f9b47fd0978fbf0 - React-RCTLinking: 2a48338252805091f7521eaf92687206401bdf2a - React-RCTNetwork: 0c1282b377257f6b1c81934f72d8a1d0c010e4c3 - React-RCTSettings: f757b679a74e5962be64ea08d7865a7debd67b40 - React-RCTText: e7d20c490b407d3b4a2daa48db4bcd8ec1032af2 - React-RCTVibration: 8228e37144ca3122a91f1de16ba8e0707159cfec + React-RCTAnimation: 395ab53fd064dff81507c15efb781c8684d9a585 + React-RCTAppDelegate: 345a6f1b82abc578437df0ce7e9c48740eca827c + React-RCTBlob: 13311e554c1a367de063c10ee7c5e6573b2dd1d6 + React-RCTFabric: 007b1a98201cc49b5bc6e1417d7fe3f6fc6e2b78 + React-RCTImage: 1b1f914bcc12187c49ba5d949dac38c2eb9f5cc8 + React-RCTLinking: 4ac7c42beb65e36fba0376f3498f3cd8dd0be7fa + React-RCTNetwork: 938902773add4381e84426a7aa17a2414f5f94f7 + React-RCTSettings: e848f1ba17a7a18479cf5a31d28145f567da8223 + React-RCTText: 7e98fafdde7d29e888b80f0b35544e0cb07913cf + React-RCTVibration: cd7d80affd97dc7afa62f9acd491419558b64b78 React-rendererconsistency: b4917053ecbaa91469c67a4319701c9dc0d40be6 - React-rendererdebug: 81becbc8852b38d9b1b68672aa504556481330d5 + React-rendererdebug: aa181c36dd6cf5b35511d1ed875d6638fd38f0ec React-rncore: 120d21715c9b4ba8f798bffe986cb769b988dd74 - React-RuntimeApple: 52ed0e9e84a7c2607a901149fb13599a3c057655 - React-RuntimeCore: ca6189d2e53d86db826e2673fe8af6571b8be157 + React-RuntimeApple: d033becbbd1eba6f9f6e3af6f1893030ce203edd + React-RuntimeCore: 38af280bb678e66ba000a3c3d42920b2a138eebb React-runtimeexecutor: 877596f82f5632d073e121cba2d2084b76a76899 - React-RuntimeHermes: 3b752dc5d8a1661c9d1687391d6d96acfa385549 - React-runtimescheduler: 8321bb09175ace2a4f0b3e3834637eb85bf42ebe + React-RuntimeHermes: 37aad735ff21ca6de2d8450a96de1afe9f86c385 + React-runtimescheduler: 8ec34cc885281a34696ea16c4fd86892d631f38d React-timing: 331cbf9f2668c67faddfd2e46bb7f41cbd9320b9 - React-utils: 54df9ada708578c8ad40d92895d6fed03e0e8a9e - ReactCodegen: 21a52ccddc6479448fc91903a437dd23ddc7366c - ReactCommon: bfd3600989d79bc3acbe7704161b171a1480b9fd + React-utils: ed818f19ab445000d6b5c4efa9d462449326cc9f + ReactCodegen: f853a20cc9125c5521c8766b4b49375fec20648b + ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a From 421455ebeef463599a9d7254986b8dbb1c51077b Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 12 Sep 2025 14:43:25 +0200 Subject: [PATCH 068/114] Remove setUser --- packages/core/ios/Tests/MockRUMMonitor.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index 4a1d0cd92..c692f6e7c 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,6 +10,22 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { + func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { + // not implemented + } + + func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { + // not implemented + } + + func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { + // not implemented + } + + func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { + // not implemented + } + func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } From 67b31937a4b9ba9c4ec6e802444a2cd42bf0d1de Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 11:45:00 +0200 Subject: [PATCH 069/114] Bump Native SDKs to 3.1.0 --- example-new-architecture/ios/Podfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/example-new-architecture/ios/Podfile.lock b/example-new-architecture/ios/Podfile.lock index f7589f814..4655380ca 100644 --- a/example-new-architecture/ios/Podfile.lock +++ b/example-new-architecture/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - boost (1.84.0) - - DatadogCore (3.0.0): - - DatadogInternal (= 3.0.0) - - DatadogCrashReporting (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogCore (3.1.0): + - DatadogInternal (= 3.1.0) + - DatadogCrashReporting (3.1.0): + - DatadogInternal (= 3.1.0) - PLCrashReporter (~> 1.12.0) - DatadogInternal (3.1.0) - DatadogLogs (3.1.0): @@ -64,11 +64,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - DatadogTrace (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogTrace (3.1.0): + - DatadogInternal (= 3.1.0) - OpenTelemetrySwiftApi (= 1.13.1) - - DatadogWebViewTracking (3.0.0): - - DatadogInternal (= 3.0.0) + - DatadogWebViewTracking (3.1.0): + - DatadogInternal (= 3.1.0) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) From c27cc8b277482d16a5141fc397d1a3d54a6c654c Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 5 Sep 2025 17:47:29 +0200 Subject: [PATCH 070/114] Use native sdk's core instance instead of the one inside RN SDK wrapper --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 ++ .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +----- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 +------ 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index 4388ad5f6..ee55d08fe 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,7 +65,9 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) + Logs.enable(logsConfiguration, Datadog.getInstance()) + Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index ef91ca549..27e4c155b 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -22,11 +22,7 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation( - reactContext, - datadog = datadogWrapper, - ddTelemetry - ) + private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 327d8ffc0..f034b292c 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -164,12 +164,7 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation( - mockReactContext, - mockDatadog, - mockDdTelemetry, - TestUiThreadExecutor() - ) + testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From 09e267927371633712f47de4cf16e55cce0760be Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Mon, 22 Sep 2025 16:20:27 +0200 Subject: [PATCH 071/114] Fixed internal testing tools and unit tests --- .../com/datadog/reactnative/DdSdkNativeInitialization.kt | 2 -- .../src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt | 6 +++++- .../src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt | 7 ++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index ee55d08fe..4388ad5f6 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -65,9 +65,7 @@ class DdSdkNativeInitialization internal constructor( datadog.initialize(appContext, sdkConfiguration, trackingConsent) Rum.enable(rumConfiguration, Datadog.getInstance()) - Logs.enable(logsConfiguration, Datadog.getInstance()) - Trace.enable(traceConfiguration, Datadog.getInstance()) } diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 27e4c155b..ef91ca549 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -22,7 +22,11 @@ class DdSdk( ddTelemetry: DdTelemetry = DdTelemetry() ) : ReactContextBaseJavaModule(reactContext) { - private val implementation = DdSdkImplementation(reactContext, datadog = datadogWrapper, ddTelemetry) + private val implementation = DdSdkImplementation( + reactContext, + datadog = datadogWrapper, + ddTelemetry + ) private var lifecycleEventListener: LifecycleEventListener? = null override fun getName(): String = DdSdkImplementation.NAME diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index f034b292c..327d8ffc0 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -164,7 +164,12 @@ internal class DdSdkTest { answer.getArgument(0).run() true } - testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog, mockDdTelemetry, TestUiThreadExecutor()) + testedBridgeSdk = DdSdkImplementation( + mockReactContext, + mockDatadog, + mockDdTelemetry, + TestUiThreadExecutor() + ) DatadogSDKWrapperStorage.onInitializedListeners.clear() } From 869d1595a4b29f7891d5d336ef14ae6bdb185937 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 19 Sep 2025 17:37:31 +0200 Subject: [PATCH 072/114] Expose clearUserInfo API --- .../ios/Sources/DdSdkImplementation.swift | 2 +- packages/core/ios/Tests/DdSdkTests.swift | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index db8c4e938..b894a7998 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -143,7 +143,7 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - + @objc public func addUserExtraInfo( extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index b7914896d..832dcc4fd 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -653,6 +653,73 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } + + func testClearUserInfo() throws { + let bridge = DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: JSRefreshRateMonitor(), + RUMMonitorProvider: { MockRUMMonitor() }, + RUMMonitorInternalProvider: { nil } + ) + bridge.initialize( + configuration: .mockAny(), + resolve: mockResolve, + reject: mockReject + ) + + bridge.setUserInfo( + userInfo: NSDictionary( + dictionary: [ + "id": "id_123", + "name": "John Doe", + "email": "john@doe.com", + "extraInfo": [ + "extra-info-1": 123, + "extra-info-2": "abc", + "extra-info-3": true, + "extra-info-4": [ + "nested-extra-info-1": 456 + ], + ], + ] + ), + resolve: mockResolve, + reject: mockReject + ) + + var ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + var userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, "id_123") + XCTAssertEqual(userInfo.name, "John Doe") + XCTAssertEqual(userInfo.email, "john@doe.com") + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, 123) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) + + if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] + as? DatadogSDKReactNative.AnyEncodable, + let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] + { + XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) + } else { + XCTFail("extra-info-4 is not of expected type or value") + } + + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) + + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() + userInfo = try XCTUnwrap(ddContext.userInfo) + + XCTAssertEqual(userInfo.id, nil) + XCTAssertEqual(userInfo.name, nil) + XCTAssertEqual(userInfo.email, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-1"] as? Int64, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, nil) + XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) + } func testClearUserInfo() throws { let bridge = DdSdkImplementation( From f68acc39a33662cad4d2e3ac7dc460408fee7944 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Mon, 29 Sep 2025 12:20:44 +0200 Subject: [PATCH 073/114] Update attribute API --- packages/core/ios/Tests/DdSdkTests.swift | 126 ++++++++++++++--------- 1 file changed, 76 insertions(+), 50 deletions(-) diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index 832dcc4fd..df5c60051 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -277,7 +277,8 @@ class DdSdkTests: XCTestCase { var isInitialized = false var coreFromCallback: DatadogCoreProtocol? = nil DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: { - core in coreFromCallback = core + core in + coreFromCallback = core isInitialized = Datadog.isInitialized() }) @@ -653,7 +654,7 @@ class DdSdkTests: XCTestCase { XCTFail("extra-info-4 is not of expected type or value") } } - + func testClearUserInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -706,12 +707,12 @@ class DdSdkTests: XCTestCase { } else { XCTFail("extra-info-4 is not of expected type or value") } - + bridge.clearUserInfo(resolve: mockResolve, reject: mockReject) - + ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() userInfo = try XCTUnwrap(ddContext.userInfo) - + XCTAssertEqual(userInfo.id, nil) XCTAssertEqual(userInfo.name, nil) XCTAssertEqual(userInfo.email, nil) @@ -854,7 +855,7 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, nil) XCTAssertEqual(userInfo.extraInfo["extra-info-4"] as? [String: Int], nil) } - + func testAddingAttribute() { let rumMonitorMock = MockRUMMonitor() let bridge = DdSdkImplementation( @@ -870,10 +871,16 @@ class DdSdkTests: XCTestCase { reject: mockReject ) - bridge.addAttribute(key: "attribute-1", value: NSDictionary(dictionary: ["value": 123]), resolve: mockResolve, reject: mockReject) - bridge.addAttribute(key: "attribute-2", value: NSDictionary(dictionary: ["value": "abc"]), resolve: mockResolve, reject: mockReject) - bridge.addAttribute(key: "attribute-3", value: NSDictionary(dictionary: ["value": true]), resolve: mockResolve, reject: mockReject) - + bridge.addAttribute( + key: "attribute-1", value: NSDictionary(dictionary: ["value": 123]), + resolve: mockResolve, reject: mockReject) + bridge.addAttribute( + key: "attribute-2", value: NSDictionary(dictionary: ["value": "abc"]), + resolve: mockResolve, reject: mockReject) + bridge.addAttribute( + key: "attribute-3", value: NSDictionary(dictionary: ["value": true]), + resolve: mockResolve, reject: mockReject) + XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-1"] as? Int64, 123) XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-2"] as? String, "abc") XCTAssertEqual(rumMonitorMock.addedAttributes["attribute-3"] as? Bool, true) @@ -1347,7 +1354,7 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(rumMonitorMock.receivedLongTasks.first?.value, 0.25) XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.25) } - + func testFrameTimeNormalizationFromCallback() { let mockRefreshRateMonitor = MockJSRefreshRateMonitor() let rumMonitorMock = MockRUMMonitor() @@ -1366,93 +1373,113 @@ class DdSdkTests: XCTestCase { resolve: mockResolve, reject: mockReject ) - + XCTAssertTrue(mockRefreshRateMonitor.isStarted) - + // 10 fps mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.1) sharedQueue.sync {} XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.1) - + // 30 fps mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.03) sharedQueue.sync {} XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.03) - + // 45 fps mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.02) sharedQueue.sync {} XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.02) - + // 60 fps mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.016) sharedQueue.sync {} - XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) - + XCTAssertEqual( + rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, + accuracy: 0.001) + // 90 fps mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.011) sharedQueue.sync {} - XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) - + XCTAssertEqual( + rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, + accuracy: 0.001) + // 120 fps mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.008) sharedQueue.sync {} - XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) + XCTAssertEqual( + rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, + accuracy: 0.001) } - + func testFrameTimeNormalizationUtilityFunction() { // 10 fps, 60fps capable device, 60 fps budget -> Normalized to 10fps - var frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.1, fpsBudget: 60.0, deviceDisplayFps: 60.0) + var frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.1, fpsBudget: 60.0, deviceDisplayFps: 60.0) XCTAssertEqual(frameTimeSeconds, 0.1, accuracy: 0.01) - + // 30 fps, 60fps capable device, 60 fps budget -> Normalized to 30fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.03, fpsBudget: 60.0, deviceDisplayFps: 60.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.03, fpsBudget: 60.0, deviceDisplayFps: 60.0) XCTAssertEqual(frameTimeSeconds, 0.03, accuracy: 0.01) - + // 60 fps, 60fps capable device, 60 fps budget-> Normalized to 60fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 60.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.016, fpsBudget: 60.0, deviceDisplayFps: 60.0) XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.01) - + // 60 fps, 120fps capable device, 60 fps budget -> Normalized to 30fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 120.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.016, fpsBudget: 60.0, deviceDisplayFps: 120.0) XCTAssertEqual(frameTimeSeconds, 0.03, accuracy: 0.01) - + // 120 fps, 120fps capable device, 60 fps budget -> Normalized to 60fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0083, fpsBudget: 60.0, deviceDisplayFps: 120.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.0083, fpsBudget: 60.0, deviceDisplayFps: 120.0) XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) - + // 90 fps, 120fps capable device, 60 fps budget -> Normalized to 45fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0111, fpsBudget: 60.0, deviceDisplayFps: 120.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.0111, fpsBudget: 60.0, deviceDisplayFps: 120.0) XCTAssertEqual(frameTimeSeconds, 0.0222, accuracy: 0.001) - + // 100 fps, 120fps capable device, 60 fps budget -> Normalized to 50fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.01, fpsBudget: 60.0, deviceDisplayFps: 120.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.01, fpsBudget: 60.0, deviceDisplayFps: 120.0) XCTAssertEqual(frameTimeSeconds, 0.02, accuracy: 0.001) - + // 120 fps, 120fps capable device, 120 fps budget -> Normalized to 120fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0083, fpsBudget: 120.0, deviceDisplayFps: 120.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.0083, fpsBudget: 120.0, deviceDisplayFps: 120.0) XCTAssertEqual(frameTimeSeconds, 0.0083, accuracy: 0.001) - + // 80 fps, 160fps capable device, 60 fps budget -> Normalized to 30fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0125, fpsBudget: 60.0, deviceDisplayFps: 160.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.0125, fpsBudget: 60.0, deviceDisplayFps: 160.0) XCTAssertEqual(frameTimeSeconds, 0.033, accuracy: 0.001) - + // 160 fps, 160fps capable device, 60 fps budget -> Normalized to 60fps - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.00625, fpsBudget: 60.0, deviceDisplayFps: 160.0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.00625, fpsBudget: 60.0, deviceDisplayFps: 160.0) XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) - + // Edge cases - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0, fpsBudget: 0, deviceDisplayFps: 0) + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0, fpsBudget: 0, deviceDisplayFps: 0) XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) - - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 0, deviceDisplayFps: 0) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.016, fpsBudget: 0, deviceDisplayFps: 0) XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) - - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 0) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.016, fpsBudget: 60.0, deviceDisplayFps: 0) XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) - - frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 0, deviceDisplayFps: 60.0) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate( + 0.016, fpsBudget: 0, deviceDisplayFps: 60.0) XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) } @@ -1919,4 +1946,3 @@ class MockOnSdkInitializedListener { self.receivedCore = core } } - From 64ae74c4fbc2a5000be5b724a70287c14deecf81 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 16 Oct 2025 11:01:21 +0200 Subject: [PATCH 074/114] JS refresh rate normalization --- packages/core/ios/Tests/DdSdkTests.swift | 108 +++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index df5c60051..948d97f46 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -1354,6 +1354,114 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(rumMonitorMock.receivedLongTasks.first?.value, 0.25) XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.25) } + + func testFrameTimeNormalizationFromCallback() { + let mockRefreshRateMonitor = MockJSRefreshRateMonitor() + let rumMonitorMock = MockRUMMonitor() + + DdSdkImplementation( + mainDispatchQueue: DispatchQueueMock(), + jsDispatchQueue: DispatchQueueMock(), + jsRefreshRateMonitor: mockRefreshRateMonitor, + RUMMonitorProvider: { rumMonitorMock }, + RUMMonitorInternalProvider: { rumMonitorMock._internalMock } + ).initialize( + configuration: .mockAny( + longTaskThresholdMs: 200, + vitalsUpdateFrequency: "average" + ), + resolve: mockResolve, + reject: mockReject + ) + + XCTAssertTrue(mockRefreshRateMonitor.isStarted) + + // 10 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.1) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.1) + + // 30 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.03) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.03) + + // 45 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.02) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds], 0.02) + + // 60 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.016) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) + + // 90 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.011) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) + + // 120 fps + mockRefreshRateMonitor.executeFrameCallback(frameTime: 0.008) + sharedQueue.sync {} + XCTAssertEqual(rumMonitorMock.lastReceivedPerformanceMetrics[.jsFrameTimeSeconds]!, 0.016, accuracy: 0.001) + } + + func testFrameTimeNormalizationUtilityFunction() { + + // 10 fps, 60fps capable device, 60 fps budget -> Normalized to 10fps + var frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.1, fpsBudget: 60.0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.1, accuracy: 0.01) + + // 30 fps, 60fps capable device, 60 fps budget -> Normalized to 30fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.03, fpsBudget: 60.0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.03, accuracy: 0.01) + + // 60 fps, 60fps capable device, 60 fps budget-> Normalized to 60fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.01) + + // 60 fps, 120fps capable device, 60 fps budget -> Normalized to 30fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.03, accuracy: 0.01) + + // 120 fps, 120fps capable device, 60 fps budget -> Normalized to 60fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0083, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + // 90 fps, 120fps capable device, 60 fps budget -> Normalized to 45fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0111, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.0222, accuracy: 0.001) + + // 100 fps, 120fps capable device, 60 fps budget -> Normalized to 50fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.01, fpsBudget: 60.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.02, accuracy: 0.001) + + // 120 fps, 120fps capable device, 120 fps budget -> Normalized to 120fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0083, fpsBudget: 120.0, deviceDisplayFps: 120.0) + XCTAssertEqual(frameTimeSeconds, 0.0083, accuracy: 0.001) + + // 80 fps, 160fps capable device, 60 fps budget -> Normalized to 30fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.0125, fpsBudget: 60.0, deviceDisplayFps: 160.0) + XCTAssertEqual(frameTimeSeconds, 0.033, accuracy: 0.001) + + // 160 fps, 160fps capable device, 60 fps budget -> Normalized to 60fps + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.00625, fpsBudget: 60.0, deviceDisplayFps: 160.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + // Edge cases + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0, fpsBudget: 0, deviceDisplayFps: 0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 0, deviceDisplayFps: 0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 60.0, deviceDisplayFps: 0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + + frameTimeSeconds = DdSdkImplementation.normalizeFrameTimeForDeviceRefreshRate(0.016, fpsBudget: 0, deviceDisplayFps: 60.0) + XCTAssertEqual(frameTimeSeconds, 0.016, accuracy: 0.001) + } func testFrameTimeNormalizationFromCallback() { let mockRefreshRateMonitor = MockJSRefreshRateMonitor() From fa0a304441e4557ab262cfc8088b430a030ad779 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 10 Oct 2025 14:52:21 +0200 Subject: [PATCH 075/114] Expose view Attributes API --- .../core/ios/Sources/DdSdkImplementation.swift | 2 +- packages/core/ios/Tests/MockRUMMonitor.swift | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index b894a7998..db8c4e938 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -143,7 +143,7 @@ public class DdSdkImplementation: NSObject { resolve(nil) } - + @objc public func addUserExtraInfo( extraInfo: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index c692f6e7c..4a1d0cd92 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,22 +10,6 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { - func addViewAttribute(forKey key: DatadogInternal.AttributeKey, value: any DatadogInternal.AttributeValue) { - // not implemented - } - - func addViewAttributes(_ attributes: [DatadogInternal.AttributeKey : any DatadogInternal.AttributeValue]) { - // not implemented - } - - func removeViewAttribute(forKey key: DatadogInternal.AttributeKey) { - // not implemented - } - - func removeViewAttributes(forKeys keys: [DatadogInternal.AttributeKey]) { - // not implemented - } - func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } From 2126d4e1438ca335ef2c7a0602a8479666e71a02 Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Mon, 3 Nov 2025 18:05:04 +0000 Subject: [PATCH 076/114] Expose `setAccountInfo` API to JS layer add account info to `applyEventMapper` --- .../datadog/reactnative/DatadogSDKWrapper.kt | 20 +++++++++++++- .../com/datadog/reactnative/DatadogWrapper.kt | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index 46efaf37c..ffbcbe62a 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -89,7 +89,25 @@ internal class DatadogSDKWrapper : DatadogWrapper { override fun clearUserInfo() { Datadog.clearUserInfo() } - + + override fun setAccountInfo( + id: String, + name: String?, + extraInfo: Map + ) { + Datadog.setAccountInfo(id, name, extraInfo) + } + + override fun addAccountExtraInfo( + extraInfo: Map + ) { + Datadog.addAccountExtraInfo(extraInfo) + } + + override fun clearAccountInfo() { + Datadog.clearAccountInfo() + } + override fun addRumGlobalAttribute(key: String, value: Any?) { this.getRumMonitor().addAttribute(key, value) } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index ab48601b2..26a17f5cf 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -91,6 +91,33 @@ interface DatadogWrapper { */ fun clearUserInfo() + /** + * Sets the account information. + * + * @param id a unique account identifier (relevant to your business domain) + * @param name (nullable) the account name + * @param extraInfo additional information. An extra information can be + * nested up to 8 levels deep. Keys using more than 8 levels will be sanitized by SDK. + */ + fun setAccountInfo( + id: String, + name: String?, + extraInfo: Map + ) + + /** + * Sets the account information. + * @param extraInfo: The additional information. (To set the id or name please use setAccountInfo). + */ + fun addAccountExtraInfo( + extraInfo: Map + ) + + /** + * Clears the account information. + */ + fun clearAccountInfo() + /** Adds a global attribute. * From 30623d7127d720e6fe3a78a06e3fa0d6216ad442 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Wed, 3 Dec 2025 17:38:46 +0100 Subject: [PATCH 077/114] Adapt internal testing tools package to changes done for v3 --- .../ios/Sources/DatadogCoreProxy.swift | 2 +- .../Sources/DdInternalTestingImplementation.swift | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift b/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift index 9f7c3db2c..0d7e1dec1 100644 --- a/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift +++ b/packages/internal-testing-tools/ios/Sources/DatadogCoreProxy.swift @@ -96,7 +96,7 @@ private final class FeatureScopeInterceptor: @unchecked Sendable { let actualWriter: Writer unowned var interception: FeatureScopeInterceptor? - func write(value: T, metadata: M) { + func write(value: T, metadata: M?, completion: @escaping DatadogInternal.CompletionHandler) where T : Encodable, M : Encodable { group.enter() defer { group.leave() } diff --git a/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift b/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift index 7b1c2862d..b460b86af 100644 --- a/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift +++ b/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift @@ -14,7 +14,7 @@ import DatadogInternal public class DdInternalTestingImplementation: NSObject { @objc public func clearData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - let coreProxy = (DatadogSDKWrapper.shared.getCoreInstance() as! DatadogCoreProxy) + let coreProxy = CoreRegistry.default as! DatadogCoreProxy coreProxy.waitAndDeleteEvents(ofFeature: "rum") coreProxy.waitAndDeleteEvents(ofFeature: "logging") coreProxy.waitAndDeleteEvents(ofFeature: "tracing") @@ -26,7 +26,7 @@ public class DdInternalTestingImplementation: NSObject { @objc public func getAllEvents(feature: String, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { do { - let coreProxy = (DatadogSDKWrapper.shared.getCoreInstance() as! DatadogCoreProxy) + let coreProxy = CoreRegistry.default as! DatadogCoreProxy let events = coreProxy.waitAndReturnEventsData(ofFeature: feature) let data = try JSONSerialization.data(withJSONObject: events, options: .prettyPrinted) resolve(String(data: data, encoding: String.Encoding.utf8) ?? "") @@ -39,10 +39,6 @@ public class DdInternalTestingImplementation: NSObject { @objc public func enable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - DatadogSDKWrapper.shared.addOnCoreInitializedListener(listener: {core in - let proxiedCore = DatadogCoreProxy(core: core) - DatadogSDKWrapper.shared.setCoreInstance(core: proxiedCore) - }) resolve(nil) } } @@ -51,9 +47,5 @@ public class DdInternalTestingImplementation: NSObject { public class DdInternalTestingNativeInitialization: NSObject { @objc public func enableFromNative() -> Void { - DatadogSDKWrapper.shared.addOnCoreInitializedListener(listener: {core in - let proxiedCore = DatadogCoreProxy(core: core) - DatadogSDKWrapper.shared.setCoreInstance(core: proxiedCore) - }) } } From 46f65b09eeccc868acd20700d92f41a69fbb9f0a Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 4 Dec 2025 12:18:45 +0100 Subject: [PATCH 078/114] Fixed ProxiedCore implementation --- .../ios/Sources/DdInternalTestingImplementation.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift b/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift index b460b86af..41a213efd 100644 --- a/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift +++ b/packages/internal-testing-tools/ios/Sources/DdInternalTestingImplementation.swift @@ -39,6 +39,11 @@ public class DdInternalTestingImplementation: NSObject { @objc public func enable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: {core in + let proxiedCore = DatadogCoreProxy(core: core) + CoreRegistry.unregisterDefault() + CoreRegistry.register(default: proxiedCore) + }) resolve(nil) } } @@ -47,5 +52,10 @@ public class DdInternalTestingImplementation: NSObject { public class DdInternalTestingNativeInitialization: NSObject { @objc public func enableFromNative() -> Void { + DatadogSDKWrapper.shared.addOnSdkInitializedListener(listener: {core in + let proxiedCore = DatadogCoreProxy(core: core) + CoreRegistry.unregisterDefault() + CoreRegistry.register(default: proxiedCore) + }) } } From fc53cc6389bf590ce0cac7be7f2671c2dd9d7b01 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 4 Dec 2025 16:49:11 +0100 Subject: [PATCH 079/114] Remove unnecessary isInitialized check on DdLogsImplementation --- packages/core/ios/Sources/Attributes.swift | 7 ---- .../ios/Sources/DdLogsImplementation.swift | 39 +------------------ 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/packages/core/ios/Sources/Attributes.swift b/packages/core/ios/Sources/Attributes.swift index adf9638f5..25dd8df20 100644 --- a/packages/core/ios/Sources/Attributes.swift +++ b/packages/core/ios/Sources/Attributes.swift @@ -109,10 +109,3 @@ internal struct InternalConfigurationAttributes { /// Expects `Bool` value. static let dropAction = "_dd.action.drop_action" } - -/// Error messages that can be thrown to the JS SDK -internal struct Errors { - /// Error thrown when a log was sent before the SDK was initialized. - /// Not sending the log prevent the logger to be set to a Noop logger. - static let logSentBeforeSDKInit = "DD_INTERNAL_LOG_SENT_BEFORE_SDK_INIT" -} diff --git a/packages/core/ios/Sources/DdLogsImplementation.swift b/packages/core/ios/Sources/DdLogsImplementation.swift index fe3fde092..7c428af45 100644 --- a/packages/core/ios/Sources/DdLogsImplementation.swift +++ b/packages/core/ios/Sources/DdLogsImplementation.swift @@ -13,27 +13,20 @@ import DatadogCore public class DdLogsImplementation: NSObject { private lazy var logger: LoggerProtocol = loggerProvider() private let loggerProvider: () -> LoggerProtocol - private let isSDKInitialized: () -> Bool - internal init(_ loggerProvider: @escaping () -> LoggerProtocol, _ isSDKInitialized: @escaping () -> Bool) { + internal init(_ loggerProvider: @escaping () -> LoggerProtocol) { self.loggerProvider = loggerProvider - self.isSDKInitialized = isSDKInitialized } @objc public override convenience init() { self.init( - { DatadogLogs.Logger.create(with: DatadogSDKWrapper.shared.loggerConfiguration) }, - { Datadog.isInitialized() } + { DatadogLogs.Logger.create(with: DatadogSDKWrapper.shared.loggerConfiguration) } ) } @objc public func debug(message: String, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger.debug(message, error: nil, attributes: attributes) resolve(nil) @@ -41,10 +34,6 @@ public class DdLogsImplementation: NSObject { @objc public func info(message: String, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger.info(message, error: nil, attributes: attributes) resolve(nil) @@ -52,10 +41,6 @@ public class DdLogsImplementation: NSObject { @objc public func warn(message: String, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger.warn(message, error: nil, attributes: attributes) resolve(nil) @@ -63,10 +48,6 @@ public class DdLogsImplementation: NSObject { @objc public func error(message: String, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger.error(message, error: nil, attributes: attributes) resolve(nil) @@ -74,10 +55,6 @@ public class DdLogsImplementation: NSObject { @objc public func debugWithError(message: String, errorKind: String?, errorMessage: String?, stacktrace: String?, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger._internal.log(level: .debug, message: message, errorKind: errorKind, errorMessage: errorMessage, stackTrace: stacktrace, attributes: attributes) resolve(nil) @@ -85,10 +62,6 @@ public class DdLogsImplementation: NSObject { @objc public func infoWithError(message: String, errorKind: String?, errorMessage: String?, stacktrace: String?, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger._internal.log(level: .info, message: message, errorKind: errorKind, errorMessage: errorMessage, stackTrace: stacktrace, attributes: attributes) resolve(nil) @@ -96,10 +69,6 @@ public class DdLogsImplementation: NSObject { @objc public func warnWithError(message: String, errorKind: String?, errorMessage: String?, stacktrace: String?, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger._internal.log(level: .warn, message: message, errorKind: errorKind, errorMessage: errorMessage, stackTrace: stacktrace, attributes: attributes) resolve(nil) @@ -107,10 +76,6 @@ public class DdLogsImplementation: NSObject { @objc public func errorWithError(message: String, errorKind: String?, errorMessage: String?, stacktrace: String?, context: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - if (!self.isSDKInitialized()) { - reject(nil, Errors.logSentBeforeSDKInit, nil) - return - } let attributes = castAttributesToSwift(context).mergeWithGlobalAttributes() logger._internal.log(level: .error, message: message, errorKind: errorKind, errorMessage: errorMessage, stackTrace: stacktrace, attributes: attributes) resolve(nil) From afb2c815955e7c5acded29efdccb0fd61475cf3f Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Thu, 4 Dec 2025 17:42:17 +0100 Subject: [PATCH 080/114] Fix iOS tests --- packages/core/ios/Tests/DdLogsTests.swift | 33 ++--------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/packages/core/ios/Tests/DdLogsTests.swift b/packages/core/ios/Tests/DdLogsTests.swift index 60640e807..d3d31a95c 100644 --- a/packages/core/ios/Tests/DdLogsTests.swift +++ b/packages/core/ios/Tests/DdLogsTests.swift @@ -14,7 +14,7 @@ func mockReject(args: String?, arg: String?, err: Error?) {} internal class DdLogsTests: XCTestCase { private let mockNativeLogger = MockNativeLogger() - private lazy var logger = DdLogsImplementation({ self.mockNativeLogger }, { true }) + private lazy var logger = DdLogsImplementation({ self.mockNativeLogger }) private let testMessage_swift: String = "message" private let testMessage_objc: NSString = "message" @@ -80,7 +80,7 @@ internal class DdLogsTests: XCTestCase { let logger = DdLogsImplementation({ [unowned self] in expectation.fulfill() return self.mockNativeLogger - }, { true }) + }) // When (0..<10).forEach { _ in logger.debug(message: "foo", context: [:], resolve: mockResolve, reject: mockReject)} @@ -372,35 +372,6 @@ internal class DdLogsTests: XCTestCase { GlobalState.globalAttributes.keys ) } - - func testDoesNotInitializeLoggerBeforeSdkIsInitialized() throws { - var isInitialized = false - let newLogger = DdLogsImplementation({ self.mockNativeLogger }, { isInitialized }) - - newLogger.debug(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.info(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.warn(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.error(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.debugWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - newLogger.infoWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - newLogger.warnWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - newLogger.errorWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - - XCTAssertEqual(mockNativeLogger.receivedMethodCalls.count, 0) - - isInitialized = true - - newLogger.debug(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.info(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.warn(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.error(message: testMessage_objc as String, context: validTestAttributes_objc, resolve: mockResolve, reject: mockReject) - newLogger.debugWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - newLogger.infoWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - newLogger.warnWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - newLogger.errorWithError(message: testMessage_objc as String, errorKind: testErrorKind_objc as String, errorMessage: testErrorMessage_objc as String, stacktrace: testErrorStacktrace_objc as String, context: invalidTestAttributes, resolve: mockResolve, reject: mockReject) - - XCTAssertEqual(mockNativeLogger.receivedMethodCalls.count, 8) - } } private class MockNativeLogger: LoggerProtocol { From 8065371a80e8e49ea8e129c7b024223ae95a7f48 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Fri, 10 Oct 2025 11:31:03 +0200 Subject: [PATCH 081/114] Attributes Safe Encoding --- packages/core/src/DdSdkReactNative.tsx | 33 +-- .../src/DdSdkReactNativeConfiguration.tsx | 17 ++ packages/core/src/logs/DdLogs.ts | 16 +- packages/core/src/nativeModulesTypes.ts | 4 +- packages/core/src/rum/DdRum.ts | 39 +-- .../instrumentation/DdRumErrorTracking.tsx | 25 +- .../DdRumUserInteractionTracking.tsx | 8 +- .../__tests__/attributesEncoding.test.ts | 266 ++++++++++++++++++ .../__tests__/defaultEncoders.test.ts | 198 +++++++++++++ .../AttributesEncoding/attributesEncoding.tsx | 41 +++ .../AttributesEncoding/defaultEncoders.tsx | 185 ++++++++++++ .../AttributesEncoding/errorUtils.tsx} | 72 +++-- .../src/sdk/AttributesEncoding/helpers.tsx | 134 +++++++++ .../core/src/sdk/AttributesEncoding/types.tsx | 33 +++ .../core/src/sdk/AttributesEncoding/utils.tsx | 31 ++ .../DatadogProvider/Buffer/BoundedBuffer.ts | 6 +- packages/core/src/sdk/DdSdk.ts | 12 - packages/core/src/sdk/DdSdk.tsx | 15 + packages/core/src/sdk/DdSdkInternal.tsx | 105 +++++++ .../core/src/sdk/EventMappers/EventMapper.ts | 4 +- packages/core/src/trace/DdTrace.ts | 6 +- packages/core/src/types.tsx | 5 +- packages/core/src/utils/argsUtils.ts | 41 --- .../react-native-apollo-client/src/helpers.ts | 4 +- 24 files changed, 1148 insertions(+), 152 deletions(-) create mode 100644 packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts create mode 100644 packages/core/src/sdk/AttributesEncoding/__tests__/defaultEncoders.test.ts create mode 100644 packages/core/src/sdk/AttributesEncoding/attributesEncoding.tsx create mode 100644 packages/core/src/sdk/AttributesEncoding/defaultEncoders.tsx rename packages/core/src/{utils/errorUtils.ts => sdk/AttributesEncoding/errorUtils.tsx} (55%) create mode 100644 packages/core/src/sdk/AttributesEncoding/helpers.tsx create mode 100644 packages/core/src/sdk/AttributesEncoding/types.tsx create mode 100644 packages/core/src/sdk/AttributesEncoding/utils.tsx delete mode 100644 packages/core/src/sdk/DdSdk.ts create mode 100644 packages/core/src/sdk/DdSdk.tsx create mode 100644 packages/core/src/sdk/DdSdkInternal.tsx delete mode 100644 packages/core/src/utils/argsUtils.ts diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index e07ba4e34..243b11491 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -35,7 +35,7 @@ import { AttributesSingleton } from './sdk/AttributesSingleton/AttributesSinglet import type { Attributes } from './sdk/AttributesSingleton/types'; import { registerNativeBridge } from './sdk/DatadogInternalBridge/DdSdkInternalNativeBridge'; import { BufferSingleton } from './sdk/DatadogProvider/Buffer/BufferSingleton'; -import { DdSdk } from './sdk/DdSdk'; +import { NativeDdSdk } from './sdk/DdSdkInternal'; import { FileBasedConfiguration } from './sdk/FileBasedConfiguration/FileBasedConfiguration'; import { GlobalState } from './sdk/GlobalState/GlobalState'; import { UserInfoSingleton } from './sdk/UserInfoSingleton/UserInfoSingleton'; @@ -84,7 +84,7 @@ export class DdSdkReactNative { SdkVerbosity.WARN ); if (!__DEV__) { - DdSdk.telemetryDebug( + NativeDdSdk.telemetryDebug( 'RN SDK was already initialized in javascript' ); } @@ -95,7 +95,7 @@ export class DdSdkReactNative { registerNativeBridge(); - await DdSdk.initialize( + await NativeDdSdk.initialize( DdSdkReactNative.buildConfiguration(configuration, params) ); @@ -189,7 +189,7 @@ export class DdSdkReactNative { `Adding attribute ${JSON.stringify(value)} for key ${key}`, SdkVerbosity.DEBUG ); - await DdSdk.addAttribute(key, { value }); + await NativeDdSdk.addAttribute(key, { value }); AttributesSingleton.getInstance().addAttribute(key, value); }; @@ -202,7 +202,7 @@ export class DdSdkReactNative { `Removing attribute for key ${key}`, SdkVerbosity.DEBUG ); - await DdSdk.removeAttribute(key); + await NativeDdSdk.removeAttribute(key); AttributesSingleton.getInstance().removeAttribute(key); }; @@ -216,7 +216,7 @@ export class DdSdkReactNative { `Adding attributes ${JSON.stringify(attributes)}`, SdkVerbosity.DEBUG ); - await DdSdk.addAttributes(attributes); + await NativeDdSdk.addAttributes(attributes); AttributesSingleton.getInstance().addAttributes(attributes); }; @@ -229,7 +229,7 @@ export class DdSdkReactNative { `Removing attributes for keys ${JSON.stringify(keys)}`, SdkVerbosity.DEBUG ); - await DdSdk.removeAttributes(keys); + await NativeDdSdk.removeAttributes(keys); AttributesSingleton.getInstance().removeAttributes(keys); }; @@ -252,7 +252,7 @@ export class DdSdkReactNative { SdkVerbosity.DEBUG ); - await DdSdk.setUserInfo(userInfo); + await NativeDdSdk.setUserInfo(userInfo); UserInfoSingleton.getInstance().setUserInfo(userInfo); }; @@ -262,7 +262,7 @@ export class DdSdkReactNative { */ static clearUserInfo = async (): Promise => { InternalLog.log('Clearing user info', SdkVerbosity.DEBUG); - await DdSdk.clearUserInfo(); + await NativeDdSdk.clearUserInfo(); UserInfoSingleton.getInstance().clearUserInfo(); }; @@ -296,7 +296,7 @@ export class DdSdkReactNative { } }; - await DdSdk.addUserExtraInfo(extraUserInfo); + await NativeDdSdk.addUserExtraInfo(extraUserInfo); UserInfoSingleton.getInstance().setUserInfo(updatedUserInfo); }; @@ -317,7 +317,7 @@ export class DdSdkReactNative { SdkVerbosity.DEBUG ); - await DdSdk.setAccountInfo(accountInfo); + await NativeDdSdk.setAccountInfo(accountInfo); AccountInfoSingleton.getInstance().setAccountInfo(accountInfo); }; @@ -327,7 +327,7 @@ export class DdSdkReactNative { */ static clearAccountInfo = async (): Promise => { InternalLog.log('Clearing account info', SdkVerbosity.DEBUG); - await DdSdk.clearAccountInfo(); + await NativeDdSdk.clearAccountInfo(); AccountInfoSingleton.getInstance().clearAccountInfo(); }; @@ -359,7 +359,7 @@ export class DdSdkReactNative { ...extraAccountInfo }; - await DdSdk.addAccountExtraInfo(extraInfo); + await NativeDdSdk.addAccountExtraInfo(extraInfo); AccountInfoSingleton.getInstance().addAccountExtraInfo( extraAccountInfo ); @@ -372,7 +372,7 @@ export class DdSdkReactNative { */ static setTrackingConsent = (consent: TrackingConsent): Promise => { InternalLog.log(`Setting consent ${consent}`, SdkVerbosity.DEBUG); - return DdSdk.setTrackingConsent(consent); + return NativeDdSdk.setTrackingConsent(consent); }; /** @@ -381,7 +381,7 @@ export class DdSdkReactNative { */ static clearAllData = (): Promise => { InternalLog.log('Clearing all data', SdkVerbosity.DEBUG); - return DdSdk.clearAllData(); + return NativeDdSdk.clearAllData(); }; private static buildConfiguration = ( @@ -464,7 +464,8 @@ export class DdSdkReactNative { configuration.trackWatchdogTerminations, configuration.batchProcessingLevel, configuration.initialResourceThreshold, - configuration.trackMemoryWarnings + configuration.trackMemoryWarnings, + configuration.attributeEncoders ); }; diff --git a/packages/core/src/DdSdkReactNativeConfiguration.tsx b/packages/core/src/DdSdkReactNativeConfiguration.tsx index 4ec1ef883..bcdf8e877 100644 --- a/packages/core/src/DdSdkReactNativeConfiguration.tsx +++ b/packages/core/src/DdSdkReactNativeConfiguration.tsx @@ -12,6 +12,7 @@ import type { ErrorEventMapper } from './rum/eventMappers/errorEventMapper'; import type { ResourceEventMapper } from './rum/eventMappers/resourceEventMapper'; import type { FirstPartyHost } from './rum/types'; import { PropagatorType } from './rum/types'; +import type { AttributeEncoder } from './sdk/AttributesEncoding/types'; import type { LogEventMapper } from './types'; export enum VitalsUpdateFrequency { @@ -323,6 +324,22 @@ export class DdSdkReactNativeConfiguration { */ public initialResourceThreshold?: number; + /** + * Optional list of custom encoders for attributes. + * + * Each encoder defines how to detect (`check`) and transform (`encode`) + * values of a specific type that is not handled by the built-in encoders + * (e.g., domain-specific objects, custom classes). + * + * These encoders are applied before the built-in ones. If an encoder + * successfully `check` a value, its `encode` result will be used. + * + * Example use cases: + * - Serializing a custom `UUID` class into a string + * - Handling third-party library objects that are not JSON-serializable + */ + public attributeEncoders: AttributeEncoder[] = []; + /** * Determines whether the SDK should track application termination by the watchdog on iOS. Default: `false`. */ diff --git a/packages/core/src/logs/DdLogs.ts b/packages/core/src/logs/DdLogs.ts index 32d3dac59..f00cbfdfa 100644 --- a/packages/core/src/logs/DdLogs.ts +++ b/packages/core/src/logs/DdLogs.ts @@ -9,8 +9,8 @@ import { DATADOG_MESSAGE_PREFIX, InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; import { debugId } from '../metro/debugIdResolver'; import type { DdNativeLogsType } from '../nativeModulesTypes'; +import { encodeAttributes } from '../sdk/AttributesEncoding/attributesEncoding'; import type { ErrorSource, LogEventMapper } from '../types'; -import { validateContext } from '../utils/argsUtils'; import { getGlobalInstance } from '../utils/singletonUtils'; import { generateEventMapper } from './eventMapper'; @@ -38,7 +38,7 @@ const isLogWithError = ( typeof args[1] === 'string' || typeof args[2] === 'string' || typeof args[3] === 'string' || - typeof args[4] === 'object' || + (args[4] !== undefined && args[4] !== null) || typeof args[5] === 'string' ); }; @@ -160,13 +160,11 @@ class DdLogsWrapper implements DdLogsType { return generateEmptyPromise(); } - const validatedContext = validateContext(event.context); - this.printLogTracked(event.message, status); try { return await this.nativeLogs[status]( event.message, - validatedContext + encodeAttributes(event.context) ); } catch (error) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -210,9 +208,11 @@ class DdLogsWrapper implements DdLogsType { this.printLogTracked(mappedEvent.message, status); try { - const updatedContext = validateContext(mappedEvent.context); - - updatedContext[DdAttributes.errorSourceType] = 'react-native'; + const encodedContext = encodeAttributes(mappedEvent.context); + const updatedContext = { + ...encodedContext, + [DdAttributes.errorSourceType]: 'react-native' + }; if (fingerprint && fingerprint !== '') { updatedContext[DdAttributes.errorFingerprint] = fingerprint; diff --git a/packages/core/src/nativeModulesTypes.ts b/packages/core/src/nativeModulesTypes.ts index b05fb6e95..9b6d71e06 100644 --- a/packages/core/src/nativeModulesTypes.ts +++ b/packages/core/src/nativeModulesTypes.ts @@ -4,6 +4,7 @@ * Copyright 2016-Present Datadog, Inc. */ +import type { AttributeEncoder } from './sdk/AttributesEncoding/types'; import type { Spec as NativeDdLogs } from './specs/NativeDdLogs'; import type { Spec as NativeDdRum } from './specs/NativeDdRum'; import type { Spec as NativeDdSdk } from './specs/NativeDdSdk'; @@ -36,7 +37,8 @@ export class DdNativeSdkConfiguration { readonly sampleRate: number, readonly site: string, readonly trackingConsent: string, - readonly additionalConfiguration: object // eslint-disable-next-line no-empty-function + readonly additionalConfiguration: object, + readonly attributeEncoders: AttributeEncoder[] // eslint-disable-next-line no-empty-function ) {} } diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index 6f4a63fc8..a65b7372a 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -10,12 +10,12 @@ import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; import { debugId } from '../metro/debugIdResolver'; import type { DdNativeRumType } from '../nativeModulesTypes'; +import { encodeAttributes } from '../sdk/AttributesEncoding/attributesEncoding'; import type { Attributes } from '../sdk/AttributesSingleton/types'; import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; -import { DdSdk } from '../sdk/DdSdk'; +import { NativeDdSdk } from '../sdk/DdSdkInternal'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; import type { ErrorSource, FeatureOperationFailure } from '../types'; -import { validateContext } from '../utils/argsUtils'; import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; import type { TimeProvider } from '../utils/time-provider/TimeProvider'; @@ -76,7 +76,7 @@ class DdRumWrapper implements DdRumType { this.nativeRum.startView( key, name, - validateContext(context), + encodeAttributes(context), timestampMs ) ); @@ -89,7 +89,7 @@ class DdRumWrapper implements DdRumType { ): Promise => { InternalLog.log(`Stopping RUM View #${key}`, SdkVerbosity.DEBUG); return bufferVoidNativeCall(() => - this.nativeRum.stopView(key, validateContext(context), timestampMs) + this.nativeRum.stopView(key, encodeAttributes(context), timestampMs) ); }; @@ -108,7 +108,7 @@ class DdRumWrapper implements DdRumType { this.nativeRum.startAction( type, name, - validateContext(context), + encodeAttributes(context), timestampMs ) ); @@ -207,8 +207,6 @@ class DdRumWrapper implements DdRumType { return generateEmptyPromise(); } - const validatedContext = validateContext(mappedEvent.context); - InternalLog.log( `Adding RUM Action “${name}” (${type})`, SdkVerbosity.DEBUG @@ -217,7 +215,7 @@ class DdRumWrapper implements DdRumType { this.nativeRum.addAction( mappedEvent.type, mappedEvent.name, - validatedContext, + encodeAttributes(mappedEvent.context), mappedEvent.timestampMs ) ); @@ -240,7 +238,7 @@ class DdRumWrapper implements DdRumType { key, method, url, - validateContext(context), + encodeAttributes(context), timestampMs ) ); @@ -284,8 +282,6 @@ class DdRumWrapper implements DdRumType { ); } - const validatedContext = validateContext(mappedEvent.context); - InternalLog.log( `Stopping RUM Resource #${key} status:${statusCode}`, SdkVerbosity.DEBUG @@ -296,7 +292,7 @@ class DdRumWrapper implements DdRumType { mappedEvent.statusCode, mappedEvent.kind, mappedEvent.size, - validatedContext, + encodeAttributes(mappedEvent.context), mappedEvent.timestampMs ) ); @@ -323,14 +319,11 @@ class DdRumWrapper implements DdRumType { return generateEmptyPromise(); } InternalLog.log(`Adding RUM Error “${message}”`, SdkVerbosity.DEBUG); - - const updatedContext: any = validateContext(mappedEvent.context); - + const updatedContext = encodeAttributes(mappedEvent.context); updatedContext[DdAttributes.errorSourceType] = 'react-native'; - const _debugId = debugId; - if (_debugId) { - updatedContext[DdAttributes.debugId] = _debugId; + if (debugId) { + updatedContext[DdAttributes.debugId] = debugId; } return bufferVoidNativeCall(() => @@ -528,13 +521,11 @@ class DdRumWrapper implements DdRumType { ); } - const validatedContext = validateContext(mappedEvent.context); - return bufferVoidNativeCall(() => this.nativeRum.stopAction( mappedEvent.type, mappedEvent.name, - validatedContext, + encodeAttributes(mappedEvent.context), mappedEvent.timestampMs ) ); @@ -561,20 +552,20 @@ class DdRumWrapper implements DdRumType { return [ args[0], args[1], - args[2] || {}, + args[2] ?? {}, args[3] || this.timeProvider.now() ]; } if (isOldStopActionAPI(args)) { if (this.lastActionData) { - DdSdk.telemetryDebug( + NativeDdSdk.telemetryDebug( 'DDdRum.stopAction called with the old signature' ); const { type, name } = this.lastActionData; return [ type, name, - args[0] || {}, + args[0] ?? {}, args[1] || this.timeProvider.now() ]; } diff --git a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx index 09cd447fb..b80309858 100644 --- a/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx +++ b/packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx @@ -8,14 +8,15 @@ import type { ErrorHandlerCallback } from 'react-native'; import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; -import { ErrorSource } from '../../types'; +import { errorEncoder } from '../../sdk/AttributesEncoding/defaultEncoders'; import { + ERROR_DEFAULT_NAME, + ERROR_EMPTY_STACKTRACE, getErrorMessage, - getErrorStackTrace, - EMPTY_STACK_TRACE, getErrorName, - DEFAULT_ERROR_NAME -} from '../../utils/errorUtils'; + getErrorStackTrace +} from '../../sdk/AttributesEncoding/errorUtils'; +import { ErrorSource } from '../../types'; import { executeWithDelay } from '../../utils/jsUtils'; import { DdRum } from '../DdRum'; @@ -71,7 +72,7 @@ export class DdRumErrorTracking { const stacktrace = getErrorStackTrace(error); this.reportError(message, ErrorSource.SOURCE, stacktrace, { '_dd.error.is_crash': isFatal, - '_dd.error.raw': error + '_dd.error.raw': errorEncoder.encode(error) }).then(async () => { DdRumErrorTracking.isInDefaultErrorHandler = true; try { @@ -95,24 +96,24 @@ export class DdRumErrorTracking { return; } - let stack: string = EMPTY_STACK_TRACE; - let errorName: string = DEFAULT_ERROR_NAME; + let stack: string = ERROR_EMPTY_STACKTRACE; + let errorName: string = ERROR_DEFAULT_NAME; for (let i = 0; i < params.length; i += 1) { const param = params[i]; const paramStack = getErrorStackTrace(param); - if (paramStack !== EMPTY_STACK_TRACE) { + if (paramStack !== ERROR_EMPTY_STACKTRACE) { stack = paramStack; } const paramErrorName = getErrorName(param); - if (paramErrorName !== DEFAULT_ERROR_NAME) { + if (paramErrorName !== ERROR_DEFAULT_NAME) { errorName = paramErrorName; } if ( - errorName !== DEFAULT_ERROR_NAME && - stack !== EMPTY_STACK_TRACE + errorName !== ERROR_DEFAULT_NAME && + stack !== ERROR_EMPTY_STACKTRACE ) { break; } diff --git a/packages/core/src/rum/instrumentation/interactionTracking/DdRumUserInteractionTracking.tsx b/packages/core/src/rum/instrumentation/interactionTracking/DdRumUserInteractionTracking.tsx index 03fe98262..0e273435c 100644 --- a/packages/core/src/rum/instrumentation/interactionTracking/DdRumUserInteractionTracking.tsx +++ b/packages/core/src/rum/instrumentation/interactionTracking/DdRumUserInteractionTracking.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { InternalLog } from '../../../InternalLog'; import { SdkVerbosity } from '../../../SdkVerbosity'; -import { DdSdk } from '../../../sdk/DdSdk'; -import { getErrorMessage } from '../../../utils/errorUtils'; +import { getErrorMessage } from '../../../sdk/AttributesEncoding/errorUtils'; +import { NativeDdSdk } from '../../../sdk/DdSdkInternal'; import { BABEL_PLUGIN_TELEMETRY } from '../../constants'; import { DdBabelInteractionTracking } from './DdBabelInteractionTracking'; @@ -72,7 +72,7 @@ export class DdRumUserInteractionTracking { return; } - DdSdk?.sendTelemetryLog( + NativeDdSdk?.sendTelemetryLog( BABEL_PLUGIN_TELEMETRY, DdBabelInteractionTracking.getTelemetryConfig(), { onlyOnce: true } @@ -116,7 +116,7 @@ export class DdRumUserInteractionTracking { }; } } catch (e) { - DdSdk.telemetryDebug(getErrorMessage(e)); + NativeDdSdk.telemetryDebug(getErrorMessage(e)); } const originalMemo = React.memo; diff --git a/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts b/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts new file mode 100644 index 000000000..9cd2718ff --- /dev/null +++ b/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts @@ -0,0 +1,266 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import { DdSdk } from '../../DdSdk'; +import { encodeAttributes } from '../attributesEncoding'; +import { warn } from '../utils'; + +jest.mock('../utils', () => ({ + ...jest.requireActual('../utils'), + warn: jest.fn() +})); + +const setEncoders = (encoders: any[]) => { + (DdSdk as any)?._setAttributeEncodersForTesting(encoders); +}; + +describe('encodeAttributes', () => { + beforeEach(() => { + (warn as jest.Mock).mockClear(); + setEncoders([]); + }); + + it('wraps root string under context', () => { + const result = encodeAttributes('foo'); + expect(result).toEqual({ context: 'foo' }); + expect(warn).toHaveBeenCalled(); + }); + + it('wraps root number under context', () => { + const result = encodeAttributes(123); + expect(result).toEqual({ context: 123 }); + expect(warn).toHaveBeenCalled(); + }); + + it('wraps root array under context', () => { + const result = encodeAttributes([1, 2, 3]); + expect(result).toEqual({ context: [1, 2, 3] }); + expect(warn).toHaveBeenCalled(); + }); + + it('drops unsupported root function', () => { + const result = encodeAttributes(() => {}); + expect(result).toEqual({}); + expect(warn).toHaveBeenCalled(); + }); + + it('drops unsupported root symbol', () => { + const result = encodeAttributes(Symbol('x')); + expect(result).toEqual({}); + expect(warn).toHaveBeenCalled(); + }); + + it('flattens nested objects using dot syntax', () => { + const input = { user: { profile: { name: 'Alice' } } }; + const result = encodeAttributes(input); + expect(result).toEqual({ 'user.profile.name': 'Alice' }); + expect(warn).not.toHaveBeenCalled(); + }); + + it('keeps arrays as arrays inside objects', () => { + const input = { tags: ['a', 'b'] }; + const result = encodeAttributes(input); + expect(result).toEqual({ tags: ['a', 'b'] }); + }); + + it('flattens nested arrays of objects', () => { + const input = { arr: [{ x: 1 }, { y: 2 }] }; + const result = encodeAttributes(input); + expect(result).toEqual({ + arr: [{ x: 1 }, { y: 2 }] + }); + }); + + it('applies custom attribute encoders before built-in ones', () => { + setEncoders([ + { + check: (v: any): v is Date => v instanceof Date, + encode: (d: Date) => 'CUSTOM_DATE' + } + ]); + + const result = encodeAttributes({ now: new Date() }); + expect(result).toEqual({ now: 'CUSTOM_DATE' }); + }); + + it('applies built-in Date encoder if no custom encoder is provided', () => { + const date = new Date('2020-01-01T12:00:00Z'); + const result = encodeAttributes({ now: date }); + expect(typeof result.now).toBe('string'); + expect(result.now).toContain('2020'); + }); + + it('applies built-in Error encoder', () => { + const error = new Error('boom'); + const result = encodeAttributes({ err: error }); + expect(result['err.name']).toBe('Error'); + expect(result['err.message']).toBe('boom'); + expect(result['err.stack']).toContain('Error: boom'); + }); + + it('applies built-in Map encoder', () => { + const map = new Map([ + ['k1', 1], + ['k2', { nested: 'yes' }] + ]); + const result = encodeAttributes({ data: map }); + expect(Array.isArray(result.data)).toBe(true); + expect(result.data).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: 'k1', + keyType: 'string', + value: 1 + }), + expect.objectContaining({ + key: 'k2', + keyType: 'string', + 'value.nested': 'yes' + }) + ]) + ); + }); + + it('drops unsupported nested values', () => { + const input = { valid: 'ok', bad: () => {} }; + const result = encodeAttributes(input); + expect(result).toEqual({ valid: 'ok' }); + }); + + it('handles deeply nested objects', () => { + const deep = { level1: { level2: { level3: { value: 42 } } } }; + const result = encodeAttributes(deep); + expect(result).toEqual({ 'level1.level2.level3.value': 42 }); + }); + + it('handles object with manual dot keys', () => { + const input = { 'user.profile.name': 'Bob' }; + const result = encodeAttributes(input); + expect(result).toEqual({ 'user.profile.name': 'Bob' }); + }); + + it('handles array with mixed values', () => { + const input = [1, 'two', { nested: true }]; + const result = encodeAttributes(input); + expect(result).toEqual({ context: [1, 'two', { nested: true }] }); + }); + + it('handles empty object gracefully', () => { + const result = encodeAttributes({}); + expect(result).toEqual({}); + expect(warn).not.toHaveBeenCalled(); + }); + + it('handles empty array gracefully at root', () => { + const result = encodeAttributes([]); + expect(result).toEqual({ context: [] }); + expect(warn).toHaveBeenCalled(); + }); + + it('handles NaN and Infinity by dropping them', () => { + const result = encodeAttributes({ + bad1: NaN, + bad2: Infinity, + good: 42 + }); + expect(result).toEqual({ good: 42 }); + }); + + it('flattens object nested inside array', () => { + const input = { arr: [{ foo: 'bar' }] }; + const result = encodeAttributes(input); + expect(result).toEqual({ arr: [{ foo: 'bar' }] }); + }); + + it('handles array of arrays correctly', () => { + const input = { + matrix: [ + [1, 2], + [3, 4] + ] + }; + const result = encodeAttributes(input); + expect(result).toEqual({ + matrix: [ + [1, 2], + [3, 4] + ] + }); + }); + + it('drops functions inside arrays', () => { + const input = { arr: [1, () => {}, 3] }; + const result = encodeAttributes(input); + expect(result.arr).toEqual([1, 3]); + }); + + it('encodes nested Maps inside objects', () => { + const map = new Map([['nested', new Map([['k', 'v']])]]); + const result = encodeAttributes({ outer: map }); + expect(result.outer).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: 'nested', + value: expect.arrayContaining([ + expect.objectContaining({ key: 'k', value: 'v' }) + ]) + }) + ]) + ); + }); + + it('handles deeply nested array of objects', () => { + const input = { items: [[{ foo: 'bar' }]] }; + const result = encodeAttributes(input); + expect(result.items).toEqual([[{ foo: 'bar' }]]); + }); + + it('handles objects with undefined values by dropping them', () => { + const input = { a: 1, b: undefined, c: 'ok' }; + const result = encodeAttributes(input); + expect(result).toEqual({ a: 1, c: 'ok' }); + }); + + it('custom encoder can override primitive handling', () => { + setEncoders([ + { + check: (v: any): v is number => typeof v === 'number', + encode: (n: number) => `num:${n}` + } + ]); + const result = encodeAttributes({ a: 5 }); + expect(result).toEqual({ a: 'num:5' }); + }); + + it('handles object with both dot syntax and nested keys without collisions', () => { + const input = { + 'user.profile.name': 'Alice', + user: { profile: { age: 30 } } + }; + const result = encodeAttributes(input); + expect(result).toEqual({ + 'user.profile.name': 'Alice', + 'user.profile.age': 30 + }); + }); + + it('handles null and undefined keys in Map', () => { + const map = new Map([ + [null, 'nullKey'], + [undefined, 'undefinedKey'] + ]); + const result = encodeAttributes({ myMap: map }); + expect(result.myMap).toEqual( + expect.arrayContaining([ + expect.objectContaining({ key: 'null', value: 'nullKey' }), + expect.objectContaining({ + key: 'undefined', + value: 'undefinedKey' + }) + ]) + ); + }); +}); diff --git a/packages/core/src/sdk/AttributesEncoding/__tests__/defaultEncoders.test.ts b/packages/core/src/sdk/AttributesEncoding/__tests__/defaultEncoders.test.ts new file mode 100644 index 000000000..88c0c0242 --- /dev/null +++ b/packages/core/src/sdk/AttributesEncoding/__tests__/defaultEncoders.test.ts @@ -0,0 +1,198 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import { + stringEncoder, + numberEncoder, + booleanEncoder, + nullishEncoder, + arrayEncoder, + dateEncoder, + errorEncoder, + mapEncoder +} from '../defaultEncoders'; +import { warn } from '../utils'; + +jest.mock('../utils', () => ({ + ...jest.requireActual('../utils'), + warn: jest.fn() +})); + +describe('default encoders', () => { + beforeEach(() => { + (warn as jest.Mock).mockClear(); + }); + + describe('stringEncoder', () => { + it('encodes a string directly', () => { + expect(stringEncoder.check('foo')).toBe(true); + expect(stringEncoder.encode('foo')).toBe('foo'); + }); + it('rejects non-strings', () => { + expect(stringEncoder.check(123)).toBe(false); + }); + }); + + describe('numberEncoder', () => { + it('encodes finite numbers', () => { + expect(numberEncoder.check(42)).toBe(true); + expect(numberEncoder.encode(42)).toBe(42); + }); + it('drops NaN and Infinity', () => { + expect(numberEncoder.encode(NaN)).toBeUndefined(); + expect(numberEncoder.encode(Infinity)).toBeUndefined(); + }); + }); + + describe('booleanEncoder', () => { + it('encodes booleans directly', () => { + expect(booleanEncoder.check(true)).toBe(true); + expect(booleanEncoder.encode(true)).toBe(true); + expect(booleanEncoder.encode(false)).toBe(false); + }); + }); + + describe('nullishEncoder', () => { + it('encodes null and undefined directly', () => { + expect(nullishEncoder.check(null)).toBe(true); + expect(nullishEncoder.check(undefined)).toBe(true); + expect(nullishEncoder.encode(null)).toBeNull(); + expect(nullishEncoder.encode(undefined)).toBeUndefined(); + }); + it('rejects non-nullish values', () => { + expect(nullishEncoder.check('')).toBe(false); + }); + }); + + describe('arrayEncoder', () => { + it('encodes array of primitives', () => { + const result = arrayEncoder.encode([1, 'a', true]); + expect(result).toEqual([1, 'a', true]); + }); + it('encodes nested objects inside array', () => { + const result = arrayEncoder.encode([{ foo: 'bar' }]); + expect((result as Record[])[0]).toHaveProperty( + 'foo', + 'bar' + ); + }); + it('encodes nested arrays recursively', () => { + const result = arrayEncoder.encode([[1, 2], ['a']]); + expect(result).toEqual([[1, 2], ['a']]); + }); + }); + + describe('dateEncoder', () => { + it('encodes Date to string', () => { + const date = new Date('2020-01-01T00:00:00Z'); + expect(dateEncoder.check(date)).toBe(true); + expect(dateEncoder.encode(date)).toEqual(String(date)); + }); + it('rejects non-Date values', () => { + expect(dateEncoder.check('2020-01-01')).toBe(false); + }); + }); + + describe('errorEncoder', () => { + it('encodes Error with name, message, and stack', () => { + const error = new Error('boom'); + const result = errorEncoder.encode(error) as Record; + expect(result.name).toBe('Error'); + expect(result.message).toBe('boom'); + expect(result.stack).toContain('Error: boom'); + }); + + it('removes duplicate fields like stack', () => { + const err = { + message: 'fail' + } as Record; + + err.name = 'CustomError'; + err.stacktrace = 'custom-stack'; + err.stack = 'error-stacktrace'; + err.componentStack = 'component-stack'; + + const result = errorEncoder.encode(err) as Record; + expect(result.name).toBe('CustomError'); + expect(result.message).toBe('fail'); + expect(result.stack).toBe('custom-stack'); + expect(result).not.toHaveProperty('stacktrace'); + expect(result).toHaveProperty('componentStack'); + }); + + it('encodes error with cause', () => { + const cause = new Error('inner'); + const err: any = new Error('outer'); + err.cause = cause; + const result = errorEncoder.encode(err) as Record; + expect(result.cause).toBe(cause); + }); + }); + + describe('mapEncoder', () => { + it('encodes map with string keys', () => { + const map = new Map([ + ['a', 1], + ['b', 'str'] + ]); + const result = mapEncoder.encode(map); + expect(result).toEqual( + expect.arrayContaining([ + { key: 'a', keyType: 'string', value: 1 }, + { key: 'b', keyType: 'string', value: 'str' } + ]) + ); + }); + + it('encodes map with object key', () => { + const keyObj = { toString: () => 'objKey' }; + const map = new Map([[keyObj, 123]]); + const result = mapEncoder.encode(map); + expect((result as Record[])[0]).toHaveProperty( + 'key', + 'objKey' + ); + expect((result as Record[])[0]).toHaveProperty( + 'keyType', + 'object' + ); + }); + + it('encodes map with symbol key', () => { + const map = new Map([[Symbol('s'), 'val']]); + const result = mapEncoder.encode(map); + expect((result as Record[])[0].key).toContain( + 'Symbol(s)' + ); + expect((result as Record[])[0].keyType).toBe('symbol'); + }); + + it('encodes map with null and undefined keys', () => { + const map = new Map([ + [null, 'nullVal'], + [undefined, 'undefVal'] + ]); + const result = mapEncoder.encode(map); + expect(result).toEqual( + expect.arrayContaining([ + { key: 'null', keyType: 'object', value: 'nullVal' }, + { + key: 'undefined', + keyType: 'undefined', + value: 'undefVal' + } + ]) + ); + }); + + it('warns and drops unsupported key types', () => { + const map = new Map([[BigInt(1), 'big']]); + const result = mapEncoder.encode(map); + expect((result as Record[])[0].key).toBe('1'); // bigint stringified + expect(warn).not.toHaveBeenCalled(); // bigint is allowed + }); + }); +}); diff --git a/packages/core/src/sdk/AttributesEncoding/attributesEncoding.tsx b/packages/core/src/sdk/AttributesEncoding/attributesEncoding.tsx new file mode 100644 index 000000000..0f05d7e11 --- /dev/null +++ b/packages/core/src/sdk/AttributesEncoding/attributesEncoding.tsx @@ -0,0 +1,41 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import { DdSdk } from '../DdSdk'; + +import { builtInEncoders } from './defaultEncoders'; +import { encodeAttributesInPlace } from './helpers'; +import type { Encodable } from './types'; +import { isPlainObject, warn } from './utils'; + +/** + * Encodes arbitrary input into a flat dictionary of attributes. + * - Objects are flattened using dot syntax. Max depth handling is done on the native layer by the + * Android and iOS SDKs. + * - We assume the input does not always conform to Record and we: + * - Fallback to { context: givenValue } if a primitive is passed + * - Apply built-in and consumer encoders to all values + * - Drop values of unsupported types + */ +export function encodeAttributes(input: unknown): Record { + const result: Record = {}; + const allEncoders = [...DdSdk.attributeEncoders, ...builtInEncoders]; + + if (isPlainObject(input)) { + for (const [k, v] of Object.entries(input)) { + encodeAttributesInPlace(v, result, [k], allEncoders); + } + } else { + // Fallback for primitive values passed as root + encodeAttributesInPlace(input, result, ['context'], allEncoders); + warn( + 'Warning: attributes root should be an object.\n' + + 'Received a primitive/array instead, which will be wrapped under the "context" key.' + ); + } + + return result; +} diff --git a/packages/core/src/sdk/AttributesEncoding/defaultEncoders.tsx b/packages/core/src/sdk/AttributesEncoding/defaultEncoders.tsx new file mode 100644 index 000000000..fdee006f1 --- /dev/null +++ b/packages/core/src/sdk/AttributesEncoding/defaultEncoders.tsx @@ -0,0 +1,185 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +/* ---------------------------------------- + * Built-in encoders + * -------------------------------------- */ +import { DdSdk } from '../DdSdk'; + +import { + getErrorMessage, + getErrorName, + getErrorStackTrace +} from './errorUtils'; +import { encodeAttributesInPlace, sanitizeForJson } from './helpers'; +import type { AttributeEncoder, Encodable } from './types'; +import { warn } from './utils'; + +/** Primitives: keep them explicit so the full pipeline is used uniformly. */ +export const stringEncoder: AttributeEncoder = { + check: (v): v is string => typeof v === 'string', + encode: v => v +}; + +export const numberEncoder: AttributeEncoder = { + check: (v): v is number => typeof v === 'number', + encode: v => (Number.isFinite(v) ? v : undefined) // drop non-finite +}; + +export const booleanEncoder: AttributeEncoder = { + check: (v): v is boolean => typeof v === 'boolean', + encode: v => v +}; + +export const nullishEncoder: AttributeEncoder = { + check: (v): v is null | undefined => v === null || v === undefined, + encode: v => v +}; + +/** + * Array encoder: + * - Sanitizes each item through the encoder pipeline. + * - Returns the sanitized array (it may later be flattened by the visitor if it still contains objects). + */ +export const arrayEncoder: AttributeEncoder = { + check: Array.isArray, + encode: (arr: unknown[]) => + arr.map(x => + sanitizeForJson(x, [...DdSdk.attributeEncoders, ...builtInEncoders]) + ) +}; + +/** + * Default Datadog Date Encoder. + * This does not make assumptions on format; uses String(date). + */ +export const dateEncoder: AttributeEncoder = { + check: (v: unknown): v is Date => v instanceof Date, + encode: (d: Date) => String(d) +}; +/* + } else if ('componentStack' in error) { + stack = String(error.componentStack); + } else if ( + 'sourceURL' in error && + 'line' in error && + 'column' in error + ) { + + +*/ +/** + * Extended Error Encoder. + * Serializes name, message, stack, and cause (ES2022+) for Error objects. + * If the error has other enumerable properties, they are included and sanitized. + */ +export const errorEncoder: AttributeEncoder = { + check: (v: unknown): v is Error => v instanceof Error, + encode: (e: any) => { + const extraAttributes: Record = {}; + + // In React Native, some errors have extra fields we want to capture + if (e && typeof e === 'object') { + const allEncoders = [ + ...DdSdk.attributeEncoders, + ...builtInEncoders + ]; + encodeAttributesInPlace(e, extraAttributes, [], allEncoders); + } + + // Remove fields that are duplicated in the dedicated fields below + if ('stacktrace' in e) { + delete extraAttributes['stacktrace']; + } else if ('stack' in e) { + delete extraAttributes['stack']; + } else if ('componentStack' in e) { + delete extraAttributes['componentStack']; + } + + return { + ...extraAttributes, + name: getErrorName(e), + message: getErrorMessage(e), + stack: getErrorStackTrace(e), + cause: (e as any).cause + }; + } +}; + +/** + * Map encoder: + * - Converts Map into an array of entries. + * - Each entry is { key: string, keyType: string, value: Encodable }. + * - Keys are stringified with type info to reduce collision risk. + * - Entries with un-stringifiable keys are dropped (with a warning). + */ +export const mapEncoder: AttributeEncoder> = { + check: (v: unknown): v is Map => v instanceof Map, + encode: (map: Map) => { + const entries: Encodable[] = []; + + for (const [k, v] of map.entries()) { + try { + const keyType = typeof k; + let keyStr: string; + + if (k === null) { + keyStr = 'null'; + } else if (k === undefined) { + keyStr = 'undefined'; + } else if ( + keyType === 'string' || + keyType === 'number' || + keyType === 'boolean' || + keyType === 'bigint' + ) { + keyStr = String(k); + } else if (typeof k === 'symbol') { + keyStr = k.description + ? `Symbol(${k.description})` + : 'Symbol'; + } else if (typeof k === 'object' || typeof k === 'function') { + // Try to get a descriptive form + if (typeof (k as any).toString === 'function') { + keyStr = (k as any).toString(); + } else { + keyStr = Object.prototype.toString.call(k); // e.g. "[object Object]" + } + } else { + warn( + `Dropping Map entry: unsupported key type "${keyType}".` + ); + continue; + } + + const allEncoders = [ + ...DdSdk.attributeEncoders, + ...builtInEncoders + ]; + entries.push({ + key: keyStr, + keyType, + value: sanitizeForJson(v, allEncoders) + }); + } catch (err) { + warn(`Failed to encode Map key: ${k}. ERROR: ${String(err)}`); + } + } + + return entries; + } +}; + +export const builtInEncoders = [ + stringEncoder, + numberEncoder, + booleanEncoder, + nullishEncoder, + arrayEncoder, + dateEncoder, + errorEncoder, + mapEncoder +]; diff --git a/packages/core/src/utils/errorUtils.ts b/packages/core/src/sdk/AttributesEncoding/errorUtils.tsx similarity index 55% rename from packages/core/src/utils/errorUtils.ts rename to packages/core/src/sdk/AttributesEncoding/errorUtils.tsx index ca7b3a715..b8692959b 100644 --- a/packages/core/src/utils/errorUtils.ts +++ b/packages/core/src/sdk/AttributesEncoding/errorUtils.tsx @@ -3,23 +3,9 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ - -export const EMPTY_MESSAGE = 'Unknown Error'; -export const EMPTY_STACK_TRACE = ''; -export const DEFAULT_ERROR_NAME = 'Error'; - -export const getErrorMessage = (error: any | undefined): string => { - let message = EMPTY_MESSAGE; - if (error === undefined || error === null) { - message = EMPTY_MESSAGE; - } else if (typeof error === 'object' && 'message' in error) { - message = String(error.message); - } else { - message = String(error); - } - - return message; -}; +export const ERROR_EMPTY_STACKTRACE = ''; +export const ERROR_EMPTY_MESSAGE = 'Unknown Error'; +export const ERROR_DEFAULT_NAME = 'Error'; /** * Will extract the stack from the error, taking the first key found among: @@ -30,13 +16,13 @@ export const getErrorMessage = (error: any | undefined): string => { * generate a stack from this information. */ export const getErrorStackTrace = (error: any | undefined): string => { - let stack = EMPTY_STACK_TRACE; + let stack = ERROR_EMPTY_STACKTRACE; try { if (error === undefined || error === null) { - stack = EMPTY_STACK_TRACE; + stack = ERROR_EMPTY_STACKTRACE; } else if (typeof error === 'string') { - stack = EMPTY_STACK_TRACE; + stack = ERROR_EMPTY_STACKTRACE; } else if (typeof error === 'object') { if ('stacktrace' in error) { stack = String(error.stacktrace); @@ -58,10 +44,52 @@ export const getErrorStackTrace = (error: any | undefined): string => { return stack; }; +export const getErrorMessage = (error: any | undefined): string => { + if (error == null) { + return ERROR_EMPTY_MESSAGE; + } + + // If it's an actual Error (or subclass) + if (error instanceof Error) { + // Prefer .message if defined, otherwise fallback to .toString() + return error.message || error.toString() || ERROR_EMPTY_MESSAGE; + } + + // If it's an object with a message property (not necessarily Error) + if ( + typeof error === 'object' && + 'message' in error && + typeof (error as any).message === 'string' + ) { + return (error as any).message || ERROR_EMPTY_MESSAGE; + } + + // If it’s a primitive (string, number, boolean, symbol) + if ( + typeof error === 'string' || + typeof error === 'number' || + typeof error === 'boolean' || + typeof error === 'symbol' + ) { + return String(error); + } + + // If it has its own toString (not the default Object one) + if ( + typeof error?.toString === 'function' && + error.toString !== Object.prototype.toString + ) { + return error.toString(); + } + + // Fallback + return ERROR_EMPTY_MESSAGE; +}; + export const getErrorName = (error: unknown): string => { try { if (typeof error !== 'object' || error === null) { - return DEFAULT_ERROR_NAME; + return ERROR_DEFAULT_NAME; } if (typeof (error as any).name === 'string') { return (error as any).name; @@ -69,5 +97,5 @@ export const getErrorName = (error: unknown): string => { } catch (e) { // Do nothing } - return DEFAULT_ERROR_NAME; + return ERROR_DEFAULT_NAME; }; diff --git a/packages/core/src/sdk/AttributesEncoding/helpers.tsx b/packages/core/src/sdk/AttributesEncoding/helpers.tsx new file mode 100644 index 000000000..3636be9e1 --- /dev/null +++ b/packages/core/src/sdk/AttributesEncoding/helpers.tsx @@ -0,0 +1,134 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import type { AttributeEncoder, Encodable } from './types'; +import { formatPathForLog, isPlainObject, warn } from './utils'; + +/** + * Recursive in-place encoder: flattens values into `out` dictionary. + * Never applies "context", that's only for the root. + */ +export function encodeAttributesInPlace( + input: unknown, + out: Record, + path: string[], + encoders: AttributeEncoder[] +): void { + const value = applyEncoders(input, encoders); + + // Nullish / primitive + if ( + value === null || + value === undefined || + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' + ) { + out[path.join('.')] = value; + return; + } + + // Arrays + if (Array.isArray(value)) { + const normalize = (x: unknown): Encodable => { + const v = applyEncoders(x, encoders); + + // Primitive / nullish are fine + if ( + v === null || + v === undefined || + typeof v === 'string' || + typeof v === 'number' || + typeof v === 'boolean' + ) { + return v; + } + + if (isPlainObject(v)) { + const nested: Record = {}; + encodeAttributesInPlace(v, nested, [], encoders); + return nested; + } + + if (Array.isArray(v)) { + return v.map(normalize); + } + + // Unsupported + warn( + `Dropped unsupported value in array at '${formatPathForLog( + path + )}': ${String(v)}` + ); + return undefined; + }; + + out[path.join('.')] = value + .map(normalize) + .filter(item => item !== undefined); // drop unsupported + return; + } + + // Plain object + if (isPlainObject(value)) { + for (const [k, v] of Object.entries(value)) { + encodeAttributesInPlace(v, out, [...path, k], encoders); + } + return; + } + + // Unsupported + warn( + `Dropped unsupported value at '${formatPathForLog(path)}': ${String( + value + )}` + ); +} + +/** + * Sanitize unknown input to be JSON-serializable using encoders. + * This does not flatten—it's a pure sanitization pass (used by some encoders). + */ +export function sanitizeForJson( + value: unknown, + encoders: AttributeEncoder[] +): Encodable { + const v = applyEncoders(value, encoders); + + // If still a plain object, sanitize shallowly + if (isPlainObject(v)) { + const out: Record = {}; + for (const [k, val] of Object.entries(v)) { + encodeAttributesInPlace(val, out, [k], encoders); + } + return out; + } + + // If array, sanitize items + if (Array.isArray(v)) { + return v.map(item => sanitizeForJson(item, encoders)); + } + + return v; +} + +export function applyEncoders( + value: unknown, + encoders: AttributeEncoder[] +): Encodable { + for (const enc of encoders) { + try { + if (enc.check(value)) { + return enc.encode(value as never); + } + } catch (err) { + warn(`Encoder error: ${String(err)}`); + return undefined; + } + } + // Not matched by any encoder; leave as-is for the visitor to decide + return value as Encodable; +} diff --git a/packages/core/src/sdk/AttributesEncoding/types.tsx b/packages/core/src/sdk/AttributesEncoding/types.tsx new file mode 100644 index 000000000..1f136d576 --- /dev/null +++ b/packages/core/src/sdk/AttributesEncoding/types.tsx @@ -0,0 +1,33 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +/** + * A value that can safely be encoded as a Datadog Attribute. + */ +export type Encodable = + | string + | number + | boolean + | null + | undefined + | Encodable[] + | { [key: string]: Encodable }; + +export type AttributeKey = string; +export type AttributeValue = Encodable; +export type EncodableAttributes = Record; +export type Attributes = Record; + +/** + * Encoders define how to handle special object types (Date, Error, Map, etc.). + * Each encoder is: + * - check: decides if the value can be handled by this encoder. + * - encode: converts the value into an Encodable. + */ +export interface AttributeEncoder { + check: (value: unknown) => boolean; + encode: (value: T) => Encodable; +} diff --git a/packages/core/src/sdk/AttributesEncoding/utils.tsx b/packages/core/src/sdk/AttributesEncoding/utils.tsx new file mode 100644 index 000000000..11e3adfdf --- /dev/null +++ b/packages/core/src/sdk/AttributesEncoding/utils.tsx @@ -0,0 +1,31 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import { InternalLog } from '../../InternalLog'; +import { SdkVerbosity } from '../../SdkVerbosity'; + +export function warn(text: string) { + InternalLog.log(`[ATTRIBUTES] ${text}`, SdkVerbosity.WARN); +} + +export function isPlainObject(v: unknown): v is Record { + return !!v && typeof v === 'object' && (v as any).constructor === Object; +} + +/** + * Utility: format a path array into dot/bracket notation (for logs). + */ +export function formatPathForLog(path: (string | number)[]): string { + return path + .map((segment, index) => { + const s = String(segment); + if (/^\d/.test(s)) { + return `[${s}]`; + } + return index === 0 ? s : `.${s}`; + }) + .join(''); +} diff --git a/packages/core/src/sdk/DatadogProvider/Buffer/BoundedBuffer.ts b/packages/core/src/sdk/DatadogProvider/Buffer/BoundedBuffer.ts index dc91a475f..93f9389b0 100644 --- a/packages/core/src/sdk/DatadogProvider/Buffer/BoundedBuffer.ts +++ b/packages/core/src/sdk/DatadogProvider/Buffer/BoundedBuffer.ts @@ -6,8 +6,8 @@ import { InternalLog } from '../../../InternalLog'; import { SdkVerbosity } from '../../../SdkVerbosity'; -import { DdSdk } from '../../../sdk/DdSdk'; -import { getErrorStackTrace } from '../../../utils/errorUtils'; +import { getErrorStackTrace } from '../../AttributesEncoding/errorUtils'; +import { NativeDdSdk } from '../../DdSdkInternal'; import { DatadogBuffer } from './DatadogBuffer'; @@ -206,7 +206,7 @@ export class BoundedBuffer extends DatadogBuffer { private drainTelemetry = () => { Object.values(this.telemetryBuffer).forEach( ({ message, stack, kind, occurrences }) => { - DdSdk.telemetryError( + NativeDdSdk.telemetryError( `${message} happened ${occurrences} times.`, stack, kind diff --git a/packages/core/src/sdk/DdSdk.ts b/packages/core/src/sdk/DdSdk.ts deleted file mode 100644 index b086d96bd..000000000 --- a/packages/core/src/sdk/DdSdk.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -import type { DdNativeSdkType } from '../nativeModulesTypes'; - -// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires -const DdSdk: DdNativeSdkType = require('../specs/NativeDdSdk').default; - -export { DdSdk }; diff --git a/packages/core/src/sdk/DdSdk.tsx b/packages/core/src/sdk/DdSdk.tsx new file mode 100644 index 000000000..a829707e3 --- /dev/null +++ b/packages/core/src/sdk/DdSdk.tsx @@ -0,0 +1,15 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ +import { getGlobalInstance } from '../utils/singletonUtils'; + +import { DdSdkWrapper } from './DdSdkInternal'; +import type { DdSdkType } from './DdSdkInternal'; + +const CORE_MODULE = 'com.datadog.reactnative.core'; +export const DdSdk = getGlobalInstance( + CORE_MODULE, + () => new DdSdkWrapper() +) as DdSdkType; diff --git a/packages/core/src/sdk/DdSdkInternal.tsx b/packages/core/src/sdk/DdSdkInternal.tsx new file mode 100644 index 000000000..7af33d2e3 --- /dev/null +++ b/packages/core/src/sdk/DdSdkInternal.tsx @@ -0,0 +1,105 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import type { + DdNativeSdkConfiguration, + DdNativeSdkType +} from '../nativeModulesTypes'; + +import type { AttributeEncoder } from './AttributesEncoding/types'; + +// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires +const NativeDdSdk: DdNativeSdkType = require('../specs/NativeDdSdk').default; + +export type DdSdkType = { + readonly attributeEncoders: AttributeEncoder[]; + + /** + * Initializes Datadog's features. + * @param configuration: The configuration to use. + */ + initialize(configuration: DdNativeSdkConfiguration): Promise; +}; + +export class DdSdkWrapper implements DdNativeSdkType { + get attributeEncoders(): AttributeEncoder[] { + return this._attributeEncoders; + } + private _attributeEncoders: AttributeEncoder[] = []; + + initialize(configuration: DdNativeSdkConfiguration): Promise { + this._attributeEncoders = [...configuration.attributeEncoders]; + return NativeDdSdk.initialize(configuration); + } + + getConstants() { + return NativeDdSdk.getConstants(); + } + + setAttributes(attributes: object): Promise { + return NativeDdSdk.setAttributes(attributes); + } + + setUserInfo(user: object): Promise { + return NativeDdSdk.setUserInfo(user); + } + + clearUserInfo(): Promise { + return NativeDdSdk.clearUserInfo(); + } + + addUserExtraInfo(extraInfo: object): Promise { + return NativeDdSdk.addUserExtraInfo(extraInfo); + } + + setTrackingConsent(trackingConsent: string): Promise { + return NativeDdSdk.setTrackingConsent(trackingConsent); + } + + sendTelemetryLog( + message: string, + attributes: object, + config: object + ): Promise { + return NativeDdSdk.sendTelemetryLog(message, attributes, config); + } + + telemetryDebug(message: string): Promise { + return NativeDdSdk.telemetryDebug(message); + } + + telemetryError( + message: string, + stack: string, + kind: string + ): Promise { + return NativeDdSdk.telemetryError(message, stack, kind); + } + + consumeWebviewEvent(message: string): Promise { + return NativeDdSdk.consumeWebviewEvent(message); + } + + clearAllData(): Promise { + return NativeDdSdk.clearAllData(); + } + + addListener(eventType: string): void { + return NativeDdSdk.addListener(eventType); + } + + removeListeners(count: number): void { + return NativeDdSdk.removeListeners(count); + } + + _setAttributeEncodersForTesting( + attributeEncoders: AttributeEncoder[] + ) { + this._attributeEncoders = [...attributeEncoders]; + } +} + +export { NativeDdSdk }; diff --git a/packages/core/src/sdk/EventMappers/EventMapper.ts b/packages/core/src/sdk/EventMappers/EventMapper.ts index e1cbaae19..0f3878fe1 100644 --- a/packages/core/src/sdk/EventMappers/EventMapper.ts +++ b/packages/core/src/sdk/EventMappers/EventMapper.ts @@ -6,11 +6,11 @@ import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; -import { DdSdk } from '../../sdk/DdSdk'; import { AccountInfoSingleton } from '../AccountInfoSingleton/AccountInfoSingleton'; import type { AccountInfo } from '../AccountInfoSingleton/types'; import { AttributesSingleton } from '../AttributesSingleton/AttributesSingleton'; import type { Attributes } from '../AttributesSingleton/types'; +import { NativeDdSdk } from '../DdSdkInternal'; import { UserInfoSingleton } from '../UserInfoSingleton/UserInfoSingleton'; import type { UserInfo } from '../UserInfoSingleton/types'; @@ -92,7 +92,7 @@ export class EventMapper { )}: ${error}`, SdkVerbosity.WARN ); - DdSdk.telemetryDebug('Error while running the event mapper'); + NativeDdSdk.telemetryDebug('Error while running the event mapper'); return this.formatMapperEventForNative(backupEvent, backupEvent); } }; diff --git a/packages/core/src/trace/DdTrace.ts b/packages/core/src/trace/DdTrace.ts index 119716914..8c57a6ed2 100644 --- a/packages/core/src/trace/DdTrace.ts +++ b/packages/core/src/trace/DdTrace.ts @@ -7,12 +7,12 @@ import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../SdkVerbosity'; import type { DdNativeTraceType } from '../nativeModulesTypes'; +import { encodeAttributes } from '../sdk/AttributesEncoding/attributesEncoding'; import { bufferNativeCallReturningId, bufferNativeCallWithId } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; import type { DdTraceType } from '../types'; -import { validateContext } from '../utils/argsUtils'; import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; @@ -33,7 +33,7 @@ class DdTraceWrapper implements DdTraceType { const spanId = bufferNativeCallReturningId(() => this.nativeTrace.startSpan( operation, - validateContext(context), + encodeAttributes(context), timestampMs ) ); @@ -54,7 +54,7 @@ class DdTraceWrapper implements DdTraceType { id => this.nativeTrace.finishSpan( id, - validateContext(context), + encodeAttributes(context), timestampMs ), spanId diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index cd697c04b..6865e2a41 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -5,6 +5,7 @@ */ import type { BatchProcessingLevel } from './DdSdkReactNativeConfiguration'; +import type { AttributeEncoder } from './sdk/AttributesEncoding/types'; declare global { // eslint-disable-next-line no-var, vars-on-top @@ -70,7 +71,8 @@ export class DdSdkConfiguration { readonly trackWatchdogTerminations: boolean | undefined, readonly batchProcessingLevel: BatchProcessingLevel, // eslint-disable-next-line no-empty-function readonly initialResourceThreshold: number | undefined, - readonly trackMemoryWarnings: boolean + readonly trackMemoryWarnings: boolean, + readonly attributeEncoders: AttributeEncoder[] ) {} } @@ -224,7 +226,6 @@ export type LogEvent = { export type LogEventMapper = (logEvent: LogEvent) => LogEvent | null; // DdRum - export enum ErrorSource { NETWORK = 'NETWORK', SOURCE = 'SOURCE', diff --git a/packages/core/src/utils/argsUtils.ts b/packages/core/src/utils/argsUtils.ts deleted file mode 100644 index 72406e037..000000000 --- a/packages/core/src/utils/argsUtils.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -import { InternalLog } from '../InternalLog'; -import { SdkVerbosity } from '../SdkVerbosity'; - -/** - * Validates the given context before passing it to the native SDKs. - * @param context the original context - * @returns the original context if it's a plain object, an object wrapping the context if it's an array, - * or an empty object if the context is of any other type. - */ -export const validateContext = (context: any) => { - if (!context) { - return {}; - } - - // eslint-disable-next-line eqeqeq - if (context.constructor == Object) { - return context; - } - - if (Array.isArray(context)) { - InternalLog.log( - "The given context is an array, it will be nested in 'context' property inside a new object.", - SdkVerbosity.WARN - ); - return { context }; - } - - InternalLog.log( - `The given context (${context}) is invalid - it must be an object. Context will be empty.`, - SdkVerbosity.ERROR - ); - - return {}; -}; diff --git a/packages/react-native-apollo-client/src/helpers.ts b/packages/react-native-apollo-client/src/helpers.ts index c7f9a435a..9812bd547 100644 --- a/packages/react-native-apollo-client/src/helpers.ts +++ b/packages/react-native-apollo-client/src/helpers.ts @@ -22,7 +22,7 @@ export const getVariables = (operation: Operation): string | null => { try { return JSON.stringify(operation.variables); } catch (e) { - DdSdk?.telemetryError( + (DdSdk as any)?.telemetryError( _getErrorMessage( ErrorCode.GQL_VARIABLE_RETRIEVAL_ERROR, apolloVersion @@ -61,7 +61,7 @@ export const getOperationType = ( })[0] || null ); } catch (e) { - DdSdk?.telemetryError( + (DdSdk as any)?.telemetryError( _getErrorMessage(ErrorCode.GQL_OPERATION_TYPE_ERROR, apolloVersion), _getErrorStack(e), ErrorCode.GQL_OPERATION_TYPE_ERROR From 73f2d9e43af0ab3e68145027faf23e1916b9ecf2 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Fri, 10 Oct 2025 11:32:05 +0200 Subject: [PATCH 082/114] Fixed existing tests --- .../src/__tests__/DdSdkReactNative.test.tsx | 65 ++-- .../DdSdkReactNativeConfiguration.test.ts | 3 + .../DdRumErrorTracking.test.tsx | 132 ++++++++- .../DdRumUserInteractionTracking.test.tsx | 17 +- .../core/src/logs/__tests__/DdLogs.test.ts | 124 +++----- packages/core/src/rum/__tests__/DdRum.test.ts | 68 ++--- .../__tests__/__utils__/XMLHttpRequestMock.ts | 67 +++-- .../XHRProxy/__tests__/XHRProxy.test.ts | 277 +++++++++++------- .../__tests__/DdSdkNativeBridge.test.tsx | 2 +- .../Buffer/__tests__/BoundedBuffer.test.ts | 10 +- .../__tests__/initialization.test.tsx | 1 + .../__tests__/EventMapper.test.ts | 6 +- .../__tests__/FileBasedConfiguration.test.ts | 2 + .../core/src/trace/__tests__/DdTrace.test.ts | 13 +- .../src/utils/__tests__/argsUtils.test.ts | 66 ----- .../src/utils/__tests__/errorUtils.test.ts | 2 +- 16 files changed, 468 insertions(+), 387 deletions(-) delete mode 100644 packages/core/src/utils/__tests__/argsUtils.test.ts diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 5e6f8c447..b6ecfdf7a 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -19,7 +19,7 @@ import { DdRumUserInteractionTracking } from '../rum/instrumentation/interaction import { DdRumResourceTracking } from '../rum/instrumentation/resourceTracking/DdRumResourceTracking'; import { PropagatorType, RumActionType } from '../rum/types'; import { AttributesSingleton } from '../sdk/AttributesSingleton/AttributesSingleton'; -import { DdSdk } from '../sdk/DdSdk'; +import { NativeDdSdk } from '../sdk/DdSdkInternal'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; import { UserInfoSingleton } from '../sdk/UserInfoSingleton/UserInfoSingleton'; import { ErrorSource } from '../types'; @@ -428,7 +428,9 @@ describe('DdSdkReactNative', () => { const ddSdkConfiguration = NativeModules.DdSdk.initialize.mock .calls[0][0] as DdSdkConfiguration; expect( - ddSdkConfiguration.additionalConfiguration['_dd.version'] + (ddSdkConfiguration.additionalConfiguration as { + '_dd.version': string; + })['_dd.version'] ).toBe('2.0.0'); }); @@ -451,10 +453,14 @@ describe('DdSdkReactNative', () => { const ddSdkConfiguration = NativeModules.DdSdk.initialize.mock .calls[0][0] as DdSdkConfiguration; expect( - ddSdkConfiguration.additionalConfiguration['_dd.version'] + (ddSdkConfiguration.additionalConfiguration as { + '_dd.version': string; + })['_dd.version'] ).toBeUndefined(); expect( - ddSdkConfiguration.additionalConfiguration['_dd.version_suffix'] + (ddSdkConfiguration.additionalConfiguration as { + '_dd.version_suffix': string; + })['_dd.version_suffix'] ).toBe('-codepush-3'); }); @@ -478,10 +484,14 @@ describe('DdSdkReactNative', () => { const ddSdkConfiguration = NativeModules.DdSdk.initialize.mock .calls[0][0] as DdSdkConfiguration; expect( - ddSdkConfiguration.additionalConfiguration['_dd.version'] + (ddSdkConfiguration.additionalConfiguration as { + '_dd.version': string; + })['_dd.version'] ).toBe('2.0.0-codepush-3'); expect( - ddSdkConfiguration.additionalConfiguration['_dd.version_suffix'] + (ddSdkConfiguration.additionalConfiguration as { + '_dd.version_suffix': string; + })['_dd.version_suffix'] ).toBeUndefined(); }); @@ -1056,8 +1066,10 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.addAttribute(key, value); // THEN - expect(DdSdk.addAttribute).toHaveBeenCalledTimes(1); - expect(DdSdk.addAttribute).toHaveBeenCalledWith(key, { value }); + expect(NativeDdSdk.addAttribute).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.addAttribute).toHaveBeenCalledWith(key, { + value + }); expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual( value ); @@ -1075,8 +1087,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.removeAttribute(key); // THEN - expect(DdSdk.removeAttribute).toHaveBeenCalledTimes(1); - expect(DdSdk.removeAttribute).toHaveBeenCalledWith(key); + expect(NativeDdSdk.removeAttribute).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.removeAttribute).toHaveBeenCalledWith(key); expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual( undefined ); @@ -1093,8 +1105,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.addAttributes(attributes); // THEN - expect(DdSdk.addAttributes).toHaveBeenCalledTimes(1); - expect(DdSdk.addAttributes).toHaveBeenCalledWith(attributes); + expect(NativeDdSdk.addAttributes).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.addAttributes).toHaveBeenCalledWith(attributes); expect(AttributesSingleton.getInstance().getAttributes()).toEqual({ foo: 'bar' }); @@ -1111,8 +1123,11 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.removeAttributes(['foo', 'baz']); // THEN - expect(DdSdk.removeAttributes).toHaveBeenCalledTimes(1); - expect(DdSdk.removeAttributes).toHaveBeenCalledWith(['foo', 'baz']); + expect(NativeDdSdk.removeAttributes).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.removeAttributes).toHaveBeenCalledWith([ + 'foo', + 'baz' + ]); expect(AttributesSingleton.getInstance().getAttributes()).toEqual( {} ); @@ -1135,8 +1150,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.setUserInfo(userInfo); // THEN - expect(DdSdk.setUserInfo).toHaveBeenCalledTimes(1); - expect(DdSdk.setUserInfo).toHaveBeenCalledWith(userInfo); + expect(NativeDdSdk.setUserInfo).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.setUserInfo).toHaveBeenCalledWith(userInfo); expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual( userInfo ); @@ -1156,8 +1171,10 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.addUserExtraInfo(extraInfo); // THEN - expect(DdSdk.addUserExtraInfo).toHaveBeenCalledTimes(1); - expect(DdSdk.addUserExtraInfo).toHaveBeenCalledWith(extraInfo); + expect(NativeDdSdk.addUserExtraInfo).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.addUserExtraInfo).toHaveBeenCalledWith( + extraInfo + ); expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({ id: 'id', extraInfo: { @@ -1186,8 +1203,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.clearUserInfo(); // THEN - expect(DdSdk.clearUserInfo).toHaveBeenCalledTimes(1); - expect(DdSdk.setUserInfo).toHaveBeenCalled(); + expect(NativeDdSdk.clearUserInfo).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.setUserInfo).toHaveBeenCalled(); expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual( undefined ); @@ -1204,8 +1221,10 @@ describe('DdSdkReactNative', () => { DdSdkReactNative.setTrackingConsent(consent); // THEN - expect(DdSdk.setTrackingConsent).toHaveBeenCalledTimes(1); - expect(DdSdk.setTrackingConsent).toHaveBeenCalledWith(consent); + expect(NativeDdSdk.setTrackingConsent).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.setTrackingConsent).toHaveBeenCalledWith( + consent + ); }); }); @@ -1215,7 +1234,7 @@ describe('DdSdkReactNative', () => { DdSdkReactNative.clearAllData(); // THEN - expect(DdSdk.clearAllData).toHaveBeenCalledTimes(1); + expect(NativeDdSdk.clearAllData).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts index 60a9d13ff..d13b00df3 100644 --- a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts +++ b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts @@ -34,6 +34,7 @@ describe('DdSdkReactNativeConfiguration', () => { "actionEventMapper": null, "additionalConfiguration": {}, "applicationId": "fake-app-id", + "attributeEncoders": [], "batchProcessingLevel": "MEDIUM", "batchSize": "MEDIUM", "bundleLogsWithRum": true, @@ -134,6 +135,7 @@ describe('DdSdkReactNativeConfiguration', () => { "additionalField": "fake-value", }, "applicationId": "fake-app-id", + "attributeEncoders": [], "batchProcessingLevel": "MEDIUM", "batchSize": "LARGE", "bundleLogsWithRum": true, @@ -222,6 +224,7 @@ describe('DdSdkReactNativeConfiguration', () => { "actionEventMapper": null, "additionalConfiguration": {}, "applicationId": "", + "attributeEncoders": [], "batchProcessingLevel": "MEDIUM", "batchSize": "MEDIUM", "bundleLogsWithRum": false, diff --git a/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx b/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx index fc34de5b0..cfa6b9014 100644 --- a/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx +++ b/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx @@ -18,13 +18,13 @@ let baseErrorHandlerCalled = false; const baseErrorHandler = (error: any, isFatal?: boolean) => { baseErrorHandlerCalled = true; }; -let originalErrorHandler; +let originalErrorHandler: any; let baseConsoleErrorCalled = false; -const baseConsoleError = (...params: unknown) => { +const baseConsoleError = (...params: unknown[]) => { baseConsoleErrorCalled = true; }; -let originalConsoleError; +let originalConsoleError: any; const flushPromises = () => new Promise(jest.requireActual('timers').setImmediate); @@ -61,11 +61,14 @@ it('M intercept and send a RUM event W onGlobalError() {no message}', async () = // THEN expect(DdRum.addError).toHaveBeenCalledTimes(1); expect(DdRum.addError).toHaveBeenCalledWith( - '[object Object]', + 'Unknown Error', 'SOURCE', 'doSomething() at ./path/to/file.js:67:3', { - '_dd.error.raw': error, + '_dd.error.raw.name': 'Error', + '_dd.error.raw.message': 'Unknown Error', + '_dd.error.raw.cause': undefined, + '_dd.error.raw.stack': 'doSomething() at ./path/to/file.js:67:3', '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -94,7 +97,10 @@ it('M intercept and send a RUM event W onGlobalError() {empty stack trace}', asy 'SOURCE', '', { - '_dd.error.raw': error, + '_dd.error.raw.name': 'Error', + '_dd.error.raw.message': 'Something bad happened', + '_dd.error.raw.cause': undefined, + '_dd.error.raw.stack': '', '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -121,7 +127,10 @@ it('M intercept and send a RUM event W onGlobalError() {Error object}', async () 'SOURCE', expect.stringContaining('Error: Something bad happened'), { - '_dd.error.raw': error, + '_dd.error.raw.name': error.name, + '_dd.error.raw.message': error.message, + '_dd.error.raw.stack': error.stack, + '_dd.error.raw.cause': undefined, '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -155,7 +164,10 @@ it('M intercept and send a RUM event W onGlobalError() {CustomError object}', as 'SOURCE', expect.stringContaining('Error: Something bad happened'), { - '_dd.error.raw': error, + '_dd.error.raw.name': error.name, + '_dd.error.raw.message': error.message, + '_dd.error.raw.stack': error.stack, + '_dd.error.raw.cause': undefined, '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -190,9 +202,15 @@ it('M intercept and send a RUM event W onGlobalError() {with source file info}', 'SOURCE', 'at ./path/to/file.js:1038:57', { - '_dd.error.raw': error, '_dd.error.is_crash': is_fatal, - '_dd.error.source_type': 'react-native' + '_dd.error.source_type': 'react-native', + '_dd.error.raw.sourceURL': './path/to/file.js', + '_dd.error.raw.line': 1038, + '_dd.error.raw.column': 57, + '_dd.error.raw.message': 'Something bad happened', + '_dd.error.raw.name': 'Error', + '_dd.error.raw.cause': undefined, + '_dd.error.raw.stack': 'at ./path/to/file.js:1038:57' }, expect.any(Number), '' @@ -224,7 +242,63 @@ it('M intercept and send a RUM event W onGlobalError() {with component stack}', 'SOURCE', 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', { - '_dd.error.raw': error, + '_dd.error.raw.message': 'Something bad happened', + '_dd.error.raw.name': 'Error', + '_dd.error.raw.stack': [ + 'doSomething() at ./path/to/file.js:67:3', + 'nestedCall() at ./path/to/file.js:1064:9', + 'root() at ./path/to/index.js:10:1' + ].join(','), + '_dd.error.raw.cause': undefined, + '_dd.error.is_crash': is_fatal, + '_dd.error.source_type': 'react-native' + }, + expect.any(Number), + '' + ); + expect(baseErrorHandlerCalled).toStrictEqual(true); +}); + +it('M intercept and send a RUM event W onGlobalError() {with stack and component stack}', async () => { + // GIVEN + DdRumErrorTracking.startTracking(); + const is_fatal = Math.random() < 0.5; + const error = { + stack: [ + 'example() at ./path/to/file.js:77:2', + 'test() at ./path/to/index.js:22:3' + ], + componentStack: [ + 'doSomething() at ./path/to/file.js:67:3', + 'nestedCall() at ./path/to/file.js:1064:9', + 'root() at ./path/to/index.js:10:1' + ], + message: 'Something bad happened' + }; + + // WHEN + DdRumErrorTracking.onGlobalError(error, is_fatal); + await flushPromises(); + + // THEN + expect(DdRum.addError).toHaveBeenCalledTimes(1); + expect(DdRum.addError).toHaveBeenCalledWith( + 'Something bad happened', + 'SOURCE', + 'example() at ./path/to/file.js:77:2,test() at ./path/to/index.js:22:3', + { + '_dd.error.raw.message': 'Something bad happened', + '_dd.error.raw.name': 'Error', + '_dd.error.raw.stack': [ + 'example() at ./path/to/file.js:77:2', + 'test() at ./path/to/index.js:22:3' + ].join(','), + '_dd.error.raw.componentStack': [ + 'doSomething() at ./path/to/file.js:67:3', + 'nestedCall() at ./path/to/file.js:1064:9', + 'root() at ./path/to/index.js:10:1' + ], + '_dd.error.raw.cause': undefined, '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -258,7 +332,14 @@ it('M intercept and send a RUM event W onGlobalError() {with stack}', async () = 'SOURCE', 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', { - '_dd.error.raw': error, + '_dd.error.raw.name': 'Error', + '_dd.error.raw.message': 'Something bad happened', + '_dd.error.raw.cause': undefined, + '_dd.error.raw.stack': [ + 'doSomething() at ./path/to/file.js:67:3', + 'nestedCall() at ./path/to/file.js:1064:9', + 'root() at ./path/to/index.js:10:1' + ].join(','), '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -292,7 +373,14 @@ it('M intercept and send a RUM event W onGlobalError() {with stacktrace}', async 'SOURCE', 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', { - '_dd.error.raw': error, + '_dd.error.raw.name': 'Error', + '_dd.error.raw.message': 'Something bad happened', + '_dd.error.raw.stack': [ + 'doSomething() at ./path/to/file.js:67:3', + 'nestedCall() at ./path/to/file.js:1064:9', + 'root() at ./path/to/index.js:10:1' + ].join(','), + '_dd.error.raw.cause': undefined, '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -332,7 +420,14 @@ it('M not report error in console handler W onGlobalError() {with console report 'SOURCE', 'doSomething() at ./path/to/file.js:67:3,nestedCall() at ./path/to/file.js:1064:9,root() at ./path/to/index.js:10:1', { - '_dd.error.raw': error, + '_dd.error.raw.name': 'Error', + '_dd.error.raw.cause': undefined, + '_dd.error.raw.message': 'Something bad happened', + '_dd.error.raw.stack': [ + 'doSomething() at ./path/to/file.js:67:3', + 'nestedCall() at ./path/to/file.js:1064:9', + 'root() at ./path/to/index.js:10:1' + ].join(','), '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, @@ -483,7 +578,10 @@ describe.each([ const errorMessage = message === undefined || message === null ? 'Unknown Error' - : String(message); + : typeof message?.toString === 'function' && + message.toString !== Object.prototype.toString + ? String(message) + : 'Unknown Error'; expect(DdRum.addError).toHaveBeenCalledTimes(1); expect(DdRum.addError).toHaveBeenCalledWith( errorMessage, @@ -517,7 +615,9 @@ it('M intercept and send a RUM event W on error() {called from RNErrorHandler}', 'SOURCE', expect.stringContaining('Error: Something bad happened'), { - '_dd.error.raw': error, + '_dd.error.raw.name': error.name, + '_dd.error.raw.message': error.message, + '_dd.error.raw.stack': error.stack, '_dd.error.is_crash': is_fatal, '_dd.error.source_type': 'react-native' }, diff --git a/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx b/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx index d94d9bb82..6ee9e9c71 100644 --- a/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx +++ b/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import type { DdNativeRumType } from '../../../nativeModulesTypes'; import { DdRumUserInteractionTracking } from '../../../rum/instrumentation/interactionTracking/DdRumUserInteractionTracking'; import { BufferSingleton } from '../../../sdk/DatadogProvider/Buffer/BufferSingleton'; -import { DdSdk } from '../../../sdk/DdSdk'; +import { NativeDdSdk } from '../../../sdk/DdSdkInternal'; const styles = StyleSheet.create({ button: { @@ -311,7 +311,7 @@ describe('startTracking memoization', () => { // GIVEN DdRumUserInteractionTracking.startTracking({}); let rendersCount = 0; - const DummyComponent = props => { + const DummyComponent = (props: { onPress: () => void }) => { rendersCount++; return ( @@ -343,7 +343,7 @@ describe('startTracking memoization', () => { // GIVEN DdRumUserInteractionTracking.startTracking({}); let rendersCount = 0; - const DummyComponent = props => { + const DummyComponent = (props: { title: string }) => { rendersCount++; return ( @@ -370,7 +370,10 @@ describe('startTracking memoization', () => { // GIVEN DdRumUserInteractionTracking.startTracking({}); let rendersCount = 0; - const DummyComponent = props => { + const DummyComponent = (props: { + onPress: () => void; + title: string; + }) => { rendersCount++; return ( @@ -410,7 +413,7 @@ describe('startTracking memoization', () => { // GIVEN DdRumUserInteractionTracking.startTracking({}); let rendersCount = 0; - const DummyComponent = props => { + const DummyComponent = (props: { onPress: () => void }) => { rendersCount++; return ( @@ -456,7 +459,7 @@ describe('startTracking', () => { jest.setMock('react/jsx-runtime', {}); DdRumUserInteractionTracking.startTracking({}); expect(DdRumUserInteractionTracking['isTracking']).toBe(true); - expect(DdSdk.telemetryDebug).toBeCalledWith( + expect(NativeDdSdk.telemetryDebug).toBeCalledWith( 'React jsx runtime does not export new jsx transform' ); }); @@ -466,7 +469,7 @@ describe('startTracking', () => { DdRumUserInteractionTracking.startTracking({}); expect(DdRumUserInteractionTracking['isTracking']).toBe(true); - expect(DdSdk.telemetryDebug).toBeCalledWith( + expect(NativeDdSdk.telemetryDebug).toBeCalledWith( 'React version does not support new jsx transform' ); }); diff --git a/packages/core/src/logs/__tests__/DdLogs.test.ts b/packages/core/src/logs/__tests__/DdLogs.test.ts index 2dd15f46a..bfd252d82 100644 --- a/packages/core/src/logs/__tests__/DdLogs.test.ts +++ b/packages/core/src/logs/__tests__/DdLogs.test.ts @@ -12,7 +12,7 @@ import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; import type { DdNativeLogsType } from '../../nativeModulesTypes'; import { ErrorSource } from '../../types'; -import type { LogEventMapper } from '../../types'; +import type { LogEventMapper, LogEvent } from '../../types'; import { DdLogs } from '../DdLogs'; jest.mock('../../InternalLog', () => { @@ -38,7 +38,7 @@ describe('DdLogs', () => { context: { newContext: 'context' }, status: 'info', userInfo: {} - }; + } as LogEvent; }; DdLogs.registerLogEventMapper(logEventMapper); @@ -506,7 +506,7 @@ describe('DdLogs', () => { it('native context is an object with nested property W context is an array', async () => { await DdLogs.debug('message', [1, 2, 3]); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), SdkVerbosity.WARN ); @@ -516,12 +516,12 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.debug('message', obj); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdLogs.debug).toHaveBeenCalledWith( 'message', @@ -549,7 +549,7 @@ describe('DdLogs', () => { it('native context is an object with nested property W context is an array', async () => { await DdLogs.warn('message', [1, 2, 3]); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), SdkVerbosity.WARN ); @@ -559,12 +559,12 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.warn('message', obj); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdLogs.warn).toHaveBeenCalledWith( 'message', @@ -592,7 +592,7 @@ describe('DdLogs', () => { it('native context is an object with nested property W context is an array', async () => { await DdLogs.info('message', [1, 2, 3]); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), SdkVerbosity.WARN ); @@ -602,12 +602,12 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.info('message', obj); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdLogs.info).toHaveBeenCalledWith( 'message', @@ -635,7 +635,7 @@ describe('DdLogs', () => { it('native context is an object with nested property W context is an array', async () => { await DdLogs.error('message', [1, 2, 3]); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), SdkVerbosity.WARN ); @@ -645,12 +645,12 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.error('message', obj); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdLogs.error).toHaveBeenCalledWith( 'message', @@ -700,17 +700,9 @@ describe('DdLogs', () => { 3 ]); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking debug log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining( - 'The given context is an array, it will be nested' - ), + expect.anything(), SdkVerbosity.WARN ); @@ -729,7 +721,7 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.debug( 'message', 'kind', @@ -738,16 +730,10 @@ describe('DdLogs', () => { obj ); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking debug log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining('Context will be empty.'), - SdkVerbosity.ERROR + expect.anything(), + SdkVerbosity.WARN ); expect( @@ -804,17 +790,9 @@ describe('DdLogs', () => { 3 ]); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking warn log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining( - 'The given context is an array, it will be nested' - ), + expect.anything(), SdkVerbosity.WARN ); @@ -831,7 +809,7 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.warn( 'message', 'kind', @@ -840,16 +818,10 @@ describe('DdLogs', () => { obj ); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking warn log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining('Context will be empty.'), - SdkVerbosity.ERROR + expect.anything(), + SdkVerbosity.WARN ); expect( @@ -906,17 +878,9 @@ describe('DdLogs', () => { 3 ]); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking info log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining( - 'The given context is an array, it will be nested' - ), + expect.anything(), SdkVerbosity.WARN ); @@ -933,7 +897,7 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.info( 'message', 'kind', @@ -942,16 +906,10 @@ describe('DdLogs', () => { obj ); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking info log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining('Context will be empty.'), - SdkVerbosity.ERROR + expect.anything(), + SdkVerbosity.WARN ); expect( @@ -1008,17 +966,9 @@ describe('DdLogs', () => { 3 ]); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking error log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining( - 'The given context is an array, it will be nested' - ), + expect.anything(), SdkVerbosity.WARN ); @@ -1037,7 +987,7 @@ describe('DdLogs', () => { }); it('native context is empty W context is raw type', async () => { - const obj: any = 123; + const obj: any = Symbol('invalid-context'); await DdLogs.error( 'message', 'kind', @@ -1046,16 +996,10 @@ describe('DdLogs', () => { obj ); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Tracking error log'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining('Context will be empty.'), - SdkVerbosity.ERROR + expect.anything(), + SdkVerbosity.WARN ); expect( diff --git a/packages/core/src/rum/__tests__/DdRum.test.ts b/packages/core/src/rum/__tests__/DdRum.test.ts index 8bdc86aac..e1c699fe1 100644 --- a/packages/core/src/rum/__tests__/DdRum.test.ts +++ b/packages/core/src/rum/__tests__/DdRum.test.ts @@ -10,7 +10,7 @@ import { NativeModules } from 'react-native'; import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../SdkVerbosity'; import { BufferSingleton } from '../../sdk/DatadogProvider/Buffer/BufferSingleton'; -import { DdSdk } from '../../sdk/DdSdk'; +import { NativeDdSdk } from '../../sdk/DdSdkInternal'; import { GlobalState } from '../../sdk/GlobalState/GlobalState'; import { ErrorSource } from '../../types'; import { DdRum } from '../DdRum'; @@ -69,13 +69,13 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.startView('key', 'name', context); expect(InternalLog.log).toHaveBeenNthCalledWith( 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdRum.startView).toHaveBeenCalledWith( @@ -122,7 +122,7 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.startView('key', 'name'); await DdRum.stopView('key', context); @@ -130,7 +130,7 @@ describe('DdRum', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 3, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdRum.stopView).toHaveBeenCalledWith( @@ -177,13 +177,13 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.startAction(RumActionType.SCROLL, 'name', context); expect(InternalLog.log).toHaveBeenNthCalledWith( 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdRum.startAction).toHaveBeenCalledWith( @@ -236,7 +236,7 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.startAction(RumActionType.SCROLL, 'name'); await DdRum.stopAction( @@ -248,7 +248,7 @@ describe('DdRum', () => { expect(InternalLog.log).toHaveBeenNthCalledWith( 3, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith( @@ -353,14 +353,14 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.startResource('key', 'method', 'url', context); expect(InternalLog.log).toHaveBeenNthCalledWith( - 2, + 3, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdRum.startResource).toHaveBeenCalledWith( @@ -414,15 +414,15 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.startResource('key', 'method', 'url', {}); await DdRum.stopResource('key', 200, 'other', -1, context); expect(InternalLog.log).toHaveBeenNthCalledWith( - 2, + 3, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdRum.stopResource).toHaveBeenCalledWith( @@ -442,7 +442,7 @@ describe('DdRum', () => { await DdRum.stopResource('key', 200, 'other', -1, context); expect(InternalLog.log).toHaveBeenNthCalledWith( - 2, + 3, expect.anything(), SdkVerbosity.WARN ); @@ -1200,13 +1200,13 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.addAction(RumActionType.SCROLL, 'name', context); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdRum.addAction).toHaveBeenCalledWith( @@ -1222,7 +1222,7 @@ describe('DdRum', () => { await DdRum.addAction(RumActionType.SCROLL, 'name', context); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), SdkVerbosity.WARN ); @@ -1264,7 +1264,7 @@ describe('DdRum', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdRum.addError( 'error', ErrorSource.CUSTOM, @@ -1272,16 +1272,10 @@ describe('DdRum', () => { context ); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Adding RUM Error'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining('Context will be empty'), - SdkVerbosity.ERROR + expect.anything(), + SdkVerbosity.WARN ); expect(NativeModules.DdRum.addError).toHaveBeenCalledWith( @@ -1305,17 +1299,9 @@ describe('DdRum', () => { context ); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('Adding RUM Error'), - SdkVerbosity.DEBUG - ); - expect(InternalLog.log).toHaveBeenNthCalledWith( 2, - expect.stringContaining( - 'The given context is an array, it will be nested' - ), + expect.anything(), SdkVerbosity.WARN ); @@ -1363,7 +1349,7 @@ describe('DdRum', () => { test('does not call the native SDK when startAction has not been called before and using old API', async () => { await DdRum.stopAction({ user: 'me' }, 789); expect(NativeModules.DdRum.stopAction).not.toHaveBeenCalled(); - expect(DdSdk.telemetryDebug).not.toHaveBeenCalled(); + expect(NativeDdSdk.telemetryDebug).not.toHaveBeenCalled(); }); test('calls the native SDK when called with old API', async () => { @@ -1375,7 +1361,7 @@ describe('DdRum', () => { { user: 'me' }, 789 ); - expect(DdSdk.telemetryDebug).toHaveBeenCalledWith( + expect(NativeDdSdk.telemetryDebug).toHaveBeenCalledWith( 'DDdRum.stopAction called with the old signature' ); }); @@ -1389,7 +1375,7 @@ describe('DdRum', () => { {}, 456 ); - expect(DdSdk.telemetryDebug).toHaveBeenCalledWith( + expect(NativeDdSdk.telemetryDebug).toHaveBeenCalledWith( 'DDdRum.stopAction called with the old signature' ); }); diff --git a/packages/core/src/rum/instrumentation/resourceTracking/__tests__/__utils__/XMLHttpRequestMock.ts b/packages/core/src/rum/instrumentation/resourceTracking/__tests__/__utils__/XMLHttpRequestMock.ts index a5725fbf4..7683b63f9 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/__tests__/__utils__/XMLHttpRequestMock.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/__tests__/__utils__/XMLHttpRequestMock.ts @@ -12,7 +12,7 @@ export class XMLHttpRequestMock implements XMLHttpRequest { static readonly DONE = 4; public response: any; - public responseType: XMLHttpRequestResponseType; + public responseType: XMLHttpRequestResponseType = ''; public status: number = 0; public readyState: number = XMLHttpRequestMock.UNSENT; public requestHeaders: Map = new Map(); @@ -20,29 +20,50 @@ export class XMLHttpRequestMock implements XMLHttpRequest { // eslint-disable-next-line no-empty-function constructor() {} - responseText: string; - responseURL: string; - responseXML: Document; - statusText: string; - timeout: number; - upload: XMLHttpRequestUpload; - withCredentials: boolean; + responseText: string = ''; + responseURL: string = ''; + responseXML: Document = {} as Document; + statusText: string = ''; + timeout: number = -1; + upload: XMLHttpRequestUpload = {} as XMLHttpRequestUpload; + withCredentials: boolean = false; getAllResponseHeaders = jest.fn(); overrideMimeType = jest.fn(); - DONE: number; - HEADERS_RECEIVED: number; - LOADING: number; - OPENED: number; - UNSENT: number; + DONE = 4 as const; + HEADERS_RECEIVED = 2 as const; + LOADING = 3 as const; + OPENED = 1 as const; + UNSENT = 0 as const; addEventListener = jest.fn(); removeEventListener = jest.fn(); - onabort: (this: XMLHttpRequest, ev: ProgressEvent) => any; - onerror: (this: XMLHttpRequest, ev: ProgressEvent) => any; - onload: (this: XMLHttpRequest, ev: ProgressEvent) => any; - onloadend: (this: XMLHttpRequest, ev: ProgressEvent) => any; - onloadstart: (this: XMLHttpRequest, ev: ProgressEvent) => any; - onprogress: (this: XMLHttpRequest, ev: ProgressEvent) => any; - ontimeout: (this: XMLHttpRequest, ev: ProgressEvent) => any; + onabort: ( + this: XMLHttpRequest, + ev: ProgressEvent + ) => any = ev => {}; + onerror: ( + this: XMLHttpRequest, + ev: ProgressEvent + ) => any = ev => {}; + onload: ( + this: XMLHttpRequest, + ev: ProgressEvent + ) => any = ev => {}; + onloadend: ( + this: XMLHttpRequest, + ev: ProgressEvent + ) => any = ev => {}; + onloadstart: ( + this: XMLHttpRequest, + ev: ProgressEvent + ) => any = ev => {}; + onprogress: ( + this: XMLHttpRequest, + ev: ProgressEvent + ) => any = ev => {}; + ontimeout: ( + this: XMLHttpRequest, + ev: ProgressEvent + ) => any = ev => {}; dispatchEvent(event: Event): boolean { throw new Error('Method not implemented.'); } @@ -85,14 +106,14 @@ export class XMLHttpRequestMock implements XMLHttpRequest { } setRequestHeader(header: string, value: string): void { - this.requestHeaders[header] = value; + this.requestHeaders.set(header, value); } setResponseHeader(header: string, value: string): void { - this.responseHeaders[header] = value; + this.responseHeaders.set(header, value); } getResponseHeader(header: string): string | null { - return this.responseHeaders[header]; + return this.responseHeaders.get(header) ?? null; } } diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts index a18825771..6599dea08 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts @@ -62,7 +62,7 @@ function randomInt(max: number): number { const flushPromises = () => new Promise(jest.requireActual('timers').setImmediate); -let xhrProxy; +let xhrProxy: any; const hexToDecimal = (hex: string): string => { return BigInt(hex, 16).toString(10); @@ -76,6 +76,9 @@ beforeEach(() => { xhrProxy = new XHRProxy({ xhrType: XMLHttpRequestMock, resourceReporter: new ResourceReporter([]) + } as { + xhrType: typeof XMLHttpRequest; + resourceReporter: ResourceReporter; }); // we need this because with ms precision between Date.now() calls we can get 0, so we advance @@ -236,17 +239,19 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - const spanId = xhr.requestHeaders[PARENT_ID_HEADER_KEY]; + const spanId = xhr.requestHeaders.get(PARENT_ID_HEADER_KEY); expect(spanId).toBeDefined(); expect(spanId).toMatch(/[1-9].+/); - const traceId = xhr.requestHeaders[TRACE_ID_HEADER_KEY]; + const traceId = xhr.requestHeaders.get(TRACE_ID_HEADER_KEY); expect(traceId).toBeDefined(); expect(traceId).toMatch(/[1-9].+/); expect(traceId !== spanId).toBeTruthy(); - expect(xhr.requestHeaders[SAMPLING_PRIORITY_HEADER_KEY]).toBe('1'); - expect(xhr.requestHeaders[ORIGIN_HEADER_KEY]).toBe(ORIGIN_RUM); + expect(xhr.requestHeaders.get(SAMPLING_PRIORITY_HEADER_KEY)).toBe( + '1' + ); + expect(xhr.requestHeaders.get(ORIGIN_HEADER_KEY)).toBe(ORIGIN_RUM); }); it('does not generate spanId and traceId in request headers when no first party hosts are provided', async () => { @@ -267,8 +272,10 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[TRACE_ID_HEADER_KEY]).toBeUndefined(); - expect(xhr.requestHeaders[PARENT_ID_HEADER_KEY]).toBeUndefined(); + expect(xhr.requestHeaders.get(TRACE_ID_HEADER_KEY)).toBeUndefined(); + expect( + xhr.requestHeaders.get(PARENT_ID_HEADER_KEY) + ).toBeUndefined(); }); it('does not generate spanId and traceId in request headers when the url does not match first party hosts', async () => { @@ -298,8 +305,10 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[TRACE_ID_HEADER_KEY]).toBeUndefined(); - expect(xhr.requestHeaders[PARENT_ID_HEADER_KEY]).toBeUndefined(); + expect(xhr.requestHeaders.get(TRACE_ID_HEADER_KEY)).toBeUndefined(); + expect( + xhr.requestHeaders.get(PARENT_ID_HEADER_KEY) + ).toBeUndefined(); }); it('does not crash when provided URL is not a valid one', async () => { @@ -325,8 +334,10 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[TRACE_ID_HEADER_KEY]).toBeUndefined(); - expect(xhr.requestHeaders[PARENT_ID_HEADER_KEY]).toBeUndefined(); + expect(xhr.requestHeaders.get(TRACE_ID_HEADER_KEY)).toBeUndefined(); + expect( + xhr.requestHeaders.get(PARENT_ID_HEADER_KEY) + ).toBeUndefined(); }); it('generates spanId and traceId with 0 sampling priority in request headers when trace is not sampled', async () => { @@ -352,12 +363,16 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[TRACE_ID_HEADER_KEY]).not.toBeUndefined(); expect( - xhr.requestHeaders[PARENT_ID_HEADER_KEY] + xhr.requestHeaders.get(TRACE_ID_HEADER_KEY) ).not.toBeUndefined(); - expect(xhr.requestHeaders[SAMPLING_PRIORITY_HEADER_KEY]).toBe('0'); - expect(xhr.requestHeaders[ORIGIN_HEADER_KEY]).toBe(ORIGIN_RUM); + expect( + xhr.requestHeaders.get(PARENT_ID_HEADER_KEY) + ).not.toBeUndefined(); + expect(xhr.requestHeaders.get(SAMPLING_PRIORITY_HEADER_KEY)).toBe( + '0' + ); + expect(xhr.requestHeaders.get(ORIGIN_HEADER_KEY)).toBe(ORIGIN_RUM); }); it('does not origin as RUM in the request headers when startTracking() + XHR.open() + XHR.send()', async () => { @@ -378,7 +393,7 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[ORIGIN_HEADER_KEY]).toBeUndefined(); + expect(xhr.requestHeaders.get(ORIGIN_HEADER_KEY)).toBeUndefined(); }); it('forces the agent to keep the request generated trace when startTracking() + XHR.open() + XHR.send()', async () => { @@ -404,7 +419,9 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[SAMPLING_PRIORITY_HEADER_KEY]).toBe('1'); + expect(xhr.requestHeaders.get(SAMPLING_PRIORITY_HEADER_KEY)).toBe( + '1' + ); }); it('forces the agent to discard the request generated trace when startTracking when the request is not traced', async () => { @@ -430,7 +447,9 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[SAMPLING_PRIORITY_HEADER_KEY]).toBe('0'); + expect(xhr.requestHeaders.get(SAMPLING_PRIORITY_HEADER_KEY)).toBe( + '0' + ); }); it('adds tracecontext request headers when the host is instrumented with tracecontext and request is sampled', async () => { @@ -460,14 +479,16 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - const contextHeader = xhr.requestHeaders[TRACECONTEXT_HEADER_KEY]; + const contextHeader = xhr.requestHeaders.get( + TRACECONTEXT_HEADER_KEY + ); expect(contextHeader).toMatch( /^00-[0-9a-f]{8}[0]{8}[0-9a-f]{16}-[0-9a-f]{16}-01$/ ); // Parent value of the context header is the 3rd part of it - const parentValue = contextHeader.split('-')[2]; - const stateHeader = xhr.requestHeaders[TRACESTATE_HEADER_KEY]; + const parentValue = contextHeader?.split('-')[2]; + const stateHeader = xhr.requestHeaders.get(TRACESTATE_HEADER_KEY); expect(stateHeader).toBe(`dd=s:1;o:rum;p:${parentValue}`); }); @@ -510,15 +531,19 @@ describe('XHRProxy', () => { /* ================================================================================= * Verify that the trace id in the traceparent header is a 128 bit trace ID (hex). * ================================================================================= */ - const traceparentHeader = - xhr.requestHeaders[TRACECONTEXT_HEADER_KEY]; - const traceparentTraceId = traceparentHeader.split('-')[1]; + const traceparentHeader = xhr.requestHeaders.get( + TRACECONTEXT_HEADER_KEY + ); + const traceparentTraceId = traceparentHeader?.split('-')[1]; expect(traceparentTraceId).toMatch( /^[0-9a-f]{8}[0]{8}[0-9a-f]{16}$/ ); expect( - TracingIdentifierUtils.isWithin128Bits(traceparentTraceId, 16) + TracingIdentifierUtils.isWithin128Bits( + traceparentTraceId as string, + 16 + ) ); /* ========================================================================= @@ -526,18 +551,20 @@ describe('XHRProxy', () => { * ========================================================================= */ // x-datadog-trace-id is a decimal representing the low 64 bits of the 128 bits Trace ID - const xDatadogTraceId = xhr.requestHeaders[TRACE_ID_HEADER_KEY]; + const xDatadogTraceId = xhr.requestHeaders.get(TRACE_ID_HEADER_KEY); - expect(TracingIdentifierUtils.isWithin64Bits(xDatadogTraceId)); + expect( + TracingIdentifierUtils.isWithin64Bits(xDatadogTraceId as string) + ); /* =============================================================== * Verify that the trace id in x-datadog-tags headers is HEX 16. * =============================================================== */ // x-datadog-tags is a HEX 16 contains the high 64 bits of the 128 bits Trace ID - const xDatadogTagsTraceId = xhr.requestHeaders[ - TAGS_HEADER_KEY - ].split('=')[1]; + const xDatadogTagsTraceId = xhr.requestHeaders + ?.get(TAGS_HEADER_KEY) + ?.split('=')[1] as string; expect(xDatadogTagsTraceId).toMatch(/^[a-f0-9]{16}$/); expect( @@ -548,8 +575,8 @@ describe('XHRProxy', () => { * Verify that the trace id in the b3 header is a 128 bit trace ID (hex). * ========================================================================= */ - const b3Header = xhr.requestHeaders[B3_HEADER_KEY]; - const b3TraceId = b3Header.split('-')[0]; + const b3Header = xhr.requestHeaders.get(B3_HEADER_KEY); + const b3TraceId = b3Header?.split('-')[0] as string; expect(b3TraceId).toMatch(/^[0-9a-f]{8}[0]{8}[0-9a-f]{16}$/); expect(TracingIdentifierUtils.isWithin128Bits(b3TraceId, 16)); @@ -558,7 +585,9 @@ describe('XHRProxy', () => { * Verify that the trace id in the X-B3-TraceId header is a 128 bit trace ID (hex). * ================================================================================= */ - const xB3TraceId = xhr.requestHeaders[B3_MULTI_TRACE_ID_HEADER_KEY]; + const xB3TraceId = xhr.requestHeaders.get( + B3_MULTI_TRACE_ID_HEADER_KEY + ) as string; expect(xB3TraceId).toMatch(/^[0-9a-f]{8}[0]{8}[0-9a-f]{16}$/); expect(TracingIdentifierUtils.isWithin128Bits(xB3TraceId, 16)); @@ -601,18 +630,21 @@ describe('XHRProxy', () => { // THEN // x-datadog-trace-id is just the low 64 bits (DECIMAL) - const datadogLowTraceValue = - xhr.requestHeaders[TRACE_ID_HEADER_KEY]; + const datadogLowTraceValue = xhr.requestHeaders.get( + TRACE_ID_HEADER_KEY + ); // We convert the low 64 bits to HEX - const datadogLowTraceValueHex = `${BigInt(datadogLowTraceValue) + const datadogLowTraceValueHex = `${BigInt( + datadogLowTraceValue as string + ) .toString(16) .padStart(16, '0')}`; // The high 64 bits are expressed in x-datadog-tags (HEX) - const datadogHighTraceValueHex = xhr.requestHeaders[ - TAGS_HEADER_KEY - ].split('=')[1]; // High HEX 64 bits + const datadogHighTraceValueHex = xhr.requestHeaders + ?.get(TAGS_HEADER_KEY) + ?.split('=')[1] as string; // High HEX 64 bits // We re-compose the full 128 bit trace-id by joining the strings const datadogTraceValue128BitHex = `${datadogHighTraceValueHex}${datadogLowTraceValueHex}`; @@ -622,18 +654,24 @@ describe('XHRProxy', () => { datadogTraceValue128BitHex ); - const datadogParentValue = xhr.requestHeaders[PARENT_ID_HEADER_KEY]; - const contextHeader = xhr.requestHeaders[TRACECONTEXT_HEADER_KEY]; - const traceContextValue = contextHeader.split('-')[1]; - const parentContextValue = contextHeader.split('-')[2]; - const b3MultiTraceHeader = - xhr.requestHeaders[B3_MULTI_TRACE_ID_HEADER_KEY]; - const b3MultiParentHeader = - xhr.requestHeaders[B3_MULTI_SPAN_ID_HEADER_KEY]; - - const b3Header = xhr.requestHeaders[B3_HEADER_KEY]; - const traceB3Value = b3Header.split('-')[0]; - const parentB3Value = b3Header.split('-')[1]; + const datadogParentValue = xhr.requestHeaders.get( + PARENT_ID_HEADER_KEY + ); + const contextHeader = xhr.requestHeaders.get( + TRACECONTEXT_HEADER_KEY + ); + const traceContextValue = contextHeader?.split('-')[1] as string; + const parentContextValue = contextHeader?.split('-')[2] as string; + const b3MultiTraceHeader = xhr.requestHeaders.get( + B3_MULTI_TRACE_ID_HEADER_KEY + ) as string; + const b3MultiParentHeader = xhr.requestHeaders.get( + B3_MULTI_SPAN_ID_HEADER_KEY + ) as string; + + const b3Header = xhr.requestHeaders.get(B3_HEADER_KEY); + const traceB3Value = b3Header?.split('-')[0] as string; + const parentB3Value = b3Header?.split('-')[1] as string; expect(hexToDecimal(traceContextValue)).toBe( datadogTraceValue128BitDec @@ -676,9 +714,11 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - const traceId = xhr.requestHeaders[B3_MULTI_TRACE_ID_HEADER_KEY]; - const spanId = xhr.requestHeaders[B3_MULTI_SPAN_ID_HEADER_KEY]; - const sampled = xhr.requestHeaders[B3_MULTI_SAMPLED_HEADER_KEY]; + const traceId = xhr.requestHeaders.get( + B3_MULTI_TRACE_ID_HEADER_KEY + ); + const spanId = xhr.requestHeaders.get(B3_MULTI_SPAN_ID_HEADER_KEY); + const sampled = xhr.requestHeaders.get(B3_MULTI_SAMPLED_HEADER_KEY); expect(traceId).toMatch(/^[0-9a-f]{8}[0]{8}[0-9a-f]{16}$/); expect(spanId).toMatch(/^[0-9a-f]{16}$/); expect(sampled).toBe('1'); @@ -711,7 +751,7 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - const headerValue = xhr.requestHeaders[B3_HEADER_KEY]; + const headerValue = xhr.requestHeaders.get(B3_HEADER_KEY); expect(headerValue).toMatch( /^[0-9a-f]{8}[0]{8}[0-9a-f]{16}-[0-9a-f]{16}-1$/ ); @@ -750,23 +790,29 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[B3_HEADER_KEY]).not.toBeUndefined(); + expect(xhr.requestHeaders.get(B3_HEADER_KEY)).not.toBeUndefined(); expect( - xhr.requestHeaders[B3_MULTI_TRACE_ID_HEADER_KEY] + xhr.requestHeaders.get(B3_MULTI_TRACE_ID_HEADER_KEY) ).not.toBeUndefined(); expect( - xhr.requestHeaders[B3_MULTI_SPAN_ID_HEADER_KEY] + xhr.requestHeaders.get(B3_MULTI_SPAN_ID_HEADER_KEY) + ).not.toBeUndefined(); + expect(xhr.requestHeaders.get(B3_MULTI_SAMPLED_HEADER_KEY)).toBe( + '1' + ); + expect( + xhr.requestHeaders.get(TRACECONTEXT_HEADER_KEY) ).not.toBeUndefined(); - expect(xhr.requestHeaders[B3_MULTI_SAMPLED_HEADER_KEY]).toBe('1'); expect( - xhr.requestHeaders[TRACECONTEXT_HEADER_KEY] + xhr.requestHeaders.get(TRACE_ID_HEADER_KEY) ).not.toBeUndefined(); - expect(xhr.requestHeaders[TRACE_ID_HEADER_KEY]).not.toBeUndefined(); expect( - xhr.requestHeaders[PARENT_ID_HEADER_KEY] + xhr.requestHeaders.get(PARENT_ID_HEADER_KEY) ).not.toBeUndefined(); - expect(xhr.requestHeaders[SAMPLING_PRIORITY_HEADER_KEY]).toBe('1'); - expect(xhr.requestHeaders[ORIGIN_HEADER_KEY]).toBe(ORIGIN_RUM); + expect(xhr.requestHeaders.get(SAMPLING_PRIORITY_HEADER_KEY)).toBe( + '1' + ); + expect(xhr.requestHeaders.get(ORIGIN_HEADER_KEY)).toBe(ORIGIN_RUM); }); it('adds rum session id to baggage headers when available', async () => { @@ -804,8 +850,10 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[BAGGAGE_HEADER_KEY]).not.toBeUndefined(); - expect(xhr.requestHeaders[BAGGAGE_HEADER_KEY]).toBe( + expect( + xhr.requestHeaders.get(BAGGAGE_HEADER_KEY) + ).not.toBeUndefined(); + expect(xhr.requestHeaders.get(BAGGAGE_HEADER_KEY)).toBe( 'session.id=TEST-SESSION-ID' ); }); @@ -845,7 +893,7 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[BAGGAGE_HEADER_KEY]).toBeUndefined(); + expect(xhr.requestHeaders.get(BAGGAGE_HEADER_KEY)).toBeUndefined(); }); it('does not add rum session id to baggage headers when propagator type is not datadog or w3c', async () => { @@ -1078,27 +1126,39 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - const timings = - DdNativeRum.stopResource.mock.calls[0][4][ - '_dd.resource_timings' - ]; + const timings = DdNativeRum.stopResource.mock.calls[0][4]; if (Platform.OS === 'ios') { - expect(timings['firstByte']['startTime']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.firstByte.startTime'] + ).toBeGreaterThan(0); } else { - expect(timings['firstByte']['startTime']).toBe(0); + expect( + timings['_dd.resource_timings.firstByte.startTime'] + ).toBe(0); } - expect(timings['firstByte']['duration']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.firstByte.duration'] + ).toBeGreaterThan(0); - expect(timings['download']['startTime']).toBeGreaterThan(0); - expect(timings['download']['duration']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.download.startTime'] + ).toBeGreaterThan(0); + + expect( + timings['_dd.resource_timings.download.duration'] + ).toBeGreaterThan(0); if (Platform.OS === 'ios') { - expect(timings['fetch']['startTime']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.fetch.startTime'] + ).toBeGreaterThan(0); } else { - expect(timings['fetch']['startTime']).toBe(0); + expect(timings['_dd.resource_timings.fetch.startTime']).toBe(0); } - expect(timings['fetch']['duration']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.fetch.duration'] + ).toBeGreaterThan(0); }); it(`M generate resource timings when startTracking() + XHR.open() + XHR.send() + XHR.abort(), platform=${platform}`, async () => { @@ -1126,27 +1186,38 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - const timings = - DdNativeRum.stopResource.mock.calls[0][4][ - '_dd.resource_timings' - ]; + const timings = DdNativeRum.stopResource.mock.calls[0][4]; if (Platform.OS === 'ios') { - expect(timings['firstByte']['startTime']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.firstByte.startTime'] + ).toBeGreaterThan(0); } else { - expect(timings['firstByte']['startTime']).toBe(0); + expect( + timings['_dd.resource_timings.firstByte.startTime'] + ).toBe(0); } - expect(timings['firstByte']['duration']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.firstByte.duration'] + ).toBeGreaterThan(0); - expect(timings['download']['startTime']).toBeGreaterThan(0); - expect(timings['download']['duration']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.download.startTime'] + ).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.download.duration'] + ).toBeGreaterThan(0); if (Platform.OS === 'ios') { - expect(timings['fetch']['startTime']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.fetch.startTime'] + ).toBeGreaterThan(0); } else { - expect(timings['fetch']['startTime']).toBe(0); + expect(timings['_dd.resource_timings.fetch.startTime']).toBe(0); } - expect(timings['fetch']['duration']).toBeGreaterThan(0); + expect( + timings['_dd.resource_timings.fetch.duration'] + ).toBeGreaterThan(0); }); }); @@ -1183,7 +1254,7 @@ describe('XHRProxy', () => { firstPartyHostsRegexMap: firstPartyHostsRegexMapBuilder([]) }); DdRum.registerResourceEventMapper(event => { - event.context['body'] = JSON.parse( + (event.context as any)['body'] = JSON.parse( event.resourceContext?.response ); return event; @@ -1200,9 +1271,7 @@ describe('XHRProxy', () => { // THEN const attributes = DdNativeRum.stopResource.mock.calls[0][4]; - expect(attributes['body']).toEqual({ - body: 'content' - }); + expect(attributes['body.body']).toEqual('content'); }); }); @@ -1453,13 +1522,13 @@ describe('XHRProxy', () => { expect(attributes['_dd.graphql.variables']).toEqual('{}'); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER) ).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_OPERATION_NAME_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_OPERATION_NAME_HEADER) ).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_VARIABLES_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_VARIABLES_HEADER) ).not.toBeDefined(); }); @@ -1491,13 +1560,13 @@ describe('XHRProxy', () => { expect(attributes['_dd.graphql.variables']).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER) ).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_OPERATION_NAME_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_OPERATION_NAME_HEADER) ).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_VARIABLES_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_VARIABLES_HEADER) ).not.toBeDefined(); }); @@ -1530,13 +1599,13 @@ describe('XHRProxy', () => { expect(attributes['_dd.graphql.variables']).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER) ).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_OPERATION_NAME_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_OPERATION_NAME_HEADER) ).not.toBeDefined(); expect( - xhr.requestHeaders[DATADOG_GRAPH_QL_VARIABLES_HEADER] + xhr.requestHeaders.get(DATADOG_GRAPH_QL_VARIABLES_HEADER) ).not.toBeDefined(); }); diff --git a/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx b/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx index 9a20162fd..3dff4e7ce 100644 --- a/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx +++ b/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx @@ -72,7 +72,7 @@ describe('DdSdkNativeBridge', () => { afterEach(() => { jest.resetModules(); jest.resetAllMocks(); - delete global.RN$Bridgeless; + delete (global as any).RN$Bridgeless; }); describe('new architecture implementation', () => { diff --git a/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts b/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts index 3762da1e5..87fd37798 100644 --- a/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts +++ b/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts @@ -5,7 +5,7 @@ */ import { InternalLog } from '../../../../InternalLog'; -import { DdSdk } from '../../../DdSdk'; +import { NativeDdSdk } from '../../../DdSdkInternal'; import { BoundedBuffer } from '../BoundedBuffer'; describe('BoundedBuffer', () => { @@ -126,7 +126,7 @@ describe('BoundedBuffer', () => { await buffer.drain(); expect(callbackWithId).toHaveBeenCalledTimes(1); expect(callbackWithId).toHaveBeenNthCalledWith(1, 'callbackId1'); - expect(DdSdk.telemetryError).toHaveBeenCalledWith( + expect(NativeDdSdk.telemetryError).toHaveBeenCalledWith( 'Could not generate enough random numbers happened 2 times.', '', 'RandomIdGenerationError' @@ -146,7 +146,7 @@ describe('BoundedBuffer', () => { await buffer.drain(); expect(fakeCallback).toHaveBeenCalledTimes(3); - expect(DdSdk.telemetryError).toHaveBeenCalledWith( + expect(NativeDdSdk.telemetryError).toHaveBeenCalledWith( 'Buffer overflow happened 1 times.', '', 'BufferOverflow' @@ -171,7 +171,7 @@ describe('BoundedBuffer', () => { expect(fakeCallback).toHaveBeenCalledTimes(1); expect(callbackReturningId).not.toHaveBeenCalled(); expect(callbackWithId).not.toHaveBeenCalled(); - expect(DdSdk.telemetryError).toHaveBeenCalledWith( + expect(NativeDdSdk.telemetryError).toHaveBeenCalledWith( 'Buffer overflow happened 2 times.', '', 'BufferOverflow' @@ -196,7 +196,7 @@ describe('BoundedBuffer', () => { expect(fakeCallback).toHaveBeenCalledTimes(1); expect(callbackReturningId).toHaveBeenCalledTimes(1); expect(callbackWithId).toHaveBeenCalledTimes(1); - expect(DdSdk.telemetryError).not.toHaveBeenCalled(); + expect(NativeDdSdk.telemetryError).not.toHaveBeenCalled(); }); }); }); diff --git a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx index 8ace4b731..66a3206b2 100644 --- a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx +++ b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx @@ -70,6 +70,7 @@ describe('DatadogProvider', () => { }, "appHangThreshold": undefined, "applicationId": "fakeApplicationId", + "attributeEncoders": [], "batchProcessingLevel": "MEDIUM", "batchSize": "MEDIUM", "bundleLogsWithRum": true, diff --git a/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts b/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts index 780b0a831..93333f7de 100644 --- a/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts +++ b/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts @@ -4,14 +4,14 @@ * Copyright 2016-Present Datadog, Inc. */ -import { DdSdk } from '../../DdSdk'; +import { NativeDdSdk } from '../../DdSdkInternal'; import { EventMapper } from '../EventMapper'; describe('EventMapper', () => { it('returns the original log when the event log mapper crashes', () => { const eventMapper = new EventMapper( (event: object) => { - event['badData'] = 'bad data'; + (event as { badData: string })['badData'] = 'bad data'; throw new Error('crashed'); }, (event: object) => event, @@ -26,7 +26,7 @@ describe('EventMapper', () => { ).toEqual({ someData: 'some data' }); - expect(DdSdk.telemetryDebug).toHaveBeenCalledWith( + expect(NativeDdSdk.telemetryDebug).toHaveBeenCalledWith( 'Error while running the event mapper' ); }); diff --git a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts index 1670e5fe2..2d10d390e 100644 --- a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts +++ b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts @@ -118,6 +118,7 @@ describe('FileBasedConfiguration', () => { "actionNameAttribute": "action-name-attr", "additionalConfiguration": {}, "applicationId": "fake-app-id", + "attributeEncoders": [], "batchProcessingLevel": "MEDIUM", "batchSize": "MEDIUM", "bundleLogsWithRum": true, @@ -180,6 +181,7 @@ describe('FileBasedConfiguration', () => { "actionNameAttribute": undefined, "additionalConfiguration": {}, "applicationId": "fake-app-id", + "attributeEncoders": [], "batchProcessingLevel": "MEDIUM", "batchSize": "MEDIUM", "bundleLogsWithRum": true, diff --git a/packages/core/src/trace/__tests__/DdTrace.test.ts b/packages/core/src/trace/__tests__/DdTrace.test.ts index a1567afef..ba7a87d26 100644 --- a/packages/core/src/trace/__tests__/DdTrace.test.ts +++ b/packages/core/src/trace/__tests__/DdTrace.test.ts @@ -52,13 +52,13 @@ describe('DdTrace', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdTrace.startSpan('operation', context); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); expect(NativeModules.DdTrace.startSpan).toHaveBeenCalledWith( @@ -104,18 +104,17 @@ describe('DdTrace', () => { }); test('uses empty context with error when context is invalid or null', async () => { - const context: any = 123; + const context: any = Symbol('invalid-context'); await DdTrace.startSpan('operation', context); const spanId = await DdTrace.startSpan('operation', {}); await DdTrace.finishSpan(spanId, context); expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, + 2, expect.anything(), - SdkVerbosity.ERROR + SdkVerbosity.WARN ); - expect(NativeModules.DdTrace.finishSpan).toHaveBeenCalledWith( spanId, {}, diff --git a/packages/core/src/utils/__tests__/argsUtils.test.ts b/packages/core/src/utils/__tests__/argsUtils.test.ts deleted file mode 100644 index 1a2f4eacb..000000000 --- a/packages/core/src/utils/__tests__/argsUtils.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -import { InternalLog } from '../../InternalLog'; -import { SdkVerbosity } from '../../SdkVerbosity'; -import { validateContext } from '../argsUtils'; - -jest.mock('../../InternalLog', () => { - return { - InternalLog: { - log: jest.fn() - }, - DATADOG_MESSAGE_PREFIX: 'DATADOG:' - }; -}); - -describe('argsUtils', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('validateContext', () => { - it('returns empty object if context is null', () => { - expect(validateContext(null)).toEqual({}); - expect(validateContext(undefined)).toEqual({}); - }); - - it('returns empty object with error if context is raw type', () => { - expect(validateContext('raw-type')).toEqual({}); - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.anything(), - SdkVerbosity.ERROR - ); - }); - - it('nests array inside of new object if context is an array', () => { - const context = [{ a: 1, b: 2 }, 1, true]; - const validatedContext = validateContext(context); - - expect(InternalLog.log).toHaveBeenNthCalledWith( - 1, - expect.anything(), - SdkVerbosity.WARN - ); - - expect(validatedContext).toEqual({ - context - }); - }); - - it('returns unmodified context if it is a valid object', () => { - const context = { - testA: 1, - testB: {} - }; - const validatedContext = validateContext(context); - - expect(validatedContext).toEqual(context); - }); - }); -}); diff --git a/packages/core/src/utils/__tests__/errorUtils.test.ts b/packages/core/src/utils/__tests__/errorUtils.test.ts index b9941d4b4..c89f7a2b7 100644 --- a/packages/core/src/utils/__tests__/errorUtils.test.ts +++ b/packages/core/src/utils/__tests__/errorUtils.test.ts @@ -4,7 +4,7 @@ * Copyright 2016-Present Datadog, Inc. */ -import { getErrorName } from '../errorUtils'; +import { getErrorName } from '../../sdk/AttributesEncoding/errorUtils'; describe('errorUtils', () => { describe('getErrorName', () => { From e88bc665551959d2a3b8b1ae71ef2337388a34a5 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Fri, 10 Oct 2025 11:36:37 +0200 Subject: [PATCH 083/114] Minor warning fixes in tests --- .../rum/instrumentation/DdRumErrorTracking.test.tsx | 6 +++--- .../requestProxy/XHRProxy/__tests__/XHRProxy.test.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx b/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx index cfa6b9014..cfc8bfbb8 100644 --- a/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx +++ b/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx @@ -137,7 +137,7 @@ it('M intercept and send a RUM event W onGlobalError() {Error object}', async () expect.any(Number), '' ); - expect(DdRum.addError.mock.calls[0][2]).toContain( + expect((DdRum.addError as any).mock.calls[0][2]).toContain( '/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx' ); expect(baseErrorHandlerCalled).toStrictEqual(true); @@ -174,7 +174,7 @@ it('M intercept and send a RUM event W onGlobalError() {CustomError object}', as expect.any(Number), '' ); - expect(DdRum.addError.mock.calls[0][2]).toContain( + expect((DdRum.addError as any).mock.calls[0][2]).toContain( '/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx' ); expect(baseErrorHandlerCalled).toStrictEqual(true); @@ -624,7 +624,7 @@ it('M intercept and send a RUM event W on error() {called from RNErrorHandler}', expect.any(Number), '' ); - expect(DdRum.addError.mock.calls[0][2]).toContain( + expect((DdRum.addError as any).mock.calls[0][2]).toContain( '/packages/core/src/__tests__/rum/instrumentation/DdRumErrorTracking.test.tsx' ); expect(baseErrorHandlerCalled).toStrictEqual(true); diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts index 6599dea08..6e944b6c3 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts @@ -1618,16 +1618,16 @@ describe('XHRProxy', () => { firstPartyHostsRegexMap: firstPartyHostsRegexMapBuilder([]) }); DdRum.registerResourceEventMapper(event => { - if (event.context['_dd.graphql.variables']) { + if ((event.context as any)['_dd.graphql.variables']) { const variables = JSON.parse( - event.context['_dd.graphql.variables'] + (event.context as any)['_dd.graphql.variables'] ); if (variables.password) { variables.password = '***'; } - event.context['_dd.graphql.variables'] = JSON.stringify( - variables - ); + (event.context as any)[ + '_dd.graphql.variables' + ] = JSON.stringify(variables); } return event; From 611ac95387875a3887f416943295c0299a9fc40a Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Mon, 13 Oct 2025 17:50:06 +0200 Subject: [PATCH 084/114] Changed .tsx to .ts + removed left-over comment --- .../{attributesEncoding.tsx => attributesEncoding.ts} | 0 .../{defaultEncoders.tsx => defaultEncoders.ts} | 10 ---------- .../{errorUtils.tsx => errorUtils.ts} | 0 .../sdk/AttributesEncoding/{helpers.tsx => helpers.ts} | 0 .../src/sdk/AttributesEncoding/{types.tsx => types.ts} | 0 .../src/sdk/AttributesEncoding/{utils.tsx => utils.ts} | 0 packages/core/src/sdk/{DdSdk.tsx => DdSdk.ts} | 0 .../src/sdk/{DdSdkInternal.tsx => DdSdkInternal.ts} | 0 8 files changed, 10 deletions(-) rename packages/core/src/sdk/AttributesEncoding/{attributesEncoding.tsx => attributesEncoding.ts} (100%) rename packages/core/src/sdk/AttributesEncoding/{defaultEncoders.tsx => defaultEncoders.ts} (95%) rename packages/core/src/sdk/AttributesEncoding/{errorUtils.tsx => errorUtils.ts} (100%) rename packages/core/src/sdk/AttributesEncoding/{helpers.tsx => helpers.ts} (100%) rename packages/core/src/sdk/AttributesEncoding/{types.tsx => types.ts} (100%) rename packages/core/src/sdk/AttributesEncoding/{utils.tsx => utils.ts} (100%) rename packages/core/src/sdk/{DdSdk.tsx => DdSdk.ts} (100%) rename packages/core/src/sdk/{DdSdkInternal.tsx => DdSdkInternal.ts} (100%) diff --git a/packages/core/src/sdk/AttributesEncoding/attributesEncoding.tsx b/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts similarity index 100% rename from packages/core/src/sdk/AttributesEncoding/attributesEncoding.tsx rename to packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts diff --git a/packages/core/src/sdk/AttributesEncoding/defaultEncoders.tsx b/packages/core/src/sdk/AttributesEncoding/defaultEncoders.ts similarity index 95% rename from packages/core/src/sdk/AttributesEncoding/defaultEncoders.tsx rename to packages/core/src/sdk/AttributesEncoding/defaultEncoders.ts index fdee006f1..420b629ec 100644 --- a/packages/core/src/sdk/AttributesEncoding/defaultEncoders.tsx +++ b/packages/core/src/sdk/AttributesEncoding/defaultEncoders.ts @@ -60,17 +60,7 @@ export const dateEncoder: AttributeEncoder = { check: (v: unknown): v is Date => v instanceof Date, encode: (d: Date) => String(d) }; -/* - } else if ('componentStack' in error) { - stack = String(error.componentStack); - } else if ( - 'sourceURL' in error && - 'line' in error && - 'column' in error - ) { - -*/ /** * Extended Error Encoder. * Serializes name, message, stack, and cause (ES2022+) for Error objects. diff --git a/packages/core/src/sdk/AttributesEncoding/errorUtils.tsx b/packages/core/src/sdk/AttributesEncoding/errorUtils.ts similarity index 100% rename from packages/core/src/sdk/AttributesEncoding/errorUtils.tsx rename to packages/core/src/sdk/AttributesEncoding/errorUtils.ts diff --git a/packages/core/src/sdk/AttributesEncoding/helpers.tsx b/packages/core/src/sdk/AttributesEncoding/helpers.ts similarity index 100% rename from packages/core/src/sdk/AttributesEncoding/helpers.tsx rename to packages/core/src/sdk/AttributesEncoding/helpers.ts diff --git a/packages/core/src/sdk/AttributesEncoding/types.tsx b/packages/core/src/sdk/AttributesEncoding/types.ts similarity index 100% rename from packages/core/src/sdk/AttributesEncoding/types.tsx rename to packages/core/src/sdk/AttributesEncoding/types.ts diff --git a/packages/core/src/sdk/AttributesEncoding/utils.tsx b/packages/core/src/sdk/AttributesEncoding/utils.ts similarity index 100% rename from packages/core/src/sdk/AttributesEncoding/utils.tsx rename to packages/core/src/sdk/AttributesEncoding/utils.ts diff --git a/packages/core/src/sdk/DdSdk.tsx b/packages/core/src/sdk/DdSdk.ts similarity index 100% rename from packages/core/src/sdk/DdSdk.tsx rename to packages/core/src/sdk/DdSdk.ts diff --git a/packages/core/src/sdk/DdSdkInternal.tsx b/packages/core/src/sdk/DdSdkInternal.ts similarity index 100% rename from packages/core/src/sdk/DdSdkInternal.tsx rename to packages/core/src/sdk/DdSdkInternal.ts From 5d564fb0d893775dff6c99c7d485afe9f2e597d0 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 21 Oct 2025 15:56:24 +0200 Subject: [PATCH 085/114] Limit encoded attributes to 128 --- .../__tests__/attributesEncoding.test.ts | 31 ++++++++++ .../AttributesEncoding/attributesEncoding.ts | 8 +-- .../src/sdk/AttributesEncoding/helpers.ts | 62 ++++++++++++++++--- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts b/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts index 9cd2718ff..759aeefd6 100644 --- a/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts +++ b/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts @@ -263,4 +263,35 @@ describe('encodeAttributes', () => { ]) ); }); + + it('drops attributes after reaching the 128 limit and warns once', () => { + // Prepare 200 simple attributes — max=128 + const input: Record = {}; + for (let i = 0; i < 200; i++) { + input[`key${i}`] = i; + } + + const result = encodeAttributes(input); + + // Check that only 128 attributes remain + expect(Object.keys(result)).toHaveLength(128); + + // Check the first ones are preserved + expect(result).toHaveProperty('key0', 0); + expect(result).toHaveProperty('key127', 127); + + // Check later ones were dropped + expect(result).not.toHaveProperty('key128'); + + // Check that a warning was shown at least once + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('Attribute limit') + ); + + // Check there is only one "limit reached" warning (even if multiple attributes were dropped) + const limitWarnings = (warn as jest.Mock).mock.calls.filter(([msg]) => + msg.includes('Attribute limit') + ); + expect(limitWarnings).toHaveLength(1); + }); }); diff --git a/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts b/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts index 0f05d7e11..cd2103f74 100644 --- a/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts +++ b/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts @@ -7,7 +7,7 @@ import { DdSdk } from '../DdSdk'; import { builtInEncoders } from './defaultEncoders'; -import { encodeAttributesInPlace } from './helpers'; +import { encodeAttributesInPlace, type EncodeContext } from './helpers'; import type { Encodable } from './types'; import { isPlainObject, warn } from './utils'; @@ -23,14 +23,14 @@ import { isPlainObject, warn } from './utils'; export function encodeAttributes(input: unknown): Record { const result: Record = {}; const allEncoders = [...DdSdk.attributeEncoders, ...builtInEncoders]; - + const context: EncodeContext = { numOfAttributes: 0 }; if (isPlainObject(input)) { for (const [k, v] of Object.entries(input)) { - encodeAttributesInPlace(v, result, [k], allEncoders); + encodeAttributesInPlace(v, result, [k], allEncoders, context); } } else { // Fallback for primitive values passed as root - encodeAttributesInPlace(input, result, ['context'], allEncoders); + encodeAttributesInPlace(input, result, ['context'], allEncoders, context); warn( 'Warning: attributes root should be an object.\n' + 'Received a primitive/array instead, which will be wrapped under the "context" key.' diff --git a/packages/core/src/sdk/AttributesEncoding/helpers.ts b/packages/core/src/sdk/AttributesEncoding/helpers.ts index 3636be9e1..1fc80dece 100644 --- a/packages/core/src/sdk/AttributesEncoding/helpers.ts +++ b/packages/core/src/sdk/AttributesEncoding/helpers.ts @@ -7,6 +7,13 @@ import type { AttributeEncoder, Encodable } from './types'; import { formatPathForLog, isPlainObject, warn } from './utils'; +const MAX_ATTRIBUTES = 128; + +export interface EncodeContext { + numOfAttributes: number; + limitReachedWarned?: boolean; +} + /** * Recursive in-place encoder: flattens values into `out` dictionary. * Never applies "context", that's only for the root. @@ -15,7 +22,8 @@ export function encodeAttributesInPlace( input: unknown, out: Record, path: string[], - encoders: AttributeEncoder[] + encoders: AttributeEncoder[], + context: EncodeContext = { numOfAttributes: 0 } ): void { const value = applyEncoders(input, encoders); @@ -27,7 +35,7 @@ export function encodeAttributesInPlace( typeof value === 'number' || typeof value === 'boolean' ) { - out[path.join('.')] = value; + addEncodedAttribute(out, path, value, context); return; } @@ -49,7 +57,7 @@ export function encodeAttributesInPlace( if (isPlainObject(v)) { const nested: Record = {}; - encodeAttributesInPlace(v, nested, [], encoders); + encodeAttributesInPlace(v, nested, [], encoders, context); return nested; } @@ -66,16 +74,20 @@ export function encodeAttributesInPlace( return undefined; }; - out[path.join('.')] = value - .map(normalize) - .filter(item => item !== undefined); // drop unsupported + addEncodedAttribute( + out, + path, + value.map(normalize).filter(item => item !== undefined), + context + ); + return; } // Plain object if (isPlainObject(value)) { for (const [k, v] of Object.entries(value)) { - encodeAttributesInPlace(v, out, [...path, k], encoders); + encodeAttributesInPlace(v, out, [...path, k], encoders, context); } return; } @@ -94,7 +106,8 @@ export function encodeAttributesInPlace( */ export function sanitizeForJson( value: unknown, - encoders: AttributeEncoder[] + encoders: AttributeEncoder[], + context: EncodeContext = { numOfAttributes: 0 } ): Encodable { const v = applyEncoders(value, encoders); @@ -102,14 +115,14 @@ export function sanitizeForJson( if (isPlainObject(v)) { const out: Record = {}; for (const [k, val] of Object.entries(v)) { - encodeAttributesInPlace(val, out, [k], encoders); + encodeAttributesInPlace(val, out, [k], encoders, context); } return out; } // If array, sanitize items if (Array.isArray(v)) { - return v.map(item => sanitizeForJson(item, encoders)); + return v.map(item => sanitizeForJson(item, encoders, context)); } return v; @@ -132,3 +145,32 @@ export function applyEncoders( // Not matched by any encoder; leave as-is for the visitor to decide return value as Encodable; } + +function addEncodedAttribute( + out: Record, + path: string[], + value: Encodable, + context: EncodeContext +): void { + if (context.numOfAttributes >= MAX_ATTRIBUTES) { + // Only warn once to avoid log spam + if (!context.limitReachedWarned) { + warn( + `Attribute limit of ${MAX_ATTRIBUTES} reached; further attributes will be dropped.` + ); + context.limitReachedWarned = true; + } + + // Optional: warn for specific dropped attribute (if desired) + warn( + `Dropped attribute at '${formatPathForLog( + path + )}' because limit of ${MAX_ATTRIBUTES} attributes was reached. All further attributes will be dropped.` + ); + + return; + } + + out[path.join('.')] = value; + context.numOfAttributes++; +} From 2a7a47ba89062e936c6faa5d87ad365138bd8629 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 21 Oct 2025 16:07:32 +0200 Subject: [PATCH 086/114] Add tests to ensure attributes are encoded by copy --- .../__tests__/attributesEncoding.test.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts b/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts index 759aeefd6..b679ee363 100644 --- a/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts +++ b/packages/core/src/sdk/AttributesEncoding/__tests__/attributesEncoding.test.ts @@ -130,6 +130,122 @@ describe('encodeAttributes', () => { expect(result).toEqual({ valid: 'ok' }); }); + it('does not modify original object when dropping values', () => { + const input = { valid: 'ok', bad: () => {} }; + const result = encodeAttributes(input); + expect(result).toEqual({ valid: 'ok' }); + expect(input).toHaveProperty('bad'); + }); + + it('does not modify original object when dropping nested invalid values', () => { + const input = { user: { profile: { bad: () => {}, good: 'ok' } } }; + const userBefore = { ...input.user }; + const profileBefore = { ...input.user.profile }; + + const result = encodeAttributes(input); + + // Encoder should flatten and drop invalid function + expect(result).toEqual({ 'user.profile.good': 'ok' }); + + // Verify that the original objects were not mutated or replaced + expect(input.user).toEqual(userBefore); + expect(input.user.profile).toEqual(profileBefore); + }); + + it('does not modify original array inside object', () => { + const arr = [1, 2, () => {}]; + const input = { data: arr }; + const arrBefore = [...arr]; + + const result = encodeAttributes(input); + expect(result).toEqual({ data: [1, 2] }); // dropped the function + expect(input.data).toEqual(arrBefore); // original array untouched + }); + + it('does not modify original nested arrays of objects', () => { + const objA = { val: 1 }; + const objB = { bad: () => {} }; + const objC = { val: 2 }; + + const input = { + matrix: [[objA, objB], [objC]] + }; + + // capture snapshots + const matrixBefore = input.matrix; + const row0Before = input.matrix[0]; + const row1Before = input.matrix[1]; + const objA_before = { ...objA }; + const objB_before = { ...objB }; + const objC_before = { ...objC }; + + const result = encodeAttributes(input); + + expect(result).toEqual({ + matrix: [ + [{ val: 1 }, {}], // objB sanitized + [{ val: 2 }] + ] + }); + + // check original references untouched + expect(input.matrix).toBe(matrixBefore); // same outer array reference + expect(input.matrix[0]).toBe(row0Before); // same row0 reference + expect(input.matrix[1]).toBe(row1Before); // same row1 reference + expect(objA).toEqual(objA_before); // object A unchanged + expect(objB).toEqual(objB_before); // object B unchanged + expect(objC).toEqual(objC_before); // object C unchanged + }); + + it('does not modify original Map when encoding', () => { + const innerMap = new Map([['x', 1]]); + const outerMap = new Map([['inner', innerMap]]); + const input = { outer: outerMap }; + + const snapshot = new Map(outerMap); + const innerSnapshot = new Map(innerMap); + + const result = encodeAttributes(input); + expect(result.outer).toBeInstanceOf(Array); + expect(input.outer).toBe(outerMap); // same reference + expect(Array.from(input.outer.entries())).toEqual( + Array.from(snapshot.entries()) + ); + expect(Array.from(innerMap.entries())).toEqual( + Array.from(innerSnapshot.entries()) + ); + }); + + it('does not modify original object when attribute limit is reached', () => { + const input: Record = {}; + for (let i = 0; i < 200; i++) { + input[`k${i}`] = `v${i}`; + } + const snapshot = { ...input }; + + const result = encodeAttributes(input); + expect(Object.keys(result)).toHaveLength(128); + expect(input).toEqual(snapshot); // original still has 200 keys + }); + + it('does not modify original when sanitizing arrays of objects', () => { + const obj1 = { ok: true }; + const obj2 = { bad: () => {} }; + const input = [obj1, obj2]; + + // Capture pre-encode snapshots manually + const obj1Before = { ...obj1 }; + const obj2Before = { ...obj2 }; + const arrayBefore = [...input]; + + const result = encodeAttributes(input); + + expect(result).toEqual({ context: [{ ok: true }, {}] }); + expect(input).toEqual(arrayBefore); + expect(input[0]).toEqual(obj1Before); + expect(input[1]).toEqual(obj2Before); + }); + it('handles deeply nested objects', () => { const deep = { level1: { level2: { level3: { value: 42 } } } }; const result = encodeAttributes(deep); From 53ee54ed87d55d62599d942980ef680367637747 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 28 Nov 2025 12:35:19 +0100 Subject: [PATCH 087/114] Missing attributes on FileBasedConfiguration and linting --- .../XHRProxy/__tests__/XHRProxy.test.ts | 15 ++++++----- .../AttributesEncoding/attributesEncoding.ts | 11 ++++++-- packages/core/src/sdk/DdSdkInternal.ts | 26 ++++++++++++++++--- .../__tests__/FileBasedConfiguration.test.ts | 1 + 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts index 6e944b6c3..367d48553 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts @@ -931,7 +931,7 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[BAGGAGE_HEADER_KEY]).toBeUndefined(); + expect(xhr.requestHeaders.get(BAGGAGE_HEADER_KEY)).toBeUndefined(); }); it('rum session id does not overwrite existing baggage headers', async () => { @@ -970,14 +970,17 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - expect(xhr.requestHeaders[BAGGAGE_HEADER_KEY]).not.toBeUndefined(); - expect(xhr.requestHeaders[BAGGAGE_HEADER_KEY]).toContain( + expect( + xhr.requestHeaders.get(BAGGAGE_HEADER_KEY) + ).not.toBeUndefined(); + expect(xhr.requestHeaders.get(BAGGAGE_HEADER_KEY)).toContain( 'existing.key=existing-value' ); - const values = xhr.requestHeaders[BAGGAGE_HEADER_KEY].split( - ',' - ).sort(); + const values = xhr.requestHeaders + .get(BAGGAGE_HEADER_KEY) + ?.split(',') + .sort(); expect(values[0]).toBe('existing.key=existing-value'); expect(values[1]).toBe('session.id=TEST-SESSION-ID'); diff --git a/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts b/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts index cd2103f74..aa35d5fca 100644 --- a/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts +++ b/packages/core/src/sdk/AttributesEncoding/attributesEncoding.ts @@ -7,7 +7,8 @@ import { DdSdk } from '../DdSdk'; import { builtInEncoders } from './defaultEncoders'; -import { encodeAttributesInPlace, type EncodeContext } from './helpers'; +import type { EncodeContext } from './helpers'; +import { encodeAttributesInPlace } from './helpers'; import type { Encodable } from './types'; import { isPlainObject, warn } from './utils'; @@ -30,7 +31,13 @@ export function encodeAttributes(input: unknown): Record { } } else { // Fallback for primitive values passed as root - encodeAttributesInPlace(input, result, ['context'], allEncoders, context); + encodeAttributesInPlace( + input, + result, + ['context'], + allEncoders, + context + ); warn( 'Warning: attributes root should be an object.\n' + 'Received a primitive/array instead, which will be wrapped under the "context" key.' diff --git a/packages/core/src/sdk/DdSdkInternal.ts b/packages/core/src/sdk/DdSdkInternal.ts index 7af33d2e3..78df1217a 100644 --- a/packages/core/src/sdk/DdSdkInternal.ts +++ b/packages/core/src/sdk/DdSdkInternal.ts @@ -39,10 +39,6 @@ export class DdSdkWrapper implements DdNativeSdkType { return NativeDdSdk.getConstants(); } - setAttributes(attributes: object): Promise { - return NativeDdSdk.setAttributes(attributes); - } - setUserInfo(user: object): Promise { return NativeDdSdk.setUserInfo(user); } @@ -55,6 +51,28 @@ export class DdSdkWrapper implements DdNativeSdkType { return NativeDdSdk.addUserExtraInfo(extraInfo); } + addAttribute(key: string, value: object): Promise { + return NativeDdSdk.addAttribute(key, value); + } + removeAttribute(key: string): Promise { + return NativeDdSdk.removeAttribute(key); + } + addAttributes(attributes: object): Promise { + return NativeDdSdk.addAttributes(attributes); + } + removeAttributes(keys: string[]): Promise { + return NativeDdSdk.removeAttributes(keys); + } + setAccountInfo(account: object): Promise { + return NativeDdSdk.setAccountInfo(account); + } + clearAccountInfo(): Promise { + return NativeDdSdk.clearAccountInfo(); + } + addAccountExtraInfo(extraInfo: object): Promise { + return NativeDdSdk.addAccountExtraInfo(extraInfo); + } + setTrackingConsent(trackingConsent: string): Promise { return NativeDdSdk.setTrackingConsent(trackingConsent); } diff --git a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts index 2d10d390e..e1fe5e511 100644 --- a/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts +++ b/packages/core/src/sdk/FileBasedConfiguration/__tests__/FileBasedConfiguration.test.ts @@ -27,6 +27,7 @@ describe('FileBasedConfiguration', () => { "actionNameAttribute": "action-name-attr", "additionalConfiguration": {}, "applicationId": "fake-app-id", + "attributeEncoders": [], "batchProcessingLevel": "MEDIUM", "batchSize": "MEDIUM", "bundleLogsWithRum": true, From ae07a87d3c7764952b139a8c09194620e77498b1 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 28 Nov 2025 16:25:22 +0100 Subject: [PATCH 088/114] Add NavigationTrackingOptions to React Navigation tracking --- example/app.json | 2 +- example/src/App.tsx | 34 ++++- .../NestedNavigator/ScreenWithLinks.tsx | 4 +- .../DdRumReactNavigationTracking.test.tsx | 134 +++++++++++++++++- packages/react-navigation/src/index.tsx | 14 +- .../DdRumReactNavigationTracking.tsx | 65 +++++++-- 6 files changed, 234 insertions(+), 19 deletions(-) diff --git a/example/app.json b/example/app.json index f0f0c83d7..70b08eb89 100644 --- a/example/app.json +++ b/example/app.json @@ -1,5 +1,5 @@ { "name": "DdSdkReactNativeExample", "displayName": "DD RN Sample", - "navigation": "react-native-navigation" + "navigation": "react-navigation" } diff --git a/example/src/App.tsx b/example/src/App.tsx index c29982a97..831d68fd6 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -12,13 +12,41 @@ import { Route } from "@react-navigation/native"; import { NestedNavigator } from './screens/NestedNavigator/NestedNavigator'; import { getDatadogConfig, onDatadogInitialization } from './ddUtils'; import { TrackingConsent } from '@datadog/mobile-react-native'; +import { NavigationTrackingOptions, ParamsTrackingPredicate, ViewTrackingPredicate } from '@datadog/mobile-react-navigation/src/rum/instrumentation/DdRumReactNavigationTracking'; const Tab = createBottomTabNavigator(); -const viewPredicate: ViewNamePredicate = function customViewNamePredicate(route: Route, trackedName: string) { +// === Navigation Tracking custom predicates +const viewNamePredicate: ViewNamePredicate = function customViewNamePredicate(route: Route, trackedName: string) { return "Custom RN " + trackedName; } +const viewTrackingPredicate: ViewTrackingPredicate = function customViewTrackingPredicate(route: Route) { + if (route.name === "AlertModal") { + return false; + } + + return true; +} + +const paramsTrackingPredicate: ParamsTrackingPredicate = function customParamsTrackingPredicate(route: Route) { + const filteredParams: any = {}; + if (route.params?.creditCardNumber) { + filteredParams["creditCardNumber"] = "XXXX XXXX XXXX XXXX"; + } + + if (route.params?.username) { + filteredParams["username"] = route.params.username; + } + + return filteredParams; +} + +const navigationTrackingOptions: NavigationTrackingOptions = { + viewNamePredicate, + viewTrackingPredicate, + paramsTrackingPredicate, +} // === Datadog Provider Configuration schemes === // 1.- Direct configuration @@ -39,7 +67,9 @@ export default function App() { return ( { - DdRumReactNavigationTracking.startTrackingViews(navigationRef.current, viewPredicate) + DdRumReactNavigationTracking.startTrackingViews( + navigationRef.current, + navigationTrackingOptions) }}> { const {navigate} = useNavigation() return (<> - {props.links.map(link =>