@datadog/mobile-react-native-session-replay 2.4.4-alpha.0 → 2.5.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/android/build.gradle +11 -1
  2. package/android/consumer-proguard-rules.pro +3 -0
  3. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +32 -27
  4. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +15 -4
  5. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +8 -10
  6. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayPrivacySettings.kt +90 -0
  7. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +14 -0
  8. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +10 -0
  9. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/ReactDrawablesExt.kt +190 -0
  10. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactEditTextMapper.kt +84 -10
  11. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactNativeImageViewMapper.kt +106 -0
  12. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +4 -5
  13. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/resources/ReactDrawableCopier.kt +34 -0
  14. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +10 -23
  15. package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +28 -4
  16. package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +35 -3
  17. package/android/src/{main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt → rn75/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt} +6 -8
  18. package/android/src/rn75/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
  19. package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt +13 -0
  20. package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
  21. package/android/src/rnlegacy/kotlin/com.datadog.reactnative.sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +88 -0
  22. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +59 -50
  23. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +7 -3
  24. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +2 -7
  25. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +2 -7
  26. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +2 -1
  27. package/ios/Sources/DdSessionReplay.mm +46 -4
  28. package/ios/Sources/DdSessionReplayImplementation.swift +83 -11
  29. package/lib/commonjs/SessionReplay.js +78 -10
  30. package/lib/commonjs/SessionReplay.js.map +1 -1
  31. package/lib/commonjs/index.js +18 -0
  32. package/lib/commonjs/index.js.map +1 -1
  33. package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -1
  34. package/lib/module/SessionReplay.js +77 -9
  35. package/lib/module/SessionReplay.js.map +1 -1
  36. package/lib/module/index.js +2 -2
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/module/specs/NativeDdSessionReplay.js.map +1 -1
  39. package/lib/typescript/SessionReplay.d.ts +73 -4
  40. package/lib/typescript/SessionReplay.d.ts.map +1 -1
  41. package/lib/typescript/index.d.ts +2 -2
  42. package/lib/typescript/index.d.ts.map +1 -1
  43. package/lib/typescript/nativeModulesTypes.d.ts +18 -3
  44. package/lib/typescript/nativeModulesTypes.d.ts.map +1 -1
  45. package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -2
  46. package/lib/typescript/specs/NativeDdSessionReplay.d.ts.map +1 -1
  47. package/package.json +2 -1
  48. package/src/SessionReplay.ts +170 -23
  49. package/src/__tests__/SessionReplay.test.ts +94 -8
  50. package/src/index.ts +14 -2
  51. package/src/nativeModulesTypes.ts +27 -4
  52. package/src/specs/NativeDdSessionReplay.ts +21 -3
  53. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +0 -66
@@ -6,41 +6,51 @@
6
6
 
7
7
  package com.datadog.reactnative.sessionreplay.mappers
8
8
 
9
+ import ReactViewBackgroundDrawableUtils
10
+ import android.view.View
9
11
  import com.datadog.android.api.InternalLogger
10
12
  import com.datadog.android.sessionreplay.model.MobileSegment
11
13
  import com.datadog.android.sessionreplay.recorder.MappingContext
14
+ import com.datadog.android.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapper
12
15
  import com.datadog.android.sessionreplay.recorder.mapper.EditTextMapper
13
- import com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper
14
16
  import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback
15
17
  import com.datadog.android.sessionreplay.utils.DefaultColorStringFormatter
16
18
  import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver
17
19
  import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver
18
20
  import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
21
+ import com.datadog.android.sessionreplay.utils.GlobalBounds
19
22
  import com.datadog.reactnative.sessionreplay.NoopTextPropertiesResolver
20
23
  import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver
21
24
  import com.datadog.reactnative.sessionreplay.TextPropertiesResolver
22
25
  import com.datadog.reactnative.sessionreplay.utils.TextViewUtils
