@datadog/mobile-react-native-session-replay 2.0.2-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 (77) hide show
  1. package/DatadogSDKReactNativeSessionReplay.podspec +41 -0
  2. package/README.md +3 -0
  3. package/android/build.gradle +239 -0
  4. package/android/detekt.yml +572 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +11 -0
  7. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DatadogSDKReactNativeSessionReplayPackage.kt +46 -0
  8. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +57 -0
  9. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/NoopTextPropertiesResolver.kt +22 -0
  10. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +77 -0
  11. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +196 -0
  12. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +24 -0
  13. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +22 -0
  14. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ShadowNodeWrapper.kt +70 -0
  15. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/TextPropertiesResolver.kt +20 -0
  16. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt +15 -0
  17. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactMaskInputTextMapper.kt +54 -0
  18. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactMaskTextMapper.kt +55 -0
  19. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt +54 -0
  20. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +58 -0
  21. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ColorUtils.kt +22 -0
  22. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +35 -0
  23. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +66 -0
  24. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReflectionUtils.kt +30 -0
  25. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtils.kt +40 -0
  26. package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +33 -0
  27. package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +34 -0
  28. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +105 -0
  29. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +127 -0
  30. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +271 -0
  31. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +131 -0
  32. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/ColorUtilsTest.kt +42 -0
  33. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +101 -0
  34. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtilsTest.kt +109 -0
  35. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt +69 -0
  36. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/MapExt.kt +29 -0
  37. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/ReflectUtils.kt +266 -0
  38. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/BaseConfigurator.kt +24 -0
  39. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ForgeConfigurator.kt +24 -0
  40. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/TextWireframeForgeryFactory.kt +64 -0
  41. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/Throwable.kt +31 -0
  42. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ThrowableForgeryFactory.kt +21 -0
  43. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/WireframeClipForgeryFactory.kt +25 -0
  44. package/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker +1 -0
  45. package/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.pbxproj +272 -0
  46. package/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  47. package/ios/Sources/DatadogSDKReactNativeSessionReplay.h +8 -0
  48. package/ios/Sources/DdSessionReplay.h +24 -0
  49. package/ios/Sources/DdSessionReplay.mm +53 -0
  50. package/ios/Sources/DdSessionReplayImplementation.swift +70 -0
  51. package/ios/Sources/RCTTextViewRecorder.swift +157 -0
  52. package/lib/commonjs/SessionReplay.js +66 -0
  53. package/lib/commonjs/SessionReplay.js.map +1 -0
  54. package/lib/commonjs/index.js +26 -0
  55. package/lib/commonjs/index.js.map +1 -0
  56. package/lib/commonjs/nativeModulesTypes.js +2 -0
  57. package/lib/commonjs/nativeModulesTypes.js.map +1 -0
  58. package/lib/commonjs/specs/NativeDdSessionReplay.js +20 -0
  59. package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -0
  60. package/lib/module/SessionReplay.js +53 -0
  61. package/lib/module/SessionReplay.js.map +1 -0
  62. package/lib/module/index.js +8 -0
  63. package/lib/module/index.js.map +1 -0
  64. package/lib/module/nativeModulesTypes.js +2 -0
  65. package/lib/module/nativeModulesTypes.js.map +1 -0
  66. package/lib/module/specs/NativeDdSessionReplay.js +14 -0
  67. package/lib/module/specs/NativeDdSessionReplay.js.map +1 -0
  68. package/lib/typescript/SessionReplay.d.ts +34 -0
  69. package/lib/typescript/index.d.ts +2 -0
  70. package/lib/typescript/nativeModulesTypes.d.ts +18 -0
  71. package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -0
  72. package/package.json +90 -0
  73. package/src/SessionReplay.ts +84 -0
  74. package/src/__tests__/SessionReplay.test.ts +49 -0
  75. package/src/index.ts +13 -0
  76. package/src/nativeModulesTypes.ts +29 -0
  77. package/src/specs/NativeDdSessionReplay.ts +28 -0
