@truewatchtech/react-native-mobile 0.4.1 → 0.4.3-alpha.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.
Files changed (130) hide show
  1. package/FTMobileReactNativeSDK.podspec +7 -5
  2. package/README.md +34 -0
  3. package/android/build.gradle +9 -2
  4. package/android/src/main/java/com/ft/sdk/reactnative/FTMobileImpl.java +45 -14
  5. package/android/src/main/java/com/ft/sdk/reactnative/FTRUMImpl.java +85 -2
  6. package/android/src/main/java/com/ft/sdk/reactnative/FTTraceImpl.java +5 -1
  7. package/android/src/main/java/com/ft/sdk/reactnative/JsLongTaskMonitor.java +281 -0
  8. package/android/src/main/java/com/ft/sdk/reactnative/utils/ReactNativeUtils.java +5 -12
  9. package/android/src/newarch/java/com/ft/sdk/reactnative/FTMobileModule.java +9 -5
  10. package/android/src/newarch/java/com/ft/sdk/reactnative/FTRUMModule.java +26 -1
  11. package/android/src/newarch/java/com/ft/sdk/reactnative/FTTraceModule.java +14 -0
  12. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTMobileModule.java +2 -2
  13. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTRUMModule.java +29 -1
  14. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTTraceModule.java +12 -0
  15. package/android/src/rnpost70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +21 -0
  16. package/android/src/rnpre70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +37 -0
  17. package/android/src/test/java/com/ft/sdk/reactnative/FTRUMModuleTest.java +117 -0
  18. package/android/src/test/java/com/ft/sdk/reactnative/JsLongTaskMonitorTest.java +446 -0
  19. package/android/src/test/java/com/ft/sdk/reactnative/utils/ReactNativeUtilsTest.java +33 -0
  20. package/ios/FTJSLongTaskMonitor.h +51 -0
  21. package/ios/FTJSLongTaskMonitor.m +250 -0
  22. package/ios/FTMobileReactNative.mm +23 -16
  23. package/ios/FTReactNativeLog.mm +3 -4
  24. package/ios/FTReactNativeRUM.mm +103 -28
  25. package/ios/FTReactNativeResource.h +4 -0
  26. package/ios/FTReactNativeResource.m +58 -0
  27. package/ios/FTReactNativeTrace.mm +56 -13
  28. package/ios/FTReactNativeUtils.m +10 -15
  29. package/ios/FTReactNativeWebSocket.h +10 -0
  30. package/ios/FTReactNativeWebSocket.mm +201 -0
  31. package/ios/FTWebSocketMetadataStore.h +25 -0
  32. package/ios/FTWebSocketMetadataStore.m +622 -0
  33. package/ios/FTWebSocketResourceData.h +16 -0
  34. package/ios/FTWebSocketResourceData.m +75 -0
  35. package/lib/commonjs/ft_mobile_agent.js +36 -3
  36. package/lib/commonjs/ft_mobile_agent.js.map +1 -1
  37. package/lib/commonjs/ft_rum.js +46 -2
  38. package/lib/commonjs/ft_rum.js.map +1 -1
  39. package/lib/commonjs/ft_tracing.js +12 -3
  40. package/lib/commonjs/ft_tracing.js.map +1 -1
  41. package/lib/commonjs/index.js +14 -0
  42. package/lib/commonjs/index.js.map +1 -1
  43. package/lib/commonjs/rum/FTBabelInteractionTracking.js +72 -0
  44. package/lib/commonjs/rum/FTBabelInteractionTracking.js.map +1 -0
  45. package/lib/commonjs/rum/FTBabelUtils.js +87 -0
  46. package/lib/commonjs/rum/FTBabelUtils.js.map +1 -0
  47. package/lib/commonjs/rum/FTRumActionTracking.js +10 -39
  48. package/lib/commonjs/rum/FTRumActionTracking.js.map +1 -1
  49. package/lib/commonjs/rum/FTRumActionTrackingContext.js +50 -0
  50. package/lib/commonjs/rum/FTRumActionTrackingContext.js.map +1 -0
  51. package/lib/commonjs/rum/FTRumWebSocketNative.js +73 -0
  52. package/lib/commonjs/rum/FTRumWebSocketNative.js.map +1 -0
  53. package/lib/commonjs/rum/FTRumWebSocketTiming.js +55 -0
  54. package/lib/commonjs/rum/FTRumWebSocketTiming.js.map +1 -0
  55. package/lib/commonjs/rum/FTRumWebSocketTracking.js +510 -0
  56. package/lib/commonjs/rum/FTRumWebSocketTracking.js.map +1 -0
  57. package/lib/commonjs/rum/longTasksUtils.js +25 -0
  58. package/lib/commonjs/rum/longTasksUtils.js.map +1 -0
  59. package/lib/commonjs/specs/NativeFTMobileReactNative.js.map +1 -1
  60. package/lib/commonjs/specs/NativeFTReactNativeRUM.js.map +1 -1
  61. package/lib/commonjs/specs/NativeFTReactNativeTrace.js.map +1 -1
  62. package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js +11 -0
  63. package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js.map +1 -0
  64. package/lib/commonjs/version.js +1 -1
  65. package/lib/commonjs/version.js.map +1 -1
  66. package/lib/module/ft_mobile_agent.js +36 -3
  67. package/lib/module/ft_mobile_agent.js.map +1 -1
  68. package/lib/module/ft_rum.js +46 -2
  69. package/lib/module/ft_rum.js.map +1 -1
  70. package/lib/module/ft_tracing.js +12 -1
  71. package/lib/module/ft_tracing.js.map +1 -1
  72. package/lib/module/index.js +3 -1
  73. package/lib/module/index.js.map +1 -1
  74. package/lib/module/rum/FTBabelInteractionTracking.js +66 -0
  75. package/lib/module/rum/FTBabelInteractionTracking.js.map +1 -0
  76. package/lib/module/rum/FTBabelUtils.js +80 -0
  77. package/lib/module/rum/FTBabelUtils.js.map +1 -0
  78. package/lib/module/rum/FTRumActionTracking.js +10 -39
  79. package/lib/module/rum/FTRumActionTracking.js.map +1 -1
  80. package/lib/module/rum/FTRumActionTrackingContext.js +43 -0
  81. package/lib/module/rum/FTRumActionTrackingContext.js.map +1 -0
  82. package/lib/module/rum/FTRumWebSocketNative.js +62 -0
  83. package/lib/module/rum/FTRumWebSocketNative.js.map +1 -0
  84. package/lib/module/rum/FTRumWebSocketTiming.js +49 -0
  85. package/lib/module/rum/FTRumWebSocketTiming.js.map +1 -0
  86. package/lib/module/rum/FTRumWebSocketTracking.js +503 -0
  87. package/lib/module/rum/FTRumWebSocketTracking.js.map +1 -0
  88. package/lib/module/rum/longTasksUtils.js +18 -0
  89. package/lib/module/rum/longTasksUtils.js.map +1 -0
  90. package/lib/module/specs/NativeFTMobileReactNative.js.map +1 -1
  91. package/lib/module/specs/NativeFTReactNativeRUM.js.map +1 -1
  92. package/lib/module/specs/NativeFTReactNativeTrace.js.map +1 -1
  93. package/lib/module/specs/NativeFTReactNativeWebSocket.js +8 -0
  94. package/lib/module/specs/NativeFTReactNativeWebSocket.js.map +1 -0
  95. package/lib/module/version.js +1 -1
  96. package/lib/module/version.js.map +1 -1
  97. package/lib/typescript/ft_mobile_agent.d.ts +7 -0
  98. package/lib/typescript/ft_rum.d.ts +9 -0
  99. package/lib/typescript/index.d.ts +3 -1
  100. package/lib/typescript/rum/FTBabelInteractionTracking.d.ts +30 -0
  101. package/lib/typescript/rum/FTBabelUtils.d.ts +1 -0
  102. package/lib/typescript/rum/FTRumActionTracking.d.ts +0 -2
  103. package/lib/typescript/rum/FTRumActionTrackingContext.d.ts +11 -0
  104. package/lib/typescript/rum/FTRumWebSocketNative.d.ts +23 -0
  105. package/lib/typescript/rum/FTRumWebSocketTiming.d.ts +2 -0
  106. package/lib/typescript/rum/FTRumWebSocketTracking.d.ts +56 -0
  107. package/lib/typescript/rum/longTasksUtils.d.ts +8 -0
  108. package/lib/typescript/specs/NativeFTMobileReactNative.d.ts +14 -0
  109. package/lib/typescript/specs/NativeFTReactNativeRUM.d.ts +4 -0
  110. package/lib/typescript/specs/NativeFTReactNativeTrace.d.ts +12 -0
  111. package/lib/typescript/specs/NativeFTReactNativeWebSocket.d.ts +13 -0
  112. package/lib/typescript/version.d.ts +1 -1
  113. package/package.json +7 -6
  114. package/src/ft_mobile_agent.tsx +49 -6
  115. package/src/ft_rum.tsx +61 -2
  116. package/src/ft_tracing.tsx +15 -1
  117. package/src/index.tsx +4 -0
  118. package/src/rum/FTBabelInteractionTracking.ts +124 -0
  119. package/src/rum/FTBabelUtils.tsx +99 -0
  120. package/src/rum/FTRumActionTracking.tsx +14 -43
  121. package/src/rum/FTRumActionTrackingContext.ts +68 -0
  122. package/src/rum/FTRumWebSocketNative.ts +106 -0
  123. package/src/rum/FTRumWebSocketTiming.ts +50 -0
  124. package/src/rum/FTRumWebSocketTracking.ts +727 -0
  125. package/src/rum/longTasksUtils.ts +17 -0
  126. package/src/specs/NativeFTMobileReactNative.ts +17 -0
  127. package/src/specs/NativeFTReactNativeRUM.ts +4 -0
  128. package/src/specs/NativeFTReactNativeTrace.ts +12 -0
  129. package/src/specs/NativeFTReactNativeWebSocket.ts +33 -0
  130. package/src/version.ts +1 -1
