@datadog/mobile-react-native-session-replay 2.6.0 → 2.6.2
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/LICENSE +201 -0
- package/README.md +52 -1
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +5 -5
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +5 -29
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ShadowNodeWrapper.kt +2 -2
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactEditTextMapper.kt +3 -26
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt +3 -25
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/text/FabricTextViewUtils.kt +74 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/text/LegacyTextViewUtils.kt +118 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/{ReactTextPropertiesResolver.kt → utils/text/TextViewUtils.kt} +60 -96
- package/android/src/rn75/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt +1 -2
- package/android/src/rn75/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +1 -3
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt +1 -1
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +1 -3
- package/android/src/rnlegacy/kotlin/com.datadog.reactnative.sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +2 -7
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +3 -17
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/{ReactTextPropertiesResolverTest.kt → utils/text/TextViewUtilsTest.kt} +171 -38
- package/ios/Sources/DdSessionReplay.mm +4 -4
- package/ios/Sources/DdSessionReplayImplementation.swift +13 -3
- package/ios/Sources/RCTFabricWrapper.h +13 -0
- package/ios/Sources/RCTFabricWrapper.mm +120 -0
- package/ios/Sources/RCTTextPropertiesWrapper.h +23 -0
- package/ios/Sources/RCTTextPropertiesWrapper.mm +28 -0
- package/ios/Sources/RCTTextViewRecorder.swift +69 -49
- package/ios/Sources/RCTVersion.h +8 -0
- package/lib/commonjs/SessionReplay.js +1 -1
- package/lib/commonjs/SessionReplay.js.map +1 -1
- package/lib/module/SessionReplay.js +1 -1
- package/lib/module/SessionReplay.js.map +1 -1
- package/package.json +6 -3
- package/scripts/set-ios-rn-version.js +47 -0
- package/src/SessionReplay.ts +1 -1
- package/src/__tests__/SessionReplay.test.ts +1 -1
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/NoopTextPropertiesResolver.kt +0 -22
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtils.kt +0 -40
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtilsTest.kt +0 -109
|
@@ -4,20 +4,30 @@
|
|
|
4
4
|
* Copyright 2016-Present Datadog, Inc.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
package com.datadog.reactnative.sessionreplay
|
|
7
|
+
package com.datadog.reactnative.sessionreplay.utils.text
|
|
8
8
|
|
|
9
|
+
import android.content.res.Resources
|
|
10
|
+
import android.graphics.Typeface
|
|
11
|
+
import android.text.Spannable
|
|
12
|
+
import android.text.style.ForegroundColorSpan
|
|
13
|
+
import android.util.DisplayMetrics
|
|
9
14
|
import android.widget.TextView
|
|
15
|
+
import com.datadog.android.api.InternalLogger
|
|
10
16
|
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
11
|
-
import com.datadog.
|
|
12
|
-
import com.datadog.
|
|
13
|
-
import com.datadog.reactnative.sessionreplay.
|
|
14
|
-
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.MONOSPACE_FAMILY_NAME
|
|
15
|
-
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.TEXT_ATTRIBUTES_FIELD_NAME
|
|
17
|
+
import com.datadog.android.sessionreplay.recorder.MappingContext
|
|
18
|
+
import com.datadog.android.sessionreplay.recorder.SystemInformation
|
|
19
|
+
import com.datadog.reactnative.sessionreplay.ShadowNodeWrapper
|
|
16
20
|
import com.datadog.reactnative.sessionreplay.ShadowNodeWrapper.Companion.UI_IMPLEMENTATION_FIELD_NAME
|
|
17
21
|
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
18
22
|
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
|
|
19
23
|
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
|
|
24
|
+
import com.datadog.reactnative.sessionreplay.utils.text.TextViewUtils.Companion.COLOR_FIELD_NAME
|
|
25
|
+
import com.datadog.reactnative.sessionreplay.utils.text.TextViewUtils.Companion.FONT_FAMILY_FIELD_NAME
|
|
26
|
+
import com.datadog.reactnative.sessionreplay.utils.text.TextViewUtils.Companion.IS_COLOR_SET_FIELD_NAME
|
|
27
|
+
import com.datadog.reactnative.sessionreplay.utils.text.TextViewUtils.Companion.MONOSPACE_FAMILY_NAME
|
|
28
|
+
import com.datadog.reactnative.sessionreplay.utils.text.TextViewUtils.Companion.TEXT_ATTRIBUTES_FIELD_NAME
|
|
20
29
|
import com.datadog.reactnative.tools.unit.forge.ForgeConfigurator
|
|
30
|
+
import com.facebook.react.bridge.NativeModule
|
|
21
31
|
import com.facebook.react.bridge.ReactContext
|
|
22
32
|
import com.facebook.react.uimanager.ReactShadowNode
|
|
23
33
|
import com.facebook.react.uimanager.UIImplementation
|
|
@@ -34,7 +44,11 @@ import org.junit.jupiter.api.BeforeEach
|
|
|
34
44
|
import org.junit.jupiter.api.Test
|
|
35
45
|
import org.junit.jupiter.api.extension.ExtendWith
|
|
36
46
|
import org.junit.jupiter.api.extension.Extensions
|
|
47
|
+
import org.mockito.ArgumentMatchers.anyInt
|
|
37
48
|
import org.mockito.Mock
|
|
49
|
+
import org.mockito.Mockito.doReturn
|
|
50
|
+
import org.mockito.Mockito.mock
|
|
51
|
+
import org.mockito.Mockito.spy
|
|
38
52
|
import org.mockito.junit.jupiter.MockitoExtension
|
|
39
53
|
import org.mockito.junit.jupiter.MockitoSettings
|
|
40
54
|
import org.mockito.kotlin.any
|
|
@@ -48,9 +62,7 @@ import org.mockito.quality.Strictness
|
|
|
48
62
|
)
|
|
49
63
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
|
50
64
|
@ForgeConfiguration(ForgeConfigurator::class)
|
|
51
|
-
internal class
|
|
52
|
-
private lateinit var testedResolver: ReactTextPropertiesResolver
|
|
53
|
-
|
|
65
|
+
internal class TextViewUtilsTest {
|
|
54
66
|
@Mock
|
|
55
67
|
lateinit var mockReactContext: ReactContext
|
|
56
68
|
|
|
@@ -84,10 +96,44 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
84
96
|
@Mock
|
|
85
97
|
private lateinit var mockShadowNode: ReactShadowNode<out ReactShadowNode<*>>
|
|
86
98
|
|
|
99
|
+
@Mock
|
|
100
|
+
private lateinit var mockLogger: InternalLogger
|
|
101
|
+
|
|
102
|
+
@Mock
|
|
103
|
+
private lateinit var mockMappingContext: MappingContext
|
|
104
|
+
|
|
105
|
+
@Mock
|
|
106
|
+
private lateinit var mockSystemInformation: SystemInformation
|
|
107
|
+
|
|
108
|
+
@Mock
|
|
109
|
+
private lateinit var mockResources: Resources
|
|
110
|
+
|
|
111
|
+
@Mock
|
|
112
|
+
private lateinit var mockDisplayMetrics: DisplayMetrics
|
|
113
|
+
|
|
114
|
+
@Mock
|
|
115
|
+
private lateinit var testedUtils: LegacyTextViewUtils
|
|
116
|
+
|
|
117
|
+
@Mock
|
|
118
|
+
private lateinit var fabricTestedUtils: FabricTextViewUtils
|
|
119
|
+
|
|
87
120
|
@BeforeEach
|
|
88
121
|
fun `set up`(forge: Forge) {
|
|
122
|
+
whenever(mockResources.displayMetrics).thenReturn(mockDisplayMetrics)
|
|
123
|
+
whenever(mockTextView.resources).thenReturn(mockResources)
|
|
124
|
+
whenever(mockSystemInformation.screenDensity).thenReturn(0f)
|
|
125
|
+
whenever(mockMappingContext.systemInformation).thenReturn(mockSystemInformation)
|
|
126
|
+
whenever(mockTextView.text).thenReturn(forge.aString())
|
|
127
|
+
whenever(mockTextView.typeface).thenReturn(Typeface.SANS_SERIF)
|
|
128
|
+
|
|
129
|
+
whenever(mockReactContext.getNativeModule(UIManagerModule::class.java))
|
|
130
|
+
.thenReturn(mockUiManagerModule)
|
|
131
|
+
|
|
89
132
|
whenever(
|
|
90
|
-
mockReflectionUtils.getDeclaredField(
|
|
133
|
+
mockReflectionUtils.getDeclaredField(
|
|
134
|
+
eq(mockUiManagerModule),
|
|
135
|
+
eq(UI_IMPLEMENTATION_FIELD_NAME)
|
|
136
|
+
)
|
|
91
137
|
).thenReturn(mockUiImplementation)
|
|
92
138
|
|
|
93
139
|
whenever(
|
|
@@ -101,12 +147,64 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
101
147
|
}
|
|
102
148
|
whenever(mockReactContext.hasActiveReactInstance()).thenReturn(true)
|
|
103
149
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
150
|
+
val realUtils =
|
|
151
|
+
LegacyTextViewUtils(
|
|
152
|
+
mockReactContext,
|
|
153
|
+
mockLogger,
|
|
154
|
+
mockReflectionUtils,
|
|
155
|
+
mockDrawableUtils
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
val realFabricUtils =
|
|
159
|
+
FabricTextViewUtils(
|
|
160
|
+
mockReactContext,
|
|
161
|
+
mockLogger,
|
|
162
|
+
mockDrawableUtils
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
testedUtils = spy(realUtils)
|
|
166
|
+
fabricTestedUtils = spy(realFabricUtils)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@Test
|
|
170
|
+
fun `M return wireframe W map() { even if not TextWireframeType }`(
|
|
171
|
+
@Mock mockImageWireframe: MobileSegment.Wireframe.ImageWireframe
|
|
172
|
+
) {
|
|
173
|
+
// When
|
|
174
|
+
val result =
|
|
175
|
+
testedUtils.mapTextViewToWireframes(
|
|
176
|
+
wireframes = listOf(mockImageWireframe),
|
|
177
|
+
view = mockTextView,
|
|
178
|
+
mappingContext = mockMappingContext
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
// Then
|
|
182
|
+
assertThat(result).contains(mockImageWireframe)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@Test
|
|
186
|
+
fun `M return textWireframe W map()`(
|
|
187
|
+
@Mock mockTextWireframe: MobileSegment.Wireframe.TextWireframe
|
|
188
|
+
) {
|
|
189
|
+
// Given
|
|
190
|
+
doReturn(mockTextWireframe)
|
|
191
|
+
.whenever(testedUtils)
|
|
192
|
+
.addReactNativeProperties(
|
|
193
|
+
originalWireframe = eq(mockTextWireframe),
|
|
194
|
+
view = eq(mockTextView),
|
|
195
|
+
pixelDensity = eq(0f)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
// When
|
|
199
|
+
val result =
|
|
200
|
+
testedUtils.mapTextViewToWireframes(
|
|
201
|
+
wireframes = listOf(mockTextWireframe),
|
|
202
|
+
view = mockTextView,
|
|
203
|
+
mappingContext = mockMappingContext
|
|
204
|
+
)[0] as MobileSegment.Wireframe.TextWireframe
|
|
205
|
+
|
|
206
|
+
// Then
|
|
207
|
+
assertThat(result).isEqualTo(mockTextWireframe)
|
|
110
208
|
}
|
|
111
209
|
|
|
112
210
|
// region addReactNativeProperties
|
|
@@ -118,16 +216,15 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
118
216
|
whenever(mockUiImplementation.resolveShadowNode(any())).thenReturn(null)
|
|
119
217
|
|
|
120
218
|
// When
|
|
121
|
-
val result =
|
|
219
|
+
val result = testedUtils.addReactNativeProperties(mockWireframe, mockTextView, 0f)
|
|
122
220
|
|
|
123
221
|
// Then
|
|
124
222
|
assertThat(result).isEqualTo(mockWireframe)
|
|
125
223
|
}
|
|
126
224
|
|
|
127
225
|
@Test
|
|
128
|
-
fun `M add drawable properties W addReactNativeProperties() { has reactBackgroundDrawable }`
|
|
129
|
-
|
|
130
|
-
) {
|
|
226
|
+
fun `M add drawable properties W addReactNativeProperties() { has reactBackgroundDrawable }`
|
|
227
|
+
(forge: Forge) {
|
|
131
228
|
// Given
|
|
132
229
|
val pixelDensity = 0f
|
|
133
230
|
val fakeBorderRadius = forge.aPositiveFloat()
|
|
@@ -150,18 +247,20 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
150
247
|
backgroundColor = formatAsRgba(fakeBorderColor),
|
|
151
248
|
opacity = 0f,
|
|
152
249
|
cornerRadius = fakeBorderRadius.toLong()
|
|
153
|
-
) to
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
250
|
+
) to
|
|
251
|
+
MobileSegment.ShapeBorder(
|
|
252
|
+
color = formatAsRgba(fakeBorderColor),
|
|
253
|
+
width = fakeBorderWidth.toLong()
|
|
254
|
+
)
|
|
157
255
|
)
|
|
158
256
|
|
|
159
257
|
// When
|
|
160
|
-
val result =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
258
|
+
val result =
|
|
259
|
+
testedUtils.addReactNativeProperties(
|
|
260
|
+
fakeWireframe,
|
|
261
|
+
mockTextView,
|
|
262
|
+
pixelDensity
|
|
263
|
+
)
|
|
165
264
|
|
|
166
265
|
// Then
|
|
167
266
|
assertThat(result.shapeStyle?.cornerRadius).isEqualTo(fakeBorderRadius.toLong())
|
|
@@ -178,8 +277,9 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
178
277
|
whenever(mockTextView.background).thenReturn(null)
|
|
179
278
|
|
|
180
279
|
// When
|
|
181
|
-
val result =
|
|
182
|
-
|
|
280
|
+
val result =
|
|
281
|
+
testedUtils
|
|
282
|
+
.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
183
283
|
|
|
184
284
|
// Then
|
|
185
285
|
assertThat(result.textStyle.family)
|
|
@@ -194,7 +294,7 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
194
294
|
.thenReturn(null)
|
|
195
295
|
|
|
196
296
|
// When
|
|
197
|
-
val result =
|
|
297
|
+
val result = testedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
198
298
|
|
|
199
299
|
// Then
|
|
200
300
|
assertThat(result.textStyle.family).isEqualTo(fakeWireframe.textStyle.family)
|
|
@@ -212,7 +312,7 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
212
312
|
whenever(mockTextAttributes.effectiveFontSize).thenReturn(fakeTextSize)
|
|
213
313
|
|
|
214
314
|
// When
|
|
215
|
-
val result =
|
|
315
|
+
val result = testedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
216
316
|
|
|
217
317
|
// Then
|
|
218
318
|
assertThat(result.textStyle.size).isEqualTo(fakeTextSize.toLong())
|
|
@@ -228,7 +328,7 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
228
328
|
.thenReturn(null)
|
|
229
329
|
|
|
230
330
|
// When
|
|
231
|
-
val result =
|
|
331
|
+
val result = testedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
232
332
|
|
|
233
333
|
// Then
|
|
234
334
|
assertThat(result.textStyle.size).isEqualTo(fakeWireframe.textStyle.size)
|
|
@@ -246,7 +346,7 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
246
346
|
.thenReturn(fakeTextColor)
|
|
247
347
|
|
|
248
348
|
// When
|
|
249
|
-
val result =
|
|
349
|
+
val result = testedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
250
350
|
|
|
251
351
|
// Then
|
|
252
352
|
assertThat(result.textStyle.color).isEqualTo(formatAsRgba(fakeTextColor))
|
|
@@ -264,7 +364,7 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
264
364
|
.thenReturn(fakeTextColor)
|
|
265
365
|
|
|
266
366
|
// When
|
|
267
|
-
val result =
|
|
367
|
+
val result = testedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
268
368
|
|
|
269
369
|
// Then
|
|
270
370
|
assertThat(result.textStyle.color).isEqualTo("#000000FF")
|
|
@@ -280,7 +380,7 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
280
380
|
.thenReturn(null)
|
|
281
381
|
|
|
282
382
|
// When
|
|
283
|
-
val result =
|
|
383
|
+
val result = testedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
284
384
|
|
|
285
385
|
// Then
|
|
286
386
|
assertThat(result.textStyle.color).isEqualTo(fakeWireframe.textStyle.color)
|
|
@@ -294,11 +394,44 @@ internal class ReactTextPropertiesResolverTest {
|
|
|
294
394
|
.thenReturn(MONOSPACE_FAMILY_NAME)
|
|
295
395
|
|
|
296
396
|
// When
|
|
297
|
-
val result =
|
|
397
|
+
val result = testedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
298
398
|
|
|
299
399
|
// Then
|
|
300
400
|
assertThat(result.textStyle.family).isNotEqualTo(MONOSPACE_FAMILY_NAME)
|
|
301
401
|
}
|
|
302
402
|
|
|
403
|
+
@Test
|
|
404
|
+
fun `M return fabric textStyle (color) W addReactNativeProperties`() {
|
|
405
|
+
val mockForegroundColorSpan = mock(ForegroundColorSpan::class.java)
|
|
406
|
+
whenever(mockForegroundColorSpan.foregroundColor).thenReturn(-1)
|
|
407
|
+
|
|
408
|
+
val spannable = mock(Spannable::class.java)
|
|
409
|
+
doReturn(spannable).whenever(fabricTestedUtils).getFieldFromView(any(), any())
|
|
410
|
+
|
|
411
|
+
whenever(spannable.getSpans(anyInt(), anyInt(), eq(ForegroundColorSpan::class.java)))
|
|
412
|
+
.thenReturn(
|
|
413
|
+
arrayOf(mockForegroundColorSpan)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
val result = fabricTestedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
417
|
+
assertThat(result.textStyle.color).isEqualTo("#ffffffff")
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// endregion
|
|
421
|
+
|
|
422
|
+
// region getUiManagerModule
|
|
423
|
+
@Test
|
|
424
|
+
fun `M return null W getUiManagerModule() { cannot get uiManagerModule }`() {
|
|
425
|
+
// Given
|
|
426
|
+
whenever(mockReactContext.getNativeModule(any<Class<NativeModule>>()))
|
|
427
|
+
.thenThrow(IllegalStateException())
|
|
428
|
+
|
|
429
|
+
// When
|
|
430
|
+
val uiManagerModule = testedUtils.getUiManagerModule()
|
|
431
|
+
|
|
432
|
+
// Then
|
|
433
|
+
assertThat(uiManagerModule).isNull()
|
|
434
|
+
}
|
|
435
|
+
|
|
303
436
|
// endregion
|
|
304
437
|
}
|
|
@@ -38,12 +38,12 @@ RCT_REMAP_METHOD(enable, withEnableReplaySampleRate:(double)replaySampleRate
|
|
|
38
38
|
|
|
39
39
|
RCT_EXPORT_METHOD(startRecording:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
|
|
40
40
|
{
|
|
41
|
-
[self
|
|
41
|
+
[self startRecording:resolve reject:reject];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
RCT_EXPORT_METHOD(stopRecording:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
|
|
45
45
|
{
|
|
46
|
-
[self
|
|
46
|
+
[self stopRecording:resolve reject:reject];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Thanks to this guard, we won't compile this code when we build for the old architecture.
|
|
@@ -85,11 +85,11 @@ RCT_EXPORT_METHOD(stopRecording:(RCTPromiseResolveBlock)resolve withRejecter:(RC
|
|
|
85
85
|
reject:reject];
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
- (void)
|
|
88
|
+
- (void)startRecording:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
89
89
|
[self.ddSessionReplayImplementation startRecordingWithResolve:resolve reject:reject];
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
- (void)
|
|
92
|
+
- (void)stopRecording:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
93
93
|
[self.ddSessionReplayImplementation stopRecordingWithResolve:resolve reject:reject];
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -15,17 +15,24 @@ public class DdSessionReplayImplementation: NSObject {
|
|
|
15
15
|
private lazy var sessionReplay: SessionReplayProtocol = sessionReplayProvider()
|
|
16
16
|
private let sessionReplayProvider: () -> SessionReplayProtocol
|
|
17
17
|
private let uiManager: RCTUIManager
|
|
18
|
+
private let fabricWrapper: RCTFabricWrapper
|
|
18
19
|
|
|
19
|
-
internal init(
|
|
20
|
+
internal init(
|
|
21
|
+
sessionReplayProvider: @escaping () -> SessionReplayProtocol,
|
|
22
|
+
uiManager: RCTUIManager,
|
|
23
|
+
fabricWrapper: RCTFabricWrapper
|
|
24
|
+
) {
|
|
20
25
|
self.sessionReplayProvider = sessionReplayProvider
|
|
21
26
|
self.uiManager = uiManager
|
|
27
|
+
self.fabricWrapper = fabricWrapper
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
@objc
|
|
25
31
|
public convenience init(bridge: RCTBridge) {
|
|
26
32
|
self.init(
|
|
27
33
|
sessionReplayProvider: { NativeSessionReplay() },
|
|
28
|
-
uiManager: bridge.uiManager
|
|
34
|
+
uiManager: bridge.uiManager,
|
|
35
|
+
fabricWrapper: RCTFabricWrapper()
|
|
29
36
|
)
|
|
30
37
|
}
|
|
31
38
|
|
|
@@ -44,6 +51,7 @@ public class DdSessionReplayImplementation: NSObject {
|
|
|
44
51
|
if (customEndpoint != "") {
|
|
45
52
|
customEndpointURL = URL(string: "\(customEndpoint)/api/v2/replay" as String)
|
|
46
53
|
}
|
|
54
|
+
|
|
47
55
|
var sessionReplayConfiguration = SessionReplay.Configuration(
|
|
48
56
|
replaySampleRate: Float(replaySampleRate),
|
|
49
57
|
textAndInputPrivacyLevel: convertTextAndInputPrivacy(textAndInputPrivacyLevel),
|
|
@@ -53,7 +61,9 @@ public class DdSessionReplayImplementation: NSObject {
|
|
|
53
61
|
customEndpoint: customEndpointURL
|
|
54
62
|
)
|
|
55
63
|
|
|
56
|
-
sessionReplayConfiguration.setAdditionalNodeRecorders([
|
|
64
|
+
sessionReplayConfiguration.setAdditionalNodeRecorders([
|
|
65
|
+
RCTTextViewRecorder(uiManager: uiManager, fabricWrapper: fabricWrapper)
|
|
66
|
+
])
|
|
57
67
|
|
|
58
68
|
if let core = DatadogSDKWrapper.shared.getCoreInstance() {
|
|
59
69
|
sessionReplay.enable(
|
|
@@ -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
|
+
#import <Foundation/Foundation.h>
|
|
7
|
+
#import "RCTTextPropertiesWrapper.h"
|
|
8
|
+
|
|
9
|
+
@interface RCTFabricWrapper : NSObject
|
|
10
|
+
|
|
11
|
+
- (nullable RCTTextPropertiesWrapper*)tryToExtractTextPropertiesFromView:(UIView* _Nonnull)view;
|
|
12
|
+
|
|
13
|
+
@end
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
#import "RCTFabricWrapper.h"
|
|
8
|
+
|
|
9
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
10
|
+
#import "RCTVersion.h"
|
|
11
|
+
|
|
12
|
+
#import <React-RCTFabric/React/RCTParagraphComponentView.h>
|
|
13
|
+
#import <React-RCTFabric/React/RCTConversions.h>
|
|
14
|
+
|
|
15
|
+
#if RCT_VERSION_MINOR > 74
|
|
16
|
+
#import <React-FabricComponents/react/renderer/components/text/ParagraphProps.h>
|
|
17
|
+
#else
|
|
18
|
+
#import <React-Fabric/react/renderer/components/text/ParagraphProps.h>
|
|
19
|
+
#endif
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
namespace rct = facebook::react;
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
@implementation RCTFabricWrapper
|
|
26
|
+
/**
|
|
27
|
+
* Extracts the text properties from the given UIView when the view is of type RCTParagraphComponentView, returns nil otherwise.
|
|
28
|
+
*/
|
|
29
|
+
- (nullable RCTTextPropertiesWrapper*)tryToExtractTextPropertiesFromView:(UIView *)view {
|
|
30
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
31
|
+
if (![view isKindOfClass:[RCTParagraphComponentView class]]) {
|
|
32
|
+
return nil;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Cast view to RCTParagraphComponentView
|
|
36
|
+
RCTParagraphComponentView* paragraphComponentView = (RCTParagraphComponentView *)view;
|
|
37
|
+
if (paragraphComponentView == nil) {
|
|
38
|
+
return nil;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Retrieve ParagraphProps from shared pointer
|
|
42
|
+
const rct::ParagraphProps* props = (rct::ParagraphProps*)paragraphComponentView.props.get();
|
|
43
|
+
if (props == nil) {
|
|
44
|
+
return nil;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Extract Attributes
|
|
48
|
+
RCTTextPropertiesWrapper* textPropertiesWrapper = [[RCTTextPropertiesWrapper alloc] init];
|
|
49
|
+
textPropertiesWrapper.text = [RCTFabricWrapper getTextFromView:paragraphComponentView];
|
|
50
|
+
textPropertiesWrapper.contentRect = paragraphComponentView.bounds;
|
|
51
|
+
|
|
52
|
+
rct::TextAttributes textAttributes = props->textAttributes;
|
|
53
|
+
textPropertiesWrapper.alignment = [RCTFabricWrapper getAlignmentFromAttributes:textAttributes];
|
|
54
|
+
textPropertiesWrapper.foregroundColor = [RCTFabricWrapper getForegroundColorFromAttributes:textAttributes];
|
|
55
|
+
textPropertiesWrapper.fontSize = [RCTFabricWrapper getFontSizeFromAttributes:textAttributes];
|
|
56
|
+
|
|
57
|
+
return textPropertiesWrapper;
|
|
58
|
+
#else
|
|
59
|
+
return nil;
|
|
60
|
+
#endif
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
64
|
+
+ (NSString* _Nonnull)getTextFromView:(RCTParagraphComponentView*)view {
|
|
65
|
+
if (view == nil || view.attributedText == nil) {
|
|
66
|
+
return RCTTextPropertiesDefaultText;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return view.attributedText.string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
+ (NSTextAlignment)getAlignmentFromAttributes:(rct::TextAttributes)textAttributes {
|
|
73
|
+
const rct::TextAlignment alignment = textAttributes.alignment.has_value() ?
|
|
74
|
+
textAttributes.alignment.value() :
|
|
75
|
+
rct::TextAlignment::Natural;
|
|
76
|
+
|
|
77
|
+
switch (alignment) {
|
|
78
|
+
case rct::TextAlignment::Natural:
|
|
79
|
+
return NSTextAlignmentNatural;
|
|
80
|
+
|
|
81
|
+
case rct::TextAlignment::Left:
|
|
82
|
+
return NSTextAlignmentLeft;
|
|
83
|
+
|
|
84
|
+
case rct::TextAlignment::Center:
|
|
85
|
+
return NSTextAlignmentCenter;
|
|
86
|
+
|
|
87
|
+
case rct::TextAlignment::Right:
|
|
88
|
+
return NSTextAlignmentRight;
|
|
89
|
+
|
|
90
|
+
case rct::TextAlignment::Justified:
|
|
91
|
+
return NSTextAlignmentJustified;
|
|
92
|
+
|
|
93
|
+
default:
|
|
94
|
+
return RCTTextPropertiesDefaultAlignment;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
+ (UIColor* _Nonnull)getForegroundColorFromAttributes:(rct::TextAttributes)textAttributes {
|
|
99
|
+
@try {
|
|
100
|
+
#if RCT_VERSION_MINOR > 73
|
|
101
|
+
rct::Color color = *textAttributes.foregroundColor;
|
|
102
|
+
UIColor* uiColor = (__bridge UIColor*)color.getUIColor().get();
|
|
103
|
+
if (uiColor != nil) {
|
|
104
|
+
return uiColor;
|
|
105
|
+
}
|
|
106
|
+
#else
|
|
107
|
+
return RCTUIColorFromSharedColor(textAttributes.foregroundColor);
|
|
108
|
+
#endif
|
|
109
|
+
} @catch (NSException *exception) {}
|
|
110
|
+
|
|
111
|
+
return RCTTextPropertiesDefaultForegroundColor;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
+ (CGFloat)getFontSizeFromAttributes:(rct::TextAttributes)textAttributes {
|
|
115
|
+
// Float is just an alias for CGFloat, but this could change in the future.
|
|
116
|
+
_Static_assert(sizeof(rct::Float) == sizeof(CGFloat), "Float and CGFloat are expected to have the same size.");
|
|
117
|
+
return isnan(textAttributes.fontSize) ? RCTTextPropertiesDefaultFontSize : (CGFloat)textAttributes.fontSize;
|
|
118
|
+
}
|
|
119
|
+
#endif
|
|
120
|
+
@end
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
@interface RCTTextPropertiesWrapper : NSObject
|
|
8
|
+
|
|
9
|
+
extern NSString* const RCTTextPropertiesDefaultText;
|
|
10
|
+
extern NSTextAlignment const RCTTextPropertiesDefaultAlignment;
|
|
11
|
+
extern UIColor* const RCTTextPropertiesDefaultForegroundColor;
|
|
12
|
+
extern CGFloat const RCTTextPropertiesDefaultFontSize;
|
|
13
|
+
extern CGRect const RCTTextPropertiesDefaultContentRect;
|
|
14
|
+
|
|
15
|
+
@property (nonatomic, strong, nonnull) NSString* text;
|
|
16
|
+
@property (nonatomic, assign) NSTextAlignment alignment;
|
|
17
|
+
@property (nonatomic, strong, nonnull) UIColor* foregroundColor;
|
|
18
|
+
@property (nonatomic, assign) CGFloat fontSize;
|
|
19
|
+
@property (nonatomic, assign) CGRect contentRect;
|
|
20
|
+
|
|
21
|
+
- (instancetype _Nonnull) init;
|
|
22
|
+
|
|
23
|
+
@end
|
|
@@ -0,0 +1,28 @@
|
|
|
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 "RCTTextPropertiesWrapper.h"
|
|
7
|
+
|
|
8
|
+
@implementation RCTTextPropertiesWrapper
|
|
9
|
+
|
|
10
|
+
NSString* const RCTTextPropertiesDefaultText = @"";
|
|
11
|
+
NSTextAlignment const RCTTextPropertiesDefaultAlignment = NSTextAlignmentNatural;
|
|
12
|
+
UIColor* const RCTTextPropertiesDefaultForegroundColor = [UIColor blackColor];
|
|
13
|
+
CGFloat const RCTTextPropertiesDefaultFontSize = 14.0;
|
|
14
|
+
CGRect const RCTTextPropertiesDefaultContentRect = CGRectZero;
|
|
15
|
+
|
|
16
|
+
- (instancetype)init {
|
|
17
|
+
self = [super init];
|
|
18
|
+
if (self) {
|
|
19
|
+
_text = RCTTextPropertiesDefaultText;
|
|
20
|
+
_alignment = RCTTextPropertiesDefaultAlignment;
|
|
21
|
+
_foregroundColor = RCTTextPropertiesDefaultForegroundColor;
|
|
22
|
+
_fontSize = RCTTextPropertiesDefaultFontSize;
|
|
23
|
+
_contentRect = RCTTextPropertiesDefaultContentRect;
|
|
24
|
+
}
|
|
25
|
+
return self;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@end
|