23
26
  import com.facebook.react.bridge.ReactContext
24
27
  import com.facebook.react.uimanager.UIManagerModule
28
+ import com.facebook.react.views.image.ReactImageView
25
29
  import com.facebook.react.views.textinput.ReactEditText
26
30
 
27
31
  internal class ReactEditTextMapper(
28
32
  private val reactTextPropertiesResolver: TextPropertiesResolver =
29
33
  NoopTextPropertiesResolver(),
30
34
  private val textViewUtils: TextViewUtils = TextViewUtils(),
31
- ): WireframeMapper<ReactEditText> {
35
+ ) : BaseAsyncBackgroundWireframeMapper<ReactEditText>(
36
+ viewIdentifierResolver = DefaultViewIdentifierResolver,
37
+ colorStringFormatter = DefaultColorStringFormatter,
38
+ viewBoundsResolver = DefaultViewBoundsResolver,
39
+ drawableToColorMapper = DrawableToColorMapper.getDefault(),
40
+ ) {
41
+ private val drawableUtils = ReactViewBackgroundDrawableUtils()
32
42
 
33
43
  private val editTextMapper = EditTextMapper(
34
- viewIdentifierResolver = DefaultViewIdentifierResolver,
35
- colorStringFormatter = DefaultColorStringFormatter,
36
- viewBoundsResolver = DefaultViewBoundsResolver,
37
- drawableToColorMapper = DrawableToColorMapper.getDefault(),
44
+ viewIdentifierResolver = viewIdentifierResolver,
45
+ colorStringFormatter = colorStringFormatter,
46
+ viewBoundsResolver = viewBoundsResolver,
47
+ drawableToColorMapper = drawableToColorMapper,
38
48
  )
39
49
 
40
50
  internal constructor(
41
51
  reactContext: ReactContext,
42
52
  uiManagerModule: UIManagerModule?
43
- ): this(
53
+ ) : this(
44
54
  reactTextPropertiesResolver = if (uiManagerModule == null) {
45
55
  NoopTextPropertiesResolver()
46
56
  } else {
@@ -50,24 +60,88 @@ internal class ReactEditTextMapper(
50
60
  )
51
61
  }
52
62
  )
63
+
53
64
  override fun map(
54
65
  view: ReactEditText,
55
66
  mappingContext: MappingContext,
56
67
  asyncJobStatusCallback: AsyncJobStatusCallback,
57
68
  internalLogger: InternalLogger
58
69
  ): List<MobileSegment.Wireframe> {
59
- val wireframes = editTextMapper.map(
70
+ val backgroundWireframes = mutableListOf<MobileSegment.Wireframe>().apply {
71
+ addAll(
72
+ super.map(
73
+ view,
74
+ mappingContext,
75
+ asyncJobStatusCallback,
76
+ internalLogger
77
+ )
78
+ )
79
+ }
80
+
81
+ backgroundWireframes += editTextMapper.map(
60
82
  view = view,
61
83
  mappingContext = mappingContext,
62
84
  asyncJobStatusCallback = asyncJobStatusCallback,
63
85
  internalLogger = internalLogger
64
- )
86
+ ).filterNot { it is MobileSegment.Wireframe.ImageWireframe }
65
87
 
66
88
  return textViewUtils.mapTextViewToWireframes(
67
- wireframes = wireframes,
89
+ wireframes = backgroundWireframes,
68
90
  view = view,
69
91
  mappingContext = mappingContext,
70
92
  reactTextPropertiesResolver = reactTextPropertiesResolver
71
93
  )
72
94
  }
95
+
96
+ @Suppress("FunctionMaxLength")
97
+ override fun resolveBackgroundAsImageWireframe(
98
+ view: View,
99
+ bounds: GlobalBounds,
100
+ width: Int,
101
+ height: Int,
102
+ mappingContext: MappingContext,
103
+ asyncJobStatusCallback: AsyncJobStatusCallback
104
+ ): MobileSegment.Wireframe? {
105
+ if (view !is ReactImageView) {
106
+ return super.resolveBackgroundAsImageWireframe(
107
+ view,
108
+ bounds,
109
+ width,
110
+ height,
111
+ mappingContext,
112
+ asyncJobStatusCallback
113
+ )
114
+ }
115
+
116
+ val backgroundDrawable = drawableUtils.getReactBackgroundFromDrawable(view.background)
117
+ ?: return null
118
+
119
+ val density = mappingContext.systemInformation.screenDensity
120
+
121
+ val identifier = viewIdentifierResolver.resolveChildUniqueIdentifier(
122
+ view,
123
+ "drawable0"
124
+ ) ?: return null
125
+
126
+ val globalBounds = viewBoundsResolver.resolveViewGlobalBounds(
127
+ view,
128
+ density
129
+ )
130
+
131
+ val (shape, border) = drawableUtils.resolveShapeAndBorder(
132
+ backgroundDrawable,
133
+ view.alpha,
134
+ mappingContext.systemInformation.screenDensity
135
+ )
136
+
137
+ return MobileSegment.Wireframe.ShapeWireframe(
138
+ identifier,
139
+ globalBounds.x,
140
+ globalBounds.y,
141
+ globalBounds.width,
142
+ globalBounds.height,
143
+ border = border,
144
+ shapeStyle = shape
145
+ )
146
+ }
73
147
  }
