@truewatchtech/react-native-mobile 0.4.2 → 0.4.3

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 (153) hide show
  1. package/FTMobileReactNativeSDK.podspec +7 -5
  2. package/android/build.gradle +10 -3
  3. package/android/src/main/java/com/ft/sdk/reactnative/FTLogImpl.java +10 -2
  4. package/android/src/main/java/com/ft/sdk/reactnative/FTMobileImpl.java +45 -10
  5. package/android/src/main/java/com/ft/sdk/reactnative/FTRUMImpl.java +131 -6
  6. package/android/src/main/java/com/ft/sdk/reactnative/FTTraceImpl.java +10 -3
  7. package/android/src/main/java/com/ft/sdk/reactnative/JsLongTaskMonitor.java +281 -0
  8. package/android/src/main/java/com/ft/sdk/reactnative/ViewTiming.java +17 -0
  9. package/android/src/main/java/com/ft/sdk/reactnative/VisitorIdConfigMapper.java +50 -0
  10. package/android/src/main/java/com/ft/sdk/reactnative/utils/ReactNativeUtils.java +5 -12
  11. package/android/src/newarch/java/com/ft/sdk/reactnative/FTMobileModule.java +14 -5
  12. package/android/src/newarch/java/com/ft/sdk/reactnative/FTRUMModule.java +44 -1
  13. package/android/src/newarch/java/com/ft/sdk/reactnative/FTTraceModule.java +14 -0
  14. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTMobileModule.java +7 -2
  15. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTRUMModule.java +39 -1
  16. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTTraceModule.java +12 -0
  17. package/android/src/rnpost70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +21 -0
  18. package/android/src/rnpre70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +37 -0
  19. package/android/src/test/java/com/ft/sdk/reactnative/FTMobileImplTest.java +53 -0
  20. package/android/src/test/java/com/ft/sdk/reactnative/FTRUMModuleTest.java +117 -0
  21. package/android/src/test/java/com/ft/sdk/reactnative/JsLongTaskMonitorTest.java +446 -0
  22. package/android/src/test/java/com/ft/sdk/reactnative/utils/ReactNativeUtilsTest.java +33 -0
  23. package/ios/FTJSLongTaskMonitor.h +51 -0
  24. package/ios/FTJSLongTaskMonitor.m +250 -0
  25. package/ios/FTMobileReactNative.mm +54 -18
  26. package/ios/FTReactNativeLog.mm +9 -5
  27. package/ios/FTReactNativeRUM.mm +196 -31
  28. package/ios/FTReactNativeResource.h +4 -0
  29. package/ios/FTReactNativeResource.m +64 -0
  30. package/ios/FTReactNativeTrace.mm +59 -14
  31. package/ios/FTReactNativeUtils.m +10 -15
  32. package/ios/FTReactNativeViewTiming.h +18 -0
  33. package/ios/FTReactNativeWebSocket.h +10 -0
  34. package/ios/FTReactNativeWebSocket.mm +201 -0
  35. package/ios/FTWebSocketMetadataStore.h +25 -0
  36. package/ios/FTWebSocketMetadataStore.m +622 -0
  37. package/ios/FTWebSocketResourceData.h +20 -0
  38. package/ios/FTWebSocketResourceData.m +106 -0
  39. package/lib/commonjs/ft_logger.js +3 -1
  40. package/lib/commonjs/ft_logger.js.map +1 -1
  41. package/lib/commonjs/ft_mobile_agent.js +47 -4
  42. package/lib/commonjs/ft_mobile_agent.js.map +1 -1
  43. package/lib/commonjs/ft_rum.js +65 -5
  44. package/lib/commonjs/ft_rum.js.map +1 -1
  45. package/lib/commonjs/ft_tracing.js +14 -4
  46. package/lib/commonjs/ft_tracing.js.map +1 -1
  47. package/lib/commonjs/index.js +21 -0
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/rum/FTBabelInteractionTracking.js +72 -0
  50. package/lib/commonjs/rum/FTBabelInteractionTracking.js.map +1 -0
  51. package/lib/commonjs/rum/FTBabelUtils.js +87 -0
  52. package/lib/commonjs/rum/FTBabelUtils.js.map +1 -0
  53. package/lib/commonjs/rum/FTRumActionTracking.js +10 -39
  54. package/lib/commonjs/rum/FTRumActionTracking.js.map +1 -1
  55. package/lib/commonjs/rum/FTRumActionTrackingContext.js +50 -0
  56. package/lib/commonjs/rum/FTRumActionTrackingContext.js.map +1 -0
  57. package/lib/commonjs/rum/FTRumViewTracking.js +147 -0
  58. package/lib/commonjs/rum/FTRumViewTracking.js.map +1 -0
  59. package/lib/commonjs/rum/FTRumWebSocketNative.js +73 -0
  60. package/lib/commonjs/rum/FTRumWebSocketNative.js.map +1 -0
  61. package/lib/commonjs/rum/FTRumWebSocketTiming.js +73 -0
  62. package/lib/commonjs/rum/FTRumWebSocketTiming.js.map +1 -0
  63. package/lib/commonjs/rum/FTRumWebSocketTracking.js +506 -0
  64. package/lib/commonjs/rum/FTRumWebSocketTracking.js.map +1 -0
  65. package/lib/commonjs/rum/longTasksUtils.js +25 -0
  66. package/lib/commonjs/rum/longTasksUtils.js.map +1 -0
  67. package/lib/commonjs/specs/NativeFTMobileReactNative.js.map +1 -1
  68. package/lib/commonjs/specs/NativeFTReactNativeRUM.js.map +1 -1
  69. package/lib/commonjs/specs/NativeFTReactNativeTrace.js.map +1 -1
  70. package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js +11 -0
  71. package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js.map +1 -0
  72. package/lib/commonjs/version.js +1 -1
  73. package/lib/commonjs/version.js.map +1 -1
  74. package/lib/commonjs/visitorIdConfig.js +37 -0
  75. package/lib/commonjs/visitorIdConfig.js.map +1 -0
  76. package/lib/module/ft_logger.js +3 -1
  77. package/lib/module/ft_logger.js.map +1 -1
  78. package/lib/module/ft_mobile_agent.js +47 -4
  79. package/lib/module/ft_mobile_agent.js.map +1 -1
  80. package/lib/module/ft_rum.js +65 -5
  81. package/lib/module/ft_rum.js.map +1 -1
  82. package/lib/module/ft_tracing.js +14 -2
  83. package/lib/module/ft_tracing.js.map +1 -1
  84. package/lib/module/index.js +4 -1
  85. package/lib/module/index.js.map +1 -1
  86. package/lib/module/rum/FTBabelInteractionTracking.js +66 -0
  87. package/lib/module/rum/FTBabelInteractionTracking.js.map +1 -0
  88. package/lib/module/rum/FTBabelUtils.js +80 -0
  89. package/lib/module/rum/FTBabelUtils.js.map +1 -0
  90. package/lib/module/rum/FTRumActionTracking.js +10 -39
  91. package/lib/module/rum/FTRumActionTracking.js.map +1 -1
  92. package/lib/module/rum/FTRumActionTrackingContext.js +43 -0
  93. package/lib/module/rum/FTRumActionTrackingContext.js.map +1 -0
  94. package/lib/module/rum/FTRumViewTracking.js +140 -0
  95. package/lib/module/rum/FTRumViewTracking.js.map +1 -0
  96. package/lib/module/rum/FTRumWebSocketNative.js +62 -0
  97. package/lib/module/rum/FTRumWebSocketNative.js.map +1 -0
  98. package/lib/module/rum/FTRumWebSocketTiming.js +67 -0
  99. package/lib/module/rum/FTRumWebSocketTiming.js.map +1 -0
  100. package/lib/module/rum/FTRumWebSocketTracking.js +499 -0
  101. package/lib/module/rum/FTRumWebSocketTracking.js.map +1 -0
  102. package/lib/module/rum/longTasksUtils.js +18 -0
  103. package/lib/module/rum/longTasksUtils.js.map +1 -0
  104. package/lib/module/specs/NativeFTMobileReactNative.js.map +1 -1
  105. package/lib/module/specs/NativeFTReactNativeRUM.js.map +1 -1
  106. package/lib/module/specs/NativeFTReactNativeTrace.js.map +1 -1
  107. package/lib/module/specs/NativeFTReactNativeWebSocket.js +8 -0
  108. package/lib/module/specs/NativeFTReactNativeWebSocket.js.map +1 -0
  109. package/lib/module/version.js +1 -1
  110. package/lib/module/version.js.map +1 -1
  111. package/lib/module/visitorIdConfig.js +31 -0
  112. package/lib/module/visitorIdConfig.js.map +1 -0
  113. package/lib/typescript/ft_logger.d.ts +11 -1
  114. package/lib/typescript/ft_mobile_agent.d.ts +28 -0
  115. package/lib/typescript/ft_rum.d.ts +42 -5
  116. package/lib/typescript/ft_tracing.d.ts +9 -1
  117. package/lib/typescript/index.d.ts +5 -1
  118. package/lib/typescript/rum/FTBabelInteractionTracking.d.ts +30 -0
  119. package/lib/typescript/rum/FTBabelUtils.d.ts +1 -0
  120. package/lib/typescript/rum/FTRumActionTracking.d.ts +0 -2
  121. package/lib/typescript/rum/FTRumActionTrackingContext.d.ts +11 -0
  122. package/lib/typescript/rum/FTRumViewTracking.d.ts +32 -0
  123. package/lib/typescript/rum/FTRumWebSocketNative.d.ts +22 -0
  124. package/lib/typescript/rum/FTRumWebSocketTiming.d.ts +7 -0
  125. package/lib/typescript/rum/FTRumWebSocketTracking.d.ts +54 -0
  126. package/lib/typescript/rum/longTasksUtils.d.ts +8 -0
  127. package/lib/typescript/specs/NativeFTMobileReactNative.d.ts +15 -0
  128. package/lib/typescript/specs/NativeFTReactNativeRUM.d.ts +11 -1
  129. package/lib/typescript/specs/NativeFTReactNativeTrace.d.ts +12 -0
  130. package/lib/typescript/specs/NativeFTReactNativeWebSocket.d.ts +13 -0
  131. package/lib/typescript/version.d.ts +1 -1
  132. package/lib/typescript/visitorIdConfig.d.ts +11 -0
  133. package/package.json +6 -4
  134. package/src/ft_logger.tsx +11 -1
  135. package/src/ft_mobile_agent.tsx +79 -7
  136. package/src/ft_rum.tsx +111 -7
  137. package/src/ft_tracing.tsx +24 -2
  138. package/src/index.tsx +8 -0
  139. package/src/rum/FTBabelInteractionTracking.ts +124 -0
  140. package/src/rum/FTBabelUtils.tsx +99 -0
  141. package/src/rum/FTRumActionTracking.tsx +14 -43
  142. package/src/rum/FTRumActionTrackingContext.ts +68 -0
  143. package/src/rum/FTRumViewTracking.ts +179 -0
  144. package/src/rum/FTRumWebSocketNative.ts +107 -0
  145. package/src/rum/FTRumWebSocketTiming.ts +82 -0
  146. package/src/rum/FTRumWebSocketTracking.ts +722 -0
  147. package/src/rum/longTasksUtils.ts +17 -0
  148. package/src/specs/NativeFTMobileReactNative.ts +18 -0
  149. package/src/specs/NativeFTReactNativeRUM.ts +19 -1
  150. package/src/specs/NativeFTReactNativeTrace.ts +12 -0
  151. package/src/specs/NativeFTReactNativeWebSocket.ts +33 -0
  152. package/src/version.ts +1 -1
  153. package/src/visitorIdConfig.ts +123 -0
