@ansight/react-native 1.3.0-preview.11 → 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.11`
81
- - Maven: `ai.ansight:ansight-android:1.3.0-preview.11`
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,
@@ -293,25 +319,19 @@ compatibility alias for `remoteTools.secureStorage`.
293
319
 
294
320
  ## Host Connection
295
321
 
296
- No connection call is needed for a simulator or emulator. On a physical
297
- 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:
298
325
 
299
326
  ```ts
300
327
  await Ansight.enrollFromQrCode({
301
328
  clientName: "My React Native App",
302
- expectedAppId: "com.example.app",
303
329
  });
304
330
  ```
305
331
 
306
- After physical-device enrollment, `connect(null, options)` and the runtime
307
- connection loop use the remembered registration:
308
-
309
- ```ts
310
- await Ansight.connect(null, { clientName: "My React Native App" });
311
- ```
312
-
313
- When Studio is closed or signed out, automatic attempts remain dormant and
314
- 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.
315
335
 
316
336
  If the app already owns a scanner, pass its result through the explicit payload
317
337
  API:
@@ -319,17 +339,17 @@ API:
319
339
  ```ts
320
340
  await Ansight.connect(enrollmentPayload, {
321
341
  clientName: "My React Native App",
322
- expectedAppId: "com.example.app",
323
- hostAddressOverride: "192.168.1.20",
324
342
  });
325
343
 
326
344
  await Ansight.clearCachedSession();
327
345
  await Ansight.disconnect();