@@ -0,0 +1,106 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay.mappers
8
+
9
+ import android.graphics.Rect
10
+ import com.datadog.android.api.InternalLogger
11
+ import com.datadog.android.internal.utils.ImageViewUtils
12
+ import com.datadog.android.internal.utils.densityNormalized
13
+ import com.datadog.android.sessionreplay.model.MobileSegment
14
+ import com.datadog.android.sessionreplay.recorder.MappingContext
15
+ import com.datadog.android.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapper
16
+ import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback
17
+ import com.datadog.android.sessionreplay.utils.DefaultColorStringFormatter
18
+ import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver
19
+ import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver
20
+ import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
21
+ import com.datadog.reactnative.sessionreplay.extensions.getScaleTypeDrawable
22
+ import com.datadog.reactnative.sessionreplay.extensions.imageViewScaleType
23
+ import com.datadog.reactnative.sessionreplay.resources.ReactDrawableCopier
24
+ import com.facebook.drawee.drawable.FadeDrawable
25
+ import com.facebook.react.views.image.ReactImageView
26
+
27
+ internal class ReactNativeImageViewMapper: BaseAsyncBackgroundWireframeMapper<ReactImageView>(
28
+ viewIdentifierResolver = DefaultViewIdentifierResolver,
29
+ colorStringFormatter = DefaultColorStringFormatter,
30
+ viewBoundsResolver = DefaultViewBoundsResolver,
31
+ drawableToColorMapper = DrawableToColorMapper.getDefault()
32
+ ) {
33
+ private val drawableCopier = ReactDrawableCopier()
34
+
35
+ override fun map(
36
+ view: ReactImageView,
37
+ mappingContext: MappingContext,
38
+ asyncJobStatusCallback: AsyncJobStatusCallback,
39
+ internalLogger: InternalLogger
40
+ ): List<MobileSegment.Wireframe> {
41
+ val wireframes = mutableListOf<MobileSegment.Wireframe>()
42
+ wireframes.addAll(super.map(view, mappingContext, asyncJobStatusCallback, internalLogger))
43
+
44
+ val drawable = view.drawable?.current ?: return wireframes
45
+
46
+ val parentRect = ImageViewUtils.resolveParentRectAbsPosition(view)
47
+ val scaleType = (drawable as? FadeDrawable)
48
+ ?.getScaleTypeDrawable()
49
+ ?.imageViewScaleType() ?: view.scaleType
50
+ val contentRect = ImageViewUtils.resolveContentRectWithScaling(view, drawable, scaleType)
51
+
52
+ val resources = view.resources
53
+ val density = resources.displayMetrics.density
54
+
55
+ val clipping = if (view.cropToPadding) {
56
+ ImageViewUtils.calculateClipping(parentRect, contentRect, density)
57
+ } else {
58
+ null
59
+ }
60
+
61
+ val contentXPosInDp = contentRect.left.densityNormalized(density).toLong()
62
+ val contentYPosInDp = contentRect.top.densityNormalized(density).toLong()
63
+ val contentWidthPx = contentRect.width()
64
+ val contentHeightPx = contentRect.height()
65
+
66
+ // resolve foreground
67
+ mappingContext.imageWireframeHelper.createImageWireframeByDrawable(
68
+ view = view,
69
+ imagePrivacy = mappingContext.imagePrivacy,
70
+ currentWireframeIndex = wireframes.size,
71
+ x = contentXPosInDp,
72
+ y = contentYPosInDp,
73
+ width = contentWidthPx,
74
+ height = contentHeightPx,
75
+ usePIIPlaceholder = true,
76
+ drawable = drawable,
77
+ drawableCopier = drawableCopier,
78
+ asyncJobStatusCallback = asyncJobStatusCallback,
79
+ clipping = clipping?.toWireframeClip(),
80
+ shapeStyle = null,
81
+ border = null,
82
+ prefix = "drawable",
83
+ customResourceIdCacheKey = generateUUID(view)
84
+ )?.let {
85
+ wireframes.add(it)
86
+ }
87
+
88
+ return wireframes
89
+ }
90
+
91
+ private fun generateUUID(reactImageView: ReactImageView): String {
92
+ val hashCode = System.identityHashCode(reactImageView).toString()
93
+ val drawableType = reactImageView.drawable.current::class.java.name
94
+ return "${drawableType}-${hashCode}"
95
+ }
96
+
97
+ private fun Rect.toWireframeClip(): MobileSegment.WireframeClip {
98
+ return MobileSegment.WireframeClip(
99
+ top = top.toLong(),
100
+ bottom = bottom.toLong(),
101
+ left = left.toLong(),
102
+ right = right.toLong()
103
+ )
104
+ }
105
+ }
106
+
@@ -6,6 +6,7 @@
6
6
 
