react-native-ai-debugger 1.6.16 → 1.8.1
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 +8 -1
- package/build/__tests__/unit/exec.test.d.ts +2 -0
- package/build/__tests__/unit/exec.test.d.ts.map +1 -0
- package/build/__tests__/unit/exec.test.js +54 -0
- package/build/__tests__/unit/exec.test.js.map +1 -0
- package/build/__tests__/unit/focusedInput.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInput.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInput.test.js +58 -0
- package/build/__tests__/unit/focusedInput.test.js.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.js +79 -0
- package/build/__tests__/unit/focusedInputTools.test.js.map +1 -0
- package/build/__tests__/unit/tap.test.js +46 -0
- package/build/__tests__/unit/tap.test.js.map +1 -1
- package/build/core/android.d.ts +3 -3
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +21 -14
- package/build/core/android.js.map +1 -1
- package/build/core/appDetection.d.ts +7 -0
- package/build/core/appDetection.d.ts.map +1 -1
- package/build/core/appDetection.js +38 -17
- package/build/core/appDetection.js.map +1 -1
- package/build/core/componentSearch.d.ts +38 -0
- package/build/core/componentSearch.d.ts.map +1 -0
- package/build/core/componentSearch.js +419 -0
- package/build/core/componentSearch.js.map +1 -0
- package/build/core/componentTree.d.ts +12 -0
- package/build/core/componentTree.d.ts.map +1 -0
- package/build/core/componentTree.js +275 -0
- package/build/core/componentTree.js.map +1 -0
- package/build/core/connection.d.ts +1 -0
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +99 -89
- package/build/core/connection.js.map +1 -1
- package/build/core/debugGlobals.d.ts +5 -0
- package/build/core/debugGlobals.d.ts.map +1 -0
- package/build/core/debugGlobals.js +300 -0
- package/build/core/debugGlobals.js.map +1 -0
- package/build/core/errors.d.ts +14 -0
- package/build/core/errors.d.ts.map +1 -0
- package/build/core/errors.js +17 -0
- package/build/core/errors.js.map +1 -0
- package/build/core/exec.d.ts +22 -0
- package/build/core/exec.d.ts.map +1 -0
- package/build/core/exec.js +54 -0
- package/build/core/exec.js.map +1 -0
- package/build/core/executor.d.ts +7 -204
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +10 -4103
- package/build/core/executor.js.map +1 -1
- package/build/core/focusedInput.d.ts +27 -0
- package/build/core/focusedInput.d.ts.map +1 -0
- package/build/core/focusedInput.js +110 -0
- package/build/core/focusedInput.js.map +1 -0
- package/build/core/focusedInputTools.d.ts +21 -0
- package/build/core/focusedInputTools.d.ts.map +1 -0
- package/build/core/focusedInputTools.js +51 -0
- package/build/core/focusedInputTools.js.map +1 -0
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +15 -2
- package/build/core/guides.js.map +1 -1
- package/build/core/index.d.ts +4 -3
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +4 -3
- package/build/core/index.js.map +1 -1
- package/build/core/inspector.d.ts +50 -0
- package/build/core/inspector.d.ts.map +1 -0
- package/build/core/inspector.js +604 -0
- package/build/core/inspector.js.map +1 -0
- package/build/core/ios.d.ts +8 -0
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +142 -2
- package/build/core/ios.js.map +1 -1
- package/build/core/jsExecute.d.ts +27 -0
- package/build/core/jsExecute.d.ts.map +1 -0
- package/build/core/jsExecute.js +453 -0
- package/build/core/jsExecute.js.map +1 -0
- package/build/core/ocr.d.ts +13 -0
- package/build/core/ocr.d.ts.map +1 -1
- package/build/core/ocr.js +37 -15
- package/build/core/ocr.js.map +1 -1
- package/build/core/posthog.d.ts.map +1 -1
- package/build/core/posthog.js +7 -1
- package/build/core/posthog.js.map +1 -1
- package/build/core/pressables.d.ts +44 -0
- package/build/core/pressables.d.ts.map +1 -0
- package/build/core/pressables.js +1480 -0
- package/build/core/pressables.js.map +1 -0
- package/build/core/register.d.ts +8 -0
- package/build/core/register.d.ts.map +1 -0
- package/build/core/register.js +216 -0
- package/build/core/register.js.map +1 -0
- package/build/core/screenLayout.d.ts +52 -0
- package/build/core/screenLayout.d.ts.map +1 -0
- package/build/core/screenLayout.js +828 -0
- package/build/core/screenLayout.js.map +1 -0
- package/build/core/toolHelpers.d.ts +24 -0
- package/build/core/toolHelpers.d.ts.map +1 -0
- package/build/core/toolHelpers.js +118 -0
- package/build/core/toolHelpers.js.map +1 -0
- package/build/core/types.d.ts +11 -6
- package/build/core/types.d.ts.map +1 -1
- package/build/index.d.ts +2 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +37 -4313
- package/build/index.js.map +1 -1
- package/build/pro/tap.d.ts +51 -5
- package/build/pro/tap.d.ts.map +1 -1
- package/build/pro/tap.js +457 -54
- package/build/pro/tap.js.map +1 -1
- package/build/tools/accountTools.d.ts +2 -0
- package/build/tools/accountTools.d.ts.map +1 -1
- package/build/tools/accountTools.js +5 -0
- package/build/tools/accountTools.js.map +1 -1
- package/build/tools/bundleTools.d.ts +3 -0
- package/build/tools/bundleTools.d.ts.map +1 -0
- package/build/tools/bundleTools.js +368 -0
- package/build/tools/bundleTools.js.map +1 -0
- package/build/tools/componentTools.d.ts +3 -0
- package/build/tools/componentTools.d.ts.map +1 -0
- package/build/tools/componentTools.js +639 -0
- package/build/tools/componentTools.js.map +1 -0
- package/build/tools/connectionTools.d.ts +3 -0
- package/build/tools/connectionTools.d.ts.map +1 -0
- package/build/tools/connectionTools.js +564 -0
- package/build/tools/connectionTools.js.map +1 -0
- package/build/tools/deviceTools.d.ts +3 -0
- package/build/tools/deviceTools.d.ts.map +1 -0
- package/build/tools/deviceTools.js +296 -0
- package/build/tools/deviceTools.js.map +1 -0
- package/build/tools/executionTools.d.ts +3 -0
- package/build/tools/executionTools.d.ts.map +1 -0
- package/build/tools/executionTools.js +173 -0
- package/build/tools/executionTools.js.map +1 -0
- package/build/tools/interactionTools.d.ts +3 -0
- package/build/tools/interactionTools.d.ts.map +1 -0
- package/build/tools/interactionTools.js +414 -0
- package/build/tools/interactionTools.js.map +1 -0
- package/build/tools/logTools.d.ts +3 -0
- package/build/tools/logTools.d.ts.map +1 -0
- package/build/tools/logTools.js +371 -0
- package/build/tools/logTools.js.map +1 -0
- package/build/tools/metaTools.d.ts +7 -0
- package/build/tools/metaTools.d.ts.map +1 -0
- package/build/tools/metaTools.js +205 -0
- package/build/tools/metaTools.js.map +1 -0
- package/build/tools/networkTools.d.ts +3 -0
- package/build/tools/networkTools.d.ts.map +1 -0
- package/build/tools/networkTools.js +478 -0
- package/build/tools/networkTools.js.map +1 -0
- package/build/tools/reduxTools.d.ts +3 -0
- package/build/tools/reduxTools.d.ts.map +1 -0
- package/build/tools/reduxTools.js +83 -0
- package/build/tools/reduxTools.js.map +1 -0
- package/build/tools/screenshotTools.d.ts +3 -0
- package/build/tools/screenshotTools.d.ts.map +1 -0
- package/build/tools/screenshotTools.js +606 -0
- package/build/tools/screenshotTools.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { executeInApp } from "./jsExecute.js";
|
|
2
|
+
function formatTreeToTonl(node, indent = 0) {
|
|
3
|
+
const prefix = " ".repeat(indent);
|
|
4
|
+
let result = `${prefix}${node.component}`;
|
|
5
|
+
// Add props inline if present
|
|
6
|
+
if (node.props && Object.keys(node.props).length > 0) {
|
|
7
|
+
const propsStr = Object.entries(node.props)
|
|
8
|
+
.map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`)
|
|
9
|
+
.join(",");
|
|
10
|
+
result += ` (${propsStr})`;
|
|
11
|
+
}
|
|
12
|
+
// Add layout inline if present
|
|
13
|
+
if (node.layout && Object.keys(node.layout).length > 0) {
|
|
14
|
+
const layoutStr = Object.entries(node.layout)
|
|
15
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
16
|
+
.join(",");
|
|
17
|
+
result += ` [${layoutStr}]`;
|
|
18
|
+
}
|
|
19
|
+
result += "\n";
|
|
20
|
+
// Recurse children
|
|
21
|
+
if (node.children && node.children.length > 0) {
|
|
22
|
+
for (const child of node.children) {
|
|
23
|
+
result += formatTreeToTonl(child, indent + 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
// Ultra-compact structure-only tree format (just component names, indented)
|
|
29
|
+
function formatTreeStructureOnly(node, indent = 0) {
|
|
30
|
+
const prefix = " ".repeat(indent);
|
|
31
|
+
let result = `${prefix}${node.component}\n`;
|
|
32
|
+
if (node.children && node.children.length > 0) {
|
|
33
|
+
for (const child of node.children) {
|
|
34
|
+
result += formatTreeStructureOnly(child, indent + 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
export async function getComponentTree(options = {}) {
|
|
40
|
+
const { includeProps = false, includeStyles = false, hideInternals = true, format = "tonl", structureOnly = false, focusedOnly = false, device } = options;
|
|
41
|
+
// Use lower default depth for structureOnly to keep output compact (~2-5KB)
|
|
42
|
+
// Full mode uses higher depth since TONL format handles it better
|
|
43
|
+
// focusedOnly mode uses moderate depth since we're already filtering to active screen
|
|
44
|
+
const maxDepth = options.maxDepth ?? 5000;
|
|
45
|
+
const expression = `
|
|
46
|
+
(function() {
|
|
47
|
+
const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
48
|
+
if (!hook) return { error: 'React DevTools hook not found. Make sure you are running a development build.' };
|
|
49
|
+
|
|
50
|
+
// Try to get fiber roots (renderer ID is usually 1)
|
|
51
|
+
let roots = [];
|
|
52
|
+
if (hook.getFiberRoots) {
|
|
53
|
+
roots = [...(hook.getFiberRoots(1) || [])];
|
|
54
|
+
}
|
|
55
|
+
if (roots.length === 0 && hook.renderers) {
|
|
56
|
+
// Try all renderers
|
|
57
|
+
for (const [id] of hook.renderers) {
|
|
58
|
+
const r = hook.getFiberRoots ? [...(hook.getFiberRoots(id) || [])] : [];
|
|
59
|
+
if (r.length > 0) {
|
|
60
|
+
roots = r;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (roots.length === 0) return { error: 'No fiber roots found. The app may not have rendered yet.' };
|
|
66
|
+
|
|
67
|
+
const maxDepth = ${maxDepth};
|
|
68
|
+
const includeProps = ${includeProps};
|
|
69
|
+
const includeStyles = ${includeStyles};
|
|
70
|
+
const hideInternals = ${hideInternals};
|
|
71
|
+
const focusedOnly = ${focusedOnly};
|
|
72
|
+
|
|
73
|
+
// Internal RN components to hide
|
|
74
|
+
const internalPatterns = /^(RCT|RNS|Animated\\(|AnimatedComponent|VirtualizedList|CellRenderer|ScrollViewContext|PerformanceLoggerContext|RootTagContext|HeaderShownContext|HeaderHeightContext|HeaderBackContext|SafeAreaFrameContext|SafeAreaInsetsContext|VirtualizedListContext|VirtualizedListCellContextProvider|StaticContainer|DelayedFreeze|Freeze|Suspender|DebugContainer|MaybeNestedStack|SceneView|NavigationContent|PreventRemoveProvider|EnsureSingleNavigator)/;
|
|
75
|
+
|
|
76
|
+
// Screen component patterns - user's actual screens (strict matching)
|
|
77
|
+
// Only match *Screen and *Page to avoid false positives like BottomTabView
|
|
78
|
+
const screenPatterns = /^[A-Z][a-zA-Z0-9]*(Screen|Page)$/;
|
|
79
|
+
|
|
80
|
+
// Navigation/internal screen patterns to SKIP (these look like screens but are framework components)
|
|
81
|
+
const internalScreenPatterns = /^(MaybeScreen|Screen$|ScreenContainer|ScreenStack|SceneView|Background$)/;
|
|
82
|
+
|
|
83
|
+
// Provider/wrapper patterns to skip when finding focused screen
|
|
84
|
+
const wrapperPatterns = /^(App|AppContainer|Provider|Context|SafeArea|Gesture|Theme|Redux|Root|Navigator|Stack|Tab|Drawer|Navigation|Container|Wrapper|Layout|ErrorBoundary|Suspense|PersistGate|LinkingContext|AppState|View|Fragment|NativeStack|BottomTab|Screen$)/i;
|
|
85
|
+
|
|
86
|
+
// Global overlay patterns - stop traversing into these subtrees
|
|
87
|
+
// Be specific to avoid blocking BottomSheetDrawer, PortalProvider, etc.
|
|
88
|
+
const overlayPatterns = /^(BottomSheet$|BottomSheetGlobal|Modal$|Toast$|Snackbar$|Dialog$|Overlay$|Popup$|MyToast$|PaywallModal$|FullScreenBannerModal$)/i;
|
|
89
|
+
|
|
90
|
+
// Navigation container patterns - skip traversing into these (screens inside are nav screens, not focused content)
|
|
91
|
+
const navContainerPatterns = /^(RootNavigation|NativeStackNavigator|BottomTabNavigator|DrawerNavigator|TabNavigator|StackNavigator)/;
|
|
92
|
+
|
|
93
|
+
function getComponentName(fiber) {
|
|
94
|
+
if (!fiber || !fiber.type) return null;
|
|
95
|
+
if (typeof fiber.type === 'string') return fiber.type; // Host component (View, Text, etc.)
|
|
96
|
+
return fiber.type.displayName || fiber.type.name || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function shouldHide(name) {
|
|
100
|
+
if (!hideInternals || !name) return false;
|
|
101
|
+
return internalPatterns.test(name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractLayoutStyles(style) {
|
|
105
|
+
try {
|
|
106
|
+
if (!style) return null;
|
|
107
|
+
const merged = Array.isArray(style)
|
|
108
|
+
? Object.assign({}, ...style.filter(Boolean).map(s => {
|
|
109
|
+
try { return typeof s === 'object' ? s : {}; }
|
|
110
|
+
catch { return {}; }
|
|
111
|
+
}))
|
|
112
|
+
: (typeof style === 'object' ? style : {});
|
|
113
|
+
|
|
114
|
+
const layout = {};
|
|
115
|
+
const layoutKeys = [
|
|
116
|
+
'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight',
|
|
117
|
+
'paddingHorizontal', 'paddingVertical',
|
|
118
|
+
'margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight',
|
|
119
|
+
'marginHorizontal', 'marginVertical',
|
|
120
|
+
'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
|
|
121
|
+
'flex', 'flexDirection', 'flexWrap', 'flexGrow', 'flexShrink',
|
|
122
|
+
'justifyContent', 'alignItems', 'alignSelf', 'alignContent',
|
|
123
|
+
'position', 'top', 'bottom', 'left', 'right',
|
|
124
|
+
'gap', 'rowGap', 'columnGap',
|
|
125
|
+
'borderWidth', 'borderTopWidth', 'borderBottomWidth', 'borderLeftWidth', 'borderRightWidth'
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
for (const key of layoutKeys) {
|
|
129
|
+
if (merged[key] !== undefined) layout[key] = merged[key];
|
|
130
|
+
}
|
|
131
|
+
return Object.keys(layout).length > 0 ? layout : null;
|
|
132
|
+
} catch { return null; }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function walkFiber(fiber, depth) {
|
|
136
|
+
if (!fiber || depth > maxDepth) return null;
|
|
137
|
+
|
|
138
|
+
const name = getComponentName(fiber);
|
|
139
|
+
|
|
140
|
+
// Skip anonymous/internal components unless they have meaningful children
|
|
141
|
+
if (!name || shouldHide(name)) {
|
|
142
|
+
// Still traverse children
|
|
143
|
+
let child = fiber.child;
|
|
144
|
+
const children = [];
|
|
145
|
+
while (child) {
|
|
146
|
+
const childResult = walkFiber(child, depth);
|
|
147
|
+
if (childResult) children.push(childResult);
|
|
148
|
+
child = child.sibling;
|
|
149
|
+
}
|
|
150
|
+
// Return first meaningful child or null
|
|
151
|
+
return children.length === 1 ? children[0] : (children.length > 1 ? { component: '(Fragment)', children } : null);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const node = { component: name };
|
|
155
|
+
|
|
156
|
+
// Include props if requested (excluding children and style for cleaner output)
|
|
157
|
+
if (includeProps && fiber.memoizedProps) {
|
|
158
|
+
const props = {};
|
|
159
|
+
for (const key of Object.keys(fiber.memoizedProps)) {
|
|
160
|
+
if (key === 'children' || key === 'style') continue;
|
|
161
|
+
try {
|
|
162
|
+
const val = fiber.memoizedProps[key];
|
|
163
|
+
if (typeof val === 'function') {
|
|
164
|
+
props[key] = '[Function]';
|
|
165
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
166
|
+
props[key] = Array.isArray(val) ? '[Array]' : '[Object]';
|
|
167
|
+
} else {
|
|
168
|
+
props[key] = val;
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
props[key] = '[Animated Value]';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (Object.keys(props).length > 0) node.props = props;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Include layout styles if requested
|
|
178
|
+
try {
|
|
179
|
+
if (includeStyles && fiber.memoizedProps?.style) {
|
|
180
|
+
const layout = extractLayoutStyles(fiber.memoizedProps.style);
|
|
181
|
+
if (layout) node.layout = layout;
|
|
182
|
+
}
|
|
183
|
+
} catch { /* animated style — skip */ }
|
|
184
|
+
|
|
185
|
+
// Traverse children
|
|
186
|
+
let child = fiber.child;
|
|
187
|
+
const children = [];
|
|
188
|
+
while (child) {
|
|
189
|
+
const childResult = walkFiber(child, depth + 1);
|
|
190
|
+
if (childResult) children.push(childResult);
|
|
191
|
+
child = child.sibling;
|
|
192
|
+
}
|
|
193
|
+
if (children.length > 0) node.children = children;
|
|
194
|
+
|
|
195
|
+
return node;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Find focused screen if requested
|
|
199
|
+
function findFocusedScreen(fiber, depth = 0) {
|
|
200
|
+
if (!fiber || depth > 5000) return null;
|
|
201
|
+
|
|
202
|
+
const name = getComponentName(fiber);
|
|
203
|
+
|
|
204
|
+
// Skip overlays (BottomSheet, Modal, Toast, etc.) - don't traverse into them
|
|
205
|
+
if (name && overlayPatterns.test(name)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Skip navigation containers - screens inside are nav screens, not focused content
|
|
210
|
+
if (name && navContainerPatterns.test(name)) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check if this is a user's screen component (not framework internals)
|
|
215
|
+
if (name && screenPatterns.test(name) && !wrapperPatterns.test(name) && !internalScreenPatterns.test(name)) {
|
|
216
|
+
return fiber;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Search children
|
|
220
|
+
let child = fiber.child;
|
|
221
|
+
while (child) {
|
|
222
|
+
const found = findFocusedScreen(child, depth + 1);
|
|
223
|
+
if (found) return found;
|
|
224
|
+
child = child.sibling;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let startFiber = roots[0].current;
|
|
231
|
+
let focusedScreenName = null;
|
|
232
|
+
|
|
233
|
+
if (focusedOnly) {
|
|
234
|
+
const focused = findFocusedScreen(roots[0].current);
|
|
235
|
+
if (focused) {
|
|
236
|
+
startFiber = focused;
|
|
237
|
+
focusedScreenName = getComponentName(focused);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const tree = walkFiber(startFiber, 0);
|
|
242
|
+
|
|
243
|
+
if (focusedOnly && focusedScreenName) {
|
|
244
|
+
return { focusedScreen: focusedScreenName, tree };
|
|
245
|
+
}
|
|
246
|
+
return { tree };
|
|
247
|
+
})()
|
|
248
|
+
`;
|
|
249
|
+
// Use a longer timeout for component tree traversal — large apps can exceed 10s
|
|
250
|
+
const result = await executeInApp(expression, false, { timeoutMs: 30000 }, device);
|
|
251
|
+
// Apply formatting if requested
|
|
252
|
+
if (result.success && result.result) {
|
|
253
|
+
try {
|
|
254
|
+
const parsed = JSON.parse(result.result);
|
|
255
|
+
if (parsed.tree) {
|
|
256
|
+
const prefix = parsed.focusedScreen ? `Focused: ${parsed.focusedScreen}\n\n` : "";
|
|
257
|
+
// Structure-only mode: ultra-compact format with just component names
|
|
258
|
+
if (structureOnly) {
|
|
259
|
+
const structure = formatTreeStructureOnly(parsed.tree);
|
|
260
|
+
return { success: true, result: prefix + structure };
|
|
261
|
+
}
|
|
262
|
+
// TONL format: compact with props/layout
|
|
263
|
+
if (format === "tonl") {
|
|
264
|
+
const tonl = formatTreeToTonl(parsed.tree);
|
|
265
|
+
return { success: true, result: prefix + tonl };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// If parsing fails, return original result
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=componentTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"componentTree.js","sourceRoot":"","sources":["../../src/core/componentTree.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,6 +23,7 @@ export declare function clearReconnectionSuppression(): void;
|
|
|
23
23
|
* Tries with Origin first, falls back to without on quick failure.
|
|
24
24
|
*/
|
|
25
25
|
export declare function createWebSocketWithOriginFallback(url: string, timeoutMs?: number): Promise<WebSocket>;
|
|
26
|
+
export declare function getWebSocketStateName(state: number): string;
|
|
26
27
|
/**
|
|
27
28
|
* Purge stale connections for scanned ports.
|
|
28
29
|
* When Metro restarts with a different app, /json returns new device IDs.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/core/connection.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAoB,YAAY,EAAkB,cAAc,EAAsB,sBAAsB,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/core/connection.ts"],"names":[],"mappings":"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"}
|
package/build/core/connection.js
CHANGED
|
@@ -5,6 +5,7 @@ import { injectNetworkInterceptor, sendNetworkEnable, isInterceptorEvent, applyI
|
|
|
5
5
|
import { findSimulatorByName } from "./ios.js";
|
|
6
6
|
import { fetchDevices, selectMainDevice, scanMetroPorts } from "./metro.js";
|
|
7
7
|
import { probeCdpAlive } from "./probe.js";
|
|
8
|
+
import { UserInputError } from "./errors.js";
|
|
8
9
|
import { scheduleAppDetection } from "./appDetection.js";
|
|
9
10
|
import { DEFAULT_RECONNECTION_CONFIG, MIN_STABLE_CONNECTION_MS, initConnectionState, updateConnectionState, getConnectionState, recordConnectionGap, closeConnectionGap, saveConnectionMetadata, clearConnectionMetadata, getConnectionMetadata, saveReconnectionTimer, cancelReconnectionTimer, calculateBackoffDelay, initContextHealth, markContextHealthy, markContextStale, getContextHealth, updateContextHealth, formatDuration, } from "./connectionState.js";
|
|
10
11
|
// Connection locks to prevent concurrent connection attempts to the same device
|
|
@@ -166,7 +167,7 @@ function findAppKeyForDevice(device) {
|
|
|
166
167
|
return null;
|
|
167
168
|
}
|
|
168
169
|
// Helper to convert WebSocket readyState to readable name
|
|
169
|
-
function getWebSocketStateName(state) {
|
|
170
|
+
export function getWebSocketStateName(state) {
|
|
170
171
|
switch (state) {
|
|
171
172
|
case WebSocket.CONNECTING: return "CONNECTING";
|
|
172
173
|
case WebSocket.OPEN: return "OPEN";
|
|
@@ -1129,10 +1130,13 @@ export function getConnectedAppByDevice(device) {
|
|
|
1129
1130
|
}
|
|
1130
1131
|
if (matches.length > 1) {
|
|
1131
1132
|
const names = matches.map(a => a.deviceInfo.deviceName || a.deviceInfo.title).join(", ");
|
|
1132
|
-
throw new
|
|
1133
|
+
throw new UserInputError(`Multiple devices match "${device}": ${names}. Be more specific (use the full device name for an exact match).`);
|
|
1133
1134
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1135
|
+
if (allOpenNames.length === 0) {
|
|
1136
|
+
throw new UserInputError(`No connected device matches "${device}". No devices are currently connected — run scan_metro to discover and connect to Metro servers.`);
|
|
1137
|
+
}
|
|
1138
|
+
const quoted = allOpenNames.map(n => `"${n}"`).join(", ");
|
|
1139
|
+
throw new UserInputError(`No connected device matches "${device}". Connected devices: ${quoted}. Retry with one of these names (substring match, case-insensitive).`);
|
|
1136
1140
|
}
|
|
1137
1141
|
// Check if any app is connected with an OPEN WebSocket
|
|
1138
1142
|
export function hasConnectedApp() {
|
|
@@ -1308,30 +1312,32 @@ async function findFirstMetroPort() {
|
|
|
1308
1312
|
*/
|
|
1309
1313
|
export async function ensureConnection(options = {}) {
|
|
1310
1314
|
const { port, healthCheck = true, forceRefresh = false } = options;
|
|
1311
|
-
let app = getFirstConnectedApp();
|
|
1312
1315
|
let wasReconnected = false;
|
|
1313
|
-
//
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1316
|
+
// forceRefresh: close every currently-connected app, then fall through to
|
|
1317
|
+
// single-device fresh connect. Full multi-device re-discovery is scan_metro's
|
|
1318
|
+
// job — ensureConnection only fresh-connects one when nothing is open.
|
|
1319
|
+
if (forceRefresh) {
|
|
1320
|
+
for (const { key, app } of getConnectedApps()) {
|
|
1321
|
+
cancelReconnectionTimer(key);
|
|
1322
|
+
try {
|
|
1323
|
+
app.ws.close();
|
|
1324
|
+
}
|
|
1325
|
+
catch { /* ignore */ }
|
|
1326
|
+
connectedApps.delete(key);
|
|
1322
1327
|
}
|
|
1323
|
-
connectedApps.delete(appKey);
|
|
1324
|
-
app = null;
|
|
1325
1328
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1329
|
+
let openApps = getConnectedApps()
|
|
1330
|
+
.filter(a => a.isConnected)
|
|
1331
|
+
.map(a => a.app);
|
|
1332
|
+
// Nothing connected — establish a single-device connection from scratch.
|
|
1333
|
+
if (openApps.length === 0) {
|
|
1328
1334
|
const targetPort = port ?? await findFirstMetroPort();
|
|
1329
1335
|
if (!targetPort) {
|
|
1330
1336
|
return {
|
|
1331
1337
|
connected: false,
|
|
1332
1338
|
wasReconnected: false,
|
|
1333
1339
|
healthCheckPassed: false,
|
|
1334
|
-
|
|
1340
|
+
connectionInfos: [],
|
|
1335
1341
|
error: "No Metro server found. Make sure Metro bundler is running.",
|
|
1336
1342
|
};
|
|
1337
1343
|
}
|
|
@@ -1342,7 +1348,7 @@ export async function ensureConnection(options = {}) {
|
|
|
1342
1348
|
connected: false,
|
|
1343
1349
|
wasReconnected: false,
|
|
1344
1350
|
healthCheckPassed: false,
|
|
1345
|
-
|
|
1351
|
+
connectionInfos: [],
|
|
1346
1352
|
error: `No debuggable devices found on port ${targetPort}. Make sure the app is running.`,
|
|
1347
1353
|
};
|
|
1348
1354
|
}
|
|
@@ -1354,32 +1360,23 @@ export async function ensureConnection(options = {}) {
|
|
|
1354
1360
|
clearReconnectionSuppression();
|
|
1355
1361
|
const connectResult = await connectToDevice(mainDevice, targetPort);
|
|
1356
1362
|
wasReconnected = true;
|
|
1357
|
-
// connectToDevice resolves with a status string for non-throw
|
|
1358
|
-
// outcomes including "Skipped X (stale CDP target …)". Surface
|
|
1359
|
-
// that explicitly instead of falling through to the generic
|
|
1360
|
-
// "Connection succeeded but app is not available" below.
|
|
1361
1363
|
if (connectResult.includes("stale CDP target")) {
|
|
1362
1364
|
return {
|
|
1363
1365
|
connected: false,
|
|
1364
1366
|
wasReconnected: false,
|
|
1365
1367
|
healthCheckPassed: false,
|
|
1366
|
-
|
|
1368
|
+
connectionInfos: [],
|
|
1367
1369
|
error: `${connectResult}. The CDP page advertised by Metro is no longer responsive — restart the React Native app, then retry.`,
|
|
1368
1370
|
};
|
|
1369
1371
|
}
|
|
1370
|
-
// Race window:
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
app = getFirstConnectedApp();
|
|
1376
|
-
for (let i = 0; !app && i < 5; i++) {
|
|
1377
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1378
|
-
app = getFirstConnectedApp();
|
|
1372
|
+
// Race window: settle briefly and retry getting the open app list.
|
|
1373
|
+
for (let i = 0; i < 5 && openApps.length === 0; i++) {
|
|
1374
|
+
if (i > 0)
|
|
1375
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1376
|
+
openApps = getConnectedApps().filter(a => a.isConnected).map(a => a.app);
|
|
1379
1377
|
}
|
|
1380
1378
|
}
|
|
1381
1379
|
catch (error) {
|
|
1382
|
-
// Ensure we always have a meaningful error message
|
|
1383
1380
|
let errorMessage;
|
|
1384
1381
|
if (error instanceof Error) {
|
|
1385
1382
|
errorMessage = error.message;
|
|
@@ -1394,82 +1391,95 @@ export async function ensureConnection(options = {}) {
|
|
|
1394
1391
|
connected: false,
|
|
1395
1392
|
wasReconnected: false,
|
|
1396
1393
|
healthCheckPassed: false,
|
|
1397
|
-
|
|
1394
|
+
connectionInfos: [],
|
|
1398
1395
|
error: `Connection failed: ${errorMessage}`,
|
|
1399
1396
|
};
|
|
1400
1397
|
}
|
|
1401
1398
|
}
|
|
1402
|
-
if (
|
|
1399
|
+
if (openApps.length === 0) {
|
|
1403
1400
|
return {
|
|
1404
1401
|
connected: false,
|
|
1405
1402
|
wasReconnected: false,
|
|
1406
1403
|
healthCheckPassed: false,
|
|
1407
|
-
|
|
1404
|
+
connectionInfos: [],
|
|
1408
1405
|
error: "Connection succeeded but app is not available",
|
|
1409
1406
|
};
|
|
1410
1407
|
}
|
|
1411
|
-
//
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
app.
|
|
1424
|
-
|
|
1425
|
-
catch {
|
|
1426
|
-
// Ignore
|
|
1427
|
-
}
|
|
1428
|
-
connectedApps.delete(appKey);
|
|
1429
|
-
// Re-fetch devices and reconnect
|
|
1430
|
-
const devices = await fetchDevices(targetPort);
|
|
1431
|
-
const mainDevice = selectMainDevice(devices);
|
|
1432
|
-
if (mainDevice) {
|
|
1408
|
+
// Per-device health-check; reconnect failed devices individually so a dead
|
|
1409
|
+
// Android doesn't take down a healthy iOS report and vice versa.
|
|
1410
|
+
const perApp = [];
|
|
1411
|
+
for (const candidate of openApps) {
|
|
1412
|
+
let app = candidate;
|
|
1413
|
+
let healthy = true;
|
|
1414
|
+
if (healthCheck) {
|
|
1415
|
+
healthy = await runQuickHealthCheck(app);
|
|
1416
|
+
if (!healthy && !wasReconnected) {
|
|
1417
|
+
const failedTitle = app.deviceInfo.title;
|
|
1418
|
+
console.error(`[execbro] Health check failed for ${failedTitle}, attempting reconnection...`);
|
|
1419
|
+
const appKey = `${app.port}-${app.deviceInfo.id}`;
|
|
1420
|
+
const appPort = app.port;
|
|
1421
|
+
cancelReconnectionTimer(appKey);
|
|
1433
1422
|
try {
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1423
|
+
app.ws.close();
|
|
1424
|
+
}
|
|
1425
|
+
catch { /* ignore */ }
|
|
1426
|
+
connectedApps.delete(appKey);
|
|
1427
|
+
const devices = await fetchDevices(appPort);
|
|
1428
|
+
const mainDevice = selectMainDevice(devices);
|
|
1429
|
+
if (mainDevice) {
|
|
1430
|
+
try {
|
|
1431
|
+
await connectToDevice(mainDevice, appPort);
|
|
1432
|
+
wasReconnected = true;
|
|
1433
|
+
const fresh = getConnectedApps()
|
|
1434
|
+
.filter(a => a.isConnected)
|
|
1435
|
+
.map(a => a.app);
|
|
1436
|
+
const replacement = fresh.find(a => a.deviceInfo.id === mainDevice.id);
|
|
1437
|
+
if (replacement) {
|
|
1438
|
+
app = replacement;
|
|
1439
|
+
healthy = await runQuickHealthCheck(app);
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
healthy = false;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
catch {
|
|
1446
|
+
healthy = false;
|
|
1440
1447
|
}
|
|
1441
1448
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
healthCheckPassed = false;
|
|
1449
|
+
else {
|
|
1450
|
+
healthy = false;
|
|
1445
1451
|
}
|
|
1446
1452
|
}
|
|
1447
1453
|
}
|
|
1454
|
+
perApp.push({ app, healthy });
|
|
1448
1455
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
return {
|
|
1460
|
-
connected: isConnected,
|
|
1461
|
-
wasReconnected,
|
|
1462
|
-
healthCheckPassed,
|
|
1463
|
-
connectionInfo: app ? {
|
|
1456
|
+
const connectionInfos = perApp.map(({ app, healthy }) => {
|
|
1457
|
+
const appKey = `${app.port}-${app.deviceInfo.id}`;
|
|
1458
|
+
const connectionState = getConnectionState(appKey);
|
|
1459
|
+
const contextHealth = getContextHealth(appKey);
|
|
1460
|
+
let uptime = "unknown";
|
|
1461
|
+
if (connectionState?.lastConnectedTime) {
|
|
1462
|
+
uptime = formatDuration(Date.now() - connectionState.lastConnectedTime.getTime());
|
|
1463
|
+
}
|
|
1464
|
+
return {
|
|
1465
|
+
deviceName: app.deviceInfo.deviceName || app.deviceInfo.title,
|
|
1464
1466
|
deviceTitle: app.deviceInfo.title,
|
|
1467
|
+
platform: app.platform,
|
|
1465
1468
|
port: app.port,
|
|
1466
1469
|
uptime,
|
|
1467
1470
|
contextId: contextHealth?.contextId ?? null,
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1471
|
+
healthCheckPassed: healthy,
|
|
1472
|
+
};
|
|
1473
|
+
});
|
|
1474
|
+
const anyOpen = perApp.some(r => r.app.ws.readyState === WebSocket.OPEN);
|
|
1475
|
+
const allHealthy = perApp.every(r => r.healthy);
|
|
1476
|
+
return {
|
|
1477
|
+
connected: anyOpen,
|
|
1478
|
+
wasReconnected,
|
|
1479
|
+
healthCheckPassed: allHealthy,
|
|
1480
|
+
connectionInfos,
|
|
1481
|
+
...(!anyOpen && {
|
|
1482
|
+
error: "App became unavailable after reconnection attempt. Try running scan_metro then ensure_connection."
|
|
1473
1483
|
}),
|
|
1474
1484
|
};
|
|
1475
1485
|
}
|