328
346
  ```
329
347
 
330
- `openSession(pairingPayload, options)` is the low-level direct session path.
331
- Prefer `connect(...)` for normal Studio sessions because it coordinates saved
332
- 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.
333
353
 
334
354
  ## Runtime API
335
355
 
@@ -339,7 +359,7 @@ The bridge exposes the native SDK runtime surface:
339
359
  | --- | --- |
340
360
  | `initialize`, `initializeAndActivate`, `activate`, `deactivate`, `clear` | Runtime lifecycle. |
341
361
  | `connect`, `disconnect`, `openSession`, `completeSession`, `closeSession` | Host and live-session control. |
342
- | `savePairingConfig`, `clearSavedPairing`, `clearCachedSession` | Pairing persistence. |
362
+ | `clearCachedSession` | Clears remembered app-installation registration state. |
343
363
  | `status`, `snapshot`, `hostConnectionStatus`, `currentOptions` | Diagnostics and state. |
344
364
  | `registerMetricChannel`, `metric`, `recordMetric` | Metric channels and samples. |
345
365
  | `event`, `recordEvent`, `screenViewed`, `trackRoute` | App events and screen views. |
@@ -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.11"
71
- implementation("ai.ansight:ansight-android:1.3.0-preview.11")
70
+ findProperty("ansightAndroidVersion") ?: "1.3.0-preview.12"
71
+ implementation("ai.ansight:ansight-android:1.3.0-preview.12")
72
72
  }
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,
@@ -1531,36 +1544,16 @@ function summarizeProps(props) {
1531
1544
  return summary;
1532
1545
  }
1533
1546
 
1534
- function reactSemanticRole(type, props, fiberTag) {
1535
- const declared = props && (props.accessibilityRole || props.role);
1536
- if (declared) return String(declared).toLowerCase();
1537
- if (fiberTag === 6 || /text/i.test(type)) return "text";
1538
- if (/button|pressable|touchable/i.test(type)) return "button";
1539
- if (/textinput/i.test(type)) return "textbox";
1540
- if (/switch/i.test(type)) return "switch";
1541
- if (/scrollview|flatlist|sectionlist/i.test(type)) return "scrollview";
1542
- return "view";
1543
- }
1544
-
1545
- function reactSupportedActions(type, props) {
1546
- const actions = [];
1547
- if (props && typeof props.onPress === "function") actions.push("tap");
1548
- if (props && (typeof props.onChangeText === "function" || /textinput/i.test(type))) {
1549
- actions.push("typeText", "focus");
1550
- }
1551
- if (/scrollview|flatlist|sectionlist/i.test(type) || (props && typeof props.onScroll === "function")) {
1552
- actions.push("scroll", "swipe");
1553
- }
1554
- return actions;
1555
- }
1556
-
1557
1547
  function applyReactTargetability(node, fiber, props, type) {
1558
1548
  const style = StyleSheet && typeof StyleSheet.flatten === "function"
1559
1549
  ? StyleSheet.flatten(props && props.style)
1560
1550
  : props && props.style;
1561
1551
  node.text = node.label || (node.visual && node.visual.text) || null;
1562
- node.role = reactSemanticRole(type, props, fiber.tag);
1563
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
+ }
1564
1557
  node.visible = !(style && style.display === "none") && !(props && props.accessibilityElementsHidden);
1565
1558
  node.enabled = !(props && (props.disabled === true || props.accessibilityState && props.accessibilityState.disabled === true));
1566
1559
  node.focusable = !!(props && props.focusable) || node.supportedActions.includes("focus");
@@ -1914,6 +1907,7 @@ async function captureReactVisualTree(rawOptions = {}) {
1914
1907
  source: "react",
1915
1908
  adapter: "react.fiber",
1916
1909
  treeKind: "component",
1910
+ coordinateSpace: createReactCoordinateSpace(Dimensions, context.nodesWithNativeTags),
1917
1911
  capturedAtUtc: new Date().toISOString(),
1918
1912
  hookAvailable: roots.hookAvailable,
1919
1913
  renderers: roots.renderers,
@@ -1974,6 +1968,7 @@ async function captureReactShadowTree(rawOptions = {}) {
1974
1968
  source: "react-native",
1975
1969
  adapter: "react-native.host-fiber",
1976
1970
  treeKind: "shadow",
1971
+ coordinateSpace: createReactCoordinateSpace(Dimensions, context.nodesWithNativeTags),
1977
1972
  capturedAtUtc: new Date().toISOString(),
1978
1973
  hookAvailable: roots.hookAvailable,
1979
1974
  renderers: roots.renderers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansight/react-native",
3
- "version": "1.3.0-preview.11",
3
+ "version": "1.3.0-preview.12",
4
4
  "description": "React Native bridge for the Ansight mobile SDK.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -25,6 +25,9 @@
25
25
  "README.md",
26
26
  "index.js",
27
27
  "index.d.ts",
28
+ "react-inspection-hook.js",
29
+ "react-tree-geometry.js",
30
+ "react-tree-semantics.js",
28
31
  "network.js",
29
32
  "session-properties.js",
30
33
  "app.plugin.js",
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ const ANSIGHT_HOOK_MARKER = "__ansightReactInspectionHook";
4
+
5
+ function hasElementState(current) {
6
+ const state = current && current.memoizedState;
7
+ if (!state || typeof state !== "object") {
8
+ return true;
9
+ }
10
+ if (!Object.prototype.hasOwnProperty.call(state, "element")) {
11
+ return true;
12
+ }
13
+ return state.element != null;
14
+ }
15
+
16
+ function createReactInspectionHook() {
17
+ const renderers = new Map();
18
+ const fiberRoots = new Map();
19
+ let nextRendererId = 1;
20
+
21
+ function rootsForRenderer(rendererId) {
22
+ let roots = fiberRoots.get(rendererId);
23
+ if (!roots) {
24
+ roots = new Set();
25
+ fiberRoots.set(rendererId, roots);
26
+ }
27
+ return roots;
28
+ }
29
+
30
+ return {
31
+ [ANSIGHT_HOOK_MARKER]: true,
32
+ supportsFiber: true,
33
+ isDisabled: false,
34
+ renderers,
35
+ _fiberRoots: fiberRoots,
36
+ inject(renderer) {
37
+ const rendererId = nextRendererId++;
38
+ renderers.set(rendererId, renderer);
39
+ rootsForRenderer(rendererId);
40
+ return rendererId;
41
+ },
42
+ getFiberRoots(rendererId) {
43
+ return rootsForRenderer(rendererId);
44
+ },
45
+ onCommitFiberRoot(rendererId, root) {
46
+ const roots = rootsForRenderer(rendererId);
47
+ if (root && root.current && hasElementState(root.current)) {
48
+ roots.add(root);
49
+ } else {
50
+ roots.delete(root);
51
+ }
52
+ },
53
+ onCommitFiberUnmount() {},
54
+ };
55
+ }
56
+
57
+ function ensureReactInspectionHook(runtimeGlobal) {
58
+ if (!runtimeGlobal || typeof runtimeGlobal !== "object") {
59
+ return undefined;
60
+ }
61
+
62
+ const existing = runtimeGlobal.__REACT_DEVTOOLS_GLOBAL_HOOK__;
63
+ if (existing) {
64
+ return existing;
65
+ }
66
+
67
+ const hook = createReactInspectionHook();
68
+ runtimeGlobal.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
69
+ return hook;
70
+ }
71
+
72
+ module.exports = {
73
+ ANSIGHT_HOOK_MARKER,
74
+ createReactInspectionHook,
75
+ ensureReactInspectionHook,
76
+ };
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ function readDimensionSize(dimensions, name) {
4
+ if (!dimensions || typeof dimensions.get !== "function") {
5
+ return null;
6
+ }
7
+ try {
8
+ const value = dimensions.get(name);
9
+ const width = Number(value && value.width);
10
+ const height = Number(value && value.height);
11
+ if (Number.isFinite(width) && width > 0 && Number.isFinite(height) && height > 0) {
12
+ return { width, height };
13
+ }
14
+ } catch (_) {
15
+ // Some non-native React renderers do not expose screen dimensions.
16
+ }
17
+ return null;
18
+ }
19
+
20
+ function viewportHostBounds(nodes, screenSize) {
21
+ const candidates = (nodes || [])
22
+ .map((node) => node && node.bounds)
23
+ .filter((bounds) => bounds
24
+ && Number.isFinite(Number(bounds.x))
25
+ && Number.isFinite(Number(bounds.y))
26
+ && Number.isFinite(Number(bounds.width))
27
+ && Number.isFinite(Number(bounds.height))
28
+ && Number(bounds.width) >= screenSize.width * 0.85
29
+ && Number(bounds.height) >= screenSize.height * 0.5);
30
+ if (candidates.length === 0) {
31
+ return null;
32
+ }
33
+ return candidates.reduce((largest, candidate) => (
34
+ Number(candidate.width) * Number(candidate.height)
35
+ > Number(largest.width) * Number(largest.height)
36
+ ? candidate
37
+ : largest
38
+ ));
39
+ }
40
+
41
+ function createReactCoordinateSpace(dimensions, measuredNodes = []) {
42
+ const screenSize = readDimensionSize(dimensions, "screen");
43
+ const windowSize = readDimensionSize(dimensions, "window");
44
+ const size = screenSize || windowSize;
45
+ if (!size) {
46
+ return undefined;
47
+ }
48
+
49
+ const hostBounds = viewportHostBounds(measuredNodes, size);
50
+ return {
51
+ x: hostBounds ? Math.min(0, Number(hostBounds.x)) : 0,
52
+ y: hostBounds ? Math.min(0, Number(hostBounds.y)) : 0,
53
+ width: size.width,
54
+ height: size.height,
55
+ source: screenSize ? "react-native.screen" : "react-native.window",
56
+ };
57
+ }
58
+
59
+ module.exports = {
60
+ createReactCoordinateSpace,
61
+ };
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ function reactSemanticRole(type, props, fiberTag) {
4
+ const declared = props && (props.accessibilityRole || props.role);
5
+ if (declared) return String(declared).toLowerCase();
6
+ if (fiberTag === 6 || /text/i.test(type)) return "text";
7
+ if (/button|pressable|touchable/i.test(type)) return "button";
8
+ if (/textinput/i.test(type)) return "textbox";
9
+ if (/switch/i.test(type)) return "switch";
10
+ if (/scrollview|flatlist|sectionlist/i.test(type)) return "scrollview";
11
+ return "view";
12
+ }
13
+
14
+ function reactSupportedActions(type, props) {
15
+ const actions = [];
16
+ if (props && (
17
+ typeof props.onPress === "function"
18
+ || typeof props.onClick === "function"
19
+ || typeof props.onResponderRelease === "function"
20
+ )) actions.push("tap");
21
+ if (props && (typeof props.onChangeText === "function" || /textinput/i.test(type))) {
22
+ actions.push("typeText", "focus");
23
+ }
24
+ if (/scrollview|flatlist|sectionlist/i.test(type) || (props && typeof props.onScroll === "function")) {
25
+ actions.push("scroll", "swipe");
26
+ }
27
+ return actions;
28
+ }
29
+
30
+ module.exports = {
31
+ reactSemanticRole,
32
+ reactSupportedActions,
33
+ };