7
7
  package com.datadog.reactnative.sessionreplay.mappers
8
8
 
9
+ import ReactViewBackgroundDrawableUtils
9
10
  import com.datadog.android.api.InternalLogger
10
11
  import com.datadog.android.sessionreplay.model.MobileSegment
11
12
  import com.datadog.android.sessionreplay.recorder.MappingContext
@@ -18,13 +19,11 @@ import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver.resolve
18
19
  import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver
19
20
  import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
20
21
  import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
21
- import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
22
22
  import com.facebook.react.views.view.ReactViewGroup
23
23
 
24
24
  internal class ReactViewGroupMapper(
25
- private val reactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils =
26
- ReactViewBackgroundDrawableUtils(),
27
- private val drawableUtils: DrawableUtils = DrawableUtils()
25
+ private val drawableUtils: DrawableUtils =
26
+ ReactViewBackgroundDrawableUtils()
28
27
  ) :
29
28
  BaseWireframeMapper<ReactViewGroup>(
30
29
  viewIdentifierResolver = DefaultViewIdentifierResolver,
@@ -49,7 +48,7 @@ internal class ReactViewGroupMapper(
49
48
 
50
49
  val (shapeStyle, border) =
51
50
  if (backgroundDrawable != null) {
52
- reactViewBackgroundDrawableUtils
51
+ drawableUtils
53
52
  .resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
54
53
  } else {
55
54
  null to null
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay.resources
8
+
9
+ import android.content.res.Resources
10
+ import android.graphics.drawable.BitmapDrawable
11
+ import android.graphics.drawable.Drawable
12
+ import com.datadog.android.sessionreplay.recorder.resources.DefaultDrawableCopier
13
+ import com.datadog.android.sessionreplay.recorder.resources.DrawableCopier
14
+ import com.datadog.reactnative.sessionreplay.extensions.tryToExtractBitmap
15
+
16
+ internal class ReactDrawableCopier : DrawableCopier {
17
+ private val defaultCopier = DefaultDrawableCopier()
18
+
19
+ override fun copy(
20
+ originalDrawable: Drawable,
21
+ resources: Resources
22
+ ): Drawable? {
23
+ return if (originalDrawable.constantState != null) {
24
+ defaultCopier.copy(originalDrawable, resources)
25
+ } else {
26
+ originalDrawable.tryToExtractBitmap(resources)?.let { bitmap ->
27
+ BitmapDrawable(resources, bitmap).apply {
28
+ bounds = originalDrawable.bounds
29
+ alpha = originalDrawable.alpha
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
@@ -7,29 +7,16 @@
7
7
  package com.datadog.reactnative.sessionreplay.utils
8
8
 
9
9
  import android.graphics.drawable.Drawable
10
- import android.graphics.drawable.InsetDrawable
11
- import android.graphics.drawable.LayerDrawable
12
- import com.facebook.react.views.view.ReactViewBackgroundDrawable
10
+ import com.datadog.android.sessionreplay.model.MobileSegment
13
11
 
14
- internal class DrawableUtils {
15
- internal fun getReactBackgroundFromDrawable(drawable: Drawable?): ReactViewBackgroundDrawable? {
16
- if (drawable is ReactViewBackgroundDrawable) {
17
- return drawable
18
- }
12
+ internal abstract class DrawableUtils(
13
+ protected val reflectionUtils: ReflectionUtils = ReflectionUtils()
14
+ ) {
15
+ internal abstract fun resolveShapeAndBorder(
16
+ drawable: Drawable,
17
+ opacity: Float,
18
+ pixelDensity: Float
19
+ ): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>
19
20
 
20
- if (drawable is InsetDrawable) {
21
- return getReactBackgroundFromDrawable(drawable.drawable)
22
- }
23
-
24
- if (drawable is LayerDrawable) {
25
- for (layerNumber in 0 until drawable.numberOfLayers) {
26
- val layer = drawable.getDrawable(layerNumber)
27
- if (layer is ReactViewBackgroundDrawable) {
28
- return layer
29
- }
30
- }
31
- }
32
-
33
- return null
34
- }
21
+ internal abstract fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable?
35
22
  }
@@ -20,20 +20,44 @@ class DdSessionReplay(
20
20
  private val implementation = DdSessionReplayImplementation(reactContext)
21
21
 
22
22
  override fun getName(): String = DdSessionReplayImplementation.NAME
23
-
23
+
24
24
  /**
25
25
  * Enable session replay and start recording session.
26
26
  * @param replaySampleRate The sample rate applied for session replay.
27
- * @param defaultPrivacyLevel The privacy level used for replay.
28
27
  * @param customEndpoint Custom server url for sending replay data.
28
+ * @param imagePrivacyLevel Defines the way images should be masked.
29
+ * @param touchPrivacyLevel Defines the way user touches should be masked.
30
+ * @param textAndInputPrivacyLevel Defines the way text and input should be masked.
31
+ * @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
29
32
  */
30
33
  @ReactMethod
31
34
  override fun enable(
32
35
  replaySampleRate: Double,
33
- defaultPrivacyLevel: String,
34
36
  customEndpoint: String,
37
+ imagePrivacyLevel: String,
38
+ touchPrivacyLevel: String,
39
+ textAndInputPrivacyLevel: String,
40
+ startRecordingImmediately: Boolean,
35
41
  promise: Promise
36
42
  ) {
37
- implementation.enable(replaySampleRate, defaultPrivacyLevel, customEndpoint, promise)
43
+ implementation.enable(
44
+ replaySampleRate,
45
+ customEndpoint,
46
+ SessionReplayPrivacySettings(
47
+ imagePrivacyLevel = imagePrivacyLevel,
48
+ touchPrivacyLevel = touchPrivacyLevel,
49
+ textAndInputPrivacyLevel = textAndInputPrivacyLevel
50
+ ),
51
+ startRecordingImmediately,
52
+ promise
53
+ )
54
+ }
55
+
56
+ override fun startRecording(promise: Promise) {
57
+ implementation.startRecording(promise)
58
+ }
59
+
60
+ override fun stopRecording(promise: Promise) {
61
+ implementation.stopRecording(promise)
38
62
  }
39
63
  }
@@ -25,16 +25,48 @@ class DdSessionReplay(
25
25
  /**
26
26
  * Enable session replay and start recording session.
27
27
  * @param replaySampleRate The sample rate applied for session replay.
28
- * @param defaultPrivacyLevel The privacy level used for replay.
29
28
  * @param customEndpoint Custom server url for sending replay data.
29
+ * @param imagePrivacyLevel Defines the way images should be masked.
30
+ * @param touchPrivacyLevel Defines the way user touches should be masked.
31
+ * @param textAndInputPrivacyLevel Defines the way text and input should be masked.
32
+ * @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
30
33
  */
31
34
  @ReactMethod
32
35
  fun enable(
33
36
  replaySampleRate: Double,
34
- defaultPrivacyLevel: String,
35
37
  customEndpoint: String,
38
+ imagePrivacyLevel: String,
39
+ touchPrivacyLevel: String,
40
+ textAndInputPrivacyLevel: String,
41
+ startRecordingImmediately: Boolean,
36
42
  promise: Promise
37
43
  ) {
38
- implementation.enable(replaySampleRate, defaultPrivacyLevel, customEndpoint, promise)
44
+ implementation.enable(
45
+ replaySampleRate,
46
+ customEndpoint,
47
+ SessionReplayPrivacySettings(
48
+ imagePrivacyLevel = imagePrivacyLevel,
49
+ touchPrivacyLevel = touchPrivacyLevel,
50
+ textAndInputPrivacyLevel = textAndInputPrivacyLevel
51
+ ),
52
+ startRecordingImmediately,
53
+ promise
54
+ )
55
+ }
56
+
57
+ /**
58
+ * Manually start recording the current session.
59
+ */
60
+ @ReactMethod
61
+ fun startRecording(promise: Promise) {
62
+ implementation.startRecording(promise)
63
+ }
64
+
65
+ /**
66
+ * Manually stop recording the current session.
67
+ */
68
+ @ReactMethod
69
+ fun stopRecording(promise: Promise) {
70
+ implementation.stopRecording(promise)
39
71
  }
40
72
  }
@@ -3,13 +3,11 @@
3
3
  * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
4
  * Copyright 2016-Present Datadog, Inc.
5
5
  */
6
-
7
6
  package com.datadog.reactnative.sessionreplay.extensions
8
7
 
9
- internal fun Long.convertToDensityNormalized(density: Float): Long {
10
- return if (density == 0f) {
11
- this
12
- } else {
13
- (this / density).toLong()
14
- }
15
- }
8
+ import com.facebook.react.uimanager.LengthPercentage
9
+
10
+ internal fun LengthPercentage?.getRadius(width: Float, height: Float) = this
11
+ ?.resolve(width, height)
12
+ ?.let { (it.horizontal + it.vertical) / 2f }
13
+ ?: 0f
@@ -0,0 +1,101 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+ import android.graphics.drawable.Drawable
7
+ import android.graphics.drawable.InsetDrawable
8
+ import android.graphics.drawable.LayerDrawable
9
+ import com.datadog.android.internal.utils.densityNormalized
10
+ import com.datadog.android.sessionreplay.model.MobileSegment
11
+ import com.datadog.reactnative.sessionreplay.extensions.getRadius
12
+ import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
13
+ import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
14
+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
15
+ import com.facebook.react.uimanager.Spacing
16
+ import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable
17
+
18
+ internal class ReactViewBackgroundDrawableUtils : DrawableUtils() {
19
+ @OptIn(UnstableReactNativeAPI::class)
20
+ override fun resolveShapeAndBorder(
21
+ drawable: Drawable,
22
+ opacity: Float,
23
+ pixelDensity: Float
24
+ ): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?> {
25
+ if (drawable !is CSSBackgroundDrawable) {
26
+ return null to null
27
+ }
28
+
29
+ val borderProps = resolveBorder(drawable, pixelDensity)
30
+ val backgroundColor = getBackgroundColor(drawable)
31
+ val colorHexString = if (backgroundColor != null) {
32
+ formatAsRgba(backgroundColor)
33
+ } else {
34
+ return null to borderProps
35
+ }
36
+
37
+ return MobileSegment.ShapeStyle(
38
+ colorHexString,
39
+ opacity,
40
+ getBorderRadius(drawable)
41
+ ) to borderProps
42
+ }
43
+
44
+ @OptIn(UnstableReactNativeAPI::class)
45
+ override fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable? {
46
+ if (drawable is CSSBackgroundDrawable) {
47
+ return drawable
48
+ }
49
+
50
+ if (drawable is InsetDrawable) {
51
+ return getReactBackgroundFromDrawable(drawable.drawable)
52
+ }
53
+
54
+ if (drawable is LayerDrawable) {
55
+ for (layerNumber in 0 until drawable.numberOfLayers) {
56
+ val layer = drawable.getDrawable(layerNumber)
57
+ if (layer is CSSBackgroundDrawable) {
58
+ return layer
59
+ }
60
+ }
61
+ }
62
+
63
+ return null
64
+ }
65
+
66
+ @OptIn(UnstableReactNativeAPI::class)
67
+ private fun getBorderRadius(drawable: CSSBackgroundDrawable): Float {
68
+ val width = drawable.intrinsicWidth.toFloat()
69
+ val height = drawable.intrinsicHeight.toFloat()
70
+ return drawable.borderRadius.uniform?.getRadius(width, height) ?: 0f
71
+ }
72
+
73
+ @OptIn(UnstableReactNativeAPI::class)
74
+ private fun getBackgroundColor(
75
+ backgroundDrawable: CSSBackgroundDrawable
76
+ ): Int? {
77
+ return reflectionUtils.getDeclaredField(
78
+ backgroundDrawable,
79
+ COLOR_FIELD_NAME
80
+ ) as Int?
81
+ }
82
+
83
+ @OptIn(UnstableReactNativeAPI::class)
84
+ private fun resolveBorder(
85
+ backgroundDrawable: CSSBackgroundDrawable,
86
+ pixelDensity: Float
87
+ ): MobileSegment.ShapeBorder {
88
+ val borderWidth =
89
+ backgroundDrawable.fullBorderWidth.toLong().densityNormalized(pixelDensity)
90
+ val borderColor = formatAsRgba(backgroundDrawable.getBorderColor(Spacing.ALL))
91
+
92
+ return MobileSegment.ShapeBorder(
93
+ color = borderColor,
94
+ width = borderWidth
95
+ )
96
+ }
97
+
98
+ private companion object {
99
+ private const val COLOR_FIELD_NAME = "mColor"
100
+ }
101
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+ package com.datadog.reactnative.sessionreplay.extensions
7
+
8
+ import com.facebook.react.uimanager.LengthPercentage
9
+
10
+ internal fun LengthPercentage?.getRadius(width: Float, height: Float) = this
11
+ ?.resolve(width, height)
12
+ ?.let { (it.horizontal + it.vertical) / 2f }
13
+ ?: 0f