@truewatchtech/react-native-session-replay 0.4.1

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 (130) hide show
  1. package/FTSessionReplayReactNative.podspec +50 -0
  2. package/README.md +33 -0
  3. package/android/build.gradle +228 -0
  4. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  5. package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  6. package/android/gradle.properties +4 -0
  7. package/android/gradlew +185 -0
  8. package/android/gradlew.bat +89 -0
  9. package/android/settings.gradle +1 -0
  10. package/android/src/main/AndroidManifest.xml +4 -0
  11. package/android/src/main/AndroidManifestNew.xml +3 -0
  12. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/FTSessionReplayImpl.java +119 -0
  13. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/NoopTextPropertiesResolver.java +23 -0
  14. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.java +57 -0
  15. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/ShadowNodeWrapper.java +84 -0
  16. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/TextPropertiesResolver.java +20 -0
  17. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/extensions/ReactDrawablesExt.java +155 -0
  18. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/mappers/DefaultMapper.java +78 -0
  19. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/mappers/Pair.java +11 -0
  20. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/mappers/ReactEditTextMapper.java +136 -0
  21. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/mappers/ReactNativeImageViewMapper.java +117 -0
  22. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/mappers/ReactTextMapper.java +57 -0
  23. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/mappers/ReactViewGroupMapper.java +22 -0
  24. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/mappers/ReactViewModalMapper.java +21 -0
  25. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/resources/ReactDrawableCopier.java +35 -0
  26. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/utils/ColorUtils.java +24 -0
  27. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/utils/DrawableUtils.java +34 -0
  28. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/utils/ReactNativeUtils.java +18 -0
  29. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/utils/ReflectionUtils.java +43 -0
  30. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/utils/text/FabricTextViewUtils.java +69 -0
  31. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/utils/text/LegacyTextViewUtils.java +97 -0
  32. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/utils/text/TextViewUtils.java +184 -0
  33. package/android/src/main/java/com/ft/sdk/reactnative/sessionreplay/views/FTPrivacyView.java +113 -0
  34. package/android/src/newarch/java/com/ft/sdk/reactnative/sessionreplay/FTSessionReplayModule.java +20 -0
  35. package/android/src/newarch/java/com/ft/sdk/reactnative/sessionreplay/views/FTPrivacyViewManager.java +87 -0
  36. package/android/src/oldarch/java/com/ft/sdk/reactnative/sessionreplay/FTSessionReplayModule.java +25 -0
  37. package/android/src/oldarch/java/com/ft/sdk/reactnative/sessionreplay/views/FTPrivacyViewManager.java +70 -0
  38. package/android/src/rn69/java/com/ft/sdk/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.java +100 -0
  39. package/android/src/rn75/java/com/ft/sdk/reactnative/sessionreplay/extensions/LengthPercentageExt.java +27 -0
  40. package/android/src/rn75/java/com/ft/sdk/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.java +116 -0
  41. package/android/src/rn76/java/com/ft/sdk/reactnative/sessionreplay/extensions/LengthPercentageExt.java +38 -0
  42. package/android/src/rn76/java/com/ft/sdk/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.java +117 -0
  43. package/android/src/rn79/java/com/ft/sdk/reactnative/sessionreplay/extensions/LengthPercentageExt.java +38 -0
  44. package/android/src/rn79/java/com/ft/sdk/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.java +132 -0
  45. package/android/src/rn80/java/com/ft/sdk/reactnative/sessionreplay/extensions/ComputedBorderRadiusExt.java +58 -0
  46. package/android/src/rn80/java/com/ft/sdk/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.java +190 -0
  47. package/android/src/rnlegacy/java/com/ft/sdk/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.java +120 -0
  48. package/android/src/rnpost74/java/com/ft/sdk/reactnative/sessionreplay/FTSessionReplayPackage.java +58 -0
  49. package/android/src/rnpre74/java/com/ft/sdk/reactnative/sessionreplay/FTSessionReplayPackage.java +30 -0
  50. package/ios/FTPrivacyViewFabric.h +22 -0
  51. package/ios/FTPrivacyViewFabric.mm +84 -0
  52. package/ios/FTPrivacyViewPaper.h +17 -0
  53. package/ios/FTPrivacyViewPaper.mm +85 -0
  54. package/ios/FTRCTFabricWrapper.h +13 -0
  55. package/ios/FTRCTFabricWrapper.mm +113 -0
  56. package/ios/FTRCTTextPropertiesWrapper.h +24 -0
  57. package/ios/FTRCTTextPropertiesWrapper.mm +28 -0
  58. package/ios/FTRCTTextViewRecorder.h +33 -0
  59. package/ios/FTRCTTextViewRecorder.m +172 -0
  60. package/ios/FTReactNativeSessionReplay.h +13 -0
  61. package/ios/FTReactNativeSessionReplay.mm +85 -0
  62. package/ios/FTSessionReplayReactNative.xcodeproj/project.pbxproj +304 -0
  63. package/lib/commonjs/components/SessionReplayView/HideView.js +19 -0
  64. package/lib/commonjs/components/SessionReplayView/HideView.js.map +1 -0
  65. package/lib/commonjs/components/SessionReplayView/MaskAllView.js +25 -0
  66. package/lib/commonjs/components/SessionReplayView/MaskAllView.js.map +1 -0
  67. package/lib/commonjs/components/SessionReplayView/MaskNoneView.js +23 -0
  68. package/lib/commonjs/components/SessionReplayView/MaskNoneView.js.map +1 -0
  69. package/lib/commonjs/components/SessionReplayView/PrivacyView.js +28 -0
  70. package/lib/commonjs/components/SessionReplayView/PrivacyView.js.map +1 -0
  71. package/lib/commonjs/components/SessionReplayView/index.js +29 -0
  72. package/lib/commonjs/components/SessionReplayView/index.js.map +1 -0
  73. package/lib/commonjs/ft_session_replay.js +69 -0
  74. package/lib/commonjs/ft_session_replay.js.map +1 -0
  75. package/lib/commonjs/index.js +50 -0
  76. package/lib/commonjs/index.js.map +1 -0
  77. package/lib/commonjs/specs/FTPrivacyViewNative.js +29 -0
  78. package/lib/commonjs/specs/FTPrivacyViewNative.js.map +1 -0
  79. package/lib/commonjs/specs/FTPrivacyViewNativeComponent.js +10 -0
  80. package/lib/commonjs/specs/FTPrivacyViewNativeComponent.js.map +1 -0
  81. package/lib/commonjs/specs/NativeFTReactNativeSessionReplay.js +10 -0
  82. package/lib/commonjs/specs/NativeFTReactNativeSessionReplay.js.map +1 -0
  83. package/lib/commonjs/types.js +6 -0
  84. package/lib/commonjs/types.js.map +1 -0
  85. package/lib/module/components/SessionReplayView/HideView.js +12 -0
  86. package/lib/module/components/SessionReplayView/HideView.js.map +1 -0
  87. package/lib/module/components/SessionReplayView/MaskAllView.js +18 -0
  88. package/lib/module/components/SessionReplayView/MaskAllView.js.map +1 -0
  89. package/lib/module/components/SessionReplayView/MaskNoneView.js +16 -0
  90. package/lib/module/components/SessionReplayView/MaskNoneView.js.map +1 -0
  91. package/lib/module/components/SessionReplayView/PrivacyView.js +21 -0
  92. package/lib/module/components/SessionReplayView/PrivacyView.js.map +1 -0
  93. package/lib/module/components/SessionReplayView/index.js +23 -0
  94. package/lib/module/components/SessionReplayView/index.js.map +1 -0
  95. package/lib/module/ft_session_replay.js +67 -0
  96. package/lib/module/ft_session_replay.js.map +1 -0
  97. package/lib/module/index.js +4 -0
  98. package/lib/module/index.js.map +1 -0
  99. package/lib/module/specs/FTPrivacyViewNative.js +24 -0
  100. package/lib/module/specs/FTPrivacyViewNative.js.map +1 -0
  101. package/lib/module/specs/FTPrivacyViewNativeComponent.js +3 -0
  102. package/lib/module/specs/FTPrivacyViewNativeComponent.js.map +1 -0
  103. package/lib/module/specs/NativeFTReactNativeSessionReplay.js +5 -0
  104. package/lib/module/specs/NativeFTReactNativeSessionReplay.js.map +1 -0
  105. package/lib/module/types.js +2 -0
  106. package/lib/module/types.js.map +1 -0
  107. package/lib/typescript/components/SessionReplayView/HideView.d.ts +7 -0
  108. package/lib/typescript/components/SessionReplayView/MaskAllView.d.ts +8 -0
  109. package/lib/typescript/components/SessionReplayView/MaskNoneView.d.ts +7 -0
  110. package/lib/typescript/components/SessionReplayView/PrivacyView.d.ts +12 -0
  111. package/lib/typescript/components/SessionReplayView/index.d.ts +22 -0
  112. package/lib/typescript/ft_session_replay.d.ts +61 -0
  113. package/lib/typescript/index.d.ts +3 -0
  114. package/lib/typescript/specs/FTPrivacyViewNative.d.ts +2 -0
  115. package/lib/typescript/specs/FTPrivacyViewNativeComponent.d.ts +10 -0
  116. package/lib/typescript/specs/NativeFTReactNativeSessionReplay.d.ts +11 -0
  117. package/lib/typescript/types.d.ts +8 -0
  118. package/package.json +97 -0
  119. package/scripts/set-ios-rn-version.js +47 -0
  120. package/src/components/SessionReplayView/HideView.tsx +15 -0
  121. package/src/components/SessionReplayView/MaskAllView.tsx +35 -0
  122. package/src/components/SessionReplayView/MaskNoneView.tsx +26 -0
  123. package/src/components/SessionReplayView/PrivacyView.tsx +40 -0
  124. package/src/components/SessionReplayView/index.ts +26 -0
  125. package/src/ft_session_replay.tsx +78 -0
  126. package/src/index.tsx +19 -0
  127. package/src/specs/FTPrivacyViewNative.ts +32 -0
  128. package/src/specs/FTPrivacyViewNativeComponent.ts +14 -0
  129. package/src/specs/NativeFTReactNativeSessionReplay.ts +13 -0
  130. package/src/types.ts +9 -0
