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 bee3b3565..7cf8e88ae 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 @@ -30,18 +30,19 @@ class DdSessionReplayImplementation( * @param replaySampleRate The sample rate applied for session replay. * @param customEndpoint Custom server url for sending replay data. * @param privacySettings Defines the way visual elements should be masked. - * @param customEndpoint Custom server url for sending replay data. * @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled. * @param enableHeatmaps Enables heatmap identifier computation. + * @param enableCompositionTreeRecording Enables the Composition Tree recording pipeline. */ @SuppressLint("VisibleForTests") - @Suppress("LongParameterList") + @Suppress("LongParameterList", "UNUSED_PARAMETER") fun enable( replaySampleRate: Double, customEndpoint: String, privacySettings: SessionReplayPrivacySettings, startRecordingImmediately: Boolean, enableHeatmaps: Boolean, + enableCompositionTreeRecording: Boolean, promise: Promise ) { val sdkCore = Datadog.getInstance() as FeatureSdkCore diff --git a/packages/react-native-session-replay/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt b/packages/react-native-session-replay/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt index c2c5fe297..a3c2a77de 100644 --- a/packages/react-native-session-replay/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +++ b/packages/react-native-session-replay/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt @@ -30,7 +30,7 @@ class DdSessionReplay( * @param textAndInputPrivacyLevel Defines the way text and input should be masked. * @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled. * @param enableHeatmaps Enables heatmap identifier computation. - * Currently unused on Android; reserved for heatmap support. + * @param enableCompositionTreeRecording Enables the Composition Tree recording pipeline. */ @Suppress("LongParameterList") @ReactMethod @@ -42,6 +42,7 @@ class DdSessionReplay( textAndInputPrivacyLevel: String, startRecordingImmediately: Boolean, enableHeatmaps: Boolean, + enableCompositionTreeRecording: Boolean, promise: Promise ) { implementation.enable( @@ -54,6 +55,7 @@ class DdSessionReplay( ), startRecordingImmediately, enableHeatmaps, + enableCompositionTreeRecording, promise ) } diff --git a/packages/react-native-session-replay/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt b/packages/react-native-session-replay/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt index 436f3289f..171d7ff65 100644 --- a/packages/react-native-session-replay/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +++ b/packages/react-native-session-replay/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt @@ -31,7 +31,7 @@ class DdSessionReplay( * @param textAndInputPrivacyLevel Defines the way text and input should be masked. * @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled. * @param enableHeatmaps Enables heatmap identifier computation. - * Currently unused on Android; reserved for heatmap support. + * @param enableCompositionTreeRecording Enables the Composition Tree recording pipeline. */ @Suppress("LongParameterList") @ReactMethod @@ -43,6 +43,7 @@ class DdSessionReplay( textAndInputPrivacyLevel: String, startRecordingImmediately: Boolean, enableHeatmaps: Boolean, + enableCompositionTreeRecording: Boolean, promise: Promise ) { implementation.enable( @@ -55,6 +56,7 @@ class DdSessionReplay( ), startRecordingImmediately, enableHeatmaps, + enableCompositionTreeRecording, promise ) } diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt index cb3385866..42c645754 100644 --- a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt @@ -137,6 +137,7 @@ internal class DdSessionReplayImplementationTest { SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy), startRecordingImmediately, false, + false, mockPromise ) @@ -172,6 +173,7 @@ internal class DdSessionReplayImplementationTest { ), startRecordingImmediately, false, + false, mockPromise ) @@ -203,6 +205,7 @@ internal class DdSessionReplayImplementationTest { SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy), startRecordingImmediately, enableHeatmaps = true, + enableCompositionTreeRecording = false, mockPromise ) @@ -228,6 +231,7 @@ internal class DdSessionReplayImplementationTest { SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy), startRecordingImmediately, enableHeatmaps = false, + enableCompositionTreeRecording = false, mockPromise ) @@ -253,6 +257,7 @@ internal class DdSessionReplayImplementationTest { SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy), startRecordingImmediately, enableHeatmaps = true, + enableCompositionTreeRecording = false, mockPromise ) @@ -280,6 +285,7 @@ internal class DdSessionReplayImplementationTest { SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy), startRecordingImmediately, enableHeatmaps = false, + enableCompositionTreeRecording = false, mockPromise ) @@ -308,6 +314,7 @@ internal class DdSessionReplayImplementationTest { SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy), startRecordingImmediately, enableHeatmaps = true, + enableCompositionTreeRecording = false, mockPromise ) } diff --git a/packages/react-native-session-replay/ios/Sources/DdSessionReplay.mm b/packages/react-native-session-replay/ios/Sources/DdSessionReplay.mm index c020819e3..616865da8 100644 --- a/packages/react-native-session-replay/ios/Sources/DdSessionReplay.mm +++ b/packages/react-native-session-replay/ios/Sources/DdSessionReplay.mm @@ -24,6 +24,7 @@ @implementation DdSessionReplay withTextAndInputPrivacyLevel:(NSString*)textAndInputPrivacyLevel withStartRecordingImmediately:(BOOL)startRecordingImmediately withEnableHeatmaps:(BOOL)enableHeatmaps + withEnableCompositionTreeRecording:(BOOL)enableCompositionTreeRecording withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) { @@ -34,6 +35,7 @@ @implementation DdSessionReplay textAndInputPrivacyLevel:textAndInputPrivacyLevel startRecordingImmediately:startRecordingImmediately enableHeatmaps:enableHeatmaps + enableCompositionTreeRecording:enableCompositionTreeRecording resolve:resolve reject:reject]; } @@ -76,6 +78,7 @@ - (void)enable:(double)replaySampleRate textAndInputPrivacyLevel:(NSString *)textAndInputPrivacyLevel startRecordingImmediately:(BOOL)startRecordingImmediately enableHeatmaps:(BOOL)enableHeatmaps + enableCompositionTreeRecording:(BOOL)enableCompositionTreeRecording resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self.ddSessionReplayImplementation enableWithReplaySampleRate:replaySampleRate @@ -85,6 +88,7 @@ - (void)enable:(double)replaySampleRate textAndInputPrivacyLevel:textAndInputPrivacyLevel startRecordingImmediately:startRecordingImmediately enableHeatmaps:enableHeatmaps + enableCompositionTreeRecording:enableCompositionTreeRecording resolve:resolve reject:reject]; } diff --git a/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift b/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift index 402c744ce..ef1f70330 100644 --- a/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift +++ b/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift @@ -20,17 +20,41 @@ internal struct SVGData: Codable { public class DdSessionReplayImplementation: NSObject { private lazy var sessionReplay: SessionReplayProtocol = sessionReplayProvider() private let sessionReplayProvider: () -> SessionReplayProtocol - private let uiManager: RCTUIManager - private let fabricWrapper: RCTFabricWrapper + private let additionalNodeRecordersProvider: () -> [SessionReplayNodeRecorder] internal init( sessionReplayProvider: @escaping () -> SessionReplayProtocol, uiManager: RCTUIManager, - fabricWrapper: RCTFabricWrapper + fabricWrapper: RCTFabricWrapper, + additionalNodeRecordersProvider: (() -> [SessionReplayNodeRecorder])? = nil ) { self.sessionReplayProvider = sessionReplayProvider - self.uiManager = uiManager - self.fabricWrapper = fabricWrapper + self.additionalNodeRecordersProvider = additionalNodeRecordersProvider ?? { + var svgMap: [String: SVGData] = [:] + + if let bundle = Bundle.ddSessionReplayResources, + let url = bundle.url(forResource: "assets", withExtension: "json") { + do { + let data = try Data(contentsOf: url) + let decoder = JSONDecoder() + svgMap = try decoder.decode([String: SVGData].self, from: data) + } catch { + consolePrint("Failed to load or decode assets.json: \(error)", .debug) + } + } + + return [ + SvgViewRecorder( + uiManager: uiManager, + fabricWrapper: fabricWrapper, + svgMap: svgMap + ), + RCTTextViewRecorder( + uiManager: uiManager, + fabricWrapper: fabricWrapper + ) + ] + } } @objc @@ -51,6 +75,7 @@ public class DdSessionReplayImplementation: NSObject { textAndInputPrivacyLevel: NSString, startRecordingImmediately: Bool, enableHeatmaps: Bool, + enableCompositionTreeRecording: Bool, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock ) -> Void { @@ -72,30 +97,11 @@ public class DdSessionReplayImplementation: NSObject { sessionReplayConfiguration.featureFlags[.heatmaps] = true } - var svgMap: [String: SVGData] = [:] - - if let bundle = Bundle.ddSessionReplayResources, - let url = bundle.url(forResource: "assets", withExtension: "json") { - do { - let data = try Data(contentsOf: url) - let decoder = JSONDecoder() - svgMap = try decoder.decode([String: SVGData].self, from: data) - } catch { - consolePrint("Failed to load or decode assets.json: \(error)", .debug) - } + if #available(iOS 13.0, tvOS 13.0, *), enableCompositionTreeRecording { + sessionReplayConfiguration.featureFlags[.compositionTreeRecording] = true + } else { + sessionReplayConfiguration.setAdditionalNodeRecorders(additionalNodeRecordersProvider()) } - - sessionReplayConfiguration.setAdditionalNodeRecorders([ - SvgViewRecorder( - uiManager: uiManager, - fabricWrapper: fabricWrapper, - svgMap: svgMap - ), - RCTTextViewRecorder( - uiManager: uiManager, - fabricWrapper: fabricWrapper - ) - ]) sessionReplay.enable(with: sessionReplayConfiguration, in: CoreRegistry.default) diff --git a/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift index 5d26344c0..741f8318a 100644 --- a/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift +++ b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift @@ -71,6 +71,7 @@ internal class DdSessionReplayTests: XCTestCase { textAndInputPrivacyLevel: NSString(string: textAndInputPrivacyLevel), startRecordingImmediately: true, enableHeatmaps: false, + enableCompositionTreeRecording: false, resolve: mockResolve, reject: mockReject) @@ -81,7 +82,8 @@ internal class DdSessionReplayTests: XCTestCase { touchPrivacyLevel: touchPrivacy, textAndInputPrivacyLevel: textAndInputPrivacy, startRecordingImmediately: true, - enableHeatmaps: false + enableHeatmaps: false, + enableCompositionTreeRecording: false )) } @@ -102,6 +104,7 @@ internal class DdSessionReplayTests: XCTestCase { textAndInputPrivacyLevel: "BAD_VALUE", startRecordingImmediately: true, enableHeatmaps: false, + enableCompositionTreeRecording: false, resolve: mockResolve, reject: mockReject) @@ -112,7 +115,8 @@ internal class DdSessionReplayTests: XCTestCase { touchPrivacyLevel: .hide, textAndInputPrivacyLevel: .maskAll, startRecordingImmediately: true, - enableHeatmaps: false + enableHeatmaps: false, + enableCompositionTreeRecording: false )) } @@ -145,6 +149,7 @@ internal class DdSessionReplayTests: XCTestCase { textAndInputPrivacyLevel: NSString(string: textAndInputPrivacyLevel), startRecordingImmediately: true, enableHeatmaps: false, + enableCompositionTreeRecording: false, resolve: mockResolve, reject: mockReject) @@ -155,9 +160,98 @@ internal class DdSessionReplayTests: XCTestCase { touchPrivacyLevel: touchPrivacy, textAndInputPrivacyLevel: textAndInputPrivacy, startRecordingImmediately: true, - enableHeatmaps: false + enableHeatmaps: false, + enableCompositionTreeRecording: false + )) + } + + @available(iOS 13.0, *) + func testEnablesCompositionTreeRecordingFeatureFlag() { + let sessionReplayMock = MockSessionReplay() + + DdSessionReplayImplementation( + sessionReplayProvider: { sessionReplayMock }, + uiManager: MockUIManager(), + fabricWrapper: MockFabricWrapper() + ).enable( + replaySampleRate: 100, + customEndpoint: "", + imagePrivacyLevel: "MASK_ALL", + touchPrivacyLevel: "HIDE", + textAndInputPrivacyLevel: "MASK_ALL", + startRecordingImmediately: true, + enableHeatmaps: false, + enableCompositionTreeRecording: true, + resolve: mockResolve, + reject: mockReject + ) + + XCTAssertEqual(sessionReplayMock.calledMethods.first, .enable( + replaySampleRate: 100, + customEndpoint: nil, + imagePrivacyLevel: .maskAll, + touchPrivacyLevel: .hide, + textAndInputPrivacyLevel: .maskAll, + startRecordingImmediately: true, + enableHeatmaps: false, + enableCompositionTreeRecording: true )) } + + func testRequestsAdditionalNodeRecordersForDefaultRecording() { + var didRequestAdditionalNodeRecorders = false + + DdSessionReplayImplementation( + sessionReplayProvider: { MockSessionReplay() }, + uiManager: MockUIManager(), + fabricWrapper: MockFabricWrapper(), + additionalNodeRecordersProvider: { + didRequestAdditionalNodeRecorders = true + return [] + } + ).enable( + replaySampleRate: 100, + customEndpoint: "", + imagePrivacyLevel: "MASK_ALL", + touchPrivacyLevel: "HIDE", + textAndInputPrivacyLevel: "MASK_ALL", + startRecordingImmediately: true, + enableHeatmaps: false, + enableCompositionTreeRecording: false, + resolve: mockResolve, + reject: mockReject + ) + + XCTAssertTrue(didRequestAdditionalNodeRecorders) + } + + @available(iOS 13.0, *) + func testDoesNotRequestAdditionalNodeRecordersForCompositionTreeRecording() { + var didRequestAdditionalNodeRecorders = false + + DdSessionReplayImplementation( + sessionReplayProvider: { MockSessionReplay() }, + uiManager: MockUIManager(), + fabricWrapper: MockFabricWrapper(), + additionalNodeRecordersProvider: { + didRequestAdditionalNodeRecorders = true + return [] + } + ).enable( + replaySampleRate: 100, + customEndpoint: "", + imagePrivacyLevel: "MASK_ALL", + touchPrivacyLevel: "HIDE", + textAndInputPrivacyLevel: "MASK_ALL", + startRecordingImmediately: true, + enableHeatmaps: false, + enableCompositionTreeRecording: true, + resolve: mockResolve, + reject: mockReject + ) + + XCTAssertFalse(didRequestAdditionalNodeRecorders) + } } private class MockSessionReplay: SessionReplayProtocol { @@ -169,7 +263,8 @@ private class MockSessionReplay: SessionReplayProtocol { touchPrivacyLevel: TouchPrivacyLevel, textAndInputPrivacyLevel: TextAndInputPrivacyLevel, startRecordingImmediately: Bool, - enableHeatmaps: Bool + enableHeatmaps: Bool, + enableCompositionTreeRecording: Bool ) case startRecording case stopRecording @@ -186,7 +281,13 @@ private class MockSessionReplay: SessionReplayProtocol { touchPrivacyLevel: configuration.touchPrivacyLevel, textAndInputPrivacyLevel: configuration.textAndInputPrivacyLevel, startRecordingImmediately: configuration.startRecordingImmediately, - enableHeatmaps: configuration.featureFlags[.heatmaps] ?? false + enableHeatmaps: configuration.featureFlags[.heatmaps] ?? false, + enableCompositionTreeRecording: { + if #available(iOS 13.0, tvOS 13.0, *) { + return configuration.featureFlags[.compositionTreeRecording] ?? false + } + return false + }() ) ) } diff --git a/packages/react-native-session-replay/src/SessionReplay.ts b/packages/react-native-session-replay/src/SessionReplay.ts index 8e9567f8f..fab2a2d29 100644 --- a/packages/react-native-session-replay/src/SessionReplay.ts +++ b/packages/react-native-session-replay/src/SessionReplay.ts @@ -103,6 +103,15 @@ export interface SessionReplayConfiguration { * Default: `false`. */ enableHeatmaps?: boolean; + + /** + * Enables the experimental Composition Tree recording pipeline. + * This pipeline captures rendered content instead of using semantic view + * recorders, so React Native text and SVG recorders do not apply. + * Currently supported on iOS 13+. This option has no effect on Android. + * Default: `false`. + */ + enableCompositionTreeRecording?: boolean; } type InternalBaseSessionReplayConfiguration = { @@ -110,6 +119,7 @@ type InternalBaseSessionReplayConfiguration = { customEndpoint: string; startRecordingImmediately: boolean; enableHeatmaps: boolean; + enableCompositionTreeRecording: boolean; }; type InternalPrivacySessionReplayConfiguration = { @@ -128,7 +138,8 @@ const DEFAULTS: InternalSessionReplayConfiguration = { touchPrivacyLevel: TouchPrivacyLevel.HIDE, textAndInputPrivacyLevel: TextAndInputPrivacyLevel.MASK_ALL, startRecordingImmediately: true, - enableHeatmaps: false + enableHeatmaps: false, + enableCompositionTreeRecording: false }; export class SessionReplayWrapper { @@ -146,7 +157,8 @@ export class SessionReplayWrapper { replaySampleRate, customEndpoint, startRecordingImmediately, - enableHeatmaps + enableHeatmaps, + enableCompositionTreeRecording } = configuration; const baseConfig: InternalBaseSessionReplayConfiguration = { @@ -165,7 +177,11 @@ export class SessionReplayWrapper { enableHeatmaps: enableHeatmaps !== undefined ? enableHeatmaps - : DEFAULTS.enableHeatmaps + : DEFAULTS.enableHeatmaps, + enableCompositionTreeRecording: + enableCompositionTreeRecording !== undefined + ? enableCompositionTreeRecording + : DEFAULTS.enableCompositionTreeRecording }; const privacyConfig: InternalPrivacySessionReplayConfiguration = { @@ -193,7 +209,8 @@ export class SessionReplayWrapper { touchPrivacyLevel, textAndInputPrivacyLevel, startRecordingImmediately, - enableHeatmaps + enableHeatmaps, + enableCompositionTreeRecording } = this.buildConfiguration(configuration); return this.nativeSessionReplay.enable( @@ -203,7 +220,8 @@ export class SessionReplayWrapper { touchPrivacyLevel, textAndInputPrivacyLevel, startRecordingImmediately, - enableHeatmaps + enableHeatmaps, + enableCompositionTreeRecording ); }; 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 1ae0b1fa4..e88a86695 100644 --- a/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts +++ b/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts @@ -37,6 +37,7 @@ describe('SessionReplay', () => { 'HIDE', 'MASK_ALL', true, + false, false ); }); @@ -54,10 +55,28 @@ describe('SessionReplay', () => { 'HIDE', 'MASK_ALL', true, + false, false ); }); + it('forwards composition tree recording configuration to native', () => { + SessionReplay.enable({ + enableCompositionTreeRecording: true + }); + + expect(NativeModules.DdSessionReplay.enable).toHaveBeenCalledWith( + 100, + '', + 'MASK_ALL', + 'HIDE', + 'MASK_ALL', + true, + false, + true + ); + }); + it('calls native session replay with provided configuration { w random privacy levels }', () => { const TIMES = 20; @@ -83,6 +102,7 @@ describe('SessionReplay', () => { touch, textAndInput, true, + false, false ); } @@ -101,6 +121,7 @@ describe('SessionReplay', () => { 'HIDE', 'MASK_ALL', true, + false, false ); }); diff --git a/packages/react-native-session-replay/src/nativeModulesTypes.ts b/packages/react-native-session-replay/src/nativeModulesTypes.ts index 0ca28ac97..07005da8d 100644 --- a/packages/react-native-session-replay/src/nativeModulesTypes.ts +++ b/packages/react-native-session-replay/src/nativeModulesTypes.ts @@ -32,6 +32,8 @@ export interface NativeSessionReplayType extends NativeDdSessionReplay { * @param startRecordingImmediately: Whether the recording should start automatically when the feature is enabled. * When `true`, the recording starts automatically; when `false` it doesn't, and the recording will need * to be started manually. Default: `true`. + * @param enableHeatmaps: Whether heatmap identifier computation is enabled. + * @param enableCompositionTreeRecording: Whether the experimental Composition Tree recording pipeline is enabled. */ enable( replaySampleRate: number, @@ -40,7 +42,8 @@ export interface NativeSessionReplayType extends NativeDdSessionReplay { touchPrivacyLevel: TouchPrivacyLevel, textAndInputPrivacyLevel: TextAndInputPrivacyLevel, startRecordingImmediately: boolean, - enableHeatmaps: boolean + enableHeatmaps: boolean, + enableCompositionTreeRecording: boolean ): Promise; /** diff --git a/packages/react-native-session-replay/src/specs/NativeDdSessionReplay.ts b/packages/react-native-session-replay/src/specs/NativeDdSessionReplay.ts index 30b91499e..22bc3e1c0 100644 --- a/packages/react-native-session-replay/src/specs/NativeDdSessionReplay.ts +++ b/packages/react-native-session-replay/src/specs/NativeDdSessionReplay.ts @@ -24,6 +24,8 @@ export interface Spec extends TurboModule { * @param startRecordingImmediately: Whether the recording should start automatically when the feature is enabled. * When `true`, the recording starts automatically; when `false` it doesn't, * and the recording will need to be started manually. Default: `true`. + * @param enableHeatmaps: Whether heatmap identifier computation is enabled. + * @param enableCompositionTreeRecording: Whether the experimental Composition Tree recording pipeline is enabled. */ enable( replaySampleRate: number, @@ -32,7 +34,8 @@ export interface Spec extends TurboModule { touchPrivacyLevel: string, textAndInputPrivacyLevel: string, startRecordingImmediately: boolean, - enableHeatmaps: boolean + enableHeatmaps: boolean, + enableCompositionTreeRecording: boolean ): Promise; /**