@@ -0,0 +1,446 @@
1
+ package com.ft.sdk.reactnative;
2
+
3
+ import org.junit.Before;
4
+ import org.junit.Test;
5
+
6
+ import java.util.ArrayList;
7
+ import java.util.List;
8
+ import java.util.Map;
9
+ import java.util.HashMap;
10
+ import java.util.Collections;
11
+
12
+ import static org.junit.Assert.assertEquals;
13
+ import static org.junit.Assert.assertFalse;
14
+ import static org.junit.Assert.assertTrue;
15
+
16
+ public class JsLongTaskMonitorTest {
17
+
18
+ private FakeExecutor executor;
19
+ private FakeSchedulerFactory schedulerFactory;
20
+ private FakeReporter reporter;
21
+ private JsLongTaskMonitor monitor;
22
+
23
+ @Before
24
+ public void setUp() {
25
+ executor = new FakeExecutor();
26
+ schedulerFactory = new FakeSchedulerFactory();
27
+ reporter = new FakeReporter();
28
+ monitor = new JsLongTaskMonitor(executor, schedulerFactory, reporter);
29
+ }
30
+
31
+ @Test
32
+ public void createsFrameSchedulerOnlyOnJsThread() {
33
+ monitor.setThresholdMilliseconds(100);
34
+ monitor.start();
35
+
36
+ assertEquals(0, schedulerFactory.createCount);
37
+ assertEquals(1, executor.runnables.size());
38
+
39
+ executor.runAll();
40
+
41
+ assertEquals(1, schedulerFactory.createCount);
42
+ assertEquals(1, schedulerFactory.scheduler.postCount);
43
+ }
44
+
45
+ @Test
46
+ public void bridgeContextUpdatesBeforeDelayedStartAndSnapshotsEachDetection() {
47
+ Map<String, Object> context = new HashMap<>();
48
+ context.put("sdk_bridge_info", "{\"react_native\":\"test-version\"}");
49
+ context.put("wgt_id", "first");
50
+ monitor.setBridgeContext(context);
51
+ monitor.setThresholdMilliseconds(100);
52
+ monitor.start();
53
+ context.put("wgt_id", "second");
54
+ monitor.setBridgeContext(context);
55
+ executor.runAll();
56
+ schedulerFactory.scheduler.fire(1_000_000_000L);
57
+ schedulerFactory.scheduler.fire(1_250_000_000L);
58
+ Map<String, Object> firstEvent = reporter.properties.get(0);
59
+ Map<String, Object> expected = new HashMap<>(context);
60
+ context.put("wgt_id", "third");
61
+ monitor.setBridgeContext(context);
62
+ schedulerFactory.scheduler.fire(1_500_000_000L);
63
+
64
+ assertEquals(java.util.Arrays.asList(250_000_000L, 250_000_000L), reporter.durations);
65
+ assertEquals(expected, firstEvent);
66
+ assertEquals(context, reporter.properties.get(1));
67
+ monitor.disable(null);
68
+ context.put("wgt_id", "after-stop");
69
+ monitor.setBridgeContext(context);
70
+ schedulerFactory.scheduler.fire(2_000_000_000L);
71
+ assertEquals(2, reporter.durations.size());
72
+ }
73
+
74
+ @Test
75
+ public void noBridgeContextReportsEmptyProperties() {
76
+ startWithThreshold(100);
77
+ schedulerFactory.scheduler.fire(1_000_000_000L);
78
+ schedulerFactory.scheduler.fire(1_250_000_000L);
79
+ assertEquals(250_000_000L, reporter.durations.get(0).longValue());
80
+ assertEquals(Collections.emptyMap(), reporter.properties.get(0));
81
+ }
82
+
83
+ @Test
84
+ public void firstFrameIsBaselineAndThresholdComparisonIsStrict() {
85
+ startWithThreshold(100);
86
+
87
+ schedulerFactory.scheduler.fire(1_000_000_000L);
88
+ schedulerFactory.scheduler.fire(1_100_000_000L);
89
+ schedulerFactory.scheduler.fire(1_200_000_001L);
90
+
91
+ assertEquals(1, reporter.durations.size());
92
+ assertEquals(100_000_001L, reporter.durations.get(0).longValue());
93
+ }
94
+
95
+ @Test
96
+ public void reportsEveryQualifyingFullFrameInterval() {
97
+ startWithThreshold(100);
98
+
99
+ schedulerFactory.scheduler.fire(1_000_000_000L);
100
+ schedulerFactory.scheduler.fire(1_050_000_000L);
101
+ schedulerFactory.scheduler.fire(1_200_000_000L);
102
+ schedulerFactory.scheduler.fire(1_450_000_000L);
103
+
104
+ assertEquals(2, reporter.durations.size());
105
+ assertEquals(150_000_000L, reporter.durations.get(0).longValue());
106
+ assertEquals(250_000_000L, reporter.durations.get(1).longValue());
107
+ }
108
+
109
+ @Test
110
+ public void preservesFractionalMillisecondThresholds() {
111
+ startWithThreshold(200.5);
112
+
113
+ schedulerFactory.scheduler.fire(1_000_000_000L);
114
+ schedulerFactory.scheduler.fire(1_200_500_000L);
115
+ schedulerFactory.scheduler.fire(1_401_000_001L);
116
+
117
+ assertEquals(1, reporter.durations.size());
118
+ assertEquals(200_500_001L, reporter.durations.get(0).longValue());
119
+ }
120
+
121
+ @Test
122
+ public void stopRemovesCallbackAndRestartResetsBaseline() {
123
+ startWithThreshold(100);
124
+ FakeScheduler firstScheduler = schedulerFactory.scheduler;
125
+ firstScheduler.fire(1_000_000_000L);
126
+
127
+ monitor.stop();
128
+ firstScheduler.fire(1_500_000_000L);
129
+ executor.runAll();
130
+
131
+ assertEquals(1, firstScheduler.removeCount);
132
+ assertTrue(reporter.durations.isEmpty());
133
+
134
+ monitor.start();
135
+ executor.runAll();
136
+ FakeScheduler secondScheduler = schedulerFactory.scheduler;
137
+ secondScheduler.fire(2_000_000_000L);
138
+ secondScheduler.fire(2_100_000_001L);
139
+
140
+ assertEquals(1, reporter.durations.size());
141
+ assertEquals(100_000_001L, reporter.durations.get(0).longValue());
142
+ }
143
+
144
+ @Test
145
+ public void repeatedStartKeepsOnlyLatestRequest() {
146
+ monitor.setThresholdMilliseconds(100);
147
+ monitor.start();
148
+ monitor.start();
149
+
150
+ executor.runAll();
151
+
152
+ assertEquals(1, schedulerFactory.createCount);
153
+ assertEquals(1, schedulerFactory.scheduler.postCount);
154
+ }
155
+
156
+ @Test
157
+ public void zeroThresholdDisablesMonitoring() {
158
+ startWithThreshold(100);
159
+ FakeScheduler scheduler = schedulerFactory.scheduler;
160
+
161
+ monitor.setThresholdMilliseconds(0);
162
+ executor.runAll();
163
+ scheduler.fire(1_000_000_000L);
164
+ scheduler.fire(2_000_000_000L);
165
+
166
+ assertEquals(1, scheduler.removeCount);
167
+ assertTrue(reporter.durations.isEmpty());
168
+ }
169
+
170
+ @Test
171
+ public void schedulerFailureDoesNotCrashOrPostFrames() {
172
+ schedulerFactory.shouldThrow = true;
173
+ monitor.setThresholdMilliseconds(100);
174
+ monitor.start();
175
+
176
+ executor.runAll();
177
+
178
+ assertEquals(1, schedulerFactory.createCount);
179
+ assertFalse(schedulerFactory.scheduler.wasPosted);
180
+ }
181
+
182
+ @Test
183
+ public void javascriptQueueFailureDoesNotCrash() {
184
+ executor.shouldThrow = true;
185
+ monitor.setThresholdMilliseconds(100);
186
+
187
+ monitor.start();
188
+ monitor.stop();
189
+
190
+ assertEquals(0, schedulerFactory.createCount);
191
+ }
192
+
193
+ @Test
194
+ public void rejectedStartDoesNotCreateScheduler() {
195
+ executor.acceptsTasks = false;
196
+ monitor.setThresholdMilliseconds(100);
197
+
198
+ monitor.start();
199
+
200
+ assertEquals(0, schedulerFactory.createCount);
201
+ assertTrue(executor.runnables.isEmpty());
202
+ }
203
+
204
+ @Test
205
+ public void rejectedStopCompletesAndSuppressesExistingFrameCallbacks() {
206
+ startWithThreshold(100);
207
+ FakeScheduler scheduler = schedulerFactory.scheduler;
208
+ scheduler.fire(1_000_000_000L);
209
+ int posts = scheduler.postCount;
210
+ executor.acceptsTasks = false;
211
+ final int[] completions = {0};
212
+
213
+ monitor.disable(() -> completions[0]++);
214
+ scheduler.fire(1_500_000_000L);
215
+ monitor.start();
216
+
217
+ assertEquals(1, completions[0]);
218
+ assertEquals(posts, scheduler.postCount);
219
+ assertEquals(1, schedulerFactory.createCount);
220
+ assertTrue(reporter.durations.isEmpty());
221
+ }
222
+
223
+ @Test
224
+ public void throwingQueueCompletesStopExactlyOnce() {
225
+ executor.shouldThrow = true;
226
+ final int[] completions = {0};
227
+
228
+ monitor.stop(() -> completions[0]++);
229
+
230
+ assertEquals(1, completions[0]);
231
+ }
232
+
233
+ @Test
234
+ public void inlineCompletionFailureIsNotInvokedTwice() {
235
+ JsLongTaskMonitor inlineMonitor = new JsLongTaskMonitor(
236
+ runnable -> {
237
+ runnable.run();
238
+ return true;
239
+ },
240
+ schedulerFactory,
241
+ reporter
242
+ );
243
+ final int[] completions = {0};
244
+
245
+ inlineMonitor.stop(() -> {
246
+ completions[0]++;
247
+ throw new IllegalStateException("Promise is already unavailable");
248
+ });
249
+
250
+ assertEquals(1, completions[0]);
251
+ }
252
+
253
+ @Test
254
+ public void framePostFailureDisablesMonitoring() {
255
+ schedulerFactory.throwOnPost = true;
256
+ monitor.setThresholdMilliseconds(100);
257
+
258
+ monitor.start();
259
+ executor.runAll();
260
+
261
+ assertEquals(1, schedulerFactory.createCount);
262
+ assertFalse(schedulerFactory.scheduler.wasPosted);
263
+ }
264
+
265
+ @Test
266
+ public void stopCompletionRunsAfterCallbackRemoval() {
267
+ startWithThreshold(100);
268
+ final boolean[] completed = {false};
269
+
270
+ monitor.stop(new Runnable() {
271
+ @Override
272
+ public void run() {
273
+ completed[0] = true;
274
+ }
275
+ });
276
+
277
+ assertFalse(completed[0]);
278
+ assertEquals(0, schedulerFactory.scheduler.removeCount);
279
+
280
+ executor.runAll();
281
+
282
+ assertTrue(completed[0]);
283
+ assertEquals(1, schedulerFactory.scheduler.removeCount);
284
+ }
285
+
286
+ @Test
287
+ public void disablePreventsRestartUntilConfiguredAgain() {
288
+ startWithThreshold(100);
289
+ FakeScheduler firstScheduler = schedulerFactory.scheduler;
290
+ firstScheduler.fire(1_000_000_000L);
291
+ final int[] completions = {0};
292
+
293
+ monitor.disable(() -> completions[0]++);
294
+ // A foreground event can arrive before the JS queue finishes stopping.
295
+ monitor.start();
296
+ firstScheduler.fire(1_500_000_000L);
297
+ assertEquals(0, completions[0]);
298
+ executor.runAll();
299
+
300
+ assertEquals(1, completions[0]);
301
+ assertEquals(1, firstScheduler.removeCount);
302
+ assertTrue(reporter.durations.isEmpty());
303
+
304
+ // Further background/foreground transitions must not re-enable monitoring.
305
+ monitor.stop();
306
+ monitor.start();
307
+ executor.runAll();
308
+ assertEquals(1, schedulerFactory.createCount);
309
+
310
+ monitor.setThresholdMilliseconds(200);
311
+ monitor.start();
312
+ executor.runAll();
313
+ assertEquals(2, schedulerFactory.createCount);
314
+ schedulerFactory.scheduler.fire(10_000_000_000L);
315
+ schedulerFactory.scheduler.fire(10_150_000_000L);
316
+ assertTrue(reporter.durations.isEmpty());
317
+ schedulerFactory.scheduler.fire(10_400_000_000L);
318
+ assertEquals(1, reporter.durations.size());
319
+ assertEquals(250_000_000L, reporter.durations.get(0).longValue());
320
+ }
321
+
322
+ @Test
323
+ public void repeatedDisableCancelsPendingStartAndCompletesEachRequest() {
324
+ monitor.setThresholdMilliseconds(100);
325
+ monitor.start();
326
+ final int[] completions = {0};
327
+
328
+ monitor.disable(() -> completions[0]++);
329
+ monitor.disable(() -> completions[0]++);
330
+ monitor.start();
331
+ executor.runAll();
332
+
333
+ assertEquals(2, completions[0]);
334
+ assertEquals(0, schedulerFactory.createCount);
335
+ assertTrue(reporter.durations.isEmpty());
336
+ }
337
+
338
+ @Test
339
+ public void reporterFailureDoesNotStopFrameCallbacks() {
340
+ reporter.shouldThrow = true;
341
+ startWithThreshold(100);
342
+ int initialPostCount = schedulerFactory.scheduler.postCount;
343
+
344
+ schedulerFactory.scheduler.fire(1_000_000_000L);
345
+ schedulerFactory.scheduler.fire(1_200_000_000L);
346
+
347
+ assertEquals(initialPostCount + 2, schedulerFactory.scheduler.postCount);
348
+ }
349
+
350
+ private void startWithThreshold(double thresholdMilliseconds) {
351
+ monitor.setThresholdMilliseconds(thresholdMilliseconds);
352
+ monitor.start();
353
+ executor.runAll();
354
+ }
355
+
356
+ private static final class FakeExecutor implements JsLongTaskMonitor.Executor {
357
+ private final List<Runnable> runnables = new ArrayList<>();
358
+ private boolean shouldThrow;
359
+ private boolean acceptsTasks = true;
360
+
361
+ @Override
362
+ public boolean runOnJsThread(Runnable runnable) {
363
+ if (shouldThrow) {
364
+ throw new IllegalStateException("JavaScript queue unavailable");
365
+ }
366
+ if (!acceptsTasks) {
367
+ return false;
368
+ }
369
+ runnables.add(runnable);
370
+ return true;
371
+ }
372
+
373
+ private void runAll() {
374
+ while (!runnables.isEmpty()) {
375
+ runnables.remove(0).run();
376
+ }
377
+ }
378
+ }
379
+
380
+ private static final class FakeSchedulerFactory
381
+ implements JsLongTaskMonitor.FrameSchedulerFactory {
382
+ private int createCount;
383
+ private boolean shouldThrow;
384
+ private boolean throwOnPost;
385
+ private FakeScheduler scheduler = new FakeScheduler();
386
+
387
+ @Override
388
+ public JsLongTaskMonitor.FrameScheduler create(
389
+ JsLongTaskMonitor.FrameCallback callback
390
+ ) {
391
+ createCount++;
392
+ if (shouldThrow) {
393
+ throw new IllegalStateException("No looper");
394
+ }
395
+ scheduler = new FakeScheduler();
396
+ scheduler.callback = callback;
397
+ scheduler.throwOnPost = throwOnPost;
398
+ return scheduler;
399
+ }
400
+ }
401
+
402
+ private static final class FakeScheduler
403
+ implements JsLongTaskMonitor.FrameScheduler {
404
+ private JsLongTaskMonitor.FrameCallback callback;
405
+ private int postCount;
406
+ private int removeCount;
407
+ private boolean wasPosted;
408
+ private boolean throwOnPost;
409
+
410
+ @Override
411
+ public void postFrameCallback() {
412
+ if (throwOnPost) {
413
+ throw new IllegalStateException("Frame scheduler unavailable");
414
+ }
415
+ postCount++;
416
+ wasPosted = true;
417
+ }
418
+
419
+ @Override
420
+ public void removeFrameCallback() {
421
+ removeCount++;
422
+ wasPosted = false;
423
+ }
424
+
425
+ private void fire(long timestampNanos) {
426
+ if (callback != null) {
427
+ callback.doFrame(timestampNanos);
428
+ }
429
+ }
430
+ }
431
+
432
+ private static final class FakeReporter implements JsLongTaskMonitor.Reporter {
433
+ private final List<Long> durations = new ArrayList<>();
434
+ private final List<Map<String, Object>> properties = new ArrayList<>();
435
+ private boolean shouldThrow;
436
+
437
+ @Override
438
+ public void reportLongTask(long durationNanos, Map<String, Object> property) {
439
+ if (shouldThrow) {
440
+ throw new IllegalStateException("Reporter unavailable");
441
+ }
442
+ durations.add(durationNanos);
443
+ properties.add(property);
444
+ }
445
+ }
446
+ }
@@ -0,0 +1,33 @@
1
+ package com.ft.sdk.reactnative.utils;
2
+
3
+ import static org.junit.Assert.assertFalse;
4
+ import static org.junit.Assert.assertTrue;
5
+
6
+ import org.junit.Test;
7
+
8
+ public class ReactNativeUtilsTest {
9
+
10
+ @Test
11
+ public void filtersAndroidEmulatorInspectorResource() {
12
+ assertTrue(ReactNativeUtils.isReactNativeDevUrl(
13
+ "http://10.0.2.2:8081/inspector/device?name=sdk_gphone64_arm64%20-%2012%20-%20API%2032"
14
+ + "&app=com.ft.sdk.reactnative.example&device=a520746d2418fde7c33ceff1d852b112040a08c0"
15
+ ));
16
+ }
17
+
18
+ @Test
19
+ public void filtersReactNativeAndExpoDevelopmentResources() {
20
+ assertTrue(ReactNativeUtils.isReactNativeDevUrl("http://localhost:8081/hot?platform=android"));
21
+ assertTrue(ReactNativeUtils.isReactNativeDevUrl("ws://10.0.2.2:8081/message?device=emulator"));
22
+ assertTrue(ReactNativeUtils.isReactNativeDevUrl("http://192.168.1.2:8082/logs"));
23
+ assertTrue(ReactNativeUtils.isReactNativeDevUrl("http://[::1]:8081/debugger-proxy?role=client"));
24
+ }
25
+
26
+ @Test
27
+ public void doesNotFilterApplicationResources() {
28
+ assertFalse(ReactNativeUtils.isReactNativeDevUrl("https://api.example.com/inspector/device"));
29
+ assertFalse(ReactNativeUtils.isReactNativeDevUrl("http://10.0.2.2:9529/inspector/device"));
30
+ assertFalse(ReactNativeUtils.isReactNativeDevUrl("http://10.0.2.2:8081/business"));
31
+ assertFalse(ReactNativeUtils.isReactNativeDevUrl(null));
32
+ }
33
+ }
@@ -0,0 +1,51 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ typedef void (^FTJSLongTaskFrameCallback)(CFTimeInterval timestamp);
6
+
7
+ @protocol FTJSLongTaskQueue <NSObject>
8
+ - (BOOL)dispatchBlock:(dispatch_block_t)block;
9
+ @end
10
+
11
+ @protocol FTJSLongTaskFrameScheduler <NSObject>
12
+ // The monitor serializes start/stop; start runs on the JS thread, while stop
13
+ // must release resources synchronously even when that thread is unavailable.
14
+ - (void)start;
15
+ - (void)stop;
16
+ @end
17
+
18
+ @protocol FTJSLongTaskFrameSchedulerFactory <NSObject>
19
+ - (id<FTJSLongTaskFrameScheduler>)createSchedulerWithCallback:(FTJSLongTaskFrameCallback)callback;
20
+ @end
21
+
22
+ @protocol FTJSLongTaskReporter <NSObject>
23
+ - (void)reportLongTaskWithDurationNanoseconds:(int64_t)durationNanoseconds property:(NSDictionary *)property;
24
+ @end
25
+
26
+ @interface FTJSLongTaskMonitor : NSObject
27
+
28
+ - (instancetype)initWithQueue:(id<FTJSLongTaskQueue>)queue
29
+ schedulerFactory:(id<FTJSLongTaskFrameSchedulerFactory>)schedulerFactory
30
+ reporter:(id<FTJSLongTaskReporter>)reporter NS_DESIGNATED_INITIALIZER;
31
+ - (instancetype)init NS_UNAVAILABLE;
32
+
33
+ - (void)setThresholdMilliseconds:(double)thresholdMilliseconds;
34
+ // Replaces the snapshot for future detections; does not change monitor lifecycle.
35
+ - (void)setBridgeContext:(NSDictionary *)context;
36
+ - (void)start;
37
+ - (void)stop;
38
+ // Completion runs on the calling thread after resources have been released.
39
+ - (void)stopWithCompletion:(nullable dispatch_block_t)completion;
40
+ // Prevents automatic restarts until a nonzero threshold is configured again.
41
+ - (void)disableWithCompletion:(nullable dispatch_block_t)completion;
42
+
43
+ @end
44
+
45
+ @class RCTBridge;
46
+
47
+ @interface FTJSLongTaskMonitor (Production)
48
+ + (instancetype)monitorWithBridge:(RCTBridge *)bridge;
49
+ @end
50
+
51
+ NS_ASSUME_NONNULL_END