@ansight/react-native 1.3.0-preview.10 → 1.3.0-preview.12
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/README.md +102 -32
- package/android/build.gradle +2 -2
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativeModule.kt +28 -0
- package/index.d.ts +93 -0
- package/index.js +222 -30
- package/ios/AnsightReactNative.swift +38 -1
- package/ios/AnsightReactNativeModule.m +4 -0
- package/network.js +764 -0
- package/package.json +8 -2
- package/react-inspection-hook.js +76 -0
- package/react-tree-geometry.js +61 -0
- package/react-tree-semantics.js +33 -0
- package/session-properties.js +151 -0
package/index.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
const { ensureReactInspectionHook } = require("./react-inspection-hook");
|
|
4
|
+
const { createReactCoordinateSpace } = require("./react-tree-geometry");
|
|
5
|
+
const { reactSemanticRole, reactSupportedActions } = require("./react-tree-semantics");
|
|
6
|
+
|
|
7
|
+
// Production React Native renderers only look for the DevTools hook once, when
|
|
8
|
+
// the renderer module initializes. Install a minimal root tracker while this
|
|
9
|
+
// package is loading so Fabric and Paper can report their roots before the app's
|
|
10
|
+
// first render. Development runtimes keep their full React DevTools hook.
|
|
11
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) {
|
|
12
|
+
ensureReactInspectionHook(typeof global !== "undefined" ? global : undefined);
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
const {
|
|
4
16
|
AppState,
|
|
17
|
+
Dimensions,
|
|
5
18
|
NativeEventEmitter,
|
|
6
19
|
NativeModules,
|
|
7
20
|
Platform,
|
|
@@ -10,6 +23,15 @@ const {
|
|
|
10
23
|
findNodeHandle,
|
|
11
24
|
processColor,
|
|
12
25
|
} = require("react-native");
|
|
26
|
+
const { version: reactVersion } = require("react");
|
|
27
|
+
const {
|
|
28
|
+
createAutomaticSessionProperties,
|
|
29
|
+
mergeSessionProperties,
|
|
30
|
+
} = require("./session-properties");
|
|
31
|
+
const {
|
|
32
|
+
installNetworkCapture: installNetworkCaptureCore,
|
|
33
|
+
sanitizeNetworkRequest,
|
|
34
|
+
} = require("./network");
|
|
13
35
|
|
|
14
36
|
const nativeModule = NativeModules.AnsightReactNative;
|
|
15
37
|
|
|
@@ -33,6 +55,9 @@ const hostConnectionStatusListeners = new Set();
|
|
|
33
55
|
let lastHostConnectionStatusKey = null;
|
|
34
56
|
const logListeners = new Set();
|
|
35
57
|
let logEventSubscription = null;
|
|
58
|
+
let networkCaptureSubscription = null;
|
|
59
|
+
let networkCaptureRegistration = null;
|
|
60
|
+
let networkConnectionEventSubscription = null;
|
|
36
61
|
const REACT_COMPONENT_TREE_TOOL_ID = "react.get_component_tree";
|
|
37
62
|
const REACT_SHADOW_TREE_TOOL_ID = "react.get_shadow_tree";
|
|
38
63
|
|
|
@@ -44,7 +69,28 @@ function normalizePairingPayload(payload) {
|
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
function normalizeOptions(options = {}) {
|
|
47
|
-
|
|
72
|
+
const normalized = {
|
|
73
|
+
...options,
|
|
74
|
+
customProperties: mergeSessionProperties(
|
|
75
|
+
automaticSessionProperties(),
|
|
76
|
+
options.customProperties
|
|
77
|
+
),
|
|
78
|
+
};
|
|
79
|
+
delete normalized.networkCapture;
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function automaticSessionProperties() {
|
|
84
|
+
return createAutomaticSessionProperties({
|
|
85
|
+
platform: Platform,
|
|
86
|
+
reactVersion,
|
|
87
|
+
runtimeGlobal: global,
|
|
88
|
+
developmentMode: typeof __DEV__ !== "undefined" && __DEV__,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function automaticSessionPropertyValue(group, key) {
|
|
93
|
+
return automaticSessionProperties()[group]?.[key];
|
|
48
94
|
}
|
|
49
95
|
|
|
50
96
|
function cloneOptions(options = {}) {
|
|
@@ -73,6 +119,17 @@ function cloneOptions(options = {}) {
|
|
|
73
119
|
if (options.hostConnection) {
|
|
74
120
|
clone.hostConnection = { ...options.hostConnection };
|
|
75
121
|
}
|
|
122
|
+
if (options.networkCapture && typeof options.networkCapture === "object") {
|
|
123
|
+
clone.networkCapture = {
|
|
124
|
+
...options.networkCapture,
|
|
125
|
+
additionalSensitiveHeaderNames: options.networkCapture.additionalSensitiveHeaderNames
|
|
126
|
+
? [...options.networkCapture.additionalSensitiveHeaderNames]
|
|
127
|
+
: undefined,
|
|
128
|
+
additionalSensitiveQueryParameterNames: options.networkCapture.additionalSensitiveQueryParameterNames
|
|
129
|
+
? [...options.networkCapture.additionalSensitiveQueryParameterNames]
|
|
130
|
+
: undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
76
133
|
if (options.secureStorage) {
|
|
77
134
|
clone.secureStorage = {
|
|
78
135
|
...options.secureStorage,
|
|
@@ -339,6 +396,52 @@ class AnsightOptionsBuilder {
|
|
|
339
396
|
return this;
|
|
340
397
|
}
|
|
341
398
|
|
|
399
|
+
withNetworkCapture(networkCapture = {}) {
|
|
400
|
+
this._options.networkCapture = { ...networkCapture };
|
|
401
|
+
return this;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
withNetworkRequestBodies(maximumBodyBytes) {
|
|
405
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
406
|
+
const current = this._options.networkCapture;
|
|
407
|
+
this._options.networkCapture = {
|
|
408
|
+
...current,
|
|
409
|
+
captureRequestBody: true,
|
|
410
|
+
...(maximumBodyBytes == null ? {} : { maximumBodyBytes }),
|
|
411
|
+
};
|
|
412
|
+
return this;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
withoutNetworkRequestBodies() {
|
|
416
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
417
|
+
const current = this._options.networkCapture;
|
|
418
|
+
this._options.networkCapture = { ...current, captureRequestBody: false };
|
|
419
|
+
return this;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
withNetworkResponseBodies(maximumBodyBytes) {
|
|
423
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
424
|
+
const current = this._options.networkCapture;
|
|
425
|
+
this._options.networkCapture = {
|
|
426
|
+
...current,
|
|
427
|
+
captureResponseBody: true,
|
|
428
|
+
...(maximumBodyBytes == null ? {} : { maximumBodyBytes }),
|
|
429
|
+
};
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
withoutNetworkResponseBodies() {
|
|
434
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
435
|
+
const current = this._options.networkCapture;
|
|
436
|
+
this._options.networkCapture = { ...current, captureResponseBody: false };
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
withoutNetworkCapture() {
|
|
441
|
+
this._options.networkCapture = false;
|
|
442
|
+
return this;
|
|
443
|
+
}
|
|
444
|
+
|
|
342
445
|
withToolGuard(toolGuard) {
|
|
343
446
|
this._options.toolGuard = toolGuard;
|
|
344
447
|
return this;
|
|
@@ -581,6 +684,7 @@ function addHostConnectionStatusListener(listener, options = {}) {
|
|
|
581
684
|
|
|
582
685
|
async function notifyAfterHostConnectionChange(operation) {
|
|
583
686
|
const result = await operation();
|
|
687
|
+
await refreshNetworkCaptureConnection();
|
|
584
688
|
await emitHostConnectionStatusChangedIfNeeded();
|
|
585
689
|
return result;
|
|
586
690
|
}
|
|
@@ -1440,36 +1544,16 @@ function summarizeProps(props) {
|
|
|
1440
1544
|
return summary;
|
|
1441
1545
|
}
|
|
1442
1546
|
|
|
1443
|
-
function reactSemanticRole(type, props, fiberTag) {
|
|
1444
|
-
const declared = props && (props.accessibilityRole || props.role);
|
|
1445
|
-
if (declared) return String(declared).toLowerCase();
|
|
1446
|
-
if (fiberTag === 6 || /text/i.test(type)) return "text";
|
|
1447
|
-
if (/button|pressable|touchable/i.test(type)) return "button";
|
|
1448
|
-
if (/textinput/i.test(type)) return "textbox";
|
|
1449
|
-
if (/switch/i.test(type)) return "switch";
|
|
1450
|
-
if (/scrollview|flatlist|sectionlist/i.test(type)) return "scrollview";
|
|
1451
|
-
return "view";
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
function reactSupportedActions(type, props) {
|
|
1455
|
-
const actions = [];
|
|
1456
|
-
if (props && typeof props.onPress === "function") actions.push("tap");
|
|
1457
|
-
if (props && (typeof props.onChangeText === "function" || /textinput/i.test(type))) {
|
|
1458
|
-
actions.push("typeText", "focus");
|
|
1459
|
-
}
|
|
1460
|
-
if (/scrollview|flatlist|sectionlist/i.test(type) || (props && typeof props.onScroll === "function")) {
|
|
1461
|
-
actions.push("scroll", "swipe");
|
|
1462
|
-
}
|
|
1463
|
-
return actions;
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
1547
|
function applyReactTargetability(node, fiber, props, type) {
|
|
1467
1548
|
const style = StyleSheet && typeof StyleSheet.flatten === "function"
|
|
1468
1549
|
? StyleSheet.flatten(props && props.style)
|
|
1469
1550
|
: props && props.style;
|
|
1470
1551
|
node.text = node.label || (node.visual && node.visual.text) || null;
|
|
1471
|
-
node.role = reactSemanticRole(type, props, fiber.tag);
|
|
1472
1552
|
node.supportedActions = reactSupportedActions(type, props);
|
|
1553
|
+
node.role = reactSemanticRole(type, props, fiber.tag);
|
|
1554
|
+
if (node.role === "view" && node.supportedActions.includes("tap")) {
|
|
1555
|
+
node.role = "button";
|
|
1556
|
+
}
|
|
1473
1557
|
node.visible = !(style && style.display === "none") && !(props && props.accessibilityElementsHidden);
|
|
1474
1558
|
node.enabled = !(props && (props.disabled === true || props.accessibilityState && props.accessibilityState.disabled === true));
|
|
1475
1559
|
node.focusable = !!(props && props.focusable) || node.supportedActions.includes("focus");
|
|
@@ -1823,6 +1907,7 @@ async function captureReactVisualTree(rawOptions = {}) {
|
|
|
1823
1907
|
source: "react",
|
|
1824
1908
|
adapter: "react.fiber",
|
|
1825
1909
|
treeKind: "component",
|
|
1910
|
+
coordinateSpace: createReactCoordinateSpace(Dimensions, context.nodesWithNativeTags),
|
|
1826
1911
|
capturedAtUtc: new Date().toISOString(),
|
|
1827
1912
|
hookAvailable: roots.hookAvailable,
|
|
1828
1913
|
renderers: roots.renderers,
|
|
@@ -1883,6 +1968,7 @@ async function captureReactShadowTree(rawOptions = {}) {
|
|
|
1883
1968
|
source: "react-native",
|
|
1884
1969
|
adapter: "react-native.host-fiber",
|
|
1885
1970
|
treeKind: "shadow",
|
|
1971
|
+
coordinateSpace: createReactCoordinateSpace(Dimensions, context.nodesWithNativeTags),
|
|
1886
1972
|
capturedAtUtc: new Date().toISOString(),
|
|
1887
1973
|
hookAvailable: roots.hookAvailable,
|
|
1888
1974
|
renderers: roots.renderers,
|
|
@@ -2313,6 +2399,7 @@ function installErrorHandlers(options = {}) {
|
|
|
2313
2399
|
|
|
2314
2400
|
async function initialize(options = {}) {
|
|
2315
2401
|
const result = await nativeModule.initialize(normalizeOptions(options));
|
|
2402
|
+
await configureNetworkCapture(options.networkCapture);
|
|
2316
2403
|
if (options.lifecycle !== false) {
|
|
2317
2404
|
startAppStateTracking();
|
|
2318
2405
|
}
|
|
@@ -2322,6 +2409,7 @@ async function initialize(options = {}) {
|
|
|
2322
2409
|
|
|
2323
2410
|
async function initializeAndActivate(options = {}) {
|
|
2324
2411
|
const result = await nativeModule.initializeAndActivate(normalizeOptions(options));
|
|
2412
|
+
await configureNetworkCapture(options.networkCapture);
|
|
2325
2413
|
if (options.lifecycle !== false) {
|
|
2326
2414
|
startAppStateTracking();
|
|
2327
2415
|
}
|
|
@@ -2396,6 +2484,93 @@ function recordCrashCandidate(input = {}) {
|
|
|
2396
2484
|
});
|
|
2397
2485
|
}
|
|
2398
2486
|
|
|
2487
|
+
function recordNetworkRequest(input, sanitizationOptions = {}) {
|
|
2488
|
+
const sanitized = sanitizeNetworkRequest(input, sanitizationOptions, global);
|
|
2489
|
+
if (!sanitized) {
|
|
2490
|
+
return Promise.resolve({ success: false, message: "Network request capture was suppressed by the sanitizer." });
|
|
2491
|
+
}
|
|
2492
|
+
return nativeModule.recordNetworkRequest(sanitized);
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
function installNetworkCapture(options = {}) {
|
|
2496
|
+
uninstallNetworkCapture();
|
|
2497
|
+
const registration = { options };
|
|
2498
|
+
networkCaptureRegistration = registration;
|
|
2499
|
+
ensureNetworkConnectionEventSubscription();
|
|
2500
|
+
void refreshNetworkCaptureConnection();
|
|
2501
|
+
return {
|
|
2502
|
+
remove() {
|
|
2503
|
+
if (networkCaptureRegistration === registration) {
|
|
2504
|
+
uninstallNetworkCapture();
|
|
2505
|
+
}
|
|
2506
|
+
},
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
function uninstallNetworkCapture() {
|
|
2511
|
+
networkCaptureRegistration = null;
|
|
2512
|
+
if (networkConnectionEventSubscription) {
|
|
2513
|
+
networkConnectionEventSubscription.remove();
|
|
2514
|
+
networkConnectionEventSubscription = null;
|
|
2515
|
+
}
|
|
2516
|
+
detachNetworkCapture();
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
function detachNetworkCapture() {
|
|
2520
|
+
if (!networkCaptureSubscription) return;
|
|
2521
|
+
networkCaptureSubscription.remove();
|
|
2522
|
+
networkCaptureSubscription = null;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
async function refreshNetworkCaptureConnection() {
|
|
2526
|
+
const registration = networkCaptureRegistration;
|
|
2527
|
+
if (!registration) {
|
|
2528
|
+
detachNetworkCapture();
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
try {
|
|
2532
|
+
const status = await nativeModule.hostConnectionStatus();
|
|
2533
|
+
if (networkCaptureRegistration !== registration) return;
|
|
2534
|
+
applyNetworkConnectionStatus(status);
|
|
2535
|
+
} catch {
|
|
2536
|
+
if (networkCaptureRegistration === registration) detachNetworkCapture();
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
function ensureNetworkConnectionEventSubscription() {
|
|
2541
|
+
if (networkConnectionEventSubscription) return;
|
|
2542
|
+
const emitter = new NativeEventEmitter(nativeModule);
|
|
2543
|
+
networkConnectionEventSubscription = emitter.addListener(
|
|
2544
|
+
"AnsightHostConnectionStatus",
|
|
2545
|
+
applyNetworkConnectionStatus,
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
function applyNetworkConnectionStatus(status) {
|
|
2550
|
+
const registration = networkCaptureRegistration;
|
|
2551
|
+
if (!registration || !status || status.isConnected !== true) {
|
|
2552
|
+
detachNetworkCapture();
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
if (!networkCaptureSubscription) {
|
|
2556
|
+
networkCaptureSubscription = installNetworkCaptureCore({
|
|
2557
|
+
globalObject: global,
|
|
2558
|
+
options: registration.options,
|
|
2559
|
+
sourcePrefix: "react-native",
|
|
2560
|
+
capture: (request) => nativeModule.recordNetworkRequest(request),
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
async function configureNetworkCapture(value) {
|
|
2566
|
+
uninstallNetworkCapture();
|
|
2567
|
+
if (!value) return;
|
|
2568
|
+
const registration = { options: typeof value === "object" ? value : {} };
|
|
2569
|
+
networkCaptureRegistration = registration;
|
|
2570
|
+
ensureNetworkConnectionEventSubscription();
|
|
2571
|
+
await refreshNetworkCaptureConnection();
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2399
2574
|
function screenViewed(name, details) {
|
|
2400
2575
|
return nativeModule.screenViewed(name, details || {});
|
|
2401
2576
|
}
|
|
@@ -2439,6 +2614,7 @@ const Ansight = {
|
|
|
2439
2614
|
event: recordEvent,
|
|
2440
2615
|
recordEvent,
|
|
2441
2616
|
recordCrashCandidate,
|
|
2617
|
+
recordNetworkRequest,
|
|
2442
2618
|
screenViewed,
|
|
2443
2619
|
trackRoute,
|
|
2444
2620
|
setAppLifecycleState: (state) => nativeModule.setAppLifecycleState(state),
|
|
@@ -2473,12 +2649,21 @@ const Ansight = {
|
|
|
2473
2649
|
captureScreenFrame: (options = {}) => nativeModule.captureScreenFrame(options || {}),
|
|
2474
2650
|
enableTouchCapture: () => nativeModule.enableTouchCapture(),
|
|
2475
2651
|
disableTouchCapture: () => nativeModule.disableTouchCapture(),
|
|
2476
|
-
updateSessionProperties: (properties) => nativeModule.updateSessionProperties(
|
|
2477
|
-
|
|
2478
|
-
|
|
2652
|
+
updateSessionProperties: (properties) => nativeModule.updateSessionProperties(
|
|
2653
|
+
mergeSessionProperties(automaticSessionProperties(), properties || {})
|
|
2654
|
+
),
|
|
2655
|
+
clearSessionProperties: () => nativeModule.updateSessionProperties(automaticSessionProperties()),
|
|
2656
|
+
updateCustomProperties: (properties) => nativeModule.updateSessionProperties(
|
|
2657
|
+
mergeSessionProperties(automaticSessionProperties(), properties || {})
|
|
2658
|
+
),
|
|
2479
2659
|
registerCustomProperty: (group, key, value) => nativeModule.registerCustomProperty(group, key, value),
|
|
2480
|
-
removeCustomProperty: (group, key) =>
|
|
2481
|
-
|
|
2660
|
+
removeCustomProperty: (group, key) => {
|
|
2661
|
+
const automaticValue = automaticSessionPropertyValue(group, key);
|
|
2662
|
+
return automaticValue == null
|
|
2663
|
+
? nativeModule.removeCustomProperty(group, key)
|
|
2664
|
+
: nativeModule.registerCustomProperty(group, key, automaticValue);
|
|
2665
|
+
},
|
|
2666
|
+
clearCustomProperties: () => nativeModule.updateSessionProperties(automaticSessionProperties()),
|
|
2482
2667
|
registerTool,
|
|
2483
2668
|
unregisterTool,
|
|
2484
2669
|
registerArtifactProvider,
|
|
@@ -2500,6 +2685,9 @@ const Ansight = {
|
|
|
2500
2685
|
installReactTools,
|
|
2501
2686
|
uninstallReactTools,
|
|
2502
2687
|
installErrorHandlers,
|
|
2688
|
+
installNetworkCapture,
|
|
2689
|
+
uninstallNetworkCapture,
|
|
2690
|
+
sanitizeNetworkRequest,
|
|
2503
2691
|
createReactNavigationTracker,
|
|
2504
2692
|
platform: Platform.OS,
|
|
2505
2693
|
};
|
|
@@ -2518,3 +2706,7 @@ module.exports.registerArtifactProviders = registerArtifactProviders;
|
|
|
2518
2706
|
module.exports.unregisterArtifactProvider = unregisterArtifactProvider;
|
|
2519
2707
|
module.exports.listRegisteredArtifactProviders = listRegisteredArtifactProviders;
|
|
2520
2708
|
module.exports.clearArtifactProviders = clearArtifactProviders;
|
|
2709
|
+
module.exports.recordNetworkRequest = recordNetworkRequest;
|
|
2710
|
+
module.exports.installNetworkCapture = installNetworkCapture;
|
|
2711
|
+
module.exports.uninstallNetworkCapture = uninstallNetworkCapture;
|
|
2712
|
+
module.exports.sanitizeNetworkRequest = sanitizeNetworkRequest;
|
|
@@ -139,6 +139,7 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
139
139
|
|
|
140
140
|
private let lock = NSLock()
|
|
141
141
|
private var hasListeners = false
|
|
142
|
+
private var hostConnectionStatusSubscription: HostConnectionStatusSubscription?
|
|
142
143
|
private var activeCustomToolIds: Set<String> = []
|
|
143
144
|
private var pendingToolCalls: [String: PendingToolCall] = [:]
|
|
144
145
|
private var reactNativeMemorySampler: ReactNativeMemorySamplerBox?
|
|
@@ -150,10 +151,14 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
150
151
|
override init() {
|
|
151
152
|
super.init()
|
|
152
153
|
AnsightLogger.registerCallback(logCallback)
|
|
154
|
+
hostConnectionStatusSubscription = AnsightRuntime.shared.addHostConnectionStatusListener { [weak self] status, _ in
|
|
155
|
+
self?.emitHostConnectionStatusEvent(status)
|
|
156
|
+
}
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
deinit {
|
|
156
160
|
AnsightLogger.removeCallback(logCallback)
|
|
161
|
+
hostConnectionStatusSubscription?.remove()
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
override static func requiresMainQueueSetup() -> Bool {
|
|
@@ -161,7 +166,7 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
override func supportedEvents() -> [String]! {
|
|
164
|
-
["AnsightToolCall", "AnsightLog"]
|
|
169
|
+
["AnsightToolCall", "AnsightLog", "AnsightHostConnectionStatus"]
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
override func startObserving() {
|
|
@@ -292,6 +297,27 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
292
297
|
}
|
|
293
298
|
}
|
|
294
299
|
|
|
300
|
+
@objc(recordNetworkRequest:resolver:rejecter:)
|
|
301
|
+
func recordNetworkRequest(
|
|
302
|
+
_ input: NSDictionary,
|
|
303
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
304
|
+
rejecter reject: RCTPromiseRejectBlock
|
|
305
|
+
) {
|
|
306
|
+
guard JSONSerialization.isValidJSONObject(input),
|
|
307
|
+
let data = try? JSONSerialization.data(withJSONObject: input),
|
|
308
|
+
let request = try? JSONDecoder().decode(AnsightNetworkRequest.self, from: data),
|
|
309
|
+
request.schema == AnsightNetworkRequest.schemaName
|
|
310
|
+
else {
|
|
311
|
+
resolve(operationResultDictionary(
|
|
312
|
+
.failure("Network request must use the ansight.network-request.v1 schema.")
|
|
313
|
+
))
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
Task {
|
|
317
|
+
resolve(operationResultDictionary(await AnsightRuntime.shared.recordNetworkRequest(request)))
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
295
321
|
@objc(recordCrashCandidate:resolver:rejecter:)
|
|
296
322
|
func recordCrashCandidate(
|
|
297
323
|
_ input: NSDictionary,
|
|
@@ -859,6 +885,17 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
859
885
|
}
|
|
860
886
|
}
|
|
861
887
|
|
|
888
|
+
private func emitHostConnectionStatusEvent(_ status: HostConnectionStatus) {
|
|
889
|
+
let shouldEmit = lock.withLock { hasListeners }
|
|
890
|
+
guard shouldEmit else { return }
|
|
891
|
+
DispatchQueue.main.async {
|
|
892
|
+
self.sendEvent(
|
|
893
|
+
withName: "AnsightHostConnectionStatus",
|
|
894
|
+
body: self.hostConnectionStatusDictionary(status)
|
|
895
|
+
)
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
862
899
|
private func configureReactNativeMemoryProfiling(_ dictionary: NSDictionary?) throws {
|
|
863
900
|
let options = ReactNativeMemoryProfilingOptions(dictionary: dictionary)
|
|
864
901
|
currentReactNativeMemoryOptions = options
|
|
@@ -33,6 +33,10 @@ RCT_EXTERN_METHOD(recordEvent:(NSDictionary *)input
|
|
|
33
33
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
34
34
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
35
35
|
|
|
36
|
+
RCT_EXTERN_METHOD(recordNetworkRequest:(NSDictionary *)input
|
|
37
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
38
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
39
|
+
|
|
36
40
|
RCT_EXTERN_METHOD(recordCrashCandidate:(NSDictionary *)input
|
|
37
41
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
38
42
|
rejecter:(RCTPromiseRejectBlock)reject)
|