@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.
- package/FTMobileReactNativeSDK.podspec +7 -5
- package/README.md +34 -0
- package/android/build.gradle +9 -2
- package/android/src/main/java/com/ft/sdk/reactnative/FTMobileImpl.java +45 -14
- package/android/src/main/java/com/ft/sdk/reactnative/FTRUMImpl.java +85 -2
- package/android/src/main/java/com/ft/sdk/reactnative/FTTraceImpl.java +5 -1
- package/android/src/main/java/com/ft/sdk/reactnative/JsLongTaskMonitor.java +281 -0
- package/android/src/main/java/com/ft/sdk/reactnative/utils/ReactNativeUtils.java +5 -12
- package/android/src/newarch/java/com/ft/sdk/reactnative/FTMobileModule.java +9 -5
- package/android/src/newarch/java/com/ft/sdk/reactnative/FTRUMModule.java +26 -1
- package/android/src/newarch/java/com/ft/sdk/reactnative/FTTraceModule.java +14 -0
- package/android/src/oldarch/java/com/ft/sdk/reactnative/FTMobileModule.java +2 -2
- package/android/src/oldarch/java/com/ft/sdk/reactnative/FTRUMModule.java +29 -1
- package/android/src/oldarch/java/com/ft/sdk/reactnative/FTTraceModule.java +12 -0
- package/android/src/rnpost70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +21 -0
- package/android/src/rnpre70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +37 -0
- package/android/src/test/java/com/ft/sdk/reactnative/FTRUMModuleTest.java +117 -0
- package/android/src/test/java/com/ft/sdk/reactnative/JsLongTaskMonitorTest.java +446 -0
- package/android/src/test/java/com/ft/sdk/reactnative/utils/ReactNativeUtilsTest.java +33 -0
- package/ios/FTJSLongTaskMonitor.h +51 -0
- package/ios/FTJSLongTaskMonitor.m +250 -0
- package/ios/FTMobileReactNative.mm +23 -16
- package/ios/FTReactNativeLog.mm +3 -4
- package/ios/FTReactNativeRUM.mm +103 -28
- package/ios/FTReactNativeResource.h +4 -0
- package/ios/FTReactNativeResource.m +58 -0
- package/ios/FTReactNativeTrace.mm +56 -13
- package/ios/FTReactNativeUtils.m +10 -15
- package/ios/FTReactNativeWebSocket.h +10 -0
- package/ios/FTReactNativeWebSocket.mm +201 -0
- package/ios/FTWebSocketMetadataStore.h +25 -0
- package/ios/FTWebSocketMetadataStore.m +622 -0
- package/ios/FTWebSocketResourceData.h +16 -0
- package/ios/FTWebSocketResourceData.m +75 -0
- package/lib/commonjs/ft_mobile_agent.js +36 -3
- package/lib/commonjs/ft_mobile_agent.js.map +1 -1
- package/lib/commonjs/ft_rum.js +46 -2
- package/lib/commonjs/ft_rum.js.map +1 -1
- package/lib/commonjs/ft_tracing.js +12 -3
- package/lib/commonjs/ft_tracing.js.map +1 -1
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/rum/FTBabelInteractionTracking.js +72 -0
- package/lib/commonjs/rum/FTBabelInteractionTracking.js.map +1 -0
- package/lib/commonjs/rum/FTBabelUtils.js +87 -0
- package/lib/commonjs/rum/FTBabelUtils.js.map +1 -0
- package/lib/commonjs/rum/FTRumActionTracking.js +10 -39
- package/lib/commonjs/rum/FTRumActionTracking.js.map +1 -1
- package/lib/commonjs/rum/FTRumActionTrackingContext.js +50 -0
- package/lib/commonjs/rum/FTRumActionTrackingContext.js.map +1 -0
- package/lib/commonjs/rum/FTRumWebSocketNative.js +73 -0
- package/lib/commonjs/rum/FTRumWebSocketNative.js.map +1 -0
- package/lib/commonjs/rum/FTRumWebSocketTiming.js +55 -0
- package/lib/commonjs/rum/FTRumWebSocketTiming.js.map +1 -0
- package/lib/commonjs/rum/FTRumWebSocketTracking.js +510 -0
- package/lib/commonjs/rum/FTRumWebSocketTracking.js.map +1 -0
- package/lib/commonjs/rum/longTasksUtils.js +25 -0
- package/lib/commonjs/rum/longTasksUtils.js.map +1 -0
- package/lib/commonjs/specs/NativeFTMobileReactNative.js.map +1 -1
- package/lib/commonjs/specs/NativeFTReactNativeRUM.js.map +1 -1
- package/lib/commonjs/specs/NativeFTReactNativeTrace.js.map +1 -1
- package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js +11 -0
- package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/ft_mobile_agent.js +36 -3
- package/lib/module/ft_mobile_agent.js.map +1 -1
- package/lib/module/ft_rum.js +46 -2
- package/lib/module/ft_rum.js.map +1 -1
- package/lib/module/ft_tracing.js +12 -1
- package/lib/module/ft_tracing.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/rum/FTBabelInteractionTracking.js +66 -0
- package/lib/module/rum/FTBabelInteractionTracking.js.map +1 -0
- package/lib/module/rum/FTBabelUtils.js +80 -0
- package/lib/module/rum/FTBabelUtils.js.map +1 -0
- package/lib/module/rum/FTRumActionTracking.js +10 -39
- package/lib/module/rum/FTRumActionTracking.js.map +1 -1
- package/lib/module/rum/FTRumActionTrackingContext.js +43 -0
- package/lib/module/rum/FTRumActionTrackingContext.js.map +1 -0
- package/lib/module/rum/FTRumWebSocketNative.js +62 -0
- package/lib/module/rum/FTRumWebSocketNative.js.map +1 -0
- package/lib/module/rum/FTRumWebSocketTiming.js +49 -0
- package/lib/module/rum/FTRumWebSocketTiming.js.map +1 -0
- package/lib/module/rum/FTRumWebSocketTracking.js +503 -0
- package/lib/module/rum/FTRumWebSocketTracking.js.map +1 -0
- package/lib/module/rum/longTasksUtils.js +18 -0
- package/lib/module/rum/longTasksUtils.js.map +1 -0
- package/lib/module/specs/NativeFTMobileReactNative.js.map +1 -1
- package/lib/module/specs/NativeFTReactNativeRUM.js.map +1 -1
- package/lib/module/specs/NativeFTReactNativeTrace.js.map +1 -1
- package/lib/module/specs/NativeFTReactNativeWebSocket.js +8 -0
- package/lib/module/specs/NativeFTReactNativeWebSocket.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/ft_mobile_agent.d.ts +7 -0
- package/lib/typescript/ft_rum.d.ts +9 -0
- package/lib/typescript/index.d.ts +3 -1
- package/lib/typescript/rum/FTBabelInteractionTracking.d.ts +30 -0
- package/lib/typescript/rum/FTBabelUtils.d.ts +1 -0
- package/lib/typescript/rum/FTRumActionTracking.d.ts +0 -2
- package/lib/typescript/rum/FTRumActionTrackingContext.d.ts +11 -0
- package/lib/typescript/rum/FTRumWebSocketNative.d.ts +23 -0
- package/lib/typescript/rum/FTRumWebSocketTiming.d.ts +2 -0
- package/lib/typescript/rum/FTRumWebSocketTracking.d.ts +56 -0
- package/lib/typescript/rum/longTasksUtils.d.ts +8 -0
- package/lib/typescript/specs/NativeFTMobileReactNative.d.ts +14 -0
- package/lib/typescript/specs/NativeFTReactNativeRUM.d.ts +4 -0
- package/lib/typescript/specs/NativeFTReactNativeTrace.d.ts +12 -0
- package/lib/typescript/specs/NativeFTReactNativeWebSocket.d.ts +13 -0
- package/lib/typescript/version.d.ts +1 -1
- package/package.json +7 -6
- package/src/ft_mobile_agent.tsx +49 -6
- package/src/ft_rum.tsx +61 -2
- package/src/ft_tracing.tsx +15 -1
- package/src/index.tsx +4 -0
- package/src/rum/FTBabelInteractionTracking.ts +124 -0
- package/src/rum/FTBabelUtils.tsx +99 -0
- package/src/rum/FTRumActionTracking.tsx +14 -43
- package/src/rum/FTRumActionTrackingContext.ts +68 -0
- package/src/rum/FTRumWebSocketNative.ts +106 -0
- package/src/rum/FTRumWebSocketTiming.ts +50 -0
- package/src/rum/FTRumWebSocketTracking.ts +727 -0
- package/src/rum/longTasksUtils.ts +17 -0
- package/src/specs/NativeFTMobileReactNative.ts +17 -0
- package/src/specs/NativeFTReactNativeRUM.ts +4 -0
- package/src/specs/NativeFTReactNativeTrace.ts +12 -0
- package/src/specs/NativeFTReactNativeWebSocket.ts +33 -0
- package/src/version.ts +1 -1
|
@@ -12,15 +12,17 @@ Pod::Spec.new do |s|
|
|
|
12
12
|
s.authors = package["author"]
|
|
13
13
|
|
|
14
14
|
s.platforms = { :ios => "10.0" }
|
|
15
|
-
s.source = { :git => "https://github.com/
|
|
15
|
+
s.source = { :git => "https://github.com/TrueWatchTech/datakit-react-native.git", :tag => "agent_#{s.version}" }
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
19
|
+
s.private_header_files = "ios/FTJSLongTaskMonitor.h", "ios/FTWebSocketMetadataStore.h", "ios/FTReactNativeWebSocket.h", "ios/FTReactNativeResource.h", "ios/FTWebSocketResourceData.h"
|
|
20
|
+
s.frameworks = "CFNetwork"
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
s.dependency "React-Core"
|
|
22
|
-
s.dependency '
|
|
23
|
-
|
|
24
|
+
s.dependency 'TrueWatchSDK', '1.6.7'
|
|
25
|
+
|
|
24
26
|
xcconfig = {
|
|
25
27
|
"HEADER_SEARCH_PATHS" => "$(inherited) " +
|
|
26
28
|
"$(PODS_ROOT)/React-RCTFabric/** " +
|
|
@@ -36,7 +38,7 @@ Pod::Spec.new do |s|
|
|
|
36
38
|
|
|
37
39
|
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
|
38
40
|
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
39
|
-
|
|
41
|
+
|
|
40
42
|
xcconfig.merge!({
|
|
41
43
|
"DEFINES_MODULE" => "YES",
|
|
42
44
|
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
|
@@ -48,5 +50,5 @@ Pod::Spec.new do |s|
|
|
|
48
50
|
if respond_to?(:install_modules_dependencies, true)
|
|
49
51
|
install_modules_dependencies(s)
|
|
50
52
|
end
|
|
51
|
-
|
|
53
|
+
|
|
52
54
|
end
|
package/README.md
CHANGED
|
@@ -28,6 +28,40 @@ import {
|
|
|
28
28
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## WebSocket handshake tracking
|
|
32
|
+
|
|
33
|
+
To collect iOS WebSocket handshakes while native Resource auto-collection is disabled:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
await FTReactNativeRUM.setConfig({
|
|
37
|
+
androidAppId: 'YOUR_ANDROID_APP_ID',
|
|
38
|
+
iOSAppId: 'YOUR_IOS_APP_ID',
|
|
39
|
+
enableNativeUserResource: false,
|
|
40
|
+
enableIOSWebSocketResource: true,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`enableIOSWebSocketResource` defaults to `false` and is independent of
|
|
45
|
+
`enableNativeUserResource`. It only affects iOS; Android WebSocket collection
|
|
46
|
+
continues through the native SDK under `enableNativeUserResource`.
|
|
47
|
+
When disabled, new iOS connections are not instrumented or injected with
|
|
48
|
+
WebSocket Trace headers. Handshakes already started can finish normally.
|
|
49
|
+
When enabled, Trace injection additionally requires `enableNativeAutoTrace`.
|
|
50
|
+
Only connections created after tracking starts are observed.
|
|
51
|
+
|
|
52
|
+
iOS starts JS capture and native setup together, without waiting for a native
|
|
53
|
+
readiness reply. JS measures the handshake and injects Trace headers; the native
|
|
54
|
+
bridge handles Resource reporting and optionally adds a response snapshot.
|
|
55
|
+
Missing socket association keeps the native URL filtering, sampling and privacy
|
|
56
|
+
path. Only older native packages without this bridge use the legacy JS route.
|
|
57
|
+
Connections created before the hooks are installed may have no native snapshot.
|
|
58
|
+
|
|
59
|
+
iOS handshake tracking reads `error.message` first. When it is absent, the SDK
|
|
60
|
+
can use `close.reason` from the immediately following abnormal close (`1006`),
|
|
61
|
+
before the next microtask. The first error still ends handshake timing; normal
|
|
62
|
+
close reasons are not collected. Native HTTP response snapshots take precedence
|
|
63
|
+
over error-text inference.
|
|
64
|
+
|
|
31
65
|
## More
|
|
32
66
|
|
|
33
67
|
[View Doc Here](https://docs.truewatch.com/real-user-monitoring/react-native/app-access/)
|
package/android/build.gradle
CHANGED
|
@@ -94,6 +94,12 @@ android {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
if (reactNativeMajorVersion == 0) {
|
|
97
|
+
if (reactNativeMinorVersion >= 70) {
|
|
98
|
+
java.srcDirs += ['src/rnpost70/java']
|
|
99
|
+
} else {
|
|
100
|
+
java.srcDirs += ['src/rnpre70/java']
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
if (reactNativeMinorVersion >= 74) {
|
|
98
104
|
java.srcDirs += ['src/rnpost74/java']
|
|
99
105
|
} else {
|
|
@@ -179,11 +185,12 @@ repositories {
|
|
|
179
185
|
dependencies {
|
|
180
186
|
// noinspection GradleDynamicVersion
|
|
181
187
|
implementation 'com.facebook.react:react-native:+'
|
|
182
|
-
implementation 'com.truewatch.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.
|
|
188
|
+
implementation 'com.truewatch.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.5'
|
|
183
189
|
implementation 'com.truewatch.ft.mobile.sdk.tracker.agent:ft-native:1.1.2'
|
|
184
|
-
|
|
185
190
|
// implementation files('../example/android/app/libs/ft-sdk-debug.aar')
|
|
186
191
|
// implementation files('../example/android/app/libs/ft-session-replay-debug.aar')
|
|
187
192
|
|
|
188
193
|
implementation 'com.google.code.gson:gson:2.8.5'
|
|
194
|
+
testImplementation 'junit:junit:4.13.2'
|
|
195
|
+
testImplementation 'org.mockito:mockito-core:4.11.0'
|
|
189
196
|
}
|
|
@@ -271,7 +271,8 @@ public class FTMobileImpl {
|
|
|
271
271
|
return false;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
if (actual instanceof String
|
|
274
|
+
if (actual instanceof String) {
|
|
275
|
+
String actualString = (String) actual;
|
|
275
276
|
Object normalizedActual = parseJsonStringIfNeeded(actualString);
|
|
276
277
|
if (normalizedActual != actual) {
|
|
277
278
|
return isEqualValue(normalizedActual, expected);
|
|
@@ -298,18 +299,23 @@ public class FTMobileImpl {
|
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
private boolean matchesCustomKey(Object actual, Object expected) {
|
|
301
|
-
if (expected instanceof Map
|
|
302
|
-
|
|
302
|
+
if (expected instanceof Map) {
|
|
303
|
+
Map<?, ?> expectedMap = (Map<?, ?>) expected;
|
|
304
|
+
if (expectedMap.containsKey("contains")) {
|
|
305
|
+
return containsValue(actual, expectedMap.get("contains"));
|
|
306
|
+
}
|
|
303
307
|
}
|
|
304
308
|
return isEqualValue(actual, expected);
|
|
305
309
|
}
|
|
306
310
|
|
|
307
311
|
private boolean containsValue(Object actual, Object expectedValue) {
|
|
308
|
-
Object normalizedActual = actual
|
|
309
|
-
|
|
310
|
-
|
|
312
|
+
Object normalizedActual = actual;
|
|
313
|
+
if (actual instanceof String) {
|
|
314
|
+
normalizedActual = parseJsonStringIfNeeded((String) actual);
|
|
315
|
+
}
|
|
311
316
|
|
|
312
|
-
if (normalizedActual instanceof JSONArray
|
|
317
|
+
if (normalizedActual instanceof JSONArray) {
|
|
318
|
+
JSONArray actualArray = (JSONArray) normalizedActual;
|
|
313
319
|
try {
|
|
314
320
|
for (int i = 0; i < actualArray.length(); i++) {
|
|
315
321
|
if (isEqualValue(actualArray.get(i), expectedValue)) {
|
|
@@ -338,6 +344,29 @@ public class FTMobileImpl {
|
|
|
338
344
|
return value;
|
|
339
345
|
}
|
|
340
346
|
|
|
347
|
+
@Nullable
|
|
348
|
+
private HashMap<String, String[]> convertDataFilters(@Nullable Map<String, Object> filters) {
|
|
349
|
+
if (filters == null) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
HashMap<String, String[]> convertedFilters = new HashMap<>();
|
|
353
|
+
for (Map.Entry<String, Object> entry : filters.entrySet()) {
|
|
354
|
+
Object value = entry.getValue();
|
|
355
|
+
if (!(value instanceof List<?>)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
List<?> filterList = (List<?>) value;
|
|
359
|
+
List<String> filterValues = new ArrayList<>();
|
|
360
|
+
for (Object filter : filterList) {
|
|
361
|
+
if (filter != null) {
|
|
362
|
+
filterValues.add(filter.toString());
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
convertedFilters.put(entry.getKey(), filterValues.toArray(new String[0]));
|
|
366
|
+
}
|
|
367
|
+
return convertedFilters;
|
|
368
|
+
}
|
|
369
|
+
|
|
341
370
|
public void sdkConfig(ReadableMap context, Promise promise) {
|
|
342
371
|
Map<String, Object> map = context.toHashMap();
|
|
343
372
|
String datakitUrl = (String) map.get("datakitUrl");
|
|
@@ -349,6 +378,8 @@ public class FTMobileImpl {
|
|
|
349
378
|
Integer syncSleepTime = ReactNativeUtils.convertToNativeInt(map.get("syncSleepTime"));
|
|
350
379
|
Boolean enableDataIntegerCompatible = (Boolean) map.get("enableDataIntegerCompatible");
|
|
351
380
|
Boolean compressIntakeRequests = (Boolean) map.get("compressIntakeRequests");
|
|
381
|
+
Boolean enableDataFilter = (Boolean) map.get("enableDataFilter");
|
|
382
|
+
Map<String, Object> dataFilters = (Map<String, Object>) map.get("dataFilters");
|
|
352
383
|
Integer env = ReactNativeUtils.convertToNativeInt(map.get("envType"));
|
|
353
384
|
String serviceName = (String) map.get("service");
|
|
354
385
|
Map<String, Object> globalContext = (Map<String, Object>) map.get("globalContext");
|
|
@@ -404,6 +435,13 @@ public class FTMobileImpl {
|
|
|
404
435
|
if (compressIntakeRequests != null && compressIntakeRequests) {
|
|
405
436
|
sdkConfig.setCompressIntakeRequests(compressIntakeRequests);
|
|
406
437
|
}
|
|
438
|
+
if (enableDataFilter != null) {
|
|
439
|
+
sdkConfig.setEnableDataFilter(enableDataFilter);
|
|
440
|
+
}
|
|
441
|
+
HashMap<String, String[]> convertedDataFilters = convertDataFilters(dataFilters);
|
|
442
|
+
if (convertedDataFilters != null) {
|
|
443
|
+
sdkConfig.setDataFilters(convertedDataFilters);
|
|
444
|
+
}
|
|
407
445
|
if (globalContext != null) {
|
|
408
446
|
for (Map.Entry<String, Object> entry : globalContext.entrySet()) {
|
|
409
447
|
sdkConfig.addGlobalContext(entry.getKey(), entry.getValue().toString());
|
|
@@ -617,11 +655,4 @@ public class FTMobileImpl {
|
|
|
617
655
|
});
|
|
618
656
|
}
|
|
619
657
|
|
|
620
|
-
public void addListener(String eventName) {
|
|
621
|
-
// Required for NativeEventEmitter. No-op because native does not need listener bookkeeping.
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
public void removeListeners(double count) {
|
|
625
|
-
// Required for NativeEventEmitter. No-op because native does not need listener bookkeeping.
|
|
626
|
-
}
|
|
627
658
|
}
|
|
@@ -2,8 +2,11 @@ package com.ft.sdk.reactnative;
|
|
|
2
2
|
|
|
3
3
|
import com.ft.sdk.reactnative.BuildConfig;
|
|
4
4
|
import com.facebook.react.bridge.Promise;
|
|
5
|
+
import com.facebook.react.bridge.LifecycleEventListener;
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
5
7
|
import com.facebook.react.bridge.ReactMethod;
|
|
6
8
|
import com.facebook.react.bridge.ReadableMap;
|
|
9
|
+
import com.facebook.react.common.LifecycleState;
|
|
7
10
|
import com.ft.sdk.DetectFrequency;
|
|
8
11
|
import com.ft.sdk.FTInTakeUrlHandler;
|
|
9
12
|
import com.ft.sdk.FTRUMConfig;
|
|
@@ -18,11 +21,28 @@ import com.ft.sdk.reactnative.utils.ReactNativeUtils;
|
|
|
18
21
|
import java.util.ArrayList;
|
|
19
22
|
import java.util.HashMap;
|
|
20
23
|
import java.util.Map;
|
|
24
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
21
25
|
|
|
22
|
-
public class FTRUMImpl {
|
|
26
|
+
public class FTRUMImpl implements LifecycleEventListener {
|
|
23
27
|
|
|
24
28
|
private static final String DEFAULT_ERROR_TYPE = "reactnative_crash";
|
|
25
29
|
public static final String NAME = "FTReactNativeRUM";
|
|
30
|
+
private final ReactApplicationContext reactContext;
|
|
31
|
+
private final JsLongTaskMonitor jsLongTaskMonitor;
|
|
32
|
+
private final AtomicBoolean destroyed = new AtomicBoolean();
|
|
33
|
+
|
|
34
|
+
public FTRUMImpl(ReactApplicationContext reactContext) {
|
|
35
|
+
this(reactContext, JsLongTaskMonitor.create(reactContext));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
FTRUMImpl(
|
|
39
|
+
ReactApplicationContext reactContext,
|
|
40
|
+
JsLongTaskMonitor jsLongTaskMonitor
|
|
41
|
+
) {
|
|
42
|
+
this.reactContext = reactContext;
|
|
43
|
+
this.jsLongTaskMonitor = jsLongTaskMonitor;
|
|
44
|
+
reactContext.addLifecycleEventListener(this);
|
|
45
|
+
}
|
|
26
46
|
|
|
27
47
|
@ReactMethod
|
|
28
48
|
public void setConfig(ReadableMap context, Promise promise) {
|
|
@@ -39,6 +59,8 @@ public class FTRUMImpl {
|
|
|
39
59
|
Boolean enableTrackNativeAppANR = (Boolean) map.get("enableTrackNativeAppANR");
|
|
40
60
|
Boolean enableTrackNativeFreeze = (Boolean) map.get("enableTrackNativeFreeze");
|
|
41
61
|
Double nativeFreezeDurationMs = (Double) map.get("nativeFreezeDurationMs");
|
|
62
|
+
Boolean enableLongTask = (Boolean) map.get("enableLongTask");
|
|
63
|
+
Double longTaskThresholdMs = (Double) map.get("longTaskThresholdMs");
|
|
42
64
|
Integer monitorType = ReactNativeUtils.convertToNativeInt(map.get("errorMonitorType"));
|
|
43
65
|
Integer deviceMonitorType = ReactNativeUtils.convertToNativeInt(map.get("deviceMonitorType"));
|
|
44
66
|
Integer detectFrequency = ReactNativeUtils.convertToNativeInt(map.get("detectFrequency"));
|
|
@@ -131,14 +153,75 @@ public class FTRUMImpl {
|
|
|
131
153
|
if (allowWebViewHost != null) {
|
|
132
154
|
String[] allowWebViewHostArr = new String[allowWebViewHost.size()];
|
|
133
155
|
allowWebViewHost.toArray(allowWebViewHostArr);
|
|
134
|
-
|
|
156
|
+
FTSdk ftSdk = FTSdk.get();
|
|
157
|
+
if (ftSdk != null) {
|
|
158
|
+
ftSdk.getBaseConfig().setAllowWebViewHost(allowWebViewHostArr);
|
|
159
|
+
}
|
|
135
160
|
}
|
|
136
161
|
|
|
137
162
|
FTSdk.initRUMWithConfig(rumConfig);
|
|
163
|
+
double configuredThresholdMilliseconds = longTaskThresholdMs != null
|
|
164
|
+
? longTaskThresholdMs
|
|
165
|
+
: 100;
|
|
166
|
+
double thresholdMilliseconds = Boolean.TRUE.equals(enableLongTask)
|
|
167
|
+
? configuredThresholdMilliseconds
|
|
168
|
+
: 0;
|
|
169
|
+
jsLongTaskMonitor.setThresholdMilliseconds(thresholdMilliseconds);
|
|
170
|
+
if (
|
|
171
|
+
thresholdMilliseconds > 0 &&
|
|
172
|
+
reactContext.getLifecycleState() == LifecycleState.RESUMED
|
|
173
|
+
) {
|
|
174
|
+
jsLongTaskMonitor.start();
|
|
175
|
+
} else {
|
|
176
|
+
jsLongTaskMonitor.stop();
|
|
177
|
+
}
|
|
138
178
|
//LogUtils.d("configCheck","rumConfig:"+new Gson().toJson(rumConfig));
|
|
139
179
|
promise.resolve(null);
|
|
140
180
|
}
|
|
141
181
|
|
|
182
|
+
@ReactMethod
|
|
183
|
+
public void stopLongTaskTracking(final Promise promise) {
|
|
184
|
+
jsLongTaskMonitor.disable(new Runnable() {
|
|
185
|
+
@Override
|
|
186
|
+
public void run() {
|
|
187
|
+
promise.resolve(null);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public boolean setLongTaskContext(ReadableMap context) {
|
|
193
|
+
if (destroyed.get()) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
jsLongTaskMonitor.setBridgeContext(context == null ? null : context.toHashMap());
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public void destroy() {
|
|
201
|
+
if (!destroyed.compareAndSet(false, true)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
reactContext.removeLifecycleEventListener(this);
|
|
205
|
+
jsLongTaskMonitor.disable(null);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@Override
|
|
209
|
+
public void onHostResume() {
|
|
210
|
+
if (!destroyed.get()) {
|
|
211
|
+
jsLongTaskMonitor.start();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@Override
|
|
216
|
+
public void onHostPause() {
|
|
217
|
+
jsLongTaskMonitor.stop();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@Override
|
|
221
|
+
public void onHostDestroy() {
|
|
222
|
+
jsLongTaskMonitor.stop();
|
|
223
|
+
}
|
|
224
|
+
|
|
142
225
|
@ReactMethod
|
|
143
226
|
public void startAction(String actionName, String actionType, ReadableMap map, Promise promise) {
|
|
144
227
|
if (map != null) {
|
|
@@ -71,6 +71,10 @@ public class FTTraceImpl {
|
|
|
71
71
|
|
|
72
72
|
@ReactMethod
|
|
73
73
|
public void getTraceHeaderFields(String url, String key, Promise promise) {
|
|
74
|
+
promise.resolve(getTraceHeaderFieldsSync(url, key));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public WritableNativeMap getTraceHeaderFieldsSync(String url, String key) {
|
|
74
78
|
HashMap<String, String> hashMap = null;
|
|
75
79
|
if (key == null) {
|
|
76
80
|
hashMap = FTTraceManager.get().getTraceHeader(url);
|
|
@@ -84,6 +88,6 @@ public class FTTraceImpl {
|
|
|
84
88
|
map.putString(entry.getKey(), entry.getValue());
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
|
-
|
|
91
|
+
return map;
|
|
88
92
|
}
|
|
89
93
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
9
|
-
|
|
10
|
-
Pattern.
|
|
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
|
-
|
|
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
|
}
|