@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 CHANGED
@@ -8,6 +8,9 @@ handled by the Ansight iOS and Android SDKs. The JavaScript layer normalizes
8
8
  React Native inputs, forwards runtime calls to the native bridge, and registers
9
9
  JavaScript-backed tools for React component-tree inspection.
10
10
 
11
+ For guarded startup and CLI verification, see the
12
+ [React Native getting-started guide](https://www.ansight.ai/docs/sdk/react-native/setup).
13
+
11
14
  ## Install
12
15
 
13
16
  ### React Native CLI
@@ -36,7 +39,7 @@ npx expo install @ansight/react-native
36
39
 
37
40
  Add the bundled config plugin to the app config. It supplies the iOS camera
38
41
  usage description required by QR enrollment and the local-network description
39
- used when connecting to Ansight Studio:
42
+ used when connecting to the local Ansight host:
40
43
 
41
44
  ```json
42
45
  {
@@ -45,8 +48,8 @@ used when connecting to Ansight Studio:
45
48
  [
46
49
  "@ansight/react-native",
47
50
  {
48
- "cameraPermission": "Allow $(PRODUCT_NAME) to scan an Ansight Studio enrollment QR code.",
49
- "localNetworkPermission": "Allow $(PRODUCT_NAME) to connect to Ansight Studio on your local network."
51
+ "cameraPermission": "Allow $(PRODUCT_NAME) to scan an Ansight enrollment QR code.",
52
+ "localNetworkPermission": "Allow $(PRODUCT_NAME) to connect to the Ansight host on your local network."
50
53
  }
51
54
  ]
52
55
  ]
@@ -77,25 +80,48 @@ bundle; its API requires the native Ansight module.
77
80
 
78
81
  This package version expects matching native SDK packages:
79
82
 
80
- - CocoaPods: `Ansight`, `AnsightObjC` version `1.3.0-preview.10`
81
- - Maven: `ai.ansight:ansight-android:1.3.0-preview.10`
83
+ - CocoaPods: `Ansight`, `AnsightObjC` version `1.3.0-preview.12`
84
+ - Maven: `ai.ansight:ansight-android:1.3.0-preview.12`
82
85
 
83
86
  ## Quickstart
84
87
 
85
88
  ```ts
86
89
  import Ansight from "@ansight/react-native";
87
90
 
88
- await Ansight.initializeAndActivate({
89
- useNativeAllInOneDefaults: __DEV__,
90
- clientName: "My React Native App",
91
- toolGuard: __DEV__ ? "readOnly" : "disabled",
92
- lifecycle: true,
93
- });
91
+ let ansightStarted = false;
92
+
93
+ export async function startAnsight() {
94
+ if (!__DEV__ || ansightStarted) {
95
+ return;
96
+ }
97
+ ansightStarted = true;
98
+
99
+ await Ansight.initializeAndActivate({
100
+ useNativeAllInOneDefaults: true,
101
+ clientName: "React Native App",
102
+ toolGuard: "readOnly",
103
+ });
104
+ }
105
+ ```
106
+
107
+ Call `startAnsight()` once from app bootstrap. Start the local host in one
108
+ terminal and leave it running:
109
+
110
+ ```sh
111
+ ansight host run
112
+ ```
113
+
114
+ Launch the native development build, then verify the connected session and tool
115
+ catalog from another terminal:
116
+
117
+ ```sh
118
+ ansight session list --connected --json
119
+ ansight app tools <session-id> --json
94
120
  ```
95
121
 
96
122
  The native iOS Simulator or Android emulator runtime registers automatically
97
- with a running, signed-in Studio. No pairing file, environment variable, host
98
- address, or build-time Studio probe is required.
123
+ through loopback. No account, pairing file, environment variable, host address,
124
+ or build-time host probe is required.
99
125
 
100
126
  `useNativeAllInOneDefaults` defaults to `false`. It only applies the native
101
127
  iOS/Android all-in-one defaults: 400 ms sampling, 120 second retention, FPS,
@@ -151,6 +177,7 @@ The TypeScript `AnsightOptions` surface mirrors Android `AnsightOptions`, iOS
151
177
  | `secureStorage` | Compatibility alias for native secure-storage allow-list settings. |
152
178
  | `remoteTools` | Native visual tree, file, database, preferences, reflection, and secure-storage tool options. |
153
179
  | `lifecycle` | JS AppState tracking toggle. Defaults to true. |
180
+ | `networkCapture` | Opt-in `fetch` and `XMLHttpRequest` metadata capture. Accepts `true` or sanitization/capture options. |
154
181
 
155
182
  Use `withOpenFileHandleTracking()` and `withJniReferenceCountTracking()` to
156
183
  opt in through the builder. Matching `without...` methods disable the channels
@@ -201,13 +228,49 @@ await Ansight.initializeAndActivate({
201
228
  ```
202
229
 
203
230
  The touch mode captures native visual trees only on touch down and touch up.
204
- Move and cancel events do not trigger capture. Native touch capture and
231
+ Move and cancel events do not trigger capture. Rapid boundaries are coalesced
232
+ and rate-limited to protect screenshot cadence. Native touch capture and
205
233
  visual-tree tools/providers must remain enabled.
206
234
 
207
235
  On iOS, `captureGpuBackedSurfaces` defaults to `true` so Metal, SceneKit, and
208
236
  similar GPU-backed views are included. Set it to `false` to use a lower-overhead
209
237
  capture path when those surfaces are not needed.
210
238
 
239
+ ## Network capture
240
+
241
+ Network capture is explicitly enabled by the React Native layer, which
242
+ instruments `fetch` and `XMLHttpRequest`, then sends a typed metadata record
243
+ through the native bridge. It is opt-in:
244
+
245
+ ```ts
246
+ await Ansight.initializeAndActivate(
247
+ Ansight.createOptionsBuilder()
248
+ .withAnsightDefaults()
249
+ .withNetworkCapture({
250
+ maximumBodyBytes: 64 * 1024,
251
+ additionalSensitiveHeaderNames: ["x-tenant-secret"],
252
+ additionalSensitiveQueryParameterNames: ["session"],
253
+ requestSanitizer: request =>
254
+ request.url.includes("/health") ? null : request,
255
+ })
256
+ .withoutNetworkRequestBodies() // optional, independent opt-out
257
+ .build(),
258
+ );
259
+ ```
260
+
261
+ Text request and response bodies are included by default after network capture
262
+ is explicitly enabled, with a 64 KiB default per-body limit. Set a larger
263
+ `maximumBodyBytes` when needed; use the request/response body builder methods to
264
+ opt either side out or back in, and `captureBinaryBodies` to explicitly allow
265
+ Base64 binary content. Standard credentials, cloud signed-URL fields, cookies,
266
+ URL user information, and sensitive text-body assignments are redacted before
267
+ the bridge. Capture hooks detach whenever the native host is disconnected.
268
+
269
+ Use `installNetworkCapture(...)` and `uninstallNetworkCapture()` when capture
270
+ must be controlled independently of initialization. `recordNetworkRequest(...)`
271
+ supports custom HTTP stacks, and `sanitizeNetworkRequest(...)` exposes the same
272
+ app-side policy for inspection or testing.
273
+
211
274
  ## Native Tool Options
212
275
 
213
276
  `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:
@@ -256,25 +319,19 @@ compatibility alias for `remoteTools.secureStorage`.
256
319
 
257
320
  ## Host Connection
258
321
 
259
- No connection call is needed for a simulator or emulator. On a physical
260
- device, scan the QR displayed by Studio once:
322
+ No connection call is needed for a simulator or emulator. For a physical
323
+ device, run `ansight pairing issue --qr`, then open the SDK scanner from a
324
+ developer-only app surface:
261
325
 
262
326
  ```ts
263
327
  await Ansight.enrollFromQrCode({
264
328
  clientName: "My React Native App",
265
- expectedAppId: "com.example.app",
266
329
  });
267
330
  ```
268
331
 
269
- After physical-device enrollment, `connect(null, options)` and the runtime
270
- connection loop use the remembered registration:
271
-
272
- ```ts
273
- await Ansight.connect(null, { clientName: "My React Native App" });
274
- ```
275
-
276
- When Studio is closed or signed out, automatic attempts remain dormant and
277
- retry later without failing the React Native app.
332
+ The SDK supplies the native app id, stores the installation registration
333
+ privately, and reconnects automatically on later launches. If the host is
334
+ unavailable, retry attempts do not fail the React Native app.
278
335
 
279
336
  If the app already owns a scanner, pass its result through the explicit payload
280
337
  API:
@@ -282,17 +339,17 @@ API:
282
339
  ```ts
283
340
  await Ansight.connect(enrollmentPayload, {
284
341
  clientName: "My React Native App",
285
- expectedAppId: "com.example.app",
286
- hostAddressOverride: "192.168.1.20",
287
342
  });
288
343
 
289
344
  await Ansight.clearCachedSession();
290
345
  await Ansight.disconnect();
291
346
  ```
292
347
 
293
- `openSession(pairingPayload, options)` is the low-level direct session path.
294
- Prefer `connect(...)` for normal Studio sessions because it coordinates saved
295
- config, host auto-probe, status, telemetry, and live tool handling.
348
+ `openSession(enrollmentPayload, options)` is the low-level direct session path.
349
+ Prefer automatic registration for simulators and emulators or
350
+ `enrollFromQrCode(...)` for physical devices. Use `connect(...)` only when the
351
+ app already owns the scanner; it coordinates saved registration, host
352
+ auto-probe, status, telemetry, and live tool handling.
296
353
 
297
354
  ## Runtime API
298
355
 
@@ -302,7 +359,7 @@ The bridge exposes the native SDK runtime surface:
302
359
  | --- | --- |
303
360
  | `initialize`, `initializeAndActivate`, `activate`, `deactivate`, `clear` | Runtime lifecycle. |
304
361
  | `connect`, `disconnect`, `openSession`, `completeSession`, `closeSession` | Host and live-session control. |
305
- | `savePairingConfig`, `clearSavedPairing`, `clearCachedSession` | Pairing persistence. |
362
+ | `clearCachedSession` | Clears remembered app-installation registration state. |
306
363
  | `status`, `snapshot`, `hostConnectionStatus`, `currentOptions` | Diagnostics and state. |
307
364
  | `registerMetricChannel`, `metric`, `recordMetric` | Metric channels and samples. |
308
365
  | `event`, `recordEvent`, `screenViewed`, `trackRoute` | App events and screen views. |
@@ -389,6 +446,19 @@ await Ansight.clearSessionProperties();
389
446
  When connected, property mutations are sent immediately. When disconnected, the
390
447
  latest values are included in the next `session.open`.
391
448
 
449
+ The bridge automatically adds these property groups:
450
+
451
+ | Group | Properties |
452
+ | --- | --- |
453
+ | `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. |
454
+ | `localization` | Canonical locale, language, optional region, IANA time zone when exposed by `Intl`, and UTC offset in minutes. |
455
+
456
+ App-provided values override an automatic value with the same group and key.
457
+ Clearing all properties, or removing one automatic property, restores the
458
+ current bridge-owned value. `localization` reflects the JavaScript runtime
459
+ locale; if the app selects a different language through an i18n library, set
460
+ `localization.locale`, `language`, and `region` explicitly.
461
+
392
462
  ## Tool Guards
393
463
 
394
464
  | Value | Allowed scopes |
@@ -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.10"
71
- implementation("ai.ansight:ansight-android:1.3.0-preview.10")
70
+ findProperty("ansightAndroidVersion") ?: "1.3.0-preview.12"
71
+ implementation("ai.ansight:ansight-android:1.3.0-preview.12")
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
  };