@@ -0,0 +1,22 @@
1
+ /*
2
+ *
3
+ * * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
4
+ * * This product includes software developed at Datadog (https://www.datadoghq.com/).
5
+ * * Copyright 2016-Present Datadog, Inc.
6
+ *
7
+ */
8
+
9
+ package com.datadog.reactnative.sessionreplay
10
+
11
+ import android.widget.TextView
12
+ import com.datadog.android.sessionreplay.model.MobileSegment
13
+
14
+ internal class NoopTextPropertiesResolver: TextPropertiesResolver {
15
+ override fun addReactNativeProperties(
16
+ originalWireframe: MobileSegment.Wireframe.TextWireframe,
17
+ view: TextView,
18
+ pixelDensity: Float
19
+ ): MobileSegment.Wireframe.TextWireframe {
20
+ return originalWireframe
21
+ }
22
+ }
@@ -0,0 +1,77 @@
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
8
+
9
+ import android.view.View
10
+ import androidx.annotation.VisibleForTesting
11
+ import com.datadog.android.api.InternalLogger
12
+ import com.datadog.android.sessionreplay.ExtensionSupport
13
+ import com.datadog.android.sessionreplay.SessionReplayPrivacy
14
+ import com.datadog.android.sessionreplay.internal.recorder.OptionSelectorDetector
15
+ import com.datadog.android.sessionreplay.internal.recorder.mapper.WireframeMapper
16
+ import com.datadog.reactnative.sessionreplay.mappers.ReactMaskInputTextMapper
17
+ import com.datadog.reactnative.sessionreplay.mappers.ReactMaskTextMapper
18
+ import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper
19
+ import com.datadog.reactnative.sessionreplay.mappers.ReactViewGroupMapper
20
+ import com.facebook.react.bridge.ReactContext
21
+ import com.facebook.react.uimanager.UIManagerModule
22
+ import com.facebook.react.views.text.ReactTextView
23
+ import com.facebook.react.views.textinput.ReactEditText
24
+ import com.facebook.react.views.view.ReactViewGroup
25
+
26
+ internal class ReactNativeSessionReplayExtensionSupport(
27
+ private val reactContext: ReactContext,
28
+ private val logger: InternalLogger
29
+ ) : ExtensionSupport {
30
+
31
+ override fun getCustomViewMappers(): Map<SessionReplayPrivacy, Map<Class<*>, WireframeMapper<View, *>>> {
32
+ val uiManagerModule = getUiManagerModule()
33
+
34
+ return mapOf(
35
+ SessionReplayPrivacy.ALLOW to mapOf(
36
+ ReactViewGroup::class.java to ReactViewGroupMapper(),
37
+ ReactTextView::class.java to ReactTextMapper(reactContext, uiManagerModule),
38
+ ReactEditText::class.java to ReactTextMapper(reactContext, uiManagerModule)
39
+ ),
40
+ SessionReplayPrivacy.MASK to mapOf(
41
+ ReactViewGroup::class.java to ReactViewGroupMapper(),
42
+ ReactTextView::class.java to ReactMaskTextMapper(reactContext, uiManagerModule),
43
+ ReactEditText::class.java to ReactMaskTextMapper(reactContext, uiManagerModule)
44
+ ),
45
+ SessionReplayPrivacy.MASK_USER_INPUT to mapOf(
46
+ ReactViewGroup::class.java to ReactViewGroupMapper(),
47
+ ReactTextView::class.java to ReactMaskInputTextMapper(reactContext, uiManagerModule),
48
+ ReactEditText::class.java to ReactMaskInputTextMapper(reactContext, uiManagerModule)
49
+ )
50
+ ).mapValues {
51
+ it.value as Map<Class<*>, WireframeMapper<View, *>>
52
+ }
53
+ }
54
+
55
+ override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
56
+ return listOf()
57
+ }
58
+
59
+ @VisibleForTesting
60
+ internal fun getUiManagerModule(): UIManagerModule? {
61
+ return try {
62
+ reactContext.getNativeModule(UIManagerModule::class.java)
63
+ } catch (e: IllegalStateException) {
64
+ logger.log(
65
+ level = InternalLogger.Level.WARN,
66
+ targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY),
67
+ messageBuilder = { RESOLVE_UIMANAGERMODULE_ERROR },
68
+ throwable = e
69
+ )
70
+ return null
71
+ }
72
+ }
73
+
74
+ internal companion object {
75
+ internal const val RESOLVE_UIMANAGERMODULE_ERROR = "Unable to resolve UIManagerModule"
76
+ }
77
+ }
@@ -0,0 +1,196 @@
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
8
+
9
+ import android.view.Gravity
10
+ import android.widget.TextView
11
+ import androidx.annotation.VisibleForTesting
12
+ import com.datadog.android.sessionreplay.model.MobileSegment
13
+ import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
14
+ import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
15
+ import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
16
+ import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
17
+ import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
18
+ import com.facebook.react.bridge.ReactContext
19
+ import com.facebook.react.uimanager.UIManagerModule
20
+ import com.facebook.react.views.text.TextAttributes
21
+ import com.facebook.react.views.view.ReactViewBackgroundDrawable
22
+ import java.util.Locale
23
+
24
+ internal class ReactTextPropertiesResolver(
25
+ private val reactContext: ReactContext,
26
+ private val uiManagerModule: UIManagerModule,
27
+ private val reflectionUtils: ReflectionUtils = ReflectionUtils(),
28
+ private val reactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils =
29
+ ReactViewBackgroundDrawableUtils(),
30
+ private val drawableUtils: DrawableUtils = DrawableUtils()
31
+ ): TextPropertiesResolver {
32
+ override fun addReactNativeProperties(
33
+ originalWireframe: MobileSegment.Wireframe.TextWireframe,
34
+ view: TextView,
35
+ pixelDensity: Float,
36
+ ): MobileSegment.Wireframe.TextWireframe {
37
+ val (shapeStyle, border) = resolveShapeStyleAndBorder(view, pixelDensity)
38
+ ?: (originalWireframe.shapeStyle to originalWireframe.border)
39
+
40
+ val (textStyle, textPosition) = resolveTextStyleAndPosition(
41
+ originalWireframe,
42
+ view,
43
+ pixelDensity
44
+ ) ?: (originalWireframe.textStyle to originalWireframe.textPosition)
45
+
46
+ // nothing changed, return the original wireframe
47
+ @Suppress("ComplexCondition")
48
+ if (shapeStyle == originalWireframe.shapeStyle
49
+ && border == originalWireframe.border
50
+ && textStyle == originalWireframe.textStyle
51
+ && textPosition == originalWireframe.textPosition
52
+ ) {
53
+ return originalWireframe
54
+ }
55
+
56
+ return originalWireframe.copy(
57
+ shapeStyle = shapeStyle,
58
+ border = border,
59
+ textStyle = textStyle,
60
+ textPosition = textPosition
61
+ )
62
+ }
63
+
64
+ private fun resolveTextStyleAndPosition(
65
+ originalWireframe: MobileSegment.Wireframe.TextWireframe,
66
+ view: TextView,
67
+ pixelDensity: Float,
68
+ ):
69
+ Pair<MobileSegment.TextStyle, MobileSegment.TextPosition>? {
70
+ val shadowNodeWrapper: ShadowNodeWrapper =
71
+ ShadowNodeWrapper.getShadowNodeWrapper(
72
+ reactContext = reactContext,
73
+ uiManagerModule = uiManagerModule,
74
+ reflectionUtils = reflectionUtils,
75
+ viewId = view.id) ?: return null
76
+
77
+ val textStyle = resolveTextStyle(originalWireframe, pixelDensity, shadowNodeWrapper)
78
+ val alignment = resolveTextAlignment(view, originalWireframe)
79
+
80
+ val textPosition = MobileSegment.TextPosition(
81
+ alignment = alignment,
82
+ padding = originalWireframe.textPosition?.padding
83
+ )
84
+
85
+ return textStyle to textPosition
86
+ }
87
+
88
+ private fun resolveShapeStyleAndBorder(
89
+ view: TextView,
90
+ pixelDensity: Float,
91
+ ): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>? {
92
+ val backgroundDrawable: ReactViewBackgroundDrawable =
93
+ drawableUtils.getReactBackgroundFromDrawable(view.background) ?: return null
94
+
95
+ // view.alpha is the value of the opacity prop on the js side
96
+ val opacity = view.alpha
97
+
98
+ val (shapeStyle, border) =
99
+ reactViewBackgroundDrawableUtils
100
+ .resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
101
+
102
+ return shapeStyle to border
103
+ }
104
+
105
+ private fun resolveTextAlignment(
106
+ view: TextView,
107
+ textWireframe: MobileSegment.Wireframe.TextWireframe
108
+ ): MobileSegment.Alignment {
109
+ val gravity = view.gravity
110
+ val horizontal = textWireframe.textPosition?.alignment?.horizontal
111
+ val vertical =
112
+ when (gravity.and(Gravity.VERTICAL_GRAVITY_MASK)) {
113
+ Gravity.TOP -> MobileSegment.Vertical.TOP
114
+ Gravity.CENTER_VERTICAL,
115
+ Gravity.CENTER -> MobileSegment.Vertical.CENTER
116
+ Gravity.BOTTOM -> MobileSegment.Vertical.BOTTOM
117
+ else -> MobileSegment.Vertical.TOP
118
+ }
119
+
120
+ return MobileSegment.Alignment(
121
+ horizontal = horizontal,
122
+ vertical = vertical
123
+ )
124
+ }
125
+
126
+ private fun resolveTextStyle(
127
+ textWireframe: MobileSegment.Wireframe.TextWireframe,
128
+ pixelsDensity: Float,
129
+ shadowNodeWrapper: ShadowNodeWrapper
130
+ ): MobileSegment.TextStyle {
131
+ val fontFamily = getFontFamily(shadowNodeWrapper)
132
+ ?: textWireframe.textStyle.family
133
+ val fontSize = getFontSize(shadowNodeWrapper)
134
+ ?.convertToDensityNormalized(pixelsDensity)
135
+ ?: textWireframe.textStyle.size
136
+ val fontColor = getTextColor(shadowNodeWrapper)
137
+ ?: textWireframe.textStyle.color
138
+
139
+ return MobileSegment.TextStyle(
140
+ family = fontFamily,
141
+ size = fontSize,
142
+ color = fontColor
143
+ )
144
+ }
145
+
146
+ private fun getTextColor(shadowNodeWrapper: ShadowNodeWrapper): String? {
147
+ val resolvedColor = shadowNodeWrapper
148
+ .getDeclaredShadowNodeField(COLOR_FIELD_NAME) as Int?
149
+ if (resolvedColor != null) {
150
+ return formatAsRgba(resolvedColor)
151
+ }
152
+
153
+ return null
154
+ }
155
+
156
+ private fun getFontSize(shadowNodeWrapper: ShadowNodeWrapper): Long? {
157
+ val textAttributes = shadowNodeWrapper
158
+ .getDeclaredShadowNodeField(TEXT_ATTRIBUTES_FIELD_NAME) as? TextAttributes?
159
+ if (textAttributes != null) {
160
+ return textAttributes.effectiveFontSize.toLong()
161
+ }
162
+
163
+ return null
164
+ }
165
+
166
+ private fun getFontFamily(shadowNodeWrapper: ShadowNodeWrapper): String? {
167
+ val fontFamily = shadowNodeWrapper
168
+ .getDeclaredShadowNodeField(FONT_FAMILY_FIELD_NAME) as? String
169
+
170
+ if (fontFamily != null) {
171
+ return resolveFontFamily(fontFamily.lowercase(Locale.US))
172
+ }
173
+
174
+ return null
175
+ }
176
+
177
+ private fun resolveFontFamily(typefaceName: String): String =
178
+ when (typefaceName) {
179
+ ROBOTO_TYPEFACE_NAME -> SANS_SERIF_FAMILY_NAME
180
+ MONOSPACE_FAMILY_NAME -> MONOSPACE_FAMILY_NAME
181
+ SERIF_FAMILY_NAME -> SERIF_FAMILY_NAME
182
+ else -> SANS_SERIF_FAMILY_NAME
183
+ }
184
+
185
+ @VisibleForTesting
186
+ internal companion object {
187
+ internal const val TEXT_ATTRIBUTES_FIELD_NAME = "mTextAttributes"
188
+ internal const val FONT_FAMILY_FIELD_NAME = "mFontFamily"
189
+ internal const val COLOR_FIELD_NAME = "mColor"
190
+
191
+ private const val ROBOTO_TYPEFACE_NAME = "roboto"
192
+ private const val SERIF_FAMILY_NAME = "serif"
193
+ private const val SANS_SERIF_FAMILY_NAME = "roboto, sans-serif"
194
+ internal const val MONOSPACE_FAMILY_NAME = "monospace"
195
+ }
196
+ }
@@ -0,0 +1,24 @@
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
8
+
9
+ import com.datadog.android.sessionreplay.SessionReplay
10
+ import com.datadog.android.sessionreplay.SessionReplayConfiguration
11
+
12
+ internal class SessionReplaySDKWrapper : SessionReplayWrapper {
13
+ /**
14
+ * Enables a SessionReplay feature based on the configuration provided.
15
+ * @param sessionReplayConfiguration Configuration to use for the feature.
16
+ */
17
+ override fun enable(
18
+ sessionReplayConfiguration: SessionReplayConfiguration,
19
+ ) {
20
+ SessionReplay.enable(
21
+ sessionReplayConfiguration,
22
+ )
23
+ }
24
+ }
@@ -0,0 +1,22 @@
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
8
+
9
+ import com.datadog.android.sessionreplay.SessionReplayConfiguration
10
+
11
+ /**
12
+ * Wrapper around [SessionReplay].
13
+ */
14
+ interface SessionReplayWrapper {
15
+ /**
16
+ * Enables a SessionReplay feature based on the configuration provided.
17
+ * @param sessionReplayConfiguration Configuration to use for the feature.
18
+ */
19
+ fun enable(
20
+ sessionReplayConfiguration: SessionReplayConfiguration,
21
+ )
22
+ }
@@ -0,0 +1,70 @@
1
+ /*
2
+ *
3
+ * * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
4
+ * * This product includes software developed at Datadog (https://www.datadoghq.com/).
5
+ * * Copyright 2016-Present Datadog, Inc.
6
+ *
7
+ */
8
+
9
+ package com.datadog.reactnative.sessionreplay
10
+
11
+ import androidx.annotation.VisibleForTesting
12
+ import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
13
+ import com.facebook.react.bridge.ReactContext
14
+ import com.facebook.react.uimanager.ReactShadowNode
15
+ import com.facebook.react.uimanager.UIImplementation
16
+ import com.facebook.react.uimanager.UIManagerModule
17
+ import java.util.concurrent.CountDownLatch
18
+ import java.util.concurrent.TimeUnit
19
+
20
+ internal class ShadowNodeWrapper(
21
+ private val shadowNode: ReactShadowNode<out ReactShadowNode<*>>?,
22
+ private val reflectionUtils: ReflectionUtils = ReflectionUtils()
23
+ ) {
24
+ internal fun getDeclaredShadowNodeField(fieldName: String): Any? {
25
+ return shadowNode?.let {
26
+ reflectionUtils.getDeclaredField(
27
+ shadowNode,
28
+ fieldName
29
+ )
30
+ }
31
+ }
32
+
33
+ internal companion object {
34
+ internal fun getShadowNodeWrapper(
35
+ reactContext: ReactContext,
36
+ uiManagerModule: UIManagerModule,
37
+ reflectionUtils: ReflectionUtils,
38
+ viewId: Int
39
+ ): ShadowNodeWrapper? {
40
+ val countDownLatch = CountDownLatch(1)
41
+ var target: ReactShadowNode<out ReactShadowNode<*>>? = null
42
+
43
+ val shadowNodeRunnable = Runnable {
44
+ val node = resolveShadowNode(reflectionUtils, uiManagerModule, viewId)
45
+ if (node != null) {
46
+ target = node
47
+ }
48
+
49
+ countDownLatch.countDown()
50
+ }
51
+
52
+ reactContext.runOnNativeModulesQueueThread(shadowNodeRunnable)
53
+ countDownLatch.await(5, TimeUnit.SECONDS)
54
+
55
+ if (target == null) {
56
+ return null
57
+ }
58
+
59
+ return ShadowNodeWrapper(reflectionUtils = reflectionUtils, shadowNode = target)
60
+ }
61
+
62
+ private fun resolveShadowNode(reflectionUtils: ReflectionUtils, uiManagerModule: UIManagerModule, tag: Int): ReactShadowNode<out ReactShadowNode<*>>? {
63
+ val uiManagerImplementation = reflectionUtils.getDeclaredField(uiManagerModule, UI_IMPLEMENTATION_FIELD_NAME) as UIImplementation?
64
+ return uiManagerImplementation?.resolveShadowNode(tag)
65
+ }
66
+
67
+ @VisibleForTesting
68
+ internal const val UI_IMPLEMENTATION_FIELD_NAME = "mUIImplementation"
69
+ }
70
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ *
3
+ * * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
4
+ * * This product includes software developed at Datadog (https://www.datadoghq.com/).
5
+ * * Copyright 2016-Present Datadog, Inc.
6
+ *
7
+ */
8
+
9
+ package com.datadog.reactnative.sessionreplay
10
+
11
+ import android.widget.TextView
12
+ import com.datadog.android.sessionreplay.model.MobileSegment
13
+
14
+ internal interface TextPropertiesResolver {
15
+ fun addReactNativeProperties(
16
+ originalWireframe: MobileSegment.Wireframe.TextWireframe,
17
+ view: TextView,
18
+ pixelDensity: Float,
19
+ ): MobileSegment.Wireframe.TextWireframe
20
+ }
@@ -0,0 +1,15 @@
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.extensions
8
+
9
+ internal fun Long.convertToDensityNormalized(density: Float): Long {
10
+ return if (density == 0f) {
11
+ this
12
+ } else {
13
+ (this / density).toLong()
14
+ }
15
+ }
@@ -0,0 +1,54 @@
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.widget.TextView
10
+ import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback
11
+ import com.datadog.android.sessionreplay.internal.recorder.MappingContext
12
+ import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskInputTextViewMapper
13
+ import com.datadog.android.sessionreplay.model.MobileSegment
14
+ import com.datadog.reactnative.sessionreplay.NoopTextPropertiesResolver
15
+ import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver
16
+ import com.datadog.reactnative.sessionreplay.TextPropertiesResolver
17
+ import com.datadog.reactnative.sessionreplay.utils.TextViewUtils
18
+ import com.facebook.react.bridge.ReactContext
19
+ import com.facebook.react.uimanager.UIManagerModule
20
+
21
+ internal class ReactMaskInputTextMapper(
22
+ private val reactTextPropertiesResolver: TextPropertiesResolver,
23
+ private val textViewUtils: TextViewUtils = TextViewUtils()
24
+ ): MaskInputTextViewMapper() {
25
+
26
+ internal constructor(
27
+ reactContext: ReactContext,
28
+ uiManagerModule: UIManagerModule?
29
+ ): this(
30
+ reactTextPropertiesResolver = if (uiManagerModule == null) {
31
+ NoopTextPropertiesResolver()
32
+ } else {
33
+ ReactTextPropertiesResolver(
34
+ reactContext = reactContext,
35
+ uiManagerModule = uiManagerModule
36
+ )
37
+ }
38
+ )
39
+
40
+ override fun map(
41
+ view: TextView,
42
+ mappingContext: MappingContext,
43
+ asyncJobStatusCallback: AsyncJobStatusCallback
44
+ ): List<MobileSegment.Wireframe> {
45
+ val wireframes = super.map(view, mappingContext, asyncJobStatusCallback)
46
+ return textViewUtils.mapTextViewToWireframes(
47
+ wireframes = wireframes,
48
+ view = view,
49
+ mappingContext = mappingContext,
50
+ reactTextPropertiesResolver = reactTextPropertiesResolver
51
+ )
52
+ }
53
+ }
54
+
@@ -0,0 +1,55 @@
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.widget.TextView
10
+ import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback
11
+ import com.datadog.android.sessionreplay.internal.recorder.MappingContext
12
+ import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskTextViewMapper
13
+ import com.datadog.android.sessionreplay.model.MobileSegment
14
+ import com.datadog.reactnative.sessionreplay.NoopTextPropertiesResolver
15
+ import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver
16
+ import com.datadog.reactnative.sessionreplay.TextPropertiesResolver
17
+ import com.datadog.reactnative.sessionreplay.utils.TextViewUtils
18
+ import com.facebook.react.bridge.ReactContext
19
+ import com.facebook.react.uimanager.UIManagerModule
20
+
21
+ internal class ReactMaskTextMapper(
22
+ private val reactTextPropertiesResolver: TextPropertiesResolver =
23
+ NoopTextPropertiesResolver(),
24
+ private val textViewUtils: TextViewUtils = TextViewUtils()
25
+ ): MaskTextViewMapper() {
26
+
27
+ internal constructor(
28
+ reactContext: ReactContext,
29
+ uiManagerModule: UIManagerModule?
30
+ ): this(
31
+ reactTextPropertiesResolver = if (uiManagerModule == null) {
32
+ NoopTextPropertiesResolver()
33
+ } else {
34
+ ReactTextPropertiesResolver(
35
+ reactContext = reactContext,
36
+ uiManagerModule = uiManagerModule
37
+ )
38
+ }
39
+ )
40
+
41
+ override fun map(
42
+ view: TextView,
43
+ mappingContext: MappingContext,
44
+ asyncJobStatusCallback: AsyncJobStatusCallback
45
+ ): List<MobileSegment.Wireframe> {
46
+ val wireframes = super.map(view, mappingContext, asyncJobStatusCallback)
47
+ return textViewUtils.mapTextViewToWireframes(
48
+ wireframes = wireframes,
49
+ view = view,
50
+ mappingContext = mappingContext,
51
+ reactTextPropertiesResolver = reactTextPropertiesResolver
52
+ )
53
+ }
54
+ }
55
+
@@ -0,0 +1,54 @@
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.widget.TextView
10
+ import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback
11
+ import com.datadog.android.sessionreplay.internal.recorder.MappingContext
12
+ import com.datadog.android.sessionreplay.internal.recorder.mapper.TextViewMapper
13
+ import com.datadog.android.sessionreplay.model.MobileSegment
14
+ import com.datadog.reactnative.sessionreplay.NoopTextPropertiesResolver
15
+ import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver
16
+ import com.datadog.reactnative.sessionreplay.TextPropertiesResolver
17
+ import com.datadog.reactnative.sessionreplay.utils.TextViewUtils
18
+ import com.facebook.react.bridge.ReactContext
19
+ import com.facebook.react.uimanager.UIManagerModule
20
+
21
+ internal class ReactTextMapper(
22
+ private val reactTextPropertiesResolver: TextPropertiesResolver =
23
+ NoopTextPropertiesResolver(),
24
+ private val textViewUtils: TextViewUtils = TextViewUtils()
25
+ ): TextViewMapper() {
26
+
27
+ internal constructor(
28
+ reactContext: ReactContext,
29
+ uiManagerModule: UIManagerModule?
30
+ ): this(
31
+ reactTextPropertiesResolver = if (uiManagerModule == null) {
32
+ NoopTextPropertiesResolver()
33
+ } else {
34
+ ReactTextPropertiesResolver(
35
+ reactContext = reactContext,
36
+ uiManagerModule = uiManagerModule
37
+ )
38
+ }
39
+ )
40
+
41
+ override fun map(
42
+ view: TextView,
43
+ mappingContext: MappingContext,
44
+ asyncJobStatusCallback: AsyncJobStatusCallback
45
+ ): List<MobileSegment.Wireframe> {
46
+ val wireframes = super.map(view, mappingContext, asyncJobStatusCallback)
47
+ return textViewUtils.mapTextViewToWireframes(
48
+ wireframes = wireframes,
49
+ view = view,
50
+ mappingContext = mappingContext,
51
+ reactTextPropertiesResolver = reactTextPropertiesResolver
52
+ )
53
+ }
54
+ }
@@ -0,0 +1,58 @@
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 com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback
10
+ import com.datadog.android.sessionreplay.internal.recorder.MappingContext
11
+ import com.datadog.android.sessionreplay.internal.recorder.mapper.BaseWireframeMapper
12
+ import com.datadog.android.sessionreplay.internal.recorder.mapper.TraverseAllChildrenMapper
13
+ import com.datadog.android.sessionreplay.model.MobileSegment
14
+ import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
15
+ import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
16
+ import com.facebook.react.views.view.ReactViewGroup
17
+
18
+ internal class ReactViewGroupMapper(
19
+ private val reactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils =
20
+ ReactViewBackgroundDrawableUtils(),
21
+ private val drawableUtils: DrawableUtils = DrawableUtils()
22
+ ) :
23
+ BaseWireframeMapper<ReactViewGroup, MobileSegment.Wireframe>(),
24
+ TraverseAllChildrenMapper<ReactViewGroup, MobileSegment.Wireframe> {
25
+
26
+ override fun map(
27
+ view: ReactViewGroup,
28
+ mappingContext: MappingContext,
29
+ asyncJobStatusCallback: AsyncJobStatusCallback
30
+ ): List<MobileSegment.Wireframe> {
31
+ val pixelDensity = mappingContext.systemInformation.screenDensity
32
+ val viewGlobalBounds = resolveViewGlobalBounds(view, pixelDensity)
33
+ val backgroundDrawable = drawableUtils.getReactBackgroundFromDrawable(view.background)
34
+
35
+ // view.alpha is the value of the opacity prop on the js side
36
+ val opacity = view.alpha
37
+
38
+ val (shapeStyle, border) =
39
+ if (backgroundDrawable != null) {
40
+ reactViewBackgroundDrawableUtils
41
+ .resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
42
+ } else {
43
+ null to null
44
+ }
45
+
46
+ return listOf(
47
+ MobileSegment.Wireframe.ShapeWireframe(
48
+ resolveViewId(view),
49
+ viewGlobalBounds.x,
50
+ viewGlobalBounds.y,
51
+ viewGlobalBounds.width,
52
+ viewGlobalBounds.height,
53
+ shapeStyle = shapeStyle,
54
+ border = border
55
+ )
56
+ )
57
+ }
58
+ }