@@ -0,0 +1,119 @@
1
+ package com.ft.sdk.reactnative.sessionreplay;
2
+
3
+ import com.ft.sdk.reactnative.sessionreplay.utils.text.TextViewUtils;
4
+ import com.ft.sdk.reactnative.sessionreplay.utils.ReactNativeUtils;
5
+ import com.facebook.react.bridge.Promise;
6
+ import com.facebook.react.bridge.ReactContext;
7
+ import com.facebook.react.bridge.ReactMethod;
8
+ import com.facebook.react.bridge.ReadableArray;
9
+ import com.facebook.react.bridge.ReadableMap;
10
+ import com.ft.sdk.FTSdk;
11
+ import com.ft.sdk.SessionReplayManager;
12
+ import com.ft.sdk.sessionreplay.FTSessionReplayConfig;
13
+ import com.ft.sdk.sessionreplay.SessionReplayPrivacy;
14
+ import com.ft.sdk.sessionreplay.TouchPrivacy;
15
+ import com.ft.sdk.sessionreplay.ImagePrivacy;
16
+ import com.ft.sdk.sessionreplay.TextAndInputPrivacy;
17
+
18
+ import java.util.HashMap;
19
+
20
+ public class FTSessionReplayImpl {
21
+
22
+ public static final String NAME = "FTReactNativeSessionReplay";
23
+
24
+
25
+ @ReactMethod
26
+ public void sessionReplayConfig(ReadableMap context, Promise promise, ReactContext reactContext) {
27
+ HashMap<String, Object> map = context.toHashMap();
28
+
29
+ Double sampleRate = map.get("sampleRate") instanceof Double ? (Double) map.get("sampleRate") : null;
30
+ Double sessionReplayOnErrorSampleRate = map.get("sessionReplayOnErrorSampleRate") instanceof Double ? (Double) map.get("sessionReplayOnErrorSampleRate") : null;
31
+ Integer privacy = ReactNativeUtils.convertToNativeInt(map.get("privacy"));
32
+ String touchPrivacy = map.get("touchPrivacy") instanceof String ? (String) map.get("touchPrivacy") : null;
33
+ String textAndInputPrivacy = map.get("textAndInputPrivacy") instanceof String ? (String) map.get("textAndInputPrivacy") : null;
34
+ String imagePrivacy = map.get("imagePrivacy") instanceof String ? (String) map.get("imagePrivacy") : null;
35
+ Object enableLinkRUMKeysObj = map.get("enableLinkRUMKeys");
36
+
37
+ FTSessionReplayConfig sessionReplayConfig = new FTSessionReplayConfig();
38
+
39
+ if (sampleRate != null) {
40
+ sessionReplayConfig.setSampleRate(sampleRate.floatValue());
41
+ }
42
+
43
+ if (sessionReplayOnErrorSampleRate != null) {
44
+ sessionReplayConfig.setSessionReplayOnErrorSampleRate(sessionReplayOnErrorSampleRate.floatValue());
45
+ }
46
+
47
+ // Handle deprecated privacy setting for backward compatibility
48
+ if (privacy != null) {
49
+ SessionReplayPrivacy sessionReplayPrivacy = switch (privacy) {
50
+ case 0 -> SessionReplayPrivacy.MASK;
51
+ case 1 -> SessionReplayPrivacy.ALLOW;
52
+ case 2 -> SessionReplayPrivacy.MASK_USER_INPUT;
53
+ default -> null;
54
+ };
55
+ sessionReplayConfig.setPrivacy(sessionReplayPrivacy);
56
+ }
57
+
58
+ // Handle fine-grained privacy settings (overrides deprecated privacy setting if provided)
59
+ if (touchPrivacy != null) {
60
+ switch (touchPrivacy) {
61
+ case "SHOW":
62
+ sessionReplayConfig.setTouchPrivacy(TouchPrivacy.SHOW);
63
+ break;
64
+ case "HIDE":
65
+ sessionReplayConfig.setTouchPrivacy(TouchPrivacy.HIDE);
66
+ break;
67
+ }
68
+ }
69
+
70
+ if (textAndInputPrivacy != null) {
71
+ switch (textAndInputPrivacy) {
72
+ case "MASK_SENSITIVE_INPUTS":
73
+ sessionReplayConfig.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS);
74
+ break;
75
+ case "MASK_ALL_INPUTS":
76
+ sessionReplayConfig.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL_INPUTS);
77
+ break;
78
+ case "MASK_ALL":
79
+ sessionReplayConfig.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL);
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (imagePrivacy != null) {
85
+ switch (imagePrivacy) {
86
+ case "MASK_NON_BUNDLED_ONLY":
87
+ // MASK_NON_BUNDLED_ONLY (iOS) maps to MASK_LARGE_ONLY (Android)
88
+ sessionReplayConfig.setImagePrivacy(ImagePrivacy.MASK_LARGE_ONLY);
89
+ break;
90
+ case "MASK_ALL":
91
+ // MASK_ALL
92
+ sessionReplayConfig.setImagePrivacy(ImagePrivacy.MASK_ALL);
93
+ break;
94
+ case "MASK_NONE":
95
+ // MASK_NONE
96
+ sessionReplayConfig.setImagePrivacy(ImagePrivacy.MASK_NONE);
97
+ break;
98
+ }
99
+ }
100
+
101
+ // Handle enableLinkRUMKeys
102
+ if (enableLinkRUMKeysObj instanceof ReadableArray rumKeysArray) {
103
+ String[] rumKeys = new String[rumKeysArray.size()];
104
+ for (int i = 0; i < rumKeysArray.size(); i++) {
105
+ rumKeys[i] = rumKeysArray.getString(i);
106
+ }
107
+ sessionReplayConfig.enableLinkRUMKeys(rumKeys);
108
+ }
109
+
110
+ TextViewUtils textViewUtils = TextViewUtils.create(reactContext,
111
+ SessionReplayManager.get().getInternalLogger());
112
+ sessionReplayConfig.addExtensionSupport(
113
+ new ReactNativeSessionReplayExtensionSupport(textViewUtils
114
+ )
115
+ ).setDelayInit(true);
116
+ FTSdk.initSessionReplayConfig(sessionReplayConfig);
117
+ promise.resolve(null);
118
+ }
119
+ }
@@ -0,0 +1,23 @@
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.ft.sdk.reactnative.sessionreplay;
10
+
11
+ import android.widget.TextView;
12
+ import com.ft.sdk.sessionreplay.model.TextWireframe;
13
+
14
+ public class NoopTextPropertiesResolver implements TextPropertiesResolver {
15
+ @Override
16
+ public TextWireframe addReactNativeProperties(
17
+ TextWireframe originalWireframe,
18
+ TextView view,
19
+ float pixelDensity
20
+ ) {
21
+ return originalWireframe;
22
+ }
23
+ }
@@ -0,0 +1,57 @@
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.ft.sdk.reactnative.sessionreplay;
8
+
9
+ import com.ft.sdk.reactnative.sessionreplay.mappers.ReactEditTextMapper;
10
+ import com.ft.sdk.reactnative.sessionreplay.mappers.ReactNativeImageViewMapper;
11
+ import com.ft.sdk.reactnative.sessionreplay.mappers.ReactTextMapper;
12
+ import com.ft.sdk.reactnative.sessionreplay.mappers.ReactViewGroupMapper;
13
+ import com.ft.sdk.reactnative.sessionreplay.mappers.ReactViewModalMapper;
14
+ import com.ft.sdk.reactnative.sessionreplay.utils.text.TextViewUtils;
15
+ import com.facebook.react.views.image.ReactImageView;
16
+ import com.facebook.react.views.modal.ReactModalHostView;
17
+ import com.facebook.react.views.text.ReactTextView;
18
+ import com.facebook.react.views.textinput.ReactEditText;
19
+ import com.facebook.react.views.view.ReactViewGroup;
20
+ import com.ft.sdk.sessionreplay.ExtensionSupport;
21
+ import com.ft.sdk.sessionreplay.MapperTypeWrapper;
22
+ import com.ft.sdk.sessionreplay.recorder.OptionSelectorDetector;
23
+ import com.ft.sdk.sessionreplay.utils.DrawableToColorMapper;
24
+
25
+ import java.util.ArrayList;
26
+ import java.util.Arrays;
27
+ import java.util.Collections;
28
+ import java.util.List;
29
+
30
+ public class ReactNativeSessionReplayExtensionSupport implements ExtensionSupport {
31
+ private final TextViewUtils textViewUtils;
32
+
33
+ public ReactNativeSessionReplayExtensionSupport(TextViewUtils textViewUtils) {
34
+ this.textViewUtils = textViewUtils;
35
+ }
36
+
37
+ @Override
38
+ public List<MapperTypeWrapper<?>> getCustomViewMappers() {
39
+ return Arrays.asList(
40
+ new MapperTypeWrapper<>(ReactImageView.class, new ReactNativeImageViewMapper()),
41
+ new MapperTypeWrapper<>(ReactViewGroup.class, new ReactViewGroupMapper()),
42
+ new MapperTypeWrapper<>(ReactTextView.class, new ReactTextMapper(textViewUtils)),
43
+ new MapperTypeWrapper<>(ReactEditText.class, new ReactEditTextMapper(textViewUtils)),
44
+ new MapperTypeWrapper<>(ReactModalHostView.class, new ReactViewModalMapper())
45
+ );
46
+ }
47
+
48
+ @Override
49
+ public List<OptionSelectorDetector> getOptionSelectorDetectors() {
50
+ return new ArrayList<>();
51
+ }
52
+
53
+ @Override
54
+ public List<DrawableToColorMapper> getCustomDrawableMapper() {
55
+ return new ArrayList<>();
56
+ }
57
+ }
@@ -0,0 +1,84 @@
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.ft.sdk.reactnative.sessionreplay;
10
+
11
+ import androidx.annotation.VisibleForTesting;
12
+ import com.ft.sdk.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
+
18
+ import java.util.concurrent.CountDownLatch;
19
+ import java.util.concurrent.TimeUnit;
20
+
21
+ public class ShadowNodeWrapper {
22
+ private final ReactShadowNode<?> shadowNode;
23
+ private final ReflectionUtils reflectionUtils;
24
+
25
+ public static final String UI_IMPLEMENTATION_FIELD_NAME = "mUIImplementation";
26
+
27
+ public ShadowNodeWrapper(ReactShadowNode<?> shadowNode, ReflectionUtils reflectionUtils) {
28
+ this.shadowNode = shadowNode;
29
+ this.reflectionUtils = reflectionUtils;
30
+ }
31
+
32
+ public Object getDeclaredShadowNodeField(String fieldName) {
33
+ if (shadowNode != null) {
34
+ return reflectionUtils.getDeclaredField(shadowNode, fieldName);
35
+ }
36
+ return null;
37
+ }
38
+
39
+ public static ShadowNodeWrapper getShadowNodeWrapper(
40
+ ReactContext reactContext,
41
+ UIManagerModule uiManagerModule,
42
+ ReflectionUtils reflectionUtils,
43
+ int viewId
44
+ ) {
45
+ if (reactContext == null) {
46
+ return null;
47
+ }
48
+
49
+ CountDownLatch countDownLatch = new CountDownLatch(1);
50
+ final ReactShadowNode<?>[] target = new ReactShadowNode[1];
51
+
52
+ Runnable shadowNodeRunnable = new Runnable() {
53
+ @Override
54
+ public void run() {
55
+ ReactShadowNode<?> node = resolveShadowNode(reflectionUtils, uiManagerModule, viewId);
56
+ if (node != null) {
57
+ target[0] = node;
58
+ }
59
+ countDownLatch.countDown();
60
+ }
61
+ };
62
+
63
+ reactContext.runOnNativeModulesQueueThread(shadowNodeRunnable);
64
+ try {
65
+ countDownLatch.await(5, TimeUnit.SECONDS);
66
+ } catch (InterruptedException e) {
67
+ Thread.currentThread().interrupt();
68
+ }
69
+
70
+ if (target[0] == null) {
71
+ return null;
72
+ }
73
+
74
+ return new ShadowNodeWrapper(target[0], reflectionUtils);
75
+ }
76
+
77
+ private static ReactShadowNode<?> resolveShadowNode(ReflectionUtils reflectionUtils, UIManagerModule uiManagerModule, int tag) {
78
+ if (uiManagerModule == null ) {
79
+ return null;
80
+ }
81
+ UIImplementation uiManagerImplementation = (UIImplementation) reflectionUtils.getDeclaredField(uiManagerModule, UI_IMPLEMENTATION_FIELD_NAME);
82
+ return uiManagerImplementation != null ? uiManagerImplementation.resolveShadowNode(tag) : null;
83
+ }
84
+ }
@@ -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.ft.sdk.reactnative.sessionreplay;
10
+
11
+ import android.widget.TextView;
12
+ import com.ft.sdk.sessionreplay.model.TextWireframe;
13
+
14
+ public interface TextPropertiesResolver {
15
+ TextWireframe addReactNativeProperties(
16
+ TextWireframe originalWireframe,
17
+ TextView view,
18
+ float pixelDensity
19
+ );
20
+ }
@@ -0,0 +1,155 @@
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.ft.sdk.reactnative.extensions;
8
+
9
+ import android.content.res.Resources;
10
+ import android.graphics.Bitmap;
11
+ import android.graphics.Bitmap.Config;
12
+ import android.graphics.Canvas;
13
+ import android.graphics.drawable.BitmapDrawable;
14
+ import android.graphics.drawable.Drawable;
15
+ import android.graphics.drawable.ShapeDrawable;
16
+ import android.graphics.drawable.VectorDrawable;
17
+ import android.widget.ImageView;
18
+ import androidx.appcompat.graphics.drawable.DrawerArrowDrawable;
19
+ import com.facebook.drawee.drawable.ArrayDrawable;
20
+ import com.facebook.drawee.drawable.ForwardingDrawable;
21
+ import com.facebook.drawee.drawable.RoundedBitmapDrawable;
22
+ import com.facebook.drawee.drawable.ScaleTypeDrawable;
23
+ import com.facebook.drawee.drawable.ScalingUtils;
24
+
25
+ public class ReactDrawablesExt {
26
+ public static ImageView.ScaleType imageViewScaleType(ScaleTypeDrawable scaleTypeDrawable) {
27
+ ScalingUtils.ScaleType scaleType = scaleTypeDrawable.getScaleType();
28
+ if (scaleType == ScalingUtils.ScaleType.CENTER) return ImageView.ScaleType.CENTER;
29
+ if (scaleType == ScalingUtils.ScaleType.CENTER_CROP) return ImageView.ScaleType.CENTER_CROP;
30
+ if (scaleType == ScalingUtils.ScaleType.CENTER_INSIDE) return ImageView.ScaleType.CENTER_INSIDE;
31
+ if (scaleType == ScalingUtils.ScaleType.FIT_CENTER) return ImageView.ScaleType.FIT_CENTER;
32
+ if (scaleType == ScalingUtils.ScaleType.FIT_START) return ImageView.ScaleType.FIT_START;
33
+ if (scaleType == ScalingUtils.ScaleType.FIT_END) return ImageView.ScaleType.FIT_END;
34
+ if (scaleType == ScalingUtils.ScaleType.FIT_XY) return ImageView.ScaleType.FIT_XY;
35
+ return null;
36
+ }
37
+
38
+ public static ScaleTypeDrawable getScaleTypeDrawable(ArrayDrawable arrayDrawable) {
39
+ for (int i = 0; i < arrayDrawable.getNumberOfLayers(); i++) {
40
+ Drawable drawable = getDrawableOrNull(arrayDrawable, i);
41
+ if (drawable instanceof ScaleTypeDrawable) return (ScaleTypeDrawable) drawable;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ public static Drawable getDrawableOrNull(ArrayDrawable arrayDrawable, int index) {
47
+ try {
48
+ return arrayDrawable.getDrawable(index);
49
+ } catch (IllegalArgumentException e) {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ public static Bitmap tryToExtractBitmap(ForwardingDrawable forwardingDrawable, Resources resources) {
55
+ Drawable forwardedDrawable = forwardingDrawable.getDrawable();
56
+ if (forwardedDrawable != null) {
57
+ return tryToExtractBitmap(forwardedDrawable, resources);
58
+ } else {
59
+ return toBitmapOrNull(forwardingDrawable, forwardingDrawable.getIntrinsicWidth(), forwardingDrawable.getIntrinsicHeight(), Config.ARGB_8888);
60
+ }
61
+ }
62
+
63
+ public static Bitmap tryToExtractBitmap(RoundedBitmapDrawable roundedBitmapDrawable) {
64
+ try {
65
+ java.lang.reflect.Field field = RoundedBitmapDrawable.class.getDeclaredField("mBitmap");
66
+ field.setAccessible(true);
67
+ Object value = field.get(roundedBitmapDrawable);
68
+ if (value instanceof Bitmap) {
69
+ return (Bitmap) value;
70
+ }
71
+ } catch (NoSuchFieldException | IllegalAccessException e) {
72
+ // ignore
73
+ } catch (Exception e) {
74
+ // ignore
75
+ }
76
+ return toBitmapOrNull(roundedBitmapDrawable, roundedBitmapDrawable.getIntrinsicWidth(), roundedBitmapDrawable.getIntrinsicHeight(), Config.ARGB_8888);
77
+ }
78
+
79
+ public static Bitmap tryToExtractBitmap(BitmapDrawable bitmapDrawable, Resources resources) {
80
+ if (bitmapDrawable.getBitmap() != null) {
81
+ return bitmapDrawable.getBitmap();
82
+ }
83
+ if (bitmapDrawable.getConstantState() != null) {
84
+ Drawable copy = bitmapDrawable.getConstantState().newDrawable(resources);
85
+ if (copy instanceof BitmapDrawable) {
86
+ Bitmap b = ((BitmapDrawable) copy).getBitmap();
87
+ if (b != null) return b;
88
+ }
89
+ return toBitmapOrNull(copy, copy.getIntrinsicWidth(), copy.getIntrinsicHeight(), Config.ARGB_8888);
90
+ }
91
+ return null;
92
+ }
93
+
94
+ public static Bitmap tryToExtractBitmap(ArrayDrawable arrayDrawable, Resources resources) {
95
+ int width = 0, height = 0;
96
+ for (int i = 0; i < arrayDrawable.getNumberOfLayers(); i++) {
97
+ Drawable drawable = getDrawableOrNull(arrayDrawable, i);
98
+ if (drawable instanceof ScaleTypeDrawable) {
99
+ return tryToExtractBitmap((ScaleTypeDrawable) drawable, resources);
100
+ }
101
+ if (drawable != null && drawable.getIntrinsicWidth() * drawable.getIntrinsicHeight() > width * height) {
102
+ width = drawable.getIntrinsicWidth();
103
+ height = drawable.getIntrinsicHeight();
104
+ }
105
+ }
106
+ if (width > 0 && height > 0) {
107
+ return toBitmapOrNull(arrayDrawable, width, height, Config.ARGB_8888);
108
+ } else {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ public static Bitmap tryToExtractBitmap(Drawable drawable, Resources resources) {
114
+ if (drawable instanceof ArrayDrawable) {
115
+ return tryToExtractBitmap((ArrayDrawable) drawable, resources);
116
+ } else if (drawable instanceof ForwardingDrawable) {
117
+ return tryToExtractBitmap((ForwardingDrawable) drawable, resources);
118
+ } else if (drawable instanceof RoundedBitmapDrawable) {
119
+ return tryToExtractBitmap((RoundedBitmapDrawable) drawable);
120
+ } else if (drawable instanceof BitmapDrawable) {
121
+ return tryToExtractBitmap((BitmapDrawable) drawable, resources);
122
+ } else if (drawable instanceof VectorDrawable || drawable instanceof ShapeDrawable || drawable instanceof DrawerArrowDrawable) {
123
+ return toBitmapOrNull(drawable, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
124
+ } else {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ public static Bitmap toBitmapOrNull(Drawable drawable, int width, int height, Config config) {
130
+ if (drawable instanceof BitmapDrawable && ((BitmapDrawable) drawable).getBitmap() == null) {
131
+ return null;
132
+ }
133
+ return toBitmap(drawable, width, height, config);
134
+ }
135
+
136
+ public static Bitmap toBitmap(Drawable drawable, int width, int height, Config config) {
137
+ if (drawable instanceof BitmapDrawable) {
138
+ Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
139
+ if (bitmap == null) {
140
+ return Bitmap.createBitmap(width, height, config != null ? config : Config.ARGB_8888);
141
+ }
142
+ if (config == null || bitmap.getConfig() == config) {
143
+ if (width == bitmap.getWidth() && height == bitmap.getHeight()) {
144
+ return bitmap;
145
+ }
146
+ return Bitmap.createScaledBitmap(bitmap, width, height, true);
147
+ }
148
+ }
149
+ Bitmap bitmap = Bitmap.createBitmap(width, height, config != null ? config : Config.ARGB_8888);
150
+ drawable.setBounds(0, 0, width, height);
151
+ drawable.draw(new Canvas(bitmap));
152
+ // restore bounds if needed
153
+ return bitmap;
154
+ }
155
+ }
@@ -0,0 +1,78 @@
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.ft.sdk.reactnative.sessionreplay.mappers;
8
+
9
+ import android.graphics.drawable.Drawable;
10
+ import android.view.View;
11
+
12
+ import com.ft.sdk.reactnative.sessionreplay.utils.DrawableUtils;
13
+ import com.ft.sdk.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils;
14
+ import com.ft.sdk.sessionreplay.model.ShapeBorder;
15
+ import com.ft.sdk.sessionreplay.model.ShapeStyle;
16
+ import com.ft.sdk.sessionreplay.model.ShapeWireframe;
17
+ import com.ft.sdk.sessionreplay.model.Wireframe;
18
+ import com.ft.sdk.sessionreplay.recorder.MappingContext;
19
+ import com.ft.sdk.sessionreplay.recorder.mapper.BaseWireframeMapper;
20
+ import com.ft.sdk.sessionreplay.utils.AsyncJobStatusCallback;
21
+ import com.ft.sdk.sessionreplay.utils.DefaultColorStringFormatter;
22
+ import com.ft.sdk.sessionreplay.utils.DefaultViewBoundsResolver;
23
+ import com.ft.sdk.sessionreplay.utils.DefaultViewIdentifierResolver;
24
+ import com.ft.sdk.sessionreplay.utils.DrawableToColorMapperFactory;
25
+ import com.ft.sdk.sessionreplay.utils.GlobalBounds;
26
+ import com.ft.sdk.sessionreplay.utils.InternalLogger;
27
+
28
+ import java.util.Collections;
29
+ import java.util.List;
30
+
31
+ public class DefaultMapper<T extends View> extends BaseWireframeMapper<T> {
32
+ private final DrawableUtils drawableUtils;
33
+
34
+ public DefaultMapper(DrawableUtils drawableUtils) {
35
+ super(
36
+ DefaultViewIdentifierResolver.get(),
37
+ DefaultColorStringFormatter.get(),
38
+ DefaultViewBoundsResolver.get(),
39
+ DrawableToColorMapperFactory.getDefault()
40
+ );
41
+ this.drawableUtils = drawableUtils;
42
+ }
43
+
44
+ @Override
45
+ public List<Wireframe> map(
46
+ T view,
47
+ MappingContext mappingContext,
48
+ AsyncJobStatusCallback asyncJobStatusCallback,
49
+ InternalLogger internalLogger
50
+ ) {
51
+ float pixelDensity = mappingContext.getSystemInformation().getScreenDensity();
52
+ GlobalBounds viewGlobalBounds =
53
+ DefaultViewBoundsResolver.get().resolveViewGlobalBounds(view, pixelDensity);
54
+ Drawable backgroundDrawable = drawableUtils.getReactBackgroundFromDrawable(view.getBackground());
55
+
56
+ float opacity = view.getAlpha();
57
+
58
+ Pair<ShapeStyle, ShapeBorder> shapeAndBorder =
59
+ backgroundDrawable != null ?
60
+ drawableUtils.resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
61
+ : new Pair<>(null, null);
62
+
63
+ ShapeStyle shapeStyle = shapeAndBorder.first;
64
+ ShapeBorder border = shapeAndBorder.second;
65
+
66
+ ShapeWireframe shapeWireframe = new ShapeWireframe(
67
+ resolveViewId(view),
68
+ viewGlobalBounds.getX(),
69
+ viewGlobalBounds.getY(),
70
+ viewGlobalBounds.getWidth(),
71
+ viewGlobalBounds.getHeight(),
72
+ null,
73
+ shapeStyle,
74
+ border
75
+ );
76
+ return Collections.singletonList(shapeWireframe);
77
+ }
78
+ }
@@ -0,0 +1,11 @@
1
+ package com.ft.sdk.reactnative.sessionreplay.mappers;
2
+
3
+ public class Pair<F, S> {
4
+ public final F first;
5
+ public final S second;
6
+
7
+ public Pair(F first, S second) {
8
+ this.first = first;
9
+ this.second = second;
10
+ }
11
+ }
@@ -0,0 +1,136 @@
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.ft.sdk.reactnative.sessionreplay.mappers;
8
+
9
+ import android.graphics.drawable.Drawable;
10
+ import android.view.View;
11
+
12
+ import com.ft.sdk.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils;
13
+ import com.ft.sdk.reactnative.sessionreplay.utils.text.TextViewUtils;
14
+ import com.facebook.react.views.textinput.ReactEditText;
15
+ import com.ft.sdk.sessionreplay.model.ImageWireframe;
16
+ import com.ft.sdk.sessionreplay.model.PlaceholderWireframe;
17
+ import com.ft.sdk.sessionreplay.model.ShapeBorder;
18
+ import com.ft.sdk.sessionreplay.model.ShapeStyle;
19
+ import com.ft.sdk.sessionreplay.model.ShapeWireframe;
20
+ import com.ft.sdk.sessionreplay.model.Wireframe;
21
+ import com.ft.sdk.sessionreplay.recorder.MappingContext;
22
+ import com.ft.sdk.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapper;
23
+ import com.ft.sdk.sessionreplay.recorder.mapper.EditTextMapper;
24
+ import com.ft.sdk.sessionreplay.utils.AsyncJobStatusCallback;
25
+ import com.ft.sdk.sessionreplay.utils.DefaultColorStringFormatter;
26
+ import com.ft.sdk.sessionreplay.utils.DefaultViewBoundsResolver;
27
+ import com.ft.sdk.sessionreplay.utils.DefaultViewIdentifierResolver;
28
+ import com.ft.sdk.sessionreplay.utils.DrawableToColorMapperFactory;
29
+ import com.ft.sdk.sessionreplay.utils.GlobalBounds;
30
+ import com.ft.sdk.sessionreplay.utils.InternalLogger;
31
+
32
+ import java.util.ArrayList;
33
+ import java.util.List;
34
+
35
+ public class ReactEditTextMapper extends BaseAsyncBackgroundWireframeMapper<ReactEditText> {
36
+ private final TextViewUtils textViewUtils;
37
+ private final ReactViewBackgroundDrawableUtils drawableUtils = new ReactViewBackgroundDrawableUtils();
38
+ private final EditTextMapper editTextMapper;
39
+
40
+ public ReactEditTextMapper(TextViewUtils textViewUtils) {
41
+ super(
42
+ DefaultViewIdentifierResolver.get(),
43
+ DefaultColorStringFormatter.get(),
44
+ DefaultViewBoundsResolver.get(),
45
+ DrawableToColorMapperFactory.getDefault()
46
+ );
47
+ this.textViewUtils = textViewUtils;
48
+ this.editTextMapper = new EditTextMapper(
49
+ viewIdentifierResolver,
50
+ colorStringFormatter,
51
+ viewBoundsResolver,
52
+ drawableToColorMapper
53
+ );
54
+ }
55
+
56
+ @Override
57
+ public List<Wireframe> map(
58
+ ReactEditText view,
59
+ MappingContext mappingContext,
60
+ AsyncJobStatusCallback asyncJobStatusCallback,
61
+ InternalLogger internalLogger
62
+ ) {
63
+ List<Wireframe> backgroundWireframes = new ArrayList<>(super.map(view, mappingContext, asyncJobStatusCallback, internalLogger));
64
+ List<Wireframe> editTextWireframes = editTextMapper.map(
65
+ view,
66
+ mappingContext,
67
+ asyncJobStatusCallback,
68
+ internalLogger
69
+ );
70
+
71
+ List<Wireframe> filteredEditTextWireframes = new ArrayList<>();
72
+ for (Wireframe wf : editTextWireframes) {
73
+ if (!(wf instanceof ImageWireframe) && !(wf instanceof PlaceholderWireframe)) {
74
+ filteredEditTextWireframes.add(wf);
75
+ }
76
+ }
77
+ backgroundWireframes.addAll(filteredEditTextWireframes);
78
+ return textViewUtils.mapTextViewToWireframes(
79
+ backgroundWireframes,
80
+ view,
81
+ mappingContext
82
+ );
83
+ }
84
+
85
+ @Override
86
+ public Wireframe resolveBackgroundAsImageWireframe(
87
+ View view,
88
+ GlobalBounds bounds,
89
+ int width,
90
+ int height,
91
+ MappingContext mappingContext,
92
+ AsyncJobStatusCallback asyncJobStatusCallback
93
+ ) {
94
+ if (!(view instanceof ReactEditText)) {
95
+ return super.resolveBackgroundAsImageWireframe(
96
+ view,
97
+ bounds,
98
+ width,
99
+ height,
100
+ mappingContext,
101
+ asyncJobStatusCallback
102
+ );
103
+ }
104
+ Drawable backgroundDrawable = drawableUtils.getReactBackgroundFromDrawable(((ReactEditText) view).getBackground());
105
+ if (backgroundDrawable == null) {
106
+ return null;
107
+ }
108
+ float density = mappingContext.getSystemInformation().getScreenDensity();
109
+ Long identifier = viewIdentifierResolver.resolveChildUniqueIdentifier(
110
+ view,
111
+ "drawable0"
112
+ );
113
+ if (identifier == null) {
114
+ return null;
115
+ }
116
+ GlobalBounds globalBounds = viewBoundsResolver.resolveViewGlobalBounds(
117
+ view,
118
+ density
119
+ );
120
+ Pair<ShapeStyle, ShapeBorder> shapeAndBorder = drawableUtils.resolveShapeAndBorder(
121
+ backgroundDrawable,
122
+ ((ReactEditText) view).getAlpha(),
123
+ mappingContext.getSystemInformation().getScreenDensity()
124
+ );
125
+ return new ShapeWireframe(
126
+ identifier,
127
+ globalBounds.getX(),
128
+ globalBounds.getY(),
129
+ globalBounds.getWidth(),
130
+ globalBounds.getHeight(),
131
+ null,
132
+ shapeAndBorder.first,
133
+ shapeAndBorder.second
134
+ );
135
+ }
136
+ }