@@ -0,0 +1,281 @@
1
+ package com.ft.sdk.reactnative;
2
+
3
+ import android.view.Choreographer;
4
+
5
+ import com.facebook.react.bridge.ReactApplicationContext;
6
+ import com.ft.sdk.FTRUMGlobalManager;
7
+
8
+ import java.util.HashMap;
9
+ import java.util.Map;
10
+ import java.util.Collections;
11
+ import java.util.concurrent.atomic.AtomicBoolean;
12
+
13
+ final class JsLongTaskMonitor {
14
+
15
+ private static final double NANOS_PER_MILLISECOND = 1_000_000d;
16
+
17
+ interface Executor {
18
+ boolean runOnJsThread(Runnable runnable);
19
+ }
20
+
21
+ interface FrameCallback {
22
+ void doFrame(long frameTimeNanos);
23
+ }
24
+
25
+ interface FrameScheduler {
26
+ void postFrameCallback();
27
+
28
+ void removeFrameCallback();
29
+ }
30
+
31
+ interface FrameSchedulerFactory {
32
+ FrameScheduler create(FrameCallback callback);
33
+ }
34
+
35
+ interface Reporter {
36
+ void reportLongTask(long durationNanos, Map<String, Object> property);
37
+ }
38
+
39
+ private final Executor executor;
40
+ private final FrameSchedulerFactory schedulerFactory;
41
+ private final Reporter reporter;
42
+ private final Object stateLock = new Object();
43
+ private final FrameCallback frameCallback;
44
+
45
+ private volatile long thresholdNanos;
46
+ private volatile int generation;
47
+ private volatile boolean requestedRunning;
48
+ private volatile Map<String, Object> bridgeContext = Collections.emptyMap();
49
+
50
+ // Accessed only on the React Native JavaScript thread.
51
+ private FrameScheduler scheduler;
52
+ private int activeGeneration;
53
+ private long lastFrameTimeNanos;
54
+
55
+ static JsLongTaskMonitor create(ReactApplicationContext reactContext) {
56
+ return new JsLongTaskMonitor(
57
+ new ReactJsThreadExecutor(reactContext),
58
+ new ChoreographerSchedulerFactory(),
59
+ new NativeLongTaskReporter()
60
+ );
61
+ }
62
+
63
+ JsLongTaskMonitor(
64
+ Executor executor,
65
+ FrameSchedulerFactory schedulerFactory,
66
+ Reporter reporter
67
+ ) {
68
+ this.executor = executor;
69
+ this.schedulerFactory = schedulerFactory;
70
+ this.reporter = reporter;
71
+ this.frameCallback = new FrameCallback() {
72
+ @Override
73
+ public void doFrame(long frameTimeNanos) {
74
+ handleFrame(frameTimeNanos);
75
+ }
76
+ };
77
+ }
78
+
79
+ void setThresholdMilliseconds(double thresholdMilliseconds) {
80
+ thresholdNanos = thresholdMilliseconds <= 0
81
+ ? 0
82
+ : (long) (thresholdMilliseconds * NANOS_PER_MILLISECOND);
83
+ if (thresholdNanos == 0) {
84
+ stop();
85
+ }
86
+ }
87
+
88
+ void setBridgeContext(Map<String, Object> context) {
89
+ bridgeContext = context == null
90
+ ? Collections.<String, Object>emptyMap()
91
+ : new HashMap<>(context);
92
+ }
93
+
94
+ void start() {
95
+ final int startGeneration;
96
+ synchronized (stateLock) {
97
+ if (thresholdNanos == 0) {
98
+ return;
99
+ }
100
+ requestedRunning = true;
101
+ startGeneration = ++generation;
102
+ }
103
+ try {
104
+ boolean accepted = executor.runOnJsThread(new Runnable() {
105
+ @Override
106
+ public void run() {
107
+ if (!isCurrentRequest(startGeneration, true)) {
108
+ return;
109
+ }
110
+ try {
111
+ if (scheduler != null) {
112
+ scheduler.removeFrameCallback();
113
+ }
114
+ scheduler = schedulerFactory.create(frameCallback);
115
+ activeGeneration = startGeneration;
116
+ lastFrameTimeNanos = 0;
117
+ scheduler.postFrameCallback();
118
+ } catch (IllegalStateException ignored) {
119
+ disableRequestOnJsThread(startGeneration);
120
+ }
121
+ }
122
+ });
123
+ if (!accepted) {
124
+ markRequestDisabled(startGeneration);
125
+ }
126
+ } catch (IllegalStateException ignored) {
127
+ markRequestDisabled(startGeneration);
128
+ }
129
+ }
130
+
131
+ void stop() {
132
+ stop(null);
133
+ }
134
+
135
+ // Unlike a lifecycle pause, SDK shutdown must prevent automatic restarts.
136
+ void disable(final Runnable completion) {
137
+ thresholdNanos = 0;
138
+ stop(completion);
139
+ }
140
+
141
+ void stop(final Runnable completion) {
142
+ final Runnable completeOnce = once(completion);
143
+ final int stopGeneration;
144
+ synchronized (stateLock) {
145
+ requestedRunning = false;
146
+ stopGeneration = ++generation;
147
+ }
148
+ try {
149
+ boolean accepted = executor.runOnJsThread(new Runnable() {
150
+ @Override
151
+ public void run() {
152
+ if (isCurrentRequest(stopGeneration, false)) {
153
+ try {
154
+ if (scheduler != null) {
155
+ scheduler.removeFrameCallback();
156
+ }
157
+ } catch (IllegalStateException ignored) {
158
+ // The request is already disabled, so only local state needs clearing.
159
+ }
160
+ scheduler = null;
161
+ activeGeneration = 0;
162
+ lastFrameTimeNanos = 0;
163
+ }
164
+ completeOnce.run();
165
+ }
166
+ });
167
+ if (!accepted) {
168
+ completeOnce.run();
169
+ }
170
+ } catch (IllegalStateException ignored) {
171
+ // The JavaScript queue may already be unavailable during teardown.
172
+ completeOnce.run();
173
+ }
174
+ }
175
+
176
+ private boolean isCurrentRequest(int requestGeneration, boolean running) {
177
+ synchronized (stateLock) {
178
+ return generation == requestGeneration && requestedRunning == running;
179
+ }
180
+ }
181
+
182
+ private void markRequestDisabled(int requestGeneration) {
183
+ synchronized (stateLock) {
184
+ if (generation == requestGeneration) {
185
+ requestedRunning = false;
186
+ }
187
+ }
188
+ }
189
+
190
+ private void disableRequestOnJsThread(int requestGeneration) {
191
+ markRequestDisabled(requestGeneration);
192
+ scheduler = null;
193
+ activeGeneration = 0;
194
+ lastFrameTimeNanos = 0;
195
+ }
196
+
197
+ private static Runnable once(final Runnable completion) {
198
+ final AtomicBoolean completed = new AtomicBoolean();
199
+ return new Runnable() {
200
+ @Override
201
+ public void run() {
202
+ if (completed.compareAndSet(false, true) && completion != null) {
203
+ completion.run();
204
+ }
205
+ }
206
+ };
207
+ }
208
+
209
+ private void handleFrame(long frameTimeNanos) {
210
+ if (!isCurrentRequest(activeGeneration, true) || scheduler == null) {
211
+ return;
212
+ }
213
+
214
+ if (lastFrameTimeNanos != 0) {
215
+ long durationNanos = frameTimeNanos - lastFrameTimeNanos;
216
+ if (durationNanos > thresholdNanos) {
217
+ try {
218
+ // Take a detection-time snapshot before handing the event to the SDK.
219
+ reporter.reportLongTask(durationNanos, new HashMap<>(bridgeContext));
220
+ } catch (RuntimeException ignored) {
221
+ // Reporting must not interrupt the JavaScript frame callback loop.
222
+ }
223
+ }
224
+ }
225
+ lastFrameTimeNanos = frameTimeNanos;
226
+
227
+ if (isCurrentRequest(activeGeneration, true) && scheduler != null) {
228
+ try {
229
+ scheduler.postFrameCallback();
230
+ } catch (IllegalStateException ignored) {
231
+ disableRequestOnJsThread(activeGeneration);
232
+ }
233
+ }
234
+ }
235
+
236
+ private static final class ChoreographerSchedulerFactory implements FrameSchedulerFactory {
237
+ @Override
238
+ public FrameScheduler create(FrameCallback callback) {
239
+ return new ChoreographerScheduler(Choreographer.getInstance(), callback);
240
+ }
241
+ }
242
+
243
+ private static final class ChoreographerScheduler implements FrameScheduler {
244
+ private final Choreographer choreographer;
245
+ private final Choreographer.FrameCallback callback;
246
+
247
+ private ChoreographerScheduler(
248
+ Choreographer choreographer,
249
+ final FrameCallback callback
250
+ ) {
251
+ this.choreographer = choreographer;
252
+ this.callback = new Choreographer.FrameCallback() {
253
+ @Override
254
+ public void doFrame(long frameTimeNanos) {
255
+ callback.doFrame(frameTimeNanos);
256
+ }
257
+ };
258
+ }
259
+
260
+ @Override
261
+ public void postFrameCallback() {
262
+ choreographer.postFrameCallback(callback);
263
+ }
264
+
265
+ @Override
266
+ public void removeFrameCallback() {
267
+ choreographer.removeFrameCallback(callback);
268
+ }
269
+ }
270
+
271
+ private static final class NativeLongTaskReporter implements Reporter {
272
+ @Override
273
+ public void reportLongTask(long durationNanos, Map<String, Object> property) {
274
+ FTRUMGlobalManager.get().addLongTask(
275
+ "",
276
+ durationNanos,
277
+ new HashMap<>(property)
278
+ );
279
+ }
280
+ }
281
+ }
@@ -0,0 +1,17 @@
1
+ package com.ft.sdk.reactnative;
2
+
3
+ final class ViewTiming {
4
+ private ViewTiming() {}
5
+
6
+ static long durationNanoseconds(double value) {
7
+ return (!Double.isNaN(value) && !Double.isInfinite(value)) && value > 0 && value < 0x1p63 ? (long) value : 0;
8
+ }
9
+
10
+ static long epochNanoseconds(double milliseconds) {
11
+ if ((Double.isNaN(milliseconds) || Double.isInfinite(milliseconds)) || milliseconds <= 0 || milliseconds >= 0x1p63 / 1000000.0) return 0;
12
+ long whole = (long) milliseconds;
13
+ long fraction = (long) ((milliseconds - whole) * 1000000.0);
14
+ if (whole > Long.MAX_VALUE / 1000000 || fraction > Long.MAX_VALUE - whole * 1000000) return 0;
15
+ return whole * 1000000 + fraction;
16
+ }
17
+ }
@@ -0,0 +1,50 @@
1
+ package com.ft.sdk.reactnative;
2
+
3
+ import com.ft.sdk.FTVisitorIdConfig;
4
+ import java.net.URI;
5
+ import java.util.List;
6
+ import java.util.Map;
7
+ import java.util.regex.Pattern;
8
+
9
+ final class VisitorIdConfigMapper {
10
+ private VisitorIdConfigMapper() {}
11
+
12
+ static FTVisitorIdConfig fromMap(Object value) {
13
+ if (value == null) return null;
14
+ if (!(value instanceof Map)) throw new IllegalArgumentException("visitorIdConfig must be an object");
15
+ Map<?, ?> map = (Map<?, ?>) value;
16
+ if (!(map.get("header") instanceof String)) throw new IllegalArgumentException("Visitor ID header is required");
17
+ FTVisitorIdConfig config = new FTVisitorIdConfig().setHeader((String) map.get("header"));
18
+ if (map.containsKey("ttlHours")) {
19
+ if (!(map.get("ttlHours") instanceof Number)) throw new IllegalArgumentException("Visitor ID TTL must be numeric");
20
+ double hours = ((Number) map.get("ttlHours")).doubleValue();
21
+ double milliseconds = hours * 3600000;
22
+ if (Double.isNaN(hours) || Double.isInfinite(hours) || milliseconds < 1 || milliseconds >= Long.MAX_VALUE) {
23
+ throw new IllegalArgumentException("Visitor ID TTL is out of range");
24
+ }
25
+ config.setTtlHours(hours);
26
+ }
27
+ int rules = 0;
28
+ for (String key : new String[]{"urlPrefixes", "regularExpressions"}) {
29
+ Object values = map.get(key);
30
+ if (values == null && !map.containsKey(key)) continue;
31
+ if (!(values instanceof List)) throw new IllegalArgumentException(key + " must be an array");
32
+ for (Object item : (List<?>) values) {
33
+ if (!(item instanceof String) || ((String) item).isEmpty()) throw new IllegalArgumentException("Invalid Visitor ID rule");
34
+ String rule = (String) item;
35
+ if ("urlPrefixes".equals(key)) {
36
+ URI uri = URI.create(rule);
37
+ if (uri.getHost() == null || !("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme()))) {
38
+ throw new IllegalArgumentException("Visitor ID prefix must be an HTTP(S) URL");
39
+ }
40
+ config.addMatch(rule);
41
+ } else {
42
+ config.addMatch(Pattern.compile(rule));
43
+ }
44
+ rules++;
45
+ }
46
+ }
47
+ if (rules == 0) throw new IllegalArgumentException("Visitor ID requires URL matching rules");
48
+ return config;
49
+ }
50
+ }
@@ -1,14 +1,13 @@
1
1
  package com.ft.sdk.reactnative.utils;
2
2
 
3
3
  import java.util.regex.Pattern;
4
- import java.util.regex.Matcher;
5
4
 
6
5
  public class ReactNativeUtils {
7
6
 
8
- private static final Pattern[] RN_DEV_INNER_URL_REGEX = {
9
- Pattern.compile("^http://((10|172|192).[0-9]+.[0-9]+.[0-9]+|localhost|127.0.0.1):808[0-9]/logs$"), // expo
10
- Pattern.compile("^http://localhost:808[0-9]/(hot|symbolicate|message|inspector).*$") // rn
11
- };
7
+ private static final Pattern RN_DEV_INNER_URL_PATTERN = Pattern.compile(
8
+ "^(?:https?|wss?)://(?:(?:10|172|192)(?:\\.[0-9]+){3}|localhost|127\\.0\\.0\\.1|\\[::1\\]):808[0-9]/(?:hot|symbolicate|message|inspector|status|assets|logs|debugger-proxy)(?:[/?#].*)?$",
9
+ Pattern.CASE_INSENSITIVE
10
+ );
12
11
 
13
12
  private ReactNativeUtils() {
14
13
  // Private constructor to prevent instantiation
@@ -48,12 +47,6 @@ public class ReactNativeUtils {
48
47
  * @return
49
48
  */
50
49
  public static boolean isReactNativeDevUrl(String url) {
51
- for (Pattern pattern : RN_DEV_INNER_URL_REGEX) {
52
- Matcher matcher = pattern.matcher(url);
53
- if (matcher.matches()) {
54
- return true;
55
- }
56
- }
57
- return false;
50
+ return url != null && RN_DEV_INNER_URL_PATTERN.matcher(url).matches();
58
51
  }
59
52
  }
@@ -39,6 +39,11 @@ public class FTMobileModule extends NativeFTMobileReactNativeSpec {
39
39
  impl.bindRUMUserData(userId, userName, userEmail, extra, promise);
40
40
  }
41
41
 
42
+ @ReactMethod
43
+ public void resetVisitorId(Promise promise) {
44
+ impl.resetVisitorId(promise);
45
+ }
46
+
42
47
  @ReactMethod
43
48
  public void unbindRUMUserData(Promise promise) {
44
49
  impl.unbindRUMUserData(promise);
@@ -81,23 +86,27 @@ public class FTMobileModule extends NativeFTMobileReactNativeSpec {
81
86
  }
82
87
 
83
88
  @ReactMethod
89
+ @Override
84
90
  public void updateRemoteConfig(Promise promise) {
85
- impl.updateRemoteConfig(promise);
91
+ impl.updateRemoteConfig(promise);
86
92
  }
87
93
 
88
94
  @ReactMethod
89
- public void updateRemoteConfigWithMiniUpdateInterval(int interval, @Nullable ReadableArray rules, Promise promise) {
90
- impl.updateRemoteConfigWithMiniUpdateInterval(interval, rules, promise);
95
+ @Override
96
+ public void updateRemoteConfigWithMiniUpdateInterval(double interval, @Nullable ReadableArray rules, Promise promise) {
97
+ impl.updateRemoteConfigWithMiniUpdateInterval((int) interval, rules, promise);
91
98
  }
92
99
 
93
100
  @ReactMethod
101
+ @Override
94
102
  public void addListener(String eventName) {
95
- impl.addListener(eventName);
103
+ // Required by NativeEventEmitter; Android does not need listener bookkeeping.
96
104
  }
97
105
 
98
106
  @ReactMethod
107
+ @Override
99
108
  public void removeListeners(double count) {
100
- impl.removeListeners(count);
109
+ // Required by NativeEventEmitter; Android does not need listener bookkeeping.
101
110
  }
102
111
 
103
112
  }
@@ -9,10 +9,11 @@ import com.facebook.react.bridge.ReadableMap;
9
9
 
10
10
  public class FTRUMModule extends NativeFTReactNativeRUMSpec {
11
11
 
12
- private final FTRUMImpl impl = new FTRUMImpl();
12
+ private final FTRUMImpl impl;
13
13
 
14
14
  public FTRUMModule(ReactApplicationContext reactContext) {
15
15
  super(reactContext);
16
+ impl = new FTRUMImpl(reactContext);
16
17
  }
17
18
 
18
19
  @Override
@@ -21,6 +22,30 @@ public class FTRUMModule extends NativeFTReactNativeRUMSpec {
21
22
  impl.setConfig(context, promise);
22
23
  }
23
24
 
25
+ @Override
26
+ @ReactMethod
27
+ public void stopLongTaskTracking(Promise promise) {
28
+ impl.stopLongTaskTracking(promise);
29
+ }
30
+
31
+ @Override
32
+ @ReactMethod(isBlockingSynchronousMethod = true)
33
+ public boolean setLongTaskContext(ReadableMap context) {
34
+ return impl.setLongTaskContext(context);
35
+ }
36
+
37
+ @Override
38
+ public void invalidate() {
39
+ impl.destroy();
40
+ super.invalidate();
41
+ }
42
+
43
+ @Override
44
+ public void onCatalystInstanceDestroy() {
45
+ impl.destroy();
46
+ super.onCatalystInstanceDestroy();
47
+ }
48
+
24
49
  @Override
25
50
  @ReactMethod
26
51
  public void startAction(String actionName, String actionType, ReadableMap map, Promise promise) {
@@ -33,6 +58,24 @@ public class FTRUMModule extends NativeFTReactNativeRUMSpec {
33
58
  impl.addAction(actionName, actionType, map, promise);
34
59
  }
35
60
 
61
+ @Override
62
+ @ReactMethod
63
+ public void onCreateViewWithLoadStart(String viewName, double duration, double loadStartEpochMs, Promise promise) {
64
+ impl.onCreateViewWithLoadStart(viewName, duration, loadStartEpochMs, promise);
65
+ }
66
+
67
+ @Override
68
+ @ReactMethod
69
+ public void startViewWithTrackingType(String viewName, ReadableMap map, double trackingType, Promise promise) {
70
+ impl.startViewWithTrackingType(viewName, map, trackingType, promise);
71
+ }
72
+
73
+ @Override
74
+ @ReactMethod
75
+ public void updateViewLoadingTime(double duration, Promise promise) {
76
+ impl.updateViewLoadingTime(duration, promise);
77
+ }
78
+
36
79
  @Override
37
80
  @ReactMethod
38
81
  public void onCreateView(String viewName, double duration, Promise promise) {
@@ -4,6 +4,7 @@ import com.facebook.react.bridge.Promise;
4
4
  import com.facebook.react.bridge.ReactApplicationContext;
5
5
  import com.facebook.react.bridge.ReactMethod;
6
6
  import com.facebook.react.bridge.ReadableMap;
7
+ import com.facebook.react.bridge.WritableMap;
7
8
 
8
9
  public class FTTraceModule extends NativeFTReactNativeTraceSpec {
9
10
  private final FTTraceImpl impl = new FTTraceImpl();
@@ -29,4 +30,17 @@ public class FTTraceModule extends NativeFTReactNativeTraceSpec {
29
30
  public void getTraceHeaderFields(String url, String key, Promise promise) {
30
31
  impl.getTraceHeaderFields(url, key, promise);
31
32
  }
33
+
34
+ @Override
35
+ @ReactMethod(isBlockingSynchronousMethod = true)
36
+ public WritableMap getTraceHeaderFieldsSync(String url, String key) {
37
+ return impl.getTraceHeaderFieldsSync(url, key);
38
+ }
39
+
40
+ @Override
41
+ @ReactMethod
42
+ public void cancelWebSocketTrace(String key, Promise promise) {
43
+ // JS WebSocket observation is iOS-only; Android owns its native lifecycle.
44
+ promise.resolve(null);
45
+ }
32
46
  }
@@ -38,6 +38,11 @@ public class FTMobileModule extends ReactContextBaseJavaModule {
38
38
  impl.bindRUMUserData(userId, userName, userEmail, extra, promise);
39
39
  }
40
40
 
41
+ @ReactMethod
42
+ public void resetVisitorId(Promise promise) {
43
+ impl.resetVisitorId(promise);
44
+ }
45
+
41
46
  @ReactMethod
42
47
  public void unbindRUMUserData(Promise promise) {
43
48
  impl.unbindRUMUserData(promise);
@@ -85,12 +90,12 @@ public class FTMobileModule extends ReactContextBaseJavaModule {
85
90
 
86
91
  @ReactMethod
87
92
  public void addListener(String eventName) {
88
- impl.addListener(eventName);
93
+ // Required by NativeEventEmitter; Android does not need listener bookkeeping.
89
94
  }
90
95
 
91
96
  @ReactMethod
92
97
  public void removeListeners(double count) {
93
- impl.removeListeners(count);
98
+ // Required by NativeEventEmitter; Android does not need listener bookkeeping.
94
99
  }
95
100
 
96
101
  @NonNull
@@ -7,10 +7,11 @@ import com.facebook.react.bridge.ReactMethod;
7
7
  import com.facebook.react.bridge.ReadableMap;
8
8
 
9
9
  public class FTRUMModule extends ReactContextBaseJavaModule {
10
- private final FTRUMImpl impl = new FTRUMImpl();
10
+ private final FTRUMImpl impl;
11
11
 
12
12
  public FTRUMModule(ReactApplicationContext reactContext) {
13
13
  super(reactContext);
14
+ impl = new FTRUMImpl(reactContext);
14
15
  }
15
16
 
16
17
  @Override
@@ -23,6 +24,28 @@ public class FTRUMModule extends ReactContextBaseJavaModule {
23
24
  impl.setConfig(context, promise);
24
25
  }
25
26
 
27
+ @ReactMethod
28
+ public void stopLongTaskTracking(Promise promise) {
29
+ impl.stopLongTaskTracking(promise);
30
+ }
31
+
32
+ @ReactMethod(isBlockingSynchronousMethod = true)
33
+ public boolean setLongTaskContext(ReadableMap context) {
34
+ return impl.setLongTaskContext(context);
35
+ }
36
+
37
+ @Override
38
+ public void invalidate() {
39
+ impl.destroy();
40
+ super.invalidate();
41
+ }
42
+
43
+ @Override
44
+ public void onCatalystInstanceDestroy() {
45
+ impl.destroy();
46
+ super.onCatalystInstanceDestroy();
47
+ }
48
+
26
49
  @ReactMethod
27
50
  public void startAction(String actionName, String actionType, ReadableMap map, Promise promise) {
28
51
  impl.startAction(actionName, actionType, map, promise);
@@ -33,6 +56,21 @@ public class FTRUMModule extends ReactContextBaseJavaModule {
33
56
  impl.addAction(actionName, actionType, map, promise);
34
57
  }
35
58
 
59
+ @ReactMethod
60
+ public void onCreateViewWithLoadStart(String viewName, double duration, double loadStartEpochMs, Promise promise) {
61
+ impl.onCreateViewWithLoadStart(viewName, duration, loadStartEpochMs, promise);
62
+ }
63
+
64
+ @ReactMethod
65
+ public void startViewWithTrackingType(String viewName, ReadableMap map, double trackingType, Promise promise) {
66
+ impl.startViewWithTrackingType(viewName, map, trackingType, promise);
67
+ }
68
+
69
+ @ReactMethod
70
+ public void updateViewLoadingTime(double duration, Promise promise) {
71
+ impl.updateViewLoadingTime(duration, promise);
72
+ }
73
+
36
74
  @ReactMethod
37
75
  public void onCreateView(String viewName, Double duration, Promise promise) {
38
76
  impl.onCreateView(viewName, duration, promise);
@@ -5,6 +5,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
5
5
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
6
6
  import com.facebook.react.bridge.ReactMethod;
7
7
  import com.facebook.react.bridge.ReadableMap;
8
+ import com.facebook.react.bridge.WritableMap;
8
9
 
9
10
  public class FTTraceModule extends ReactContextBaseJavaModule {
10
11
  private final FTTraceImpl impl = new FTTraceImpl();
@@ -27,4 +28,15 @@ public class FTTraceModule extends ReactContextBaseJavaModule {
27
28
  public void getTraceHeaderFields(String url, String key, Promise promise) {
28
29
  impl.getTraceHeaderFields(url, key, promise);
29
30
  }
31
+
32
+ @ReactMethod(isBlockingSynchronousMethod = true)
33
+ public WritableMap getTraceHeaderFieldsSync(String url, String key) {
34
+ return impl.getTraceHeaderFieldsSync(url, key);
35
+ }
36
+
37
+ @ReactMethod
38
+ public void cancelWebSocketTrace(String key, Promise promise) {
39
+ // JS WebSocket observation is iOS-only; Android owns its native lifecycle.
40
+ promise.resolve(null);
41
+ }
30
42
  }
@@ -0,0 +1,21 @@
1
+ package com.ft.sdk.reactnative;
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext;
4
+
5
+ final class ReactJsThreadExecutor implements JsLongTaskMonitor.Executor {
6
+ private final ReactApplicationContext reactContext;
7
+
8
+ ReactJsThreadExecutor(ReactApplicationContext reactContext) {
9
+ this.reactContext = reactContext;
10
+ }
11
+
12
+ @Override
13
+ public boolean runOnJsThread(Runnable runnable) {
14
+ try {
15
+ return reactContext.runOnJSQueueThread(runnable);
16
+ } catch (AssertionError ignored) {
17
+ // ReactContext asserts if its JS queue has not been initialized yet.
18
+ return false;
19
+ }
20
+ }
21
+ }