From b7a6e5b37f2a00810bbe17a3aa7b6f636fc13322 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 9 Sep 2025 12:08:35 +0200 Subject: [PATCH 1/2] iOS: Always use SDK default core instance --- .../core/ios/Sources/DatadogSDKWrapper.swift | 137 ++---------------- .../ios/Sources/DdLogsImplementation.swift | 7 +- .../ios/Sources/DdSdkImplementation.swift | 15 +- .../Sources/DdSdkNativeInitialization.swift | 21 ++- packages/core/ios/Sources/DdTelemetry.swift | 49 +++++++ .../ios/Tests/DatadogSdkWrapperTests.swift | 17 ++- packages/core/ios/Tests/DdSdkTests.swift | 58 +++----- .../DdSessionReplayImplementation.swift | 24 +-- .../ios/Tests/DdSessionReplayTests.swift | 6 +- .../Sources/RCTDatadogWebViewTracking.swift | 18 ++- .../DatadogSDKReactNativeWebViewTests.swift | 14 +- 11 files changed, 145 insertions(+), 221 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 842ee5d89..0b9e51573 100644 --- a/packages/core/ios/Sources/DatadogSDKWrapper.swift +++ b/packages/core/ios/Sources/DatadogSDKWrapper.swift @@ -14,7 +14,7 @@ import DatadogWebViewTracking import DatadogInternal import Foundation -public typealias OnCoreInitializedListener = (DatadogCoreProtocol) -> Void +public typealias OnSdkInitializedListener = () -> Void /// Wrapper around the Datadog SDK. Use DatadogSDKWrapper.shared to access the instance. public class DatadogSDKWrapper { @@ -22,25 +22,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 @@ -49,124 +38,22 @@ 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) - } - - self.loggerConfiguration = loggerConfiguration - } - - 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 - }() + Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent) - 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) + for listener in onSdkInitializedListeners { + listener() } - } - 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 - ) + self.loggerConfiguration = loggerConfiguration } // 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..df6abe8c7 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, in: CoreRegistry.default) }, + { Datadog.isInitialized(instanceName: CoreRegistry.defaultInstanceName) } + ) } @objc diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 6d060dc31..51e12a387 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -133,40 +133,41 @@ 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) } - + @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) } @objc public func clearAllData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { - DatadogSDKWrapper.shared.clearAllData() + Datadog.clearAllData(in: CoreRegistry.default) 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 d68d89d06..cbd7f5f4d 100644 --- a/packages/core/ios/Sources/DdSdkNativeInitialization.swift +++ b/packages/core/ios/Sources/DdSdkNativeInitialization.swift @@ -30,12 +30,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 } @@ -78,19 +79,17 @@ public class DdSdkNativeInitialization: NSObject { func enableFeatures(sdkConfiguration: DdSdkConfiguration) { let rumConfig = buildRUMConfiguration(configuration: sdkConfiguration) - DatadogSDKWrapper.shared.enableRUM(with: rumConfig) + RUM.enable(with: rumConfig, in: CoreRegistry.default) let logsConfig = buildLogsConfiguration(configuration: sdkConfiguration) - DatadogSDKWrapper.shared.enableLogs(with: logsConfig) + Logs.enable(with: logsConfig, in: CoreRegistry.default) let traceConfig = buildTraceConfiguration(configuration: sdkConfiguration) - DatadogSDKWrapper.shared.enableTrace(with: traceConfig) + Trace.enable(with: traceConfig, in: CoreRegistry.default) if sdkConfiguration.nativeCrashReportEnabled ?? false { - DatadogSDKWrapper.shared.enableCrashReporting() + CrashReporting.enable(in: CoreRegistry.default) } - - DatadogSDKWrapper.shared.enableWebviewTracking() } 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 e576cb0ae..649283c30 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 @@ -65,37 +66,20 @@ public class DdSessionReplayImplementation: NSObject { RCTTextViewRecorder(uiManager: uiManager, 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 ffb43c82973ac345d6f41e80f65deca4f2e3c8a5 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 9 Sep 2025 15:27:13 +0200 Subject: [PATCH 2/2] Moved WebView legacy implementation to webview package --- packages/codepush/__mocks__/react-native.ts | 3 -- packages/core/__mocks__/react-native.ts | 3 -- .../datadog/reactnative/DatadogSDKWrapper.kt | 15 ------ .../com/datadog/reactnative/DatadogWrapper.kt | 5 -- .../reactnative/DdSdkImplementation.kt | 9 ---- .../kotlin/com/datadog/reactnative/DdSdk.kt | 11 ---- .../kotlin/com/datadog/reactnative/DdSdk.kt | 9 ---- .../core/ios/Sources/DatadogSDKWrapper.swift | 12 ----- packages/core/ios/Sources/DdSdk.mm | 11 ---- .../ios/Sources/DdSdkImplementation.swift | 13 ----- .../Sources/DdSdkNativeInitialization.swift | 1 - packages/core/src/specs/NativeDdSdk.ts | 6 --- .../__mocks__/react-native.ts | 3 -- .../__mocks__/react-native.ts | 5 +- .../DdSdkLegacyWebViewImplementation.kt | 30 +++++++++++ .../webview/DdSdkLegacyWebViewModule.kt | 27 ++++++++++ .../webview/DdSdkReactNativeWebViewPackage.kt | 7 ++- .../webview/DdSdkLegacyWebViewModule.kt | 28 ++++++++++ .../webview/DdSdkReactNativeWebViewPackage.kt | 5 +- .../ios/Sources/DatadogWebViewLegacy.h | 24 +++++++++ .../ios/Sources/DatadogWebViewLegacy.mm | 51 +++++++++++++++++++ .../DatadogWebViewLegacyImplementation.swift | 23 +++++++++ .../ios/Sources/RCTDatadogWebViewManager.mm | 2 +- .../src/ext-specs/NativeDdSdk.ts | 1 - packages/react-native-webview/src/index.tsx | 6 ++- .../src/specs/NativeDdWebViewLegacy.ts | 23 +++++++++ 26 files changed, 221 insertions(+), 112 deletions(-) create mode 100644 packages/react-native-webview/android/src/main/kotlin/com/datadog/reactnative/webview/DdSdkLegacyWebViewImplementation.kt create mode 100644 packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt create mode 100644 packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt create mode 100644 packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.h create mode 100644 packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.mm create mode 100644 packages/react-native-webview/ios/Sources/DatadogWebViewLegacyImplementation.swift create mode 100644 packages/react-native-webview/src/specs/NativeDdWebViewLegacy.ts diff --git a/packages/codepush/__mocks__/react-native.ts b/packages/codepush/__mocks__/react-native.ts index b35df4e31..999a275ff 100644 --- a/packages/codepush/__mocks__/react-native.ts +++ b/packages/codepush/__mocks__/react-native.ts @@ -33,9 +33,6 @@ actualRN.NativeModules.DdSdk = { telemetryError: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - consumeWebviewEvent: jest.fn().mockImplementation( - () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, clearAllData: 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..b10d8f1cf 100644 --- a/packages/core/__mocks__/react-native.ts +++ b/packages/core/__mocks__/react-native.ts @@ -42,9 +42,6 @@ actualRN.NativeModules.DdSdk = { telemetryError: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - consumeWebviewEvent: jest.fn().mockImplementation( - () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, clearAllData: 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 7452c40ef..98cb57c19 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 @@ -87,17 +87,6 @@ internal class DatadogSDKWrapper : DatadogWrapper { 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()) - } - - return field - } - override fun setVerbosity(level: Int) { Datadog.setVerbosity(level) } @@ -188,10 +177,6 @@ internal class DatadogSDKWrapper : DatadogWrapper { telemetryProxy?.error(message, throwable) } - override fun consumeWebviewEvent(message: String) { - webViewProxy?.consumeWebviewEvent(message) - } - override fun isInitialized(): Boolean { return Datadog.isInitialized() } 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..e0a54a575 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 @@ -165,11 +165,6 @@ interface DatadogWrapper { */ fun telemetryError(message: String, throwable: Throwable?) - /** - * Sends Webview events. - */ - fun consumeWebviewEvent(message: String) - /** * Returns whether the SDK is initialized. */ 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 7705483b2..5e17a68a6 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 @@ -169,15 +169,6 @@ class DdSdkImplementation( promise.resolve(null) } - /** - * Sends WebView Events. - * @param message User action. - */ - fun consumeWebviewEvent(message: String, promise: Promise) { - datadog.consumeWebviewEvent(message) - promise.resolve(null) - } - /** * Clears all data that has not already been sent to Datadog servers. */ 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..abedb9598 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 @@ -7,13 +7,11 @@ package com.datadog.reactnative import android.app.Activity -import androidx.annotation.MainThread 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.ReadableMap -import com.facebook.react.modules.core.DeviceEventManagerModule /** The entry point to initialize Datadog's features. */ class DdSdk( @@ -119,15 +117,6 @@ class DdSdk( implementation.telemetryError(message, stack, kind, promise) } - /** - * Sends WebView Events. - * @param message User action. - */ - @ReactMethod - override fun consumeWebviewEvent(message: String, promise: Promise) { - implementation.consumeWebviewEvent(message, promise) - } - /** * Clears all data that has not already been sent to Datadog servers. */ 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..70587ecd1 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 @@ -145,15 +145,6 @@ class DdSdk( implementation.telemetryError(message, stack, kind, promise) } - /** - * Sends WebView Events. - * @param message User action. - */ - @ReactMethod - fun consumeWebviewEvent(message: String, promise: Promise) { - implementation.consumeWebviewEvent(message, promise) - } - /** * Clears all data that has not already been sent to Datadog servers. */ diff --git a/packages/core/ios/Sources/DatadogSDKWrapper.swift b/packages/core/ios/Sources/DatadogSDKWrapper.swift index 0b9e51573..e26dff6d3 100644 --- a/packages/core/ios/Sources/DatadogSDKWrapper.swift +++ b/packages/core/ios/Sources/DatadogSDKWrapper.swift @@ -10,7 +10,6 @@ import DatadogRUM import DatadogLogs import DatadogTrace import DatadogCrashReporting -import DatadogWebViewTracking import DatadogInternal import Foundation @@ -46,17 +45,6 @@ public class DatadogSDKWrapper { self.loggerConfiguration = loggerConfiguration } - - // Webview - private var webviewMessageEmitter: InternalExtension.AbstractMessageEmitter? - - internal func enableWebviewTracking() { - 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/DdSdk.mm b/packages/core/ios/Sources/DdSdk.mm index 663da0a79..20c796b7a 100644 --- a/packages/core/ios/Sources/DdSdk.mm +++ b/packages/core/ios/Sources/DdSdk.mm @@ -81,13 +81,6 @@ + (void)initFromNative { [self telemetryError:message stack:stack kind:kind resolve:resolve reject:reject]; } -RCT_REMAP_METHOD(consumeWebviewEvent, withWebviewMessage:(NSString*)message - withResolver:(RCTPromiseResolveBlock)resolve - withRejecter:(RCTPromiseRejectBlock)reject) -{ - [self consumeWebviewEvent:message resolve:resolve reject:reject]; -} - RCT_REMAP_METHOD(clearAllData, withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) { @@ -128,10 +121,6 @@ - (dispatch_queue_t)methodQueue { return [RNQueue getSharedQueue]; } -- (void)consumeWebviewEvent:(NSString *)message resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { - [self.ddSdkImplementation consumeWebviewEventWithMessage:message resolve:resolve reject:reject]; -} - - (void)initialize:(NSDictionary *)configuration resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self.ddSdkImplementation initializeWithConfiguration:configuration resolve:resolve reject:reject]; } diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 51e12a387..9ad3055dd 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -10,7 +10,6 @@ import DatadogRUM import DatadogLogs import DatadogTrace import DatadogCrashReporting -import DatadogWebViewTracking import DatadogInternal import React @@ -27,7 +26,6 @@ public class DdSdkImplementation: NSObject { let mainDispatchQueue: DispatchQueueType let RUMMonitorProvider: () -> RUMMonitorProtocol let RUMMonitorInternalProvider: () -> RUMMonitorInternalProtocol? - var webviewMessageEmitter: InternalExtension.AbstractMessageEmitter? private let jsLongTaskThresholdInSeconds: TimeInterval = 0.1; @@ -148,17 +146,6 @@ public class DdSdkImplementation: NSObject { 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) -> Void { - 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) - } - - resolve(nil) - } @objc public func clearAllData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { diff --git a/packages/core/ios/Sources/DdSdkNativeInitialization.swift b/packages/core/ios/Sources/DdSdkNativeInitialization.swift index cbd7f5f4d..dd09a24ac 100644 --- a/packages/core/ios/Sources/DdSdkNativeInitialization.swift +++ b/packages/core/ios/Sources/DdSdkNativeInitialization.swift @@ -10,7 +10,6 @@ import DatadogRUM import DatadogLogs import DatadogTrace import DatadogCrashReporting -import DatadogWebViewTracking import DatadogInternal import React diff --git a/packages/core/src/specs/NativeDdSdk.ts b/packages/core/src/specs/NativeDdSdk.ts index 6f1ce82a5..8da659e3c 100644 --- a/packages/core/src/specs/NativeDdSdk.ts +++ b/packages/core/src/specs/NativeDdSdk.ts @@ -81,12 +81,6 @@ export interface Spec extends TurboModule { */ telemetryError(message: string, stack: string, kind: string): Promise; - /** - * Send webview telemetry logs - * @param message event description - */ - consumeWebviewEvent(message: string): Promise; - /** * Clears all data that has not already been sent to Datadog servers */ diff --git a/packages/react-native-apollo-client/__mocks__/react-native.ts b/packages/react-native-apollo-client/__mocks__/react-native.ts index b35df4e31..999a275ff 100644 --- a/packages/react-native-apollo-client/__mocks__/react-native.ts +++ b/packages/react-native-apollo-client/__mocks__/react-native.ts @@ -33,9 +33,6 @@ actualRN.NativeModules.DdSdk = { telemetryError: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, - consumeWebviewEvent: jest.fn().mockImplementation( - () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, clearAllData: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction diff --git a/packages/react-native-webview/__mocks__/react-native.ts b/packages/react-native-webview/__mocks__/react-native.ts index 25a4ea88d..e0c185b5b 100644 --- a/packages/react-native-webview/__mocks__/react-native.ts +++ b/packages/react-native-webview/__mocks__/react-native.ts @@ -12,10 +12,7 @@ const actualRN = require('react-native'); actualRN.NativeModules.DdSdk = { telemetryError: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) - ) as jest.MockedFunction, - consumeWebviewEvent: jest.fn().mockImplementation( - () => new Promise(resolve => resolve()) - ) as jest.MockedFunction + ) as jest.MockedFunction }; module.exports = actualRN; diff --git a/packages/react-native-webview/android/src/main/kotlin/com/datadog/reactnative/webview/DdSdkLegacyWebViewImplementation.kt b/packages/react-native-webview/android/src/main/kotlin/com/datadog/reactnative/webview/DdSdkLegacyWebViewImplementation.kt new file mode 100644 index 000000000..5d99be282 --- /dev/null +++ b/packages/react-native-webview/android/src/main/kotlin/com/datadog/reactnative/webview/DdSdkLegacyWebViewImplementation.kt @@ -0,0 +1,30 @@ +/* +* 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.Datadog +import com.datadog.android.webview.WebViewTracking + +class DdSdkLegacyWebViewImplementation { + private var webViewProxy: WebViewTracking._InternalWebViewProxy? = null + get() { + if (field == null && Datadog.isInitialized()) { + field = WebViewTracking._InternalWebViewProxy( + Datadog.getInstance() + ) + } + + return field + } + + fun consumeWebviewEvent(message: String) { + webViewProxy?.consumeWebviewEvent(message) + } + + companion object { + internal const val NAME = "DdWebViewLegacy" + } +} \ No newline at end of file diff --git a/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt b/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt new file mode 100644 index 000000000..08abd1c91 --- /dev/null +++ b/packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt @@ -0,0 +1,27 @@ +/* +* 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.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactMethod + +class DdSdkLegacyWebViewModule( + reactContext: ReactApplicationContext +) : NativeDdWebViewLegacySpec(reactContext) { + private val implementation = DdSdkLegacyWebViewImplementation() + override fun getName(): String = DdSdkLegacyWebViewImplementation.NAME + + /** + * Sends WebView Events. + * @param message User action. + */ + @ReactMethod + override fun consumeWebviewEvent(message: String, promise: Promise) { + implementation.consumeWebviewEvent(message) + promise.resolve(null) + } +} \ No newline at end of file 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..97232ada3 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 @@ -22,9 +22,12 @@ class DdSdkReactNativeWebViewPackage : TurboReactPackage() { RNCWebViewManager() ) } - + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { - return null + return when (name) { + DdSdkLegacyWebViewImplementation.NAME -> DdSdkLegacyWebViewModule(reactContext) + else -> null + } } override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { diff --git a/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt new file mode 100644 index 000000000..af17a8484 --- /dev/null +++ b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkLegacyWebViewModule.kt @@ -0,0 +1,28 @@ +/* +* 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.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod + +class DdSdkLegacyWebViewModule( + reactContext: ReactApplicationContext +) : ReactContextBaseJavaModule(reactContext) { + private val implementation = DdSdkLegacyWebViewImplementation() + override fun getName(): String = DdSdkLegacyWebViewImplementation.NAME + + /** + * Sends WebView Events. + * @param message User action. + */ + @ReactMethod + fun consumeWebviewEvent(message: String, promise: Promise) { + implementation.consumeWebviewEvent(message) + promise.resolve(null) + } +} \ No newline at end of file diff --git a/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt index 26e0d7831..49e939322 100644 --- a/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt +++ b/packages/react-native-webview/android/src/oldarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt @@ -25,7 +25,10 @@ class DdSdkReactNativeWebViewPackage : TurboReactPackage() { } override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { - return null + return when (name) { + DdSdkLegacyWebViewImplementation.NAME -> DdSdkLegacyWebViewModule(reactContext) + else -> null + } } override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { diff --git a/packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.h b/packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.h new file mode 100644 index 000000000..e68cdeab1 --- /dev/null +++ b/packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.h @@ -0,0 +1,24 @@ +/* + * 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 +#import +#import + +@class DatadogWebViewLegacy; + +#ifdef RCT_NEW_ARCH_ENABLED + +#import +@interface DatadogWebViewLegacy : NSObject + +#else + +#import +@interface DatadogWebViewLegacy : NSObject + +#endif + +@end diff --git a/packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.mm b/packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.mm new file mode 100644 index 000000000..c1db19ce2 --- /dev/null +++ b/packages/react-native-webview/ios/Sources/DatadogWebViewLegacy.mm @@ -0,0 +1,51 @@ +// +// DatadogWebViewLegacy.mm +// Pods +// +// Created by Marco Saia on 08.09.25. +// + +#if __has_include("DatadogSDKReactNativeWebView-Swift.h") +#import +#else +#import +#endif +#import "DatadogWebViewLegacy.h" + +@interface DatadogWebViewLegacy () +@property(nonatomic, strong) DatadogWebViewLegacyImplementation* _Nonnull datadogWebViewLegacyImplementation; +@end + +@implementation DatadogWebViewLegacy + +RCT_EXPORT_MODULE(DdWebViewLegacy) + +RCT_REMAP_METHOD(consumeWebviewEvent, withWebviewMessage:(NSString*)message + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self consumeWebviewEvent:message resolve:resolve reject:reject]; +} + + +- (void)consumeWebviewEvent:(NSString *)message resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.datadogWebViewLegacyImplementation consumeWebviewEventWithMessage:message resolve:resolve reject:reject]; +} + +- (DatadogWebViewLegacyImplementation*)datadogWebViewLegacyImplementation +{ + if (_datadogWebViewLegacyImplementation == nil) { + _datadogWebViewLegacyImplementation = [[DatadogWebViewLegacyImplementation alloc] init]; + } + return _datadogWebViewLegacyImplementation; +} + +#ifdef RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} +#endif + +@end diff --git a/packages/react-native-webview/ios/Sources/DatadogWebViewLegacyImplementation.swift b/packages/react-native-webview/ios/Sources/DatadogWebViewLegacyImplementation.swift new file mode 100644 index 000000000..d8a805370 --- /dev/null +++ b/packages/react-native-webview/ios/Sources/DatadogWebViewLegacyImplementation.swift @@ -0,0 +1,23 @@ +/* + * 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 DatadogWebViewTracking +import DatadogSDKReactNative +import DatadogCore +import DatadogInternal + +@objc public class DatadogWebViewLegacyImplementation: NSObject { + private var messageEmitter: InternalExtension.AbstractMessageEmitter? = { + guard Datadog.isInitialized(instanceName: CoreRegistry.defaultInstanceName) else { + return nil + } + return WebViewTracking._internal.messageEmitter(in: CoreRegistry.default) + }() + + @objc + public func consumeWebviewEvent(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + self.messageEmitter?.send(body: message) + } +} diff --git a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewManager.mm b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewManager.mm index 03932ba9e..30def4621 100644 --- a/packages/react-native-webview/ios/Sources/RCTDatadogWebViewManager.mm +++ b/packages/react-native-webview/ios/Sources/RCTDatadogWebViewManager.mm @@ -16,7 +16,7 @@ #endif @interface RCTDatadogWebViewManager () - @property (nonatomic, strong) NSMutableSet *allowedHosts; +@property (nonatomic, strong) NSMutableSet *allowedHosts; @property (nonatomic, strong) RCTDatadogWebViewTracking* webViewTracking; @end diff --git a/packages/react-native-webview/src/ext-specs/NativeDdSdk.ts b/packages/react-native-webview/src/ext-specs/NativeDdSdk.ts index e31acf242..0ef4c14dd 100644 --- a/packages/react-native-webview/src/ext-specs/NativeDdSdk.ts +++ b/packages/react-native-webview/src/ext-specs/NativeDdSdk.ts @@ -12,7 +12,6 @@ import { TurboModuleRegistry } from 'react-native'; * We don't declare it in a spec file so we don't end up with a duplicate definition of the native module. */ export interface PartialNativeDdSdkSpec extends TurboModule { - consumeWebviewEvent(message: string): Promise; telemetryError(message: string, stack: string, kind: string): Promise; } diff --git a/packages/react-native-webview/src/index.tsx b/packages/react-native-webview/src/index.tsx index 3812e952c..f4470b06d 100644 --- a/packages/react-native-webview/src/index.tsx +++ b/packages/react-native-webview/src/index.tsx @@ -8,7 +8,7 @@ import { WebView as RNWebView } from 'react-native-webview'; import React, { forwardRef, useCallback } from 'react'; import NativeDdLogs from './ext-specs/NativeDdLogs'; -import { NativeDdSdk } from './ext-specs/NativeDdSdk'; +import { NativeDdWebViewLegacy } from './specs/NativeDdWebViewLegacy'; import { NativeDdWebView } from './specs/NativeDdWebView'; import { isNewArchitecture } from './utils/env-utils'; import { @@ -48,7 +48,9 @@ const WebViewComponent = (props: Props, ref: React.Ref>) => { ddMessage.type === 'NATIVE_EVENT' && ddMessage.message != null ) { - NativeDdSdk?.consumeWebviewEvent(ddMessage.message); + NativeDdWebViewLegacy?.consumeWebviewEvent( + ddMessage.message + ); } }; diff --git a/packages/react-native-webview/src/specs/NativeDdWebViewLegacy.ts b/packages/react-native-webview/src/specs/NativeDdWebViewLegacy.ts new file mode 100644 index 000000000..52ac323ba --- /dev/null +++ b/packages/react-native-webview/src/specs/NativeDdWebViewLegacy.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/ban-types */ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + readonly getConstants: () => {}; + + /** + * Send webview telemetry logs + * @param message event description + */ + consumeWebviewEvent(message: string): Promise; +} + +export const NativeDdWebViewLegacy = TurboModuleRegistry.get( + 'DdWebViewLegacy' +);