@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 +51 -31
- package/android/build.gradle +2 -2
- package/index.js +19 -24
- package/package.json +4 -1
- package/react-inspection-hook.js +76 -0
- package/react-tree-geometry.js +61 -0
- package/react-tree-semantics.js +33 -0
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
|
|
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
|
|
49
|
-
"localNetworkPermission": "Allow $(PRODUCT_NAME) to connect to Ansight
|
|
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.
|
|
81
|
-
- Maven: `ai.ansight:ansight-android:1.3.0-preview.
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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.
|
|
297
|
-
device,
|
|
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
|
-
|
|
307
|
-
|
|
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(
|
|
331
|
-
Prefer
|
|
332
|
-
|
|
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
|
-
| `
|
|
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. |
|
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.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.
|
|
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
|
+
};
|