@datadog/mobile-react-native-session-replay 2.4.4-alpha.0 → 2.6.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.
- package/DatadogSDKReactNativeSessionReplay.podspec +1 -1
- package/android/build.gradle +11 -1
- package/android/consumer-proguard-rules.pro +3 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +32 -27
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +15 -4
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +8 -10
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayPrivacySettings.kt +90 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +14 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +10 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/ReactDrawablesExt.kt +190 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactEditTextMapper.kt +84 -10
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactNativeImageViewMapper.kt +106 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +4 -5
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/resources/ReactDrawableCopier.kt +34 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +10 -23
- package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +28 -4
- package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +35 -3
- package/android/src/{main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt → rn75/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt} +6 -8
- package/android/src/rn75/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt +13 -0
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
- package/android/src/rnlegacy/kotlin/com.datadog.reactnative.sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +88 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +59 -50
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +7 -3
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +2 -7
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +2 -7
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +2 -1
- package/ios/Sources/DdSessionReplay.mm +46 -4
- package/ios/Sources/DdSessionReplayImplementation.swift +83 -11
- package/ios/Sources/RCTTextViewRecorder.swift +1 -1
- package/lib/commonjs/SessionReplay.js +78 -10
- package/lib/commonjs/SessionReplay.js.map +1 -1
- package/lib/commonjs/index.js +18 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -1
- package/lib/module/SessionReplay.js +77 -9
- package/lib/module/SessionReplay.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/NativeDdSessionReplay.js.map +1 -1
- package/lib/typescript/SessionReplay.d.ts +73 -4
- package/lib/typescript/SessionReplay.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/nativeModulesTypes.d.ts +18 -3
- package/lib/typescript/nativeModulesTypes.d.ts.map +1 -1
- package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -2
- package/lib/typescript/specs/NativeDdSessionReplay.d.ts.map +1 -1
- package/package.json +85 -84
- package/src/SessionReplay.ts +170 -23
- package/src/__tests__/SessionReplay.test.ts +94 -8
- package/src/index.ts +14 -2
- package/src/nativeModulesTypes.ts +27 -4
- package/src/specs/NativeDdSessionReplay.ts +21 -3
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +0 -66
|
@@ -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,88 @@
|
|
|
1
|
+
import android.graphics.drawable.Drawable
|
|
2
|
+
import android.graphics.drawable.InsetDrawable
|
|
3
|
+
import android.graphics.drawable.LayerDrawable
|
|
4
|
+
import com.datadog.android.internal.utils.densityNormalized
|
|
5
|
+
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
6
|
+
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
7
|
+
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
|
|
8
|
+
import com.facebook.react.uimanager.Spacing
|
|
9
|
+
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
10
|
+
|
|
11
|
+
internal class ReactViewBackgroundDrawableUtils() : DrawableUtils() {
|
|
12
|
+
override fun resolveShapeAndBorder(
|
|
13
|
+
drawable: Drawable,
|
|
14
|
+
opacity: Float,
|
|
15
|
+
pixelDensity: Float
|
|
16
|
+
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?> {
|
|
17
|
+
if (drawable !is ReactViewBackgroundDrawable) {
|
|
18
|
+
return null to null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
val borderProps = resolveBorder(drawable, pixelDensity)
|
|
22
|
+
val cornerRadius = drawable
|
|
23
|
+
.fullBorderRadius
|
|
24
|
+
.toLong()
|
|
25
|
+
.densityNormalized(pixelDensity)
|
|
26
|
+
|
|
27
|
+
val backgroundColor = getBackgroundColor(drawable)
|
|
28
|
+
val colorHexString = if (backgroundColor != null) {
|
|
29
|
+
formatAsRgba(backgroundColor)
|
|
30
|
+
} else {
|
|
31
|
+
return null to borderProps
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return MobileSegment.ShapeStyle(
|
|
35
|
+
colorHexString,
|
|
36
|
+
opacity,
|
|
37
|
+
cornerRadius
|
|
38
|
+
) to borderProps
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable? {
|
|
42
|
+
if (drawable is ReactViewBackgroundDrawable) {
|
|
43
|
+
return drawable
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (drawable is InsetDrawable) {
|
|
47
|
+
return getReactBackgroundFromDrawable(drawable.drawable)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (drawable is LayerDrawable) {
|
|
51
|
+
for (layerNumber in 0 until drawable.numberOfLayers) {
|
|
52
|
+
val layer = drawable.getDrawable(layerNumber)
|
|
53
|
+
if (layer is ReactViewBackgroundDrawable) {
|
|
54
|
+
return layer
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private fun resolveBorder(
|
|
63
|
+
backgroundDrawable: ReactViewBackgroundDrawable,
|
|
64
|
+
pixelDensity: Float
|
|
65
|
+
): MobileSegment.ShapeBorder {
|
|
66
|
+
val borderWidth =
|
|
67
|
+
backgroundDrawable.fullBorderWidth.toLong().densityNormalized(pixelDensity)
|
|
68
|
+
val borderColor = formatAsRgba(backgroundDrawable.getBorderColor(Spacing.ALL))
|
|
69
|
+
|
|
70
|
+
return MobileSegment.ShapeBorder(
|
|
71
|
+
color = borderColor,
|
|
72
|
+
width = borderWidth
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private fun getBackgroundColor(
|
|
77
|
+
backgroundDrawable: ReactViewBackgroundDrawable
|
|
78
|
+
): Int? {
|
|
79
|
+
return reflectionUtils.getDeclaredField(
|
|
80
|
+
backgroundDrawable,
|
|
81
|
+
COLOR_FIELD_NAME
|
|
82
|
+
) as Int?
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private companion object {
|
|
86
|
+
private const val COLOR_FIELD_NAME = "mColor"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -16,10 +16,10 @@ import com.facebook.react.bridge.NativeModule
|
|
|
16
16
|
import com.facebook.react.bridge.Promise
|
|
17
17
|
import com.facebook.react.bridge.ReactContext
|
|
18
18
|
import com.facebook.react.uimanager.UIManagerModule
|
|
19
|
+
import fr.xgouchet.elmyr.annotation.BoolForgery
|
|
19
20
|
import fr.xgouchet.elmyr.annotation.DoubleForgery
|
|
20
21
|
import fr.xgouchet.elmyr.annotation.StringForgery
|
|
21
22
|
import fr.xgouchet.elmyr.junit5.ForgeExtension
|
|
22
|
-
import java.util.Locale
|
|
23
23
|
import org.junit.jupiter.api.AfterEach
|
|
24
24
|
import org.junit.jupiter.api.BeforeEach
|
|
25
25
|
import org.junit.jupiter.api.Test
|
|
@@ -56,6 +56,23 @@ internal class DdSessionReplayImplementationTest {
|
|
|
56
56
|
@Mock
|
|
57
57
|
lateinit var mockUiManagerModule: UIManagerModule
|
|
58
58
|
|
|
59
|
+
private val imagePrivacyMap = mapOf(
|
|
60
|
+
"MASK_ALL" to ImagePrivacy.MASK_ALL,
|
|
61
|
+
"MASK_NON_BUNDLED_ONLY" to ImagePrivacy.MASK_LARGE_ONLY,
|
|
62
|
+
"MASK_NONE" to ImagePrivacy.MASK_NONE
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
private val touchPrivacyMap = mapOf(
|
|
66
|
+
"SHOW" to TouchPrivacy.SHOW,
|
|
67
|
+
"HIDE" to TouchPrivacy.HIDE
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
private val inputPrivacyMap = mapOf(
|
|
71
|
+
"MASK_ALL" to TextAndInputPrivacy.MASK_ALL,
|
|
72
|
+
"MASK_ALL_INPUTS" to TextAndInputPrivacy.MASK_ALL_INPUTS,
|
|
73
|
+
"MASK_SENSITIVE_INPUTS" to TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
|
|
74
|
+
)
|
|
75
|
+
|
|
59
76
|
@BeforeEach
|
|
60
77
|
fun `set up`() {
|
|
61
78
|
whenever(mockReactContext.getNativeModule(any<Class<NativeModule>>()))
|
|
@@ -70,33 +87,32 @@ internal class DdSessionReplayImplementationTest {
|
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
@Test
|
|
73
|
-
fun `M enable session replay W privacy
|
|
74
|
-
@DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
|
|
75
|
-
@StringForgery(regex = ".+") customEndpoint: String
|
|
76
|
-
) {
|
|
77
|
-
testSessionReplayEnable("ALLOW", replaySampleRate, customEndpoint)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
@Test
|
|
81
|
-
fun `M enable session replay W privacy = MASK`(
|
|
90
|
+
fun `M enable session replay W random privacy settings`(
|
|
82
91
|
@DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
|
|
83
|
-
@StringForgery(regex = ".+") customEndpoint: String
|
|
92
|
+
@StringForgery(regex = ".+") customEndpoint: String,
|
|
93
|
+
@BoolForgery startRecordingImmediately: Boolean
|
|
84
94
|
) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
val imagePrivacy = imagePrivacyMap.keys.random()
|
|
96
|
+
val touchPrivacy = touchPrivacyMap.keys.random()
|
|
97
|
+
val textAndInputPrivacy = inputPrivacyMap.keys.random()
|
|
98
|
+
|
|
99
|
+
testSessionReplayEnable(
|
|
100
|
+
replaySampleRate = replaySampleRate,
|
|
101
|
+
customEndpoint = customEndpoint,
|
|
102
|
+
imagePrivacy = imagePrivacy,
|
|
103
|
+
touchPrivacy = touchPrivacy,
|
|
104
|
+
textAndInputPrivacy = textAndInputPrivacy,
|
|
105
|
+
startRecordingImmediately = startRecordingImmediately
|
|
106
|
+
)
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
private fun testSessionReplayEnable(
|
|
97
|
-
privacy: String,
|
|
98
110
|
replaySampleRate: Double,
|
|
99
|
-
customEndpoint: String
|
|
111
|
+
customEndpoint: String,
|
|
112
|
+
imagePrivacy: String,
|
|
113
|
+
touchPrivacy: String,
|
|
114
|
+
textAndInputPrivacy: String,
|
|
115
|
+
startRecordingImmediately: Boolean
|
|
100
116
|
) {
|
|
101
117
|
// Given
|
|
102
118
|
val sessionReplayConfigCaptor = argumentCaptor<SessionReplayConfiguration>()
|
|
@@ -104,8 +120,9 @@ internal class DdSessionReplayImplementationTest {
|
|
|
104
120
|
// When
|
|
105
121
|
testedSessionReplay.enable(
|
|
106
122
|
replaySampleRate,
|
|
107
|
-
privacy,
|
|
108
123
|
customEndpoint,
|
|
124
|
+
SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy),
|
|
125
|
+
startRecordingImmediately,
|
|
109
126
|
mockPromise
|
|
110
127
|
)
|
|
111
128
|
|
|
@@ -114,49 +131,41 @@ internal class DdSessionReplayImplementationTest {
|
|
|
114
131
|
assertThat(sessionReplayConfigCaptor.firstValue)
|
|
115
132
|
.hasFieldEqualTo("sampleRate", replaySampleRate.toFloat())
|
|
116
133
|
.hasFieldEqualTo("customEndpointUrl", customEndpoint)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"
|
|
120
|
-
assertThat(sessionReplayConfigCaptor.firstValue)
|
|
121
|
-
.hasFieldEqualTo("textAndInputPrivacy", TextAndInputPrivacy.MASK_ALL_INPUTS)
|
|
122
|
-
.hasFieldEqualTo("imagePrivacy", ImagePrivacy.MASK_NONE)
|
|
123
|
-
.hasFieldEqualTo("touchPrivacy", TouchPrivacy.HIDE)
|
|
124
|
-
}
|
|
125
|
-
"allow" -> {
|
|
126
|
-
assertThat(sessionReplayConfigCaptor.firstValue)
|
|
127
|
-
.hasFieldEqualTo(
|
|
128
|
-
"textAndInputPrivacy",
|
|
129
|
-
TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
|
|
130
|
-
)
|
|
131
|
-
.hasFieldEqualTo("imagePrivacy", ImagePrivacy.MASK_NONE)
|
|
132
|
-
.hasFieldEqualTo("touchPrivacy", TouchPrivacy.SHOW)
|
|
133
|
-
}
|
|
134
|
-
else -> {
|
|
135
|
-
assertThat(sessionReplayConfigCaptor.firstValue)
|
|
136
|
-
.hasFieldEqualTo("textAndInputPrivacy", TextAndInputPrivacy.MASK_ALL)
|
|
137
|
-
.hasFieldEqualTo("imagePrivacy", ImagePrivacy.MASK_ALL)
|
|
138
|
-
.hasFieldEqualTo("touchPrivacy", TouchPrivacy.HIDE)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
134
|
+
.hasFieldEqualTo("textAndInputPrivacy", inputPrivacyMap[textAndInputPrivacy])
|
|
135
|
+
.hasFieldEqualTo("imagePrivacy", imagePrivacyMap[imagePrivacy])
|
|
136
|
+
.hasFieldEqualTo("touchPrivacy", touchPrivacyMap[touchPrivacy])
|
|
141
137
|
}
|
|
142
138
|
|
|
143
139
|
@Test
|
|
144
140
|
fun `M enable session replay without custom endpoint W empty string()`(
|
|
145
141
|
@DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
|
|
146
|
-
|
|
147
|
-
@StringForgery(regex = "^/(?!ALLOW|MASK_USER_INPUT)([a-z0-9]+)$/i") privacy: String
|
|
142
|
+
@BoolForgery startRecordingImmediately: Boolean
|
|
148
143
|
) {
|
|
149
144
|
// Given
|
|
145
|
+
val imagePrivacy = imagePrivacyMap.keys.random()
|
|
146
|
+
val touchPrivacy = touchPrivacyMap.keys.random()
|
|
147
|
+
val textAndInputPrivacy = inputPrivacyMap.keys.random()
|
|
150
148
|
val sessionReplayConfigCaptor = argumentCaptor<SessionReplayConfiguration>()
|
|
151
149
|
|
|
152
150
|
// When
|
|
153
|
-
testedSessionReplay.enable(
|
|
151
|
+
testedSessionReplay.enable(
|
|
152
|
+
replaySampleRate,
|
|
153
|
+
"",
|
|
154
|
+
SessionReplayPrivacySettings(
|
|
155
|
+
imagePrivacyLevel = imagePrivacy,
|
|
156
|
+
touchPrivacyLevel = touchPrivacy,
|
|
157
|
+
textAndInputPrivacyLevel = textAndInputPrivacy
|
|
158
|
+
),
|
|
159
|
+
startRecordingImmediately,
|
|
160
|
+
mockPromise
|
|
161
|
+
)
|
|
154
162
|
|
|
155
163
|
// Then
|
|
156
164
|
verify(mockSessionReplay).enable(sessionReplayConfigCaptor.capture(), any())
|
|
157
165
|
assertThat(sessionReplayConfigCaptor.firstValue)
|
|
158
166
|
.hasFieldEqualTo("sampleRate", replaySampleRate.toFloat())
|
|
159
167
|
.hasFieldEqualTo("privacy", SessionReplayPrivacy.MASK)
|
|
168
|
+
.hasFieldEqualTo("startRecordingImmediately", startRecordingImmediately)
|
|
160
169
|
.doesNotHaveField("customEndpointUrl")
|
|
161
170
|
}
|
|
162
171
|
}
|
|
@@ -8,6 +8,7 @@ package com.datadog.reactnative.sessionreplay
|
|
|
8
8
|
|
|
9
9
|
import com.datadog.android.api.InternalLogger
|
|
10
10
|
import com.datadog.reactnative.sessionreplay.mappers.ReactEditTextMapper
|
|
11
|
+
import com.datadog.reactnative.sessionreplay.mappers.ReactNativeImageViewMapper
|
|
11
12
|
import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper
|
|
12
13
|
import com.datadog.reactnative.sessionreplay.mappers.ReactViewGroupMapper
|
|
13
14
|
import com.facebook.react.bridge.NativeModule
|
|
@@ -62,15 +63,18 @@ internal class ReactNativeSessionReplayExtensionSupportTest {
|
|
|
62
63
|
val customViewMappers = testedExtensionSupport.getCustomViewMappers()
|
|
63
64
|
|
|
64
65
|
// Then
|
|
65
|
-
assertThat(customViewMappers).hasSize(
|
|
66
|
+
assertThat(customViewMappers).hasSize(4)
|
|
66
67
|
|
|
67
68
|
assertThat(customViewMappers[0].getUnsafeMapper())
|
|
68
|
-
.isInstanceOf(
|
|
69
|
+
.isInstanceOf(ReactNativeImageViewMapper::class.java)
|
|
69
70
|
|
|
70
71
|
assertThat(customViewMappers[1].getUnsafeMapper())
|
|
71
|
-
.isInstanceOf(
|
|
72
|
+
.isInstanceOf(ReactViewGroupMapper::class.java)
|
|
72
73
|
|
|
73
74
|
assertThat(customViewMappers[2].getUnsafeMapper())
|
|
75
|
+
.isInstanceOf(ReactTextMapper::class.java)
|
|
76
|
+
|
|
77
|
+
assertThat(customViewMappers[3].getUnsafeMapper())
|
|
74
78
|
.isInstanceOf(ReactEditTextMapper::class.java)
|
|
75
79
|
}
|
|
76
80
|
|
|
@@ -15,7 +15,6 @@ import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Compani
|
|
|
15
15
|
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.TEXT_ATTRIBUTES_FIELD_NAME
|
|
16
16
|
import com.datadog.reactnative.sessionreplay.ShadowNodeWrapper.Companion.UI_IMPLEMENTATION_FIELD_NAME
|
|
17
17
|
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
18
|
-
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
|
|
19
18
|
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
|
|
20
19
|
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
|
|
21
20
|
import com.datadog.reactnative.tools.unit.forge.ForgeConfigurator
|
|
@@ -64,14 +63,11 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
64
63
|
@Mock
|
|
65
64
|
lateinit var mockTextView: TextView
|
|
66
65
|
|
|
67
|
-
@Mock
|
|
68
|
-
lateinit var mockDrawableUtils: DrawableUtils
|
|
69
|
-
|
|
70
66
|
@Mock
|
|
71
67
|
lateinit var mockReactViewBackgroundDrawable: ReactViewBackgroundDrawable
|
|
72
68
|
|
|
73
69
|
@Mock
|
|
74
|
-
lateinit var
|
|
70
|
+
lateinit var mockDrawableUtils: DrawableUtils
|
|
75
71
|
|
|
76
72
|
@Mock
|
|
77
73
|
lateinit var mockShadowNodeWrapper: ShadowNodeWrapper
|
|
@@ -108,7 +104,6 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
108
104
|
testedResolver = ReactTextPropertiesResolver(
|
|
109
105
|
reactContext = mockReactContext,
|
|
110
106
|
uiManagerModule = mockUiManagerModule,
|
|
111
|
-
reactViewBackgroundDrawableUtils = mockReactViewBackgroundDrawableUtils,
|
|
112
107
|
drawableUtils = mockDrawableUtils,
|
|
113
108
|
reflectionUtils = mockReflectionUtils
|
|
114
109
|
)
|
|
@@ -145,7 +140,7 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
145
140
|
)
|
|
146
141
|
).thenReturn(mockReactViewBackgroundDrawable)
|
|
147
142
|
whenever(
|
|
148
|
-
|
|
143
|
+
mockDrawableUtils.resolveShapeAndBorder(
|
|
149
144
|
drawable = eq(mockReactViewBackgroundDrawable),
|
|
150
145
|
opacity = eq(0f),
|
|
151
146
|
pixelDensity = eq(0f)
|
|
@@ -15,7 +15,6 @@ import com.datadog.android.sessionreplay.recorder.MappingContext
|
|
|
15
15
|
import com.datadog.android.sessionreplay.recorder.SystemInformation
|
|
16
16
|
import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback
|
|
17
17
|
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
18
|
-
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
|
|
19
18
|
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
20
19
|
import com.facebook.react.views.view.ReactViewGroup
|
|
21
20
|
import fr.xgouchet.elmyr.junit5.ForgeExtension
|
|
@@ -40,7 +39,7 @@ internal class ReactViewGroupMapperTest {
|
|
|
40
39
|
private lateinit var testedMapper: ReactViewGroupMapper
|
|
41
40
|
|
|
42
41
|
@Mock
|
|
43
|
-
private lateinit var
|
|
42
|
+
private lateinit var mockDrawableUtils: DrawableUtils
|
|
44
43
|
|
|
45
44
|
@Mock
|
|
46
45
|
private lateinit var mockReactViewGroup: ReactViewGroup
|
|
@@ -57,9 +56,6 @@ internal class ReactViewGroupMapperTest {
|
|
|
57
56
|
@Mock
|
|
58
57
|
private lateinit var mockSystemInformation: SystemInformation
|
|
59
58
|
|
|
60
|
-
@Mock
|
|
61
|
-
private lateinit var mockDrawableUtils: DrawableUtils
|
|
62
|
-
|
|
63
59
|
@Mock
|
|
64
60
|
private lateinit var mockReactViewBackgroundDrawable: ReactViewBackgroundDrawable
|
|
65
61
|
|
|
@@ -75,7 +71,6 @@ internal class ReactViewGroupMapperTest {
|
|
|
75
71
|
whenever(mockSystemInformation.screenDensity).thenReturn(0f)
|
|
76
72
|
|
|
77
73
|
testedMapper = ReactViewGroupMapper(
|
|
78
|
-
reactViewBackgroundDrawableUtils = mockReactViewBackgroundDrawableUtils,
|
|
79
74
|
drawableUtils = mockDrawableUtils
|
|
80
75
|
)
|
|
81
76
|
}
|
|
@@ -115,7 +110,7 @@ internal class ReactViewGroupMapperTest {
|
|
|
115
110
|
)
|
|
116
111
|
).thenReturn(mockReactViewBackgroundDrawable)
|
|
117
112
|
whenever(
|
|
118
|
-
|
|
113
|
+
mockDrawableUtils.resolveShapeAndBorder(
|
|
119
114
|
drawable = eq(mockReactViewBackgroundDrawable),
|
|
120
115
|
pixelDensity = eq(0f),
|
|
121
116
|
opacity = eq(0f)
|
package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
package com.datadog.reactnative.sessionreplay.utils
|
|
8
8
|
|
|
9
|
+
import ReactViewBackgroundDrawableUtils
|
|
9
10
|
import android.graphics.drawable.ColorDrawable
|
|
10
11
|
import android.graphics.drawable.InsetDrawable
|
|
11
12
|
import android.graphics.drawable.LayerDrawable
|
|
@@ -47,7 +48,7 @@ internal class DrawableUtilsTest {
|
|
|
47
48
|
whenever(mockLayerDrawable.numberOfLayers).thenReturn(3)
|
|
48
49
|
whenever(mockLayerDrawable.getDrawable(0)).thenReturn(mockReactViewBackgroundDrawable)
|
|
49
50
|
|
|
50
|
-
testedDrawableUtils =
|
|
51
|
+
testedDrawableUtils = ReactViewBackgroundDrawableUtils()
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
@Test
|
|
@@ -18,12 +18,32 @@
|
|
|
18
18
|
RCT_EXPORT_MODULE()
|
|
19
19
|
|
|
20
20
|
RCT_REMAP_METHOD(enable, withEnableReplaySampleRate:(double)replaySampleRate
|
|
21
|
-
withDefaultPrivacyLevel:(NSString*)defaultPrivacyLevel
|
|
22
21
|
withCustomEndpoint:(NSString*)customEndpoint
|
|
22
|
+
withImagePrivacyLevel:(NSString*)imagePrivacyLevel
|
|
23
|
+
withTouchPrivacyLevel:(NSString*)touchPrivacyLevel
|
|
24
|
+
withTextAndInputPrivacyLevel:(NSString*)textAndInputPrivacyLevel
|
|
25
|
+
withStartRecordingImmediately:(BOOL)startRecordingImmediately
|
|
23
26
|
withResolver:(RCTPromiseResolveBlock)resolve
|
|
24
27
|
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
25
28
|
{
|
|
26
|
-
[self enable:replaySampleRate
|
|
29
|
+
[self enable:replaySampleRate
|
|
30
|
+
customEndpoint:customEndpoint
|
|
31
|
+
imagePrivacyLevel:imagePrivacyLevel
|
|
32
|
+
touchPrivacyLevel:touchPrivacyLevel
|
|
33
|
+
textAndInputPrivacyLevel:textAndInputPrivacyLevel
|
|
34
|
+
startRecordingImmediately:startRecordingImmediately
|
|
35
|
+
resolve:resolve
|
|
36
|
+
reject:reject];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
RCT_EXPORT_METHOD(startRecording:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
|
|
40
|
+
{
|
|
41
|
+
[self startRecordingWithResolver:resolve reject:reject];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
RCT_EXPORT_METHOD(stopRecording:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
|
|
45
|
+
{
|
|
46
|
+
[self stopRecordingWithResolver:resolve reject:reject];
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
// Thanks to this guard, we won't compile this code when we build for the old architecture.
|
|
@@ -47,8 +67,30 @@ RCT_REMAP_METHOD(enable, withEnableReplaySampleRate:(double)replaySampleRate
|
|
|
47
67
|
return NO;
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
- (void)enable:(double)replaySampleRate
|
|
51
|
-
|
|
70
|
+
- (void)enable:(double)replaySampleRate
|
|
71
|
+
customEndpoint:(NSString*)customEndpoint
|
|
72
|
+
imagePrivacyLevel:(NSString *)imagePrivacyLevel
|
|
73
|
+
touchPrivacyLevel:(NSString *)touchPrivacyLevel
|
|
74
|
+
textAndInputPrivacyLevel:(NSString *)textAndInputPrivacyLevel
|
|
75
|
+
startRecordingImmediately:(BOOL)startRecordingImmediately
|
|
76
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
77
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
78
|
+
[self.ddSessionReplayImplementation enableWithReplaySampleRate:replaySampleRate
|
|
79
|
+
customEndpoint:customEndpoint
|
|
80
|
+
imagePrivacyLevel:imagePrivacyLevel
|
|
81
|
+
touchPrivacyLevel:touchPrivacyLevel
|
|
82
|
+
textAndInputPrivacyLevel:textAndInputPrivacyLevel
|
|
83
|
+
startRecordingImmediately:startRecordingImmediately
|
|
84
|
+
resolve:resolve
|
|
85
|
+
reject:reject];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
- (void)startRecordingWithResolver:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
89
|
+
[self.ddSessionReplayImplementation startRecordingWithResolve:resolve reject:reject];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)stopRecordingWithResolver:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
93
|
+
[self.ddSessionReplayImplementation stopRecordingWithResolve:resolve reject:reject];
|
|
52
94
|
}
|
|
53
95
|
|
|
54
96
|
@end
|