@ansight/react-native 1.3.0-preview.10 → 1.3.0-preview.11
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 +53 -3
- 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 +203 -6
- package/ios/AnsightReactNative.swift +38 -1
- package/ios/AnsightReactNativeModule.m +4 -0
- package/network.js +764 -0
- package/package.json +5 -2
- package/session-properties.js +151 -0
package/README.md
CHANGED
|
@@ -77,8 +77,8 @@ bundle; its API requires the native Ansight module.
|
|
|
77
77
|
|
|
78
78
|
This package version expects matching native SDK packages:
|
|
79
79
|
|
|
80
|
-
- CocoaPods: `Ansight`, `AnsightObjC` version `1.3.0-preview.
|
|
81
|
-
- Maven: `ai.ansight:ansight-android:1.3.0-preview.
|
|
80
|
+
- CocoaPods: `Ansight`, `AnsightObjC` version `1.3.0-preview.11`
|
|
81
|
+
- Maven: `ai.ansight:ansight-android:1.3.0-preview.11`
|
|
82
82
|
|
|
83
83
|
## Quickstart
|
|
84
84
|
|
|
@@ -151,6 +151,7 @@ The TypeScript `AnsightOptions` surface mirrors Android `AnsightOptions`, iOS
|
|
|
151
151
|
| `secureStorage` | Compatibility alias for native secure-storage allow-list settings. |
|
|
152
152
|
| `remoteTools` | Native visual tree, file, database, preferences, reflection, and secure-storage tool options. |
|
|
153
153
|
| `lifecycle` | JS AppState tracking toggle. Defaults to true. |
|
|
154
|
+
| `networkCapture` | Opt-in `fetch` and `XMLHttpRequest` metadata capture. Accepts `true` or sanitization/capture options. |
|
|
154
155
|
|
|
155
156
|
Use `withOpenFileHandleTracking()` and `withJniReferenceCountTracking()` to
|
|
156
157
|
opt in through the builder. Matching `without...` methods disable the channels
|
|
@@ -201,13 +202,49 @@ await Ansight.initializeAndActivate({
|
|
|
201
202
|
```
|
|
202
203
|
|
|
203
204
|
The touch mode captures native visual trees only on touch down and touch up.
|
|
204
|
-
Move and cancel events do not trigger capture.
|
|
205
|
+
Move and cancel events do not trigger capture. Rapid boundaries are coalesced
|
|
206
|
+
and rate-limited to protect screenshot cadence. Native touch capture and
|
|
205
207
|
visual-tree tools/providers must remain enabled.
|
|
206
208
|
|
|
207
209
|
On iOS, `captureGpuBackedSurfaces` defaults to `true` so Metal, SceneKit, and
|
|
208
210
|
similar GPU-backed views are included. Set it to `false` to use a lower-overhead
|
|
209
211
|
capture path when those surfaces are not needed.
|
|
210
212
|
|
|
213
|
+
## Network capture
|
|
214
|
+
|
|
215
|
+
Network capture is explicitly enabled by the React Native layer, which
|
|
216
|
+
instruments `fetch` and `XMLHttpRequest`, then sends a typed metadata record
|
|
217
|
+
through the native bridge. It is opt-in:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
await Ansight.initializeAndActivate(
|
|
221
|
+
Ansight.createOptionsBuilder()
|
|
222
|
+
.withAnsightDefaults()
|
|
223
|
+
.withNetworkCapture({
|
|
224
|
+
maximumBodyBytes: 64 * 1024,
|
|
225
|
+
additionalSensitiveHeaderNames: ["x-tenant-secret"],
|
|
226
|
+
additionalSensitiveQueryParameterNames: ["session"],
|
|
227
|
+
requestSanitizer: request =>
|
|
228
|
+
request.url.includes("/health") ? null : request,
|
|
229
|
+
})
|
|
230
|
+
.withoutNetworkRequestBodies() // optional, independent opt-out
|
|
231
|
+
.build(),
|
|
232
|
+
);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Text request and response bodies are included by default after network capture
|
|
236
|
+
is explicitly enabled, with a 64 KiB default per-body limit. Set a larger
|
|
237
|
+
`maximumBodyBytes` when needed; use the request/response body builder methods to
|
|
238
|
+
opt either side out or back in, and `captureBinaryBodies` to explicitly allow
|
|
239
|
+
Base64 binary content. Standard credentials, cloud signed-URL fields, cookies,
|
|
240
|
+
URL user information, and sensitive text-body assignments are redacted before
|
|
241
|
+
the bridge. Capture hooks detach whenever the native host is disconnected.
|
|
242
|
+
|
|
243
|
+
Use `installNetworkCapture(...)` and `uninstallNetworkCapture()` when capture
|
|
244
|
+
must be controlled independently of initialization. `recordNetworkRequest(...)`
|
|
245
|
+
supports custom HTTP stacks, and `sanitizeNetworkRequest(...)` exposes the same
|
|
246
|
+
app-side policy for inspection or testing.
|
|
247
|
+
|
|
211
248
|
## Native Tool Options
|
|
212
249
|
|
|
213
250
|
`remoteTools` configures the native tool suites registered by the bridge. `useNativeAllInOneDefaults: true` enables visual tree tools by default so Studio can pair `ui.get_visual_tree` data with `ui.get_screenshot` frames. Apps that do not use all-in-one defaults can opt in explicitly:
|
|
@@ -389,6 +426,19 @@ await Ansight.clearSessionProperties();
|
|
|
389
426
|
When connected, property mutations are sent immediately. When disconnected, the
|
|
390
427
|
latest values are included in the next `session.open`.
|
|
391
428
|
|
|
429
|
+
The bridge automatically adds these property groups:
|
|
430
|
+
|
|
431
|
+
| Group | Properties |
|
|
432
|
+
| --- | --- |
|
|
433
|
+
| `reactNative` | Ansight SDK, React Native and React versions; platform and runtime language; JavaScript engine and available Hermes version/bytecode details; legacy/new architecture; bridgeless state; development mode. |
|
|
434
|
+
| `localization` | Canonical locale, language, optional region, IANA time zone when exposed by `Intl`, and UTC offset in minutes. |
|
|
435
|
+
|
|
436
|
+
App-provided values override an automatic value with the same group and key.
|
|
437
|
+
Clearing all properties, or removing one automatic property, restores the
|
|
438
|
+
current bridge-owned value. `localization` reflects the JavaScript runtime
|
|
439
|
+
locale; if the app selects a different language through an i18n library, set
|
|
440
|
+
`localization.locale`, `language`, and `region` explicitly.
|
|
441
|
+
|
|
392
442
|
## Tool Guards
|
|
393
443
|
|
|
394
444
|
| Value | Allowed scopes |
|
package/android/build.gradle
CHANGED
|
@@ -67,6 +67,6 @@ repositories {
|
|
|
67
67
|
dependencies {
|
|
68
68
|
implementation("com.facebook.react:react-android")
|
|
69
69
|
def ansightAndroidVersion =
|
|
70
|
-
findProperty("ansightAndroidVersion") ?: "1.3.0-preview.
|
|
71
|
-
implementation("ai.ansight:ansight-android:1.3.0-preview.
|
|
70
|
+
findProperty("ansightAndroidVersion") ?: "1.3.0-preview.11"
|
|
71
|
+
implementation("ai.ansight:ansight-android:1.3.0-preview.11")
|
|
72
72
|
}
|
|
@@ -12,6 +12,7 @@ import ai.ansight.runtime.AnsightHostConnectionOptions
|
|
|
12
12
|
import ai.ansight.runtime.AnsightLogCallback
|
|
13
13
|
import ai.ansight.runtime.AnsightLogLevel
|
|
14
14
|
import ai.ansight.runtime.AnsightLogger
|
|
15
|
+
import ai.ansight.runtime.AnsightNetworkRequest
|
|
15
16
|
import ai.ansight.runtime.AnsightOptions
|
|
16
17
|
import ai.ansight.runtime.AnsightOptionsBuilder
|
|
17
18
|
import ai.ansight.runtime.AnsightRuntime
|
|
@@ -28,6 +29,7 @@ import ai.ansight.runtime.HostConnectionRequestKind
|
|
|
28
29
|
import ai.ansight.runtime.HostConnectionCapabilities
|
|
29
30
|
import ai.ansight.runtime.HostConnectionResult
|
|
30
31
|
import ai.ansight.runtime.HostConnectionStatus
|
|
32
|
+
import ai.ansight.runtime.HostConnectionStatusSubscription
|
|
31
33
|
import ai.ansight.runtime.OperationResult
|
|
32
34
|
import ai.ansight.runtime.OpenSessionResult
|
|
33
35
|
import ai.ansight.runtime.PairingOpenOptions
|
|
@@ -104,16 +106,21 @@ class AnsightReactNativeModule(
|
|
|
104
106
|
private val logCallback = AnsightLogCallback { level, message, throwable ->
|
|
105
107
|
emitLogEvent(level, message, throwable)
|
|
106
108
|
}
|
|
109
|
+
private val hostConnectionStatusSubscription: HostConnectionStatusSubscription
|
|
107
110
|
|
|
108
111
|
init {
|
|
109
112
|
reactContext.addLifecycleEventListener(this)
|
|
110
113
|
AnsightLogger.registerCallback(logCallback)
|
|
114
|
+
hostConnectionStatusSubscription = AnsightRuntime.addHostConnectionStatusListener(
|
|
115
|
+
listener = { status, _ -> emitHostConnectionStatusEvent(status) },
|
|
116
|
+
)
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
override fun getName(): String = "AnsightReactNative"
|
|
114
120
|
|
|
115
121
|
override fun invalidate() {
|
|
116
122
|
AnsightLogger.removeCallback(logCallback)
|
|
123
|
+
hostConnectionStatusSubscription.remove()
|
|
117
124
|
reactContext.removeLifecycleEventListener(this)
|
|
118
125
|
super.invalidate()
|
|
119
126
|
}
|
|
@@ -226,6 +233,17 @@ class AnsightReactNativeModule(
|
|
|
226
233
|
}.resolve(promise)
|
|
227
234
|
}
|
|
228
235
|
|
|
236
|
+
@ReactMethod
|
|
237
|
+
fun recordNetworkRequest(input: ReadableMap, promise: Promise) {
|
|
238
|
+
runCatching {
|
|
239
|
+
val request = AnsightNetworkRequest.fromJson(JSONObject(input.toHashMap()))
|
|
240
|
+
?: return@runCatching operationResultMap(
|
|
241
|
+
OperationResult.failure("Network request must use the ansight.network-request.v1 schema."),
|
|
242
|
+
)
|
|
243
|
+
operationResultMap(AnsightRuntime.recordNetworkRequest(request))
|
|
244
|
+
}.resolve(promise)
|
|
245
|
+
}
|
|
246
|
+
|
|
229
247
|
@ReactMethod
|
|
230
248
|
fun recordCrashCandidate(input: ReadableMap, promise: Promise) {
|
|
231
249
|
runCatching {
|
|
@@ -677,6 +695,16 @@ class AnsightReactNativeModule(
|
|
|
677
695
|
}
|
|
678
696
|
}
|
|
679
697
|
|
|
698
|
+
private fun emitHostConnectionStatusEvent(status: HostConnectionStatus) {
|
|
699
|
+
if (listenerCount.get() <= 0) return
|
|
700
|
+
val event = hostConnectionStatusMap(status)
|
|
701
|
+
UiThreadUtil.runOnUiThread {
|
|
702
|
+
reactContext
|
|
703
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
704
|
+
.emit("AnsightHostConnectionStatus", event)
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
680
708
|
private fun configureReactNativeMemoryProfiling(map: ReadableMap?) {
|
|
681
709
|
val options = reactNativeMemoryProfilingOptions(map)
|
|
682
710
|
currentReactNativeMemoryOptions = options
|
package/index.d.ts
CHANGED
|
@@ -48,6 +48,75 @@ export interface AnsightTouchCaptureOptions {
|
|
|
48
48
|
moveCaptureFramesPerSecond?: number;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface AnsightNetworkHeader {
|
|
52
|
+
name: string;
|
|
53
|
+
value: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface AnsightNetworkBody {
|
|
57
|
+
contentType?: string;
|
|
58
|
+
encoding: "utf8" | "base64";
|
|
59
|
+
data: string;
|
|
60
|
+
capturedBytes: number;
|
|
61
|
+
totalBytes?: number;
|
|
62
|
+
truncated: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** V1 network metadata with optional, bounded bodies. */
|
|
66
|
+
export interface AnsightNetworkRequest {
|
|
67
|
+
schema: "ansight.network-request.v1";
|
|
68
|
+
id: string;
|
|
69
|
+
source: string;
|
|
70
|
+
startedAtUtc: string;
|
|
71
|
+
completedAtUtc: string;
|
|
72
|
+
durationMilliseconds: number;
|
|
73
|
+
method: string;
|
|
74
|
+
url: string;
|
|
75
|
+
protocol?: string;
|
|
76
|
+
requestHeaders: AnsightNetworkHeader[];
|
|
77
|
+
requestBodySizeBytes?: number;
|
|
78
|
+
requestBody?: AnsightNetworkBody;
|
|
79
|
+
statusCode?: number;
|
|
80
|
+
reasonPhrase?: string;
|
|
81
|
+
responseHeaders: AnsightNetworkHeader[];
|
|
82
|
+
responseBodySizeBytes?: number;
|
|
83
|
+
responseBody?: AnsightNetworkBody;
|
|
84
|
+
errorType?: string;
|
|
85
|
+
errorMessage?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type AnsightNetworkRequestInput = Partial<AnsightNetworkRequest> & {
|
|
89
|
+
method: string;
|
|
90
|
+
url: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export interface AnsightNetworkSanitizationOptions {
|
|
94
|
+
includeRequestHeaders?: boolean;
|
|
95
|
+
includeResponseHeaders?: boolean;
|
|
96
|
+
includeQueryString?: boolean;
|
|
97
|
+
includeBodySizes?: boolean;
|
|
98
|
+
/** Defaults to true. */
|
|
99
|
+
captureRequestBody?: boolean;
|
|
100
|
+
/** Defaults to true. */
|
|
101
|
+
captureResponseBody?: boolean;
|
|
102
|
+
/** Decoded bytes retained per body. Defaults to 64 KiB; larger explicit limits are honored. */
|
|
103
|
+
maximumBodyBytes?: number;
|
|
104
|
+
captureBinaryBodies?: boolean;
|
|
105
|
+
additionalSensitiveHeaderNames?: string[];
|
|
106
|
+
additionalSensitiveQueryParameterNames?: string[];
|
|
107
|
+
urlSanitizer?: (url: string) => string;
|
|
108
|
+
/** Return a replacement request, or null to suppress capture. */
|
|
109
|
+
requestSanitizer?: (
|
|
110
|
+
request: AnsightNetworkRequest,
|
|
111
|
+
) => AnsightNetworkRequest | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface AnsightNetworkCaptureOptions
|
|
115
|
+
extends AnsightNetworkSanitizationOptions {
|
|
116
|
+
captureFetch?: boolean;
|
|
117
|
+
captureXmlHttpRequest?: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
51
120
|
export interface AnsightNativeToolRoot {
|
|
52
121
|
alias: string;
|
|
53
122
|
path: string;
|
|
@@ -155,6 +224,8 @@ export interface AnsightOptions {
|
|
|
155
224
|
remoteTools?: AnsightRemoteToolsOptions;
|
|
156
225
|
additionalChannels?: AnsightChannel[];
|
|
157
226
|
lifecycle?: boolean;
|
|
227
|
+
/** Explicitly opt in to fetch/XMLHttpRequest instrumentation. */
|
|
228
|
+
networkCapture?: boolean | AnsightNetworkCaptureOptions;
|
|
158
229
|
}
|
|
159
230
|
|
|
160
231
|
export interface AnsightCrashCaptureOptions {
|
|
@@ -300,6 +371,12 @@ export class AnsightOptionsBuilder {
|
|
|
300
371
|
withCrashCapture(crashCapture?: AnsightCrashCaptureOptions): this;
|
|
301
372
|
withoutCrashCapture(): this;
|
|
302
373
|
withLifecycleCapture(lifecycleCapture?: NonNullable<AnsightOptions["lifecycleCapture"]>): this;
|
|
374
|
+
withNetworkCapture(networkCapture?: AnsightNetworkCaptureOptions): this;
|
|
375
|
+
withNetworkRequestBodies(maximumBodyBytes?: number): this;
|
|
376
|
+
withoutNetworkRequestBodies(): this;
|
|
377
|
+
withNetworkResponseBodies(maximumBodyBytes?: number): this;
|
|
378
|
+
withoutNetworkResponseBodies(): this;
|
|
379
|
+
withoutNetworkCapture(): this;
|
|
303
380
|
withToolGuard(toolGuard: NonNullable<AnsightOptions["toolGuard"]>): this;
|
|
304
381
|
withToolsDisabled(): this;
|
|
305
382
|
withReadOnlyToolAccess(): this;
|
|
@@ -543,6 +620,18 @@ export function recordMetric(value: number, channel?: number): Promise<AnsightDe
|
|
|
543
620
|
export function event(input: string | { label: string; type?: string; details?: string; channel?: number }): Promise<AnsightDebugSnapshot>;
|
|
544
621
|
export function recordEvent(input: string | { label: string; type?: string; details?: string; channel?: number }): Promise<AnsightDebugSnapshot>;
|
|
545
622
|
export function recordCrashCandidate(input?: AnsightCrashCandidate): Promise<{ candidateId?: string }>;
|
|
623
|
+
export function recordNetworkRequest(
|
|
624
|
+
input: AnsightNetworkRequestInput,
|
|
625
|
+
sanitizationOptions?: AnsightNetworkSanitizationOptions,
|
|
626
|
+
): Promise<AnsightOperationResult>;
|
|
627
|
+
export function sanitizeNetworkRequest(
|
|
628
|
+
input: AnsightNetworkRequestInput,
|
|
629
|
+
sanitizationOptions?: AnsightNetworkSanitizationOptions,
|
|
630
|
+
): AnsightNetworkRequest | null;
|
|
631
|
+
export function installNetworkCapture(
|
|
632
|
+
options?: AnsightNetworkCaptureOptions,
|
|
633
|
+
): AnsightSubscription;
|
|
634
|
+
export function uninstallNetworkCapture(): void;
|
|
546
635
|
export function screenViewed(name: string, details?: Record<string, string>): Promise<AnsightDebugSnapshot>;
|
|
547
636
|
export function trackRoute(name: string, details?: Record<string, string>): Promise<AnsightDebugSnapshot>;
|
|
548
637
|
export function setAppLifecycleState(state: AnsightLifecycleState): Promise<AnsightDebugSnapshot>;
|
|
@@ -617,6 +706,7 @@ declare const Ansight: {
|
|
|
617
706
|
event: typeof event;
|
|
618
707
|
recordEvent: typeof recordEvent;
|
|
619
708
|
recordCrashCandidate: typeof recordCrashCandidate;
|
|
709
|
+
recordNetworkRequest: typeof recordNetworkRequest;
|
|
620
710
|
screenViewed: typeof screenViewed;
|
|
621
711
|
trackRoute: typeof trackRoute;
|
|
622
712
|
setAppLifecycleState: typeof setAppLifecycleState;
|
|
@@ -671,6 +761,9 @@ declare const Ansight: {
|
|
|
671
761
|
installReactTools: typeof installReactTools;
|
|
672
762
|
uninstallReactTools: typeof uninstallReactTools;
|
|
673
763
|
installErrorHandlers: typeof installErrorHandlers;
|
|
764
|
+
installNetworkCapture: typeof installNetworkCapture;
|
|
765
|
+
uninstallNetworkCapture: typeof uninstallNetworkCapture;
|
|
766
|
+
sanitizeNetworkRequest: typeof sanitizeNetworkRequest;
|
|
674
767
|
createReactNavigationTracker: typeof createReactNavigationTracker;
|
|
675
768
|
platform: "ios" | "android" | string;
|
|
676
769
|
};
|
package/index.js
CHANGED
|
@@ -10,6 +10,15 @@ const {
|
|
|
10
10
|
findNodeHandle,
|
|
11
11
|
processColor,
|
|
12
12
|
} = require("react-native");
|
|
13
|
+
const { version: reactVersion } = require("react");
|
|
14
|
+
const {
|
|
15
|
+
createAutomaticSessionProperties,
|
|
16
|
+
mergeSessionProperties,
|
|
17
|
+
} = require("./session-properties");
|
|
18
|
+
const {
|
|
19
|
+
installNetworkCapture: installNetworkCaptureCore,
|
|
20
|
+
sanitizeNetworkRequest,
|
|
21
|
+
} = require("./network");
|
|
13
22
|
|
|
14
23
|
const nativeModule = NativeModules.AnsightReactNative;
|
|
15
24
|
|
|
@@ -33,6 +42,9 @@ const hostConnectionStatusListeners = new Set();
|
|
|
33
42
|
let lastHostConnectionStatusKey = null;
|
|
34
43
|
const logListeners = new Set();
|
|
35
44
|
let logEventSubscription = null;
|
|
45
|
+
let networkCaptureSubscription = null;
|
|
46
|
+
let networkCaptureRegistration = null;
|
|
47
|
+
let networkConnectionEventSubscription = null;
|
|
36
48
|
const REACT_COMPONENT_TREE_TOOL_ID = "react.get_component_tree";
|
|
37
49
|
const REACT_SHADOW_TREE_TOOL_ID = "react.get_shadow_tree";
|
|
38
50
|
|
|
@@ -44,7 +56,28 @@ function normalizePairingPayload(payload) {
|
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
function normalizeOptions(options = {}) {
|
|
47
|
-
|
|
59
|
+
const normalized = {
|
|
60
|
+
...options,
|
|
61
|
+
customProperties: mergeSessionProperties(
|
|
62
|
+
automaticSessionProperties(),
|
|
63
|
+
options.customProperties
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
delete normalized.networkCapture;
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function automaticSessionProperties() {
|
|
71
|
+
return createAutomaticSessionProperties({
|
|
72
|
+
platform: Platform,
|
|
73
|
+
reactVersion,
|
|
74
|
+
runtimeGlobal: global,
|
|
75
|
+
developmentMode: typeof __DEV__ !== "undefined" && __DEV__,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function automaticSessionPropertyValue(group, key) {
|
|
80
|
+
return automaticSessionProperties()[group]?.[key];
|
|
48
81
|
}
|
|
49
82
|
|
|
50
83
|
function cloneOptions(options = {}) {
|
|
@@ -73,6 +106,17 @@ function cloneOptions(options = {}) {
|
|
|
73
106
|
if (options.hostConnection) {
|
|
74
107
|
clone.hostConnection = { ...options.hostConnection };
|
|
75
108
|
}
|
|
109
|
+
if (options.networkCapture && typeof options.networkCapture === "object") {
|
|
110
|
+
clone.networkCapture = {
|
|
111
|
+
...options.networkCapture,
|
|
112
|
+
additionalSensitiveHeaderNames: options.networkCapture.additionalSensitiveHeaderNames
|
|
113
|
+
? [...options.networkCapture.additionalSensitiveHeaderNames]
|
|
114
|
+
: undefined,
|
|
115
|
+
additionalSensitiveQueryParameterNames: options.networkCapture.additionalSensitiveQueryParameterNames
|
|
116
|
+
? [...options.networkCapture.additionalSensitiveQueryParameterNames]
|
|
117
|
+
: undefined,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
76
120
|
if (options.secureStorage) {
|
|
77
121
|
clone.secureStorage = {
|
|
78
122
|
...options.secureStorage,
|
|
@@ -339,6 +383,52 @@ class AnsightOptionsBuilder {
|
|
|
339
383
|
return this;
|
|
340
384
|
}
|
|
341
385
|
|
|
386
|
+
withNetworkCapture(networkCapture = {}) {
|
|
387
|
+
this._options.networkCapture = { ...networkCapture };
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
withNetworkRequestBodies(maximumBodyBytes) {
|
|
392
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
393
|
+
const current = this._options.networkCapture;
|
|
394
|
+
this._options.networkCapture = {
|
|
395
|
+
...current,
|
|
396
|
+
captureRequestBody: true,
|
|
397
|
+
...(maximumBodyBytes == null ? {} : { maximumBodyBytes }),
|
|
398
|
+
};
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
withoutNetworkRequestBodies() {
|
|
403
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
404
|
+
const current = this._options.networkCapture;
|
|
405
|
+
this._options.networkCapture = { ...current, captureRequestBody: false };
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
withNetworkResponseBodies(maximumBodyBytes) {
|
|
410
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
411
|
+
const current = this._options.networkCapture;
|
|
412
|
+
this._options.networkCapture = {
|
|
413
|
+
...current,
|
|
414
|
+
captureResponseBody: true,
|
|
415
|
+
...(maximumBodyBytes == null ? {} : { maximumBodyBytes }),
|
|
416
|
+
};
|
|
417
|
+
return this;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
withoutNetworkResponseBodies() {
|
|
421
|
+
if (!this._options.networkCapture || typeof this._options.networkCapture !== "object") return this;
|
|
422
|
+
const current = this._options.networkCapture;
|
|
423
|
+
this._options.networkCapture = { ...current, captureResponseBody: false };
|
|
424
|
+
return this;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
withoutNetworkCapture() {
|
|
428
|
+
this._options.networkCapture = false;
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
|
|
342
432
|
withToolGuard(toolGuard) {
|
|
343
433
|
this._options.toolGuard = toolGuard;
|
|
344
434
|
return this;
|
|
@@ -581,6 +671,7 @@ function addHostConnectionStatusListener(listener, options = {}) {
|
|
|
581
671
|
|
|
582
672
|
async function notifyAfterHostConnectionChange(operation) {
|
|
583
673
|
const result = await operation();
|
|
674
|
+
await refreshNetworkCaptureConnection();
|
|
584
675
|
await emitHostConnectionStatusChangedIfNeeded();
|
|
585
676
|
return result;
|
|
586
677
|
}
|
|
@@ -2313,6 +2404,7 @@ function installErrorHandlers(options = {}) {
|
|
|
2313
2404
|
|
|
2314
2405
|
async function initialize(options = {}) {
|
|
2315
2406
|
const result = await nativeModule.initialize(normalizeOptions(options));
|
|
2407
|
+
await configureNetworkCapture(options.networkCapture);
|
|
2316
2408
|
if (options.lifecycle !== false) {
|
|
2317
2409
|
startAppStateTracking();
|
|
2318
2410
|
}
|
|
@@ -2322,6 +2414,7 @@ async function initialize(options = {}) {
|
|
|
2322
2414
|
|
|
2323
2415
|
async function initializeAndActivate(options = {}) {
|
|
2324
2416
|
const result = await nativeModule.initializeAndActivate(normalizeOptions(options));
|
|
2417
|
+
await configureNetworkCapture(options.networkCapture);
|
|
2325
2418
|
if (options.lifecycle !== false) {
|
|
2326
2419
|
startAppStateTracking();
|
|
2327
2420
|
}
|
|
@@ -2396,6 +2489,93 @@ function recordCrashCandidate(input = {}) {
|
|
|
2396
2489
|
});
|
|
2397
2490
|
}
|
|
2398
2491
|
|
|
2492
|
+
function recordNetworkRequest(input, sanitizationOptions = {}) {
|
|
2493
|
+
const sanitized = sanitizeNetworkRequest(input, sanitizationOptions, global);
|
|
2494
|
+
if (!sanitized) {
|
|
2495
|
+
return Promise.resolve({ success: false, message: "Network request capture was suppressed by the sanitizer." });
|
|
2496
|
+
}
|
|
2497
|
+
return nativeModule.recordNetworkRequest(sanitized);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
function installNetworkCapture(options = {}) {
|
|
2501
|
+
uninstallNetworkCapture();
|
|
2502
|
+
const registration = { options };
|
|
2503
|
+
networkCaptureRegistration = registration;
|
|
2504
|
+
ensureNetworkConnectionEventSubscription();
|
|
2505
|
+
void refreshNetworkCaptureConnection();
|
|
2506
|
+
return {
|
|
2507
|
+
remove() {
|
|
2508
|
+
if (networkCaptureRegistration === registration) {
|
|
2509
|
+
uninstallNetworkCapture();
|
|
2510
|
+
}
|
|
2511
|
+
},
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
function uninstallNetworkCapture() {
|
|
2516
|
+
networkCaptureRegistration = null;
|
|
2517
|
+
if (networkConnectionEventSubscription) {
|
|
2518
|
+
networkConnectionEventSubscription.remove();
|
|
2519
|
+
networkConnectionEventSubscription = null;
|
|
2520
|
+
}
|
|
2521
|
+
detachNetworkCapture();
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
function detachNetworkCapture() {
|
|
2525
|
+
if (!networkCaptureSubscription) return;
|
|
2526
|
+
networkCaptureSubscription.remove();
|
|
2527
|
+
networkCaptureSubscription = null;
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
async function refreshNetworkCaptureConnection() {
|
|
2531
|
+
const registration = networkCaptureRegistration;
|
|
2532
|
+
if (!registration) {
|
|
2533
|
+
detachNetworkCapture();
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
try {
|
|
2537
|
+
const status = await nativeModule.hostConnectionStatus();
|
|
2538
|
+
if (networkCaptureRegistration !== registration) return;
|
|
2539
|
+
applyNetworkConnectionStatus(status);
|
|
2540
|
+
} catch {
|
|
2541
|
+
if (networkCaptureRegistration === registration) detachNetworkCapture();
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
function ensureNetworkConnectionEventSubscription() {
|
|
2546
|
+
if (networkConnectionEventSubscription) return;
|
|
2547
|
+
const emitter = new NativeEventEmitter(nativeModule);
|
|
2548
|
+
networkConnectionEventSubscription = emitter.addListener(
|
|
2549
|
+
"AnsightHostConnectionStatus",
|
|
2550
|
+
applyNetworkConnectionStatus,
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
function applyNetworkConnectionStatus(status) {
|
|
2555
|
+
const registration = networkCaptureRegistration;
|
|
2556
|
+
if (!registration || !status || status.isConnected !== true) {
|
|
2557
|
+
detachNetworkCapture();
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
if (!networkCaptureSubscription) {
|
|
2561
|
+
networkCaptureSubscription = installNetworkCaptureCore({
|
|
2562
|
+
globalObject: global,
|
|
2563
|
+
options: registration.options,
|
|
2564
|
+
sourcePrefix: "react-native",
|
|
2565
|
+
capture: (request) => nativeModule.recordNetworkRequest(request),
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
async function configureNetworkCapture(value) {
|
|
2571
|
+
uninstallNetworkCapture();
|
|
2572
|
+
if (!value) return;
|
|
2573
|
+
const registration = { options: typeof value === "object" ? value : {} };
|
|
2574
|
+
networkCaptureRegistration = registration;
|
|
2575
|
+
ensureNetworkConnectionEventSubscription();
|
|
2576
|
+
await refreshNetworkCaptureConnection();
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2399
2579
|
function screenViewed(name, details) {
|
|
2400
2580
|
return nativeModule.screenViewed(name, details || {});
|
|
2401
2581
|
}
|
|
@@ -2439,6 +2619,7 @@ const Ansight = {
|
|
|
2439
2619
|
event: recordEvent,
|
|
2440
2620
|
recordEvent,
|
|
2441
2621
|
recordCrashCandidate,
|
|
2622
|
+
recordNetworkRequest,
|
|
2442
2623
|
screenViewed,
|
|
2443
2624
|
trackRoute,
|
|
2444
2625
|
setAppLifecycleState: (state) => nativeModule.setAppLifecycleState(state),
|
|
@@ -2473,12 +2654,21 @@ const Ansight = {
|
|
|
2473
2654
|
captureScreenFrame: (options = {}) => nativeModule.captureScreenFrame(options || {}),
|
|
2474
2655
|
enableTouchCapture: () => nativeModule.enableTouchCapture(),
|
|
2475
2656
|
disableTouchCapture: () => nativeModule.disableTouchCapture(),
|
|
2476
|
-
updateSessionProperties: (properties) => nativeModule.updateSessionProperties(
|
|
2477
|
-
|
|
2478
|
-
|
|
2657
|
+
updateSessionProperties: (properties) => nativeModule.updateSessionProperties(
|
|
2658
|
+
mergeSessionProperties(automaticSessionProperties(), properties || {})
|
|
2659
|
+
),
|
|
2660
|
+
clearSessionProperties: () => nativeModule.updateSessionProperties(automaticSessionProperties()),
|
|
2661
|
+
updateCustomProperties: (properties) => nativeModule.updateSessionProperties(
|
|
2662
|
+
mergeSessionProperties(automaticSessionProperties(), properties || {})
|
|
2663
|
+
),
|
|
2479
2664
|
registerCustomProperty: (group, key, value) => nativeModule.registerCustomProperty(group, key, value),
|
|
2480
|
-
removeCustomProperty: (group, key) =>
|
|
2481
|
-
|
|
2665
|
+
removeCustomProperty: (group, key) => {
|
|
2666
|
+
const automaticValue = automaticSessionPropertyValue(group, key);
|
|
2667
|
+
return automaticValue == null
|
|
2668
|
+
? nativeModule.removeCustomProperty(group, key)
|
|
2669
|
+
: nativeModule.registerCustomProperty(group, key, automaticValue);
|
|
2670
|
+
},
|
|
2671
|
+
clearCustomProperties: () => nativeModule.updateSessionProperties(automaticSessionProperties()),
|
|
2482
2672
|
registerTool,
|
|
2483
2673
|
unregisterTool,
|
|
2484
2674
|
registerArtifactProvider,
|
|
@@ -2500,6 +2690,9 @@ const Ansight = {
|
|
|
2500
2690
|
installReactTools,
|
|
2501
2691
|
uninstallReactTools,
|
|
2502
2692
|
installErrorHandlers,
|
|
2693
|
+
installNetworkCapture,
|
|
2694
|
+
uninstallNetworkCapture,
|
|
2695
|
+
sanitizeNetworkRequest,
|
|
2503
2696
|
createReactNavigationTracker,
|
|
2504
2697
|
platform: Platform.OS,
|
|
2505
2698
|
};
|
|
@@ -2518,3 +2711,7 @@ module.exports.registerArtifactProviders = registerArtifactProviders;
|
|
|
2518
2711
|
module.exports.unregisterArtifactProvider = unregisterArtifactProvider;
|
|
2519
2712
|
module.exports.listRegisteredArtifactProviders = listRegisteredArtifactProviders;
|
|
2520
2713
|
module.exports.clearArtifactProviders = clearArtifactProviders;
|
|
2714
|
+
module.exports.recordNetworkRequest = recordNetworkRequest;
|
|
2715
|
+
module.exports.installNetworkCapture = installNetworkCapture;
|
|
2716
|
+
module.exports.uninstallNetworkCapture = uninstallNetworkCapture;
|
|
2717
|
+
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)
|