@ohah/react-native-mcp-server 0.1.0-rc.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 +108 -0
- package/babel-plugin-app-registry.cjs +36 -0
- package/babel-plugin-inject-testid.cjs +115 -0
- package/babel-preset.cjs +26 -0
- package/dist/adb-utils-DreOsWp-.js +206 -0
- package/dist/adb-utils-R7jCnMjU.js +3 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +3829 -0
- package/dist/init-202kCwU5.js +330 -0
- package/dist/transformer-entry.d.ts +44 -0
- package/dist/transformer-entry.js +39036 -0
- package/package.json +87 -0
- package/runtime.js +2722 -0
package/runtime.js
ADDED
|
@@ -0,0 +1,2722 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
(function() {
|
|
4
|
+
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
|
|
10
|
+
//#region src/runtime/devtools-hook.ts
|
|
11
|
+
var init_devtools_hook = __esmMin(() => {
|
|
12
|
+
(function() {
|
|
13
|
+
var g = typeof globalThis !== "undefined" ? globalThis : typeof global !== "undefined" ? global : null;
|
|
14
|
+
if (!g || g.__REACT_DEVTOOLS_GLOBAL_HOOK__) return;
|
|
15
|
+
var _renderers = /* @__PURE__ */ new Map();
|
|
16
|
+
var _roots = /* @__PURE__ */ new Map();
|
|
17
|
+
g.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
|
18
|
+
supportsFiber: true,
|
|
19
|
+
renderers: _renderers,
|
|
20
|
+
inject: function(internals) {
|
|
21
|
+
var id = _renderers.size + 1;
|
|
22
|
+
_renderers.set(id, internals);
|
|
23
|
+
return id;
|
|
24
|
+
},
|
|
25
|
+
onCommitFiberRoot: function(rendererID, root) {
|
|
26
|
+
if (!_roots.has(rendererID)) _roots.set(rendererID, /* @__PURE__ */ new Set());
|
|
27
|
+
_roots.get(rendererID).add(root);
|
|
28
|
+
},
|
|
29
|
+
onCommitFiberUnmount: function() {},
|
|
30
|
+
getFiberRoots: function(rendererID) {
|
|
31
|
+
return _roots.get(rendererID) || /* @__PURE__ */ new Set();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/runtime/shared.ts
|
|
39
|
+
function pushConsoleLog(entry) {
|
|
40
|
+
consoleLogs.push(entry);
|
|
41
|
+
if (consoleLogs.length > CONSOLE_BUFFER_SIZE) consoleLogs.shift();
|
|
42
|
+
}
|
|
43
|
+
function nextConsoleLogId() {
|
|
44
|
+
return ++consoleLogId;
|
|
45
|
+
}
|
|
46
|
+
function resetConsoleLogs() {
|
|
47
|
+
consoleLogs = [];
|
|
48
|
+
consoleLogId = 0;
|
|
49
|
+
}
|
|
50
|
+
function nextNetworkRequestId() {
|
|
51
|
+
return ++networkRequestId;
|
|
52
|
+
}
|
|
53
|
+
function resetNetworkRequests() {
|
|
54
|
+
networkRequests = [];
|
|
55
|
+
networkRequestId = 0;
|
|
56
|
+
}
|
|
57
|
+
function pushStateChange(entry) {
|
|
58
|
+
stateChanges.push(entry);
|
|
59
|
+
if (stateChanges.length > STATE_CHANGE_BUFFER) stateChanges.shift();
|
|
60
|
+
}
|
|
61
|
+
function nextStateChangeId() {
|
|
62
|
+
return ++stateChangeId;
|
|
63
|
+
}
|
|
64
|
+
function resetStateChanges() {
|
|
65
|
+
stateChanges = [];
|
|
66
|
+
stateChangeId = 0;
|
|
67
|
+
}
|
|
68
|
+
function setRenderProfileActive(active) {
|
|
69
|
+
renderProfileActive = active;
|
|
70
|
+
}
|
|
71
|
+
function setRenderProfileStartTime(t) {
|
|
72
|
+
renderProfileStartTime = t;
|
|
73
|
+
}
|
|
74
|
+
function setRenderComponentFilter(components) {
|
|
75
|
+
renderComponentFilter = components;
|
|
76
|
+
}
|
|
77
|
+
function setRenderIgnoreFilter(ignore) {
|
|
78
|
+
renderIgnoreFilter = ignore;
|
|
79
|
+
}
|
|
80
|
+
function incrementRenderCommitCount() {
|
|
81
|
+
return ++renderCommitCount;
|
|
82
|
+
}
|
|
83
|
+
function pushRenderEntry(entry) {
|
|
84
|
+
renderEntries.push(entry);
|
|
85
|
+
if (renderEntries.length > RENDER_BUFFER_SIZE) renderEntries.shift();
|
|
86
|
+
}
|
|
87
|
+
function resetRenderProfile() {
|
|
88
|
+
renderProfileActive = false;
|
|
89
|
+
renderProfileStartTime = 0;
|
|
90
|
+
renderCommitCount = 0;
|
|
91
|
+
renderEntries = [];
|
|
92
|
+
renderComponentFilter = null;
|
|
93
|
+
renderIgnoreFilter = null;
|
|
94
|
+
}
|
|
95
|
+
var pressHandlers, consoleLogs, consoleLogId, CONSOLE_BUFFER_SIZE, networkRequests, networkRequestId, NETWORK_BUFFER_SIZE, NETWORK_BODY_LIMIT, networkMockRules, stateChanges, stateChangeId, STATE_CHANGE_BUFFER, renderProfileActive, renderProfileStartTime, renderCommitCount, renderEntries, renderComponentFilter, renderIgnoreFilter, RENDER_BUFFER_SIZE;
|
|
96
|
+
var init_shared = __esmMin(() => {
|
|
97
|
+
pressHandlers = {};
|
|
98
|
+
consoleLogs = [];
|
|
99
|
+
consoleLogId = 0;
|
|
100
|
+
CONSOLE_BUFFER_SIZE = 500;
|
|
101
|
+
networkRequests = [];
|
|
102
|
+
networkRequestId = 0;
|
|
103
|
+
NETWORK_BUFFER_SIZE = 200;
|
|
104
|
+
NETWORK_BODY_LIMIT = 1e4;
|
|
105
|
+
networkMockRules = [];
|
|
106
|
+
stateChanges = [];
|
|
107
|
+
stateChangeId = 0;
|
|
108
|
+
STATE_CHANGE_BUFFER = 300;
|
|
109
|
+
renderProfileActive = false;
|
|
110
|
+
renderProfileStartTime = 0;
|
|
111
|
+
renderCommitCount = 0;
|
|
112
|
+
renderEntries = [];
|
|
113
|
+
renderComponentFilter = null;
|
|
114
|
+
renderIgnoreFilter = null;
|
|
115
|
+
RENDER_BUFFER_SIZE = 5e3;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/runtime/fiber-helpers.ts
|
|
120
|
+
/** DevTools hook에서 root Fiber를 얻는다. hook.getFiberRoots 우선, fallback으로 getCurrentFiber 사용. */
|
|
121
|
+
function getFiberRootFromHook(hook, rendererID) {
|
|
122
|
+
if (!hook) return null;
|
|
123
|
+
function toRootFiber(r) {
|
|
124
|
+
return r && r.current ? r.current : r;
|
|
125
|
+
}
|
|
126
|
+
if (typeof hook.getFiberRoots === "function") try {
|
|
127
|
+
var roots = hook.getFiberRoots(rendererID);
|
|
128
|
+
if (roots && roots.size > 0) {
|
|
129
|
+
var first = roots.values().next().value;
|
|
130
|
+
if (first) return toRootFiber(first);
|
|
131
|
+
}
|
|
132
|
+
} catch (_e) {}
|
|
133
|
+
var renderer = hook.renderers && hook.renderers.get(rendererID);
|
|
134
|
+
if (renderer && typeof renderer.getCurrentFiber === "function") {
|
|
135
|
+
var fiber = renderer.getCurrentFiber();
|
|
136
|
+
if (fiber) {
|
|
137
|
+
while (fiber && fiber.return) fiber = fiber.return;
|
|
138
|
+
return fiber || null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
/** __REACT_DEVTOOLS_GLOBAL_HOOK__ 반환. 없으면 null. */
|
|
144
|
+
function getDevToolsHook() {
|
|
145
|
+
return typeof global !== "undefined" && global.__REACT_DEVTOOLS_GLOBAL_HOOK__ || typeof globalThis !== "undefined" && globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ || null;
|
|
146
|
+
}
|
|
147
|
+
/** DevTools hook → fiber root. 없으면 null. */
|
|
148
|
+
function getFiberRoot() {
|
|
149
|
+
var hook = getDevToolsHook();
|
|
150
|
+
if (!hook || !hook.renderers) return null;
|
|
151
|
+
return getFiberRootFromHook(hook, 1);
|
|
152
|
+
}
|
|
153
|
+
/** fiber 하위 Text 노드의 문자열 수집 */
|
|
154
|
+
function collectText(fiber, TextComponent) {
|
|
155
|
+
if (!fiber) return "";
|
|
156
|
+
if (fiber.type === TextComponent && fiber.memoizedProps) {
|
|
157
|
+
var c = fiber.memoizedProps.children;
|
|
158
|
+
if (typeof c === "string") return c.trim();
|
|
159
|
+
if (typeof c === "number" || typeof c === "boolean") return String(c);
|
|
160
|
+
if (Array.isArray(c)) return c.map(function(x) {
|
|
161
|
+
if (typeof x === "string") return x;
|
|
162
|
+
if (typeof x === "number" || typeof x === "boolean") return String(x);
|
|
163
|
+
return "";
|
|
164
|
+
}).join("").trim();
|
|
165
|
+
}
|
|
166
|
+
var s = "";
|
|
167
|
+
var ch = fiber.child;
|
|
168
|
+
while (ch) {
|
|
169
|
+
s += collectText(ch, TextComponent);
|
|
170
|
+
ch = ch.sibling;
|
|
171
|
+
}
|
|
172
|
+
return s;
|
|
173
|
+
}
|
|
174
|
+
/** fiber의 accessibilityLabel 또는 자식 Image의 accessibilityLabel 수집 */
|
|
175
|
+
function collectAccessibilityLabel(fiber, ImageComponent) {
|
|
176
|
+
if (!fiber || !fiber.memoizedProps) return "";
|
|
177
|
+
var p = fiber.memoizedProps;
|
|
178
|
+
if (typeof p.accessibilityLabel === "string" && p.accessibilityLabel.trim()) return p.accessibilityLabel.trim();
|
|
179
|
+
var ch = fiber.child;
|
|
180
|
+
while (ch) {
|
|
181
|
+
if (ch.type === ImageComponent && ch.memoizedProps && typeof ch.memoizedProps.accessibilityLabel === "string") return ch.memoizedProps.accessibilityLabel.trim();
|
|
182
|
+
ch = ch.sibling;
|
|
183
|
+
}
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
/** fiber에서 사용자에게 보이는 라벨 추출 (text 우선, a11y fallback) */
|
|
187
|
+
function getLabel(fiber, TextComponent, ImageComponent) {
|
|
188
|
+
var text = collectText(fiber, TextComponent).replace(/\s+/g, " ").trim();
|
|
189
|
+
var a11y = collectAccessibilityLabel(fiber, ImageComponent);
|
|
190
|
+
return text || a11y || "";
|
|
191
|
+
}
|
|
192
|
+
/** require('react-native')에서 Text, Image 컴포넌트 추출 */
|
|
193
|
+
function getRNComponents() {
|
|
194
|
+
var rn = typeof require !== "undefined" && require("react-native");
|
|
195
|
+
return {
|
|
196
|
+
Text: rn && rn.Text,
|
|
197
|
+
Image: rn && rn.Image
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/** fiber 자신(또는 조상)에서 처음 나오는 testID */
|
|
201
|
+
function getAncestorTestID(fiber) {
|
|
202
|
+
var f = fiber;
|
|
203
|
+
while (f && f.memoizedProps) {
|
|
204
|
+
if (typeof f.memoizedProps.testID === "string" && f.memoizedProps.testID.trim()) return f.memoizedProps.testID.trim();
|
|
205
|
+
f = f.return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Text fiber 한 노드의 직접 children 문자열만 (자식 Text 노드 제외) */
|
|
209
|
+
function getTextNodeContent(fiber, TextComponent) {
|
|
210
|
+
if (!fiber || fiber.type !== TextComponent || !fiber.memoizedProps) return "";
|
|
211
|
+
var c = fiber.memoizedProps.children;
|
|
212
|
+
if (typeof c === "string") return c.trim();
|
|
213
|
+
if (Array.isArray(c)) return c.map(function(x) {
|
|
214
|
+
return typeof x === "string" ? x : "";
|
|
215
|
+
}).join("").trim();
|
|
216
|
+
return "";
|
|
217
|
+
}
|
|
218
|
+
/** Fiber의 컴포넌트 타입 이름 (displayName/name/문자열) */
|
|
219
|
+
function getFiberTypeName(fiber) {
|
|
220
|
+
if (!fiber || !fiber.type) return "Unknown";
|
|
221
|
+
var t = fiber.type;
|
|
222
|
+
if (typeof t === "string") return t;
|
|
223
|
+
if (t.displayName && typeof t.displayName === "string") return t.displayName;
|
|
224
|
+
if (t.name && typeof t.name === "string") return t.name;
|
|
225
|
+
return "Component";
|
|
226
|
+
}
|
|
227
|
+
var init_fiber_helpers = __esmMin(() => {});
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/runtime/state-hooks.ts
|
|
231
|
+
/** fiber의 memoizedState 체인에서 state Hook(queue 존재)만 추출 */
|
|
232
|
+
function parseHooks(fiber) {
|
|
233
|
+
var hooks = [];
|
|
234
|
+
var hook = fiber ? fiber.memoizedState : null;
|
|
235
|
+
var i = 0;
|
|
236
|
+
while (hook && typeof hook === "object") {
|
|
237
|
+
if (hook.queue) hooks.push({
|
|
238
|
+
index: i,
|
|
239
|
+
type: "state",
|
|
240
|
+
value: hook.memoizedState
|
|
241
|
+
});
|
|
242
|
+
hook = hook.next;
|
|
243
|
+
i++;
|
|
244
|
+
}
|
|
245
|
+
return hooks;
|
|
246
|
+
}
|
|
247
|
+
/** 얕은 비교. 참조 동일 → true, 타입 불일치/키 다름 → false */
|
|
248
|
+
function shallowEqual(a, b) {
|
|
249
|
+
if (a === b) return true;
|
|
250
|
+
if (a == null || b == null) return false;
|
|
251
|
+
if (typeof a !== typeof b) return false;
|
|
252
|
+
if (typeof a !== "object") return false;
|
|
253
|
+
if (Array.isArray(a)) {
|
|
254
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
255
|
+
for (var i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
var ka = Object.keys(a);
|
|
259
|
+
var kb = Object.keys(b);
|
|
260
|
+
if (ka.length !== kb.length) return false;
|
|
261
|
+
for (var j = 0; j < ka.length; j++) {
|
|
262
|
+
var key = ka[j];
|
|
263
|
+
if (key === void 0) continue;
|
|
264
|
+
if (a[key] !== b[key]) return false;
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
/** JSON.stringify 안전 래퍼 (depth 제한 + 순환 참조 방지) */
|
|
269
|
+
function safeClone(val, maxDepth) {
|
|
270
|
+
if (maxDepth === void 0) maxDepth = 4;
|
|
271
|
+
var seen = [];
|
|
272
|
+
function clone(v, depth) {
|
|
273
|
+
if (v === null || v === void 0) return v;
|
|
274
|
+
if (typeof v !== "object" && typeof v !== "function") return v;
|
|
275
|
+
if (typeof v === "function") return "[Function]";
|
|
276
|
+
if (depth > maxDepth) return "[depth limit]";
|
|
277
|
+
if (seen.indexOf(v) !== -1) return "[Circular]";
|
|
278
|
+
seen.push(v);
|
|
279
|
+
if (Array.isArray(v)) {
|
|
280
|
+
var arr = [];
|
|
281
|
+
for (var i = 0; i < Math.min(v.length, 100); i++) arr.push(clone(v[i], depth + 1));
|
|
282
|
+
if (v.length > 100) arr.push("..." + (v.length - 100) + " more");
|
|
283
|
+
return arr;
|
|
284
|
+
}
|
|
285
|
+
var obj = {};
|
|
286
|
+
var keys = Object.keys(v);
|
|
287
|
+
for (var j = 0; j < Math.min(keys.length, 50); j++) {
|
|
288
|
+
var key = keys[j];
|
|
289
|
+
if (key === void 0) continue;
|
|
290
|
+
obj[key] = clone(v[key], depth + 1);
|
|
291
|
+
}
|
|
292
|
+
if (keys.length > 50) obj["..."] = keys.length - 50 + " more keys";
|
|
293
|
+
return obj;
|
|
294
|
+
}
|
|
295
|
+
return clone(val, 0);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* onCommitFiberRoot에서 호출: 변경된 state Hook을 _stateChanges에 수집.
|
|
299
|
+
* fiber.alternate(이전 버전)와 비교해 memoizedState가 달라진 Hook만 기록.
|
|
300
|
+
*/
|
|
301
|
+
function collectStateChanges(fiber) {
|
|
302
|
+
if (!fiber) return;
|
|
303
|
+
if (fiber.tag === 0 || fiber.tag === 1) {
|
|
304
|
+
var prev = fiber.alternate;
|
|
305
|
+
if (prev) {
|
|
306
|
+
var prevHook = prev.memoizedState;
|
|
307
|
+
var nextHook = fiber.memoizedState;
|
|
308
|
+
var hookIdx = 0;
|
|
309
|
+
while (prevHook && nextHook && typeof prevHook === "object" && typeof nextHook === "object") {
|
|
310
|
+
if (nextHook.queue && !shallowEqual(prevHook.memoizedState, nextHook.memoizedState)) {
|
|
311
|
+
var name = getFiberTypeName(fiber);
|
|
312
|
+
pushStateChange({
|
|
313
|
+
id: nextStateChangeId(),
|
|
314
|
+
timestamp: Date.now(),
|
|
315
|
+
component: name,
|
|
316
|
+
hookIndex: hookIdx,
|
|
317
|
+
prev: safeClone(prevHook.memoizedState),
|
|
318
|
+
next: safeClone(nextHook.memoizedState)
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
prevHook = prevHook.next;
|
|
322
|
+
nextHook = nextHook.next;
|
|
323
|
+
hookIdx++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
collectStateChanges(fiber.child);
|
|
328
|
+
collectStateChanges(fiber.sibling);
|
|
329
|
+
}
|
|
330
|
+
var init_state_hooks = __esmMin(() => {
|
|
331
|
+
init_fiber_helpers();
|
|
332
|
+
init_shared();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/runtime/render-tracking.ts
|
|
337
|
+
/** 접두사 목록에 해당하는 이름인지 (_접두사 변형 포함) */
|
|
338
|
+
function matchesPrefixList(name, prefixes) {
|
|
339
|
+
var target = name;
|
|
340
|
+
if (target.length > 1 && target.charAt(0) === "_") target = target.substring(1);
|
|
341
|
+
for (var i = 0; i < prefixes.length; i++) if (target.indexOf(prefixes[i]) === 0) return true;
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
/** 컴포넌트 필터링: whitelist > blacklist > default ignore 순으로 판별 */
|
|
345
|
+
function shouldSkipComponent(name) {
|
|
346
|
+
if (renderComponentFilter !== null) return renderComponentFilter.indexOf(name) === -1;
|
|
347
|
+
if (renderIgnoreFilter !== null && renderIgnoreFilter.indexOf(name) !== -1) return true;
|
|
348
|
+
return matchesPrefixList(name, DEFAULT_IGNORED_PREFIXES);
|
|
349
|
+
}
|
|
350
|
+
/** 빌트인 컴포넌트인지 판별 */
|
|
351
|
+
function isBuiltinComponent(name) {
|
|
352
|
+
return BUILTIN_COMPONENTS.indexOf(name) !== -1;
|
|
353
|
+
}
|
|
354
|
+
/** fiber.return을 올라가며 빌트인이 아닌 첫 사용자 컴포넌트 이름 */
|
|
355
|
+
function getNearestUserParent(fiber) {
|
|
356
|
+
var p = fiber.return;
|
|
357
|
+
while (p) {
|
|
358
|
+
if (p.tag === 0 || p.tag === 1) {
|
|
359
|
+
var pName = getFiberTypeName(p);
|
|
360
|
+
if (!isBuiltinComponent(pName) && !matchesPrefixList(pName, DEFAULT_IGNORED_PREFIXES)) return pName;
|
|
361
|
+
}
|
|
362
|
+
p = p.return;
|
|
363
|
+
}
|
|
364
|
+
return "Root";
|
|
365
|
+
}
|
|
366
|
+
/** fiber.return을 올라가며 첫 FunctionComponent/ClassComponent 이름 */
|
|
367
|
+
function getParentComponentName(fiber) {
|
|
368
|
+
var p = fiber.return;
|
|
369
|
+
while (p) {
|
|
370
|
+
if (p.tag === 0 || p.tag === 1) return getFiberTypeName(p);
|
|
371
|
+
p = p.return;
|
|
372
|
+
}
|
|
373
|
+
return "Root";
|
|
374
|
+
}
|
|
375
|
+
/** fiber가 React.memo로 감싸져 있는지 (MemoComponent=14, SimpleMemoComponent=15) */
|
|
376
|
+
function isMemoWrapped(fiber) {
|
|
377
|
+
var parent = fiber.return;
|
|
378
|
+
return parent != null && (parent.tag === 14 || parent.tag === 15);
|
|
379
|
+
}
|
|
380
|
+
/** child를 탐색하여 첫 HostComponent(tag=5)의 type 이름 반환 (View, Text, Pressable 등) */
|
|
381
|
+
function getFirstHostType(fiber) {
|
|
382
|
+
var child = fiber.child;
|
|
383
|
+
return _findHostType(child, 5);
|
|
384
|
+
}
|
|
385
|
+
function _findHostType(fiber, depth) {
|
|
386
|
+
if (!fiber || depth <= 0) return void 0;
|
|
387
|
+
if (fiber.tag === 5 && typeof fiber.type === "string") return fiber.type;
|
|
388
|
+
var found = _findHostType(fiber.child, depth - 1);
|
|
389
|
+
if (found) return found;
|
|
390
|
+
return _findHostType(fiber.sibling, depth - 1);
|
|
391
|
+
}
|
|
392
|
+
/** props에서 변경된 key들 추출 */
|
|
393
|
+
function diffProps(prevProps, nextProps) {
|
|
394
|
+
if (!prevProps || !nextProps) return void 0;
|
|
395
|
+
var changes = [];
|
|
396
|
+
var keys = Object.keys(nextProps);
|
|
397
|
+
for (var i = 0; i < keys.length; i++) {
|
|
398
|
+
var key = keys[i];
|
|
399
|
+
if (key === "children") continue;
|
|
400
|
+
if (prevProps[key] !== nextProps[key]) changes.push({
|
|
401
|
+
key,
|
|
402
|
+
prev: safeClone(prevProps[key], 3),
|
|
403
|
+
next: safeClone(nextProps[key], 3)
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
var prevKeys = Object.keys(prevProps);
|
|
407
|
+
for (var j = 0; j < prevKeys.length; j++) {
|
|
408
|
+
var pk = prevKeys[j];
|
|
409
|
+
if (pk === "children") continue;
|
|
410
|
+
if (!(pk in nextProps)) changes.push({
|
|
411
|
+
key: pk,
|
|
412
|
+
prev: safeClone(prevProps[pk], 3),
|
|
413
|
+
next: void 0
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return changes.length > 0 ? changes : void 0;
|
|
417
|
+
}
|
|
418
|
+
/** state hooks에서 변경된 것들 추출 */
|
|
419
|
+
function diffStateHooks(prevFiber, nextFiber) {
|
|
420
|
+
var changes = [];
|
|
421
|
+
var prevHook = prevFiber.memoizedState;
|
|
422
|
+
var nextHook = nextFiber.memoizedState;
|
|
423
|
+
var idx = 0;
|
|
424
|
+
while (prevHook && nextHook && typeof prevHook === "object" && typeof nextHook === "object") {
|
|
425
|
+
if (nextHook.queue && !shallowEqual(prevHook.memoizedState, nextHook.memoizedState)) changes.push({
|
|
426
|
+
hookIndex: idx,
|
|
427
|
+
prev: safeClone(prevHook.memoizedState, 3),
|
|
428
|
+
next: safeClone(nextHook.memoizedState, 3)
|
|
429
|
+
});
|
|
430
|
+
prevHook = prevHook.next;
|
|
431
|
+
nextHook = nextHook.next;
|
|
432
|
+
idx++;
|
|
433
|
+
}
|
|
434
|
+
return changes.length > 0 ? changes : void 0;
|
|
435
|
+
}
|
|
436
|
+
/** context dependencies에서 변경된 것 추출 */
|
|
437
|
+
function diffContext(fiber) {
|
|
438
|
+
var alt = fiber.alternate;
|
|
439
|
+
if (!alt) return void 0;
|
|
440
|
+
var deps = fiber.dependencies;
|
|
441
|
+
if (!deps || !deps.firstContext) return void 0;
|
|
442
|
+
var changes = [];
|
|
443
|
+
var ctx = deps.firstContext;
|
|
444
|
+
var altDeps = alt.dependencies;
|
|
445
|
+
var altCtx = altDeps ? altDeps.firstContext : null;
|
|
446
|
+
while (ctx) {
|
|
447
|
+
var ctxName = "Context";
|
|
448
|
+
if (ctx.context) {
|
|
449
|
+
var c = ctx.context;
|
|
450
|
+
if (c.displayName) ctxName = c.displayName;
|
|
451
|
+
else if (c._context && c._context.displayName) ctxName = c._context.displayName;
|
|
452
|
+
else if (c.Provider && c.Provider._context && c.Provider._context.displayName) ctxName = c.Provider._context.displayName;
|
|
453
|
+
}
|
|
454
|
+
if (altCtx && ctx.memoizedValue !== altCtx.memoizedValue) changes.push({
|
|
455
|
+
name: ctxName,
|
|
456
|
+
prev: safeClone(altCtx.memoizedValue, 3),
|
|
457
|
+
next: safeClone(ctx.memoizedValue, 3)
|
|
458
|
+
});
|
|
459
|
+
ctx = ctx.next;
|
|
460
|
+
if (altCtx) altCtx = altCtx.next;
|
|
461
|
+
}
|
|
462
|
+
return changes.length > 0 ? changes : void 0;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* fiber 트리를 순회하며 RenderEntry를 수집.
|
|
466
|
+
* onCommitFiberRoot에서 호출. commitId는 현재 renderCommitCount.
|
|
467
|
+
*/
|
|
468
|
+
function collectRenderEntries(fiber) {
|
|
469
|
+
if (!fiber || !renderProfileActive) return;
|
|
470
|
+
if (fiber.tag === 0 || fiber.tag === 1) {
|
|
471
|
+
var name = getFiberTypeName(fiber);
|
|
472
|
+
if (shouldSkipComponent(name)) {
|
|
473
|
+
collectRenderEntries(fiber.child);
|
|
474
|
+
collectRenderEntries(fiber.sibling);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
var isBuiltin = isBuiltinComponent(name);
|
|
478
|
+
var componentKey = isBuiltin ? getNearestUserParent(fiber) + " > " + name : name;
|
|
479
|
+
var alt = fiber.alternate;
|
|
480
|
+
var parentName = getParentComponentName(fiber);
|
|
481
|
+
var hostType = isBuiltin ? void 0 : getFirstHostType(fiber);
|
|
482
|
+
if (alt === null) {
|
|
483
|
+
var entry = {
|
|
484
|
+
component: componentKey,
|
|
485
|
+
type: "mount",
|
|
486
|
+
trigger: "parent",
|
|
487
|
+
timestamp: Date.now(),
|
|
488
|
+
commitId: renderCommitCount,
|
|
489
|
+
parent: parentName,
|
|
490
|
+
isMemoized: isMemoWrapped(fiber)
|
|
491
|
+
};
|
|
492
|
+
if (hostType) entry.nativeType = hostType;
|
|
493
|
+
pushRenderEntry(entry);
|
|
494
|
+
} else {
|
|
495
|
+
var flags = fiber.flags;
|
|
496
|
+
if (flags === void 0) flags = fiber.effectTag;
|
|
497
|
+
if (typeof flags === "number" && (flags & 1) === 0) {} else {
|
|
498
|
+
var stateChanges = diffStateHooks(alt, fiber);
|
|
499
|
+
var propChanges = diffProps(alt.memoizedProps, fiber.memoizedProps);
|
|
500
|
+
var contextChanges = diffContext(fiber);
|
|
501
|
+
var trigger;
|
|
502
|
+
if (stateChanges) trigger = "state";
|
|
503
|
+
else if (propChanges) trigger = "props";
|
|
504
|
+
else if (contextChanges) trigger = "context";
|
|
505
|
+
else trigger = "parent";
|
|
506
|
+
var changes = {};
|
|
507
|
+
if (stateChanges) changes.state = stateChanges;
|
|
508
|
+
if (propChanges) changes.props = propChanges;
|
|
509
|
+
if (contextChanges) changes.context = contextChanges;
|
|
510
|
+
var hasChanges = stateChanges || propChanges || contextChanges;
|
|
511
|
+
var updateEntry = {
|
|
512
|
+
component: componentKey,
|
|
513
|
+
type: "update",
|
|
514
|
+
trigger,
|
|
515
|
+
timestamp: Date.now(),
|
|
516
|
+
commitId: renderCommitCount,
|
|
517
|
+
parent: parentName,
|
|
518
|
+
isMemoized: isMemoWrapped(fiber)
|
|
519
|
+
};
|
|
520
|
+
if (hostType) updateEntry.nativeType = hostType;
|
|
521
|
+
if (hasChanges) updateEntry.changes = changes;
|
|
522
|
+
pushRenderEntry(updateEntry);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
collectRenderEntries(fiber.child);
|
|
527
|
+
collectRenderEntries(fiber.sibling);
|
|
528
|
+
}
|
|
529
|
+
var DEFAULT_IGNORED_PREFIXES, BUILTIN_COMPONENTS;
|
|
530
|
+
var init_render_tracking = __esmMin(() => {
|
|
531
|
+
init_fiber_helpers();
|
|
532
|
+
init_state_hooks();
|
|
533
|
+
init_shared();
|
|
534
|
+
DEFAULT_IGNORED_PREFIXES = [
|
|
535
|
+
"LogBox",
|
|
536
|
+
"Pressability",
|
|
537
|
+
"YellowBox",
|
|
538
|
+
"RCT",
|
|
539
|
+
"Debugging",
|
|
540
|
+
"AppContainer",
|
|
541
|
+
"TextAncestor",
|
|
542
|
+
"VirtualizedList",
|
|
543
|
+
"CellRenderer"
|
|
544
|
+
];
|
|
545
|
+
BUILTIN_COMPONENTS = [
|
|
546
|
+
"Text",
|
|
547
|
+
"View",
|
|
548
|
+
"Image",
|
|
549
|
+
"Pressable",
|
|
550
|
+
"TouchableOpacity",
|
|
551
|
+
"TouchableHighlight",
|
|
552
|
+
"TouchableWithoutFeedback",
|
|
553
|
+
"TouchableNativeFeedback",
|
|
554
|
+
"ScrollView",
|
|
555
|
+
"FlatList",
|
|
556
|
+
"SectionList",
|
|
557
|
+
"TextInput",
|
|
558
|
+
"ActivityIndicator",
|
|
559
|
+
"Switch",
|
|
560
|
+
"SafeAreaView",
|
|
561
|
+
"KeyboardAvoidingView",
|
|
562
|
+
"StatusBar",
|
|
563
|
+
"Modal",
|
|
564
|
+
"RefreshControl"
|
|
565
|
+
];
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region src/runtime/state-change-tracking.ts
|
|
570
|
+
var init_state_change_tracking = __esmMin(() => {
|
|
571
|
+
init_state_hooks();
|
|
572
|
+
init_render_tracking();
|
|
573
|
+
init_shared();
|
|
574
|
+
(function() {
|
|
575
|
+
var g = typeof globalThis !== "undefined" ? globalThis : typeof global !== "undefined" ? global : null;
|
|
576
|
+
if (!g || !g.__REACT_DEVTOOLS_GLOBAL_HOOK__) return;
|
|
577
|
+
var hook = g.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
578
|
+
var orig = hook.onCommitFiberRoot;
|
|
579
|
+
hook.onCommitFiberRoot = function(rendererID, root) {
|
|
580
|
+
if (typeof orig === "function") orig.call(hook, rendererID, root);
|
|
581
|
+
try {
|
|
582
|
+
if (root && root.current) collectStateChanges(root.current);
|
|
583
|
+
} catch (_e) {}
|
|
584
|
+
try {
|
|
585
|
+
if (renderProfileActive && root && root.current) {
|
|
586
|
+
incrementRenderCommitCount();
|
|
587
|
+
collectRenderEntries(root.current);
|
|
588
|
+
}
|
|
589
|
+
} catch (_e) {}
|
|
590
|
+
};
|
|
591
|
+
})();
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/runtime/query-selector.ts
|
|
596
|
+
/**
|
|
597
|
+
* 셀렉터 문자열을 AST로 파싱한다 (재귀 하강 파서).
|
|
598
|
+
* 지원 문법:
|
|
599
|
+
* Type#testID[attr="val"]:text("..."):display-name("..."):nth-of-type(N):has-press:has-scroll
|
|
600
|
+
* A > B (직접 자식), A B (후손), A, B (OR)
|
|
601
|
+
*/
|
|
602
|
+
function parseSelector(input) {
|
|
603
|
+
var pos = 0;
|
|
604
|
+
var len = input.length;
|
|
605
|
+
function isIdentChar(ch) {
|
|
606
|
+
return /[A-Za-z0-9_.-]/.test(ch);
|
|
607
|
+
}
|
|
608
|
+
function skipSpaces() {
|
|
609
|
+
while (pos < len && (input.charAt(pos) === " " || input.charAt(pos) === " ")) pos++;
|
|
610
|
+
}
|
|
611
|
+
function readIdentifier() {
|
|
612
|
+
var start = pos;
|
|
613
|
+
while (pos < len && isIdentChar(input.charAt(pos))) pos++;
|
|
614
|
+
return input.substring(start, pos);
|
|
615
|
+
}
|
|
616
|
+
function readQuotedString() {
|
|
617
|
+
var quote = input.charAt(pos);
|
|
618
|
+
if (quote !== "\"" && quote !== "'") return "";
|
|
619
|
+
pos++;
|
|
620
|
+
var start = pos;
|
|
621
|
+
while (pos < len && input.charAt(pos) !== quote) {
|
|
622
|
+
if (input.charAt(pos) === "\\") pos++;
|
|
623
|
+
pos++;
|
|
624
|
+
}
|
|
625
|
+
if (pos >= len) return null;
|
|
626
|
+
var str = input.substring(start, pos);
|
|
627
|
+
pos++;
|
|
628
|
+
return str;
|
|
629
|
+
}
|
|
630
|
+
function readNumber() {
|
|
631
|
+
var start = pos;
|
|
632
|
+
while (pos < len && /[0-9]/.test(input.charAt(pos))) pos++;
|
|
633
|
+
return parseInt(input.substring(start, pos), 10);
|
|
634
|
+
}
|
|
635
|
+
function parseCompound() {
|
|
636
|
+
var sel = {
|
|
637
|
+
type: null,
|
|
638
|
+
testID: null,
|
|
639
|
+
attrs: [],
|
|
640
|
+
text: null,
|
|
641
|
+
displayName: null,
|
|
642
|
+
nth: -1,
|
|
643
|
+
hasPress: false,
|
|
644
|
+
hasScroll: false
|
|
645
|
+
};
|
|
646
|
+
var ch = pos < len ? input.charAt(pos) : "";
|
|
647
|
+
if (/[A-Za-z_]/.test(ch)) sel.type = readIdentifier();
|
|
648
|
+
if (pos < len && input.charAt(pos) === "#") {
|
|
649
|
+
pos++;
|
|
650
|
+
sel.testID = readIdentifier();
|
|
651
|
+
}
|
|
652
|
+
while (pos < len && input.charAt(pos) === "[") {
|
|
653
|
+
pos++;
|
|
654
|
+
skipSpaces();
|
|
655
|
+
var attrName = readIdentifier();
|
|
656
|
+
skipSpaces();
|
|
657
|
+
if (pos < len && input.charAt(pos) === "=") {
|
|
658
|
+
pos++;
|
|
659
|
+
skipSpaces();
|
|
660
|
+
var attrVal = readQuotedString();
|
|
661
|
+
if (attrVal === null) throw new Error("Unclosed quote in selector [attr=\"...\"]");
|
|
662
|
+
sel.attrs.push({
|
|
663
|
+
name: attrName,
|
|
664
|
+
value: attrVal
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
skipSpaces();
|
|
668
|
+
if (pos < len && input.charAt(pos) === "]") pos++;
|
|
669
|
+
}
|
|
670
|
+
while (pos < len && input.charAt(pos) === ":") {
|
|
671
|
+
pos++;
|
|
672
|
+
var pseudo = readIdentifier();
|
|
673
|
+
if (pseudo === "text") {
|
|
674
|
+
if (pos < len && input.charAt(pos) === "(") {
|
|
675
|
+
pos++;
|
|
676
|
+
skipSpaces();
|
|
677
|
+
var textVal = readQuotedString();
|
|
678
|
+
if (textVal === null) throw new Error("Unclosed quote in selector :text(...)");
|
|
679
|
+
sel.text = textVal;
|
|
680
|
+
skipSpaces();
|
|
681
|
+
if (pos < len && input.charAt(pos) === ")") pos++;
|
|
682
|
+
}
|
|
683
|
+
} else if (pseudo === "nth-of-type") {
|
|
684
|
+
if (pos < len && input.charAt(pos) === "(") {
|
|
685
|
+
pos++;
|
|
686
|
+
skipSpaces();
|
|
687
|
+
sel.nth = readNumber() - 1;
|
|
688
|
+
skipSpaces();
|
|
689
|
+
if (pos < len && input.charAt(pos) === ")") pos++;
|
|
690
|
+
}
|
|
691
|
+
} else if (pseudo === "first-of-type") sel.nth = 0;
|
|
692
|
+
else if (pseudo === "last-of-type") sel.nth = -2;
|
|
693
|
+
else if (pseudo === "display-name") {
|
|
694
|
+
if (pos < len && input.charAt(pos) === "(") {
|
|
695
|
+
pos++;
|
|
696
|
+
skipSpaces();
|
|
697
|
+
var dn = readQuotedString();
|
|
698
|
+
if (dn === null) throw new Error("Unclosed quote in selector :display-name(\"...\")");
|
|
699
|
+
skipSpaces();
|
|
700
|
+
if (pos < len && input.charAt(pos) === ")") pos++;
|
|
701
|
+
sel.displayName = dn;
|
|
702
|
+
}
|
|
703
|
+
} else if (pseudo === "has-press") sel.hasPress = true;
|
|
704
|
+
else if (pseudo === "has-scroll") sel.hasScroll = true;
|
|
705
|
+
}
|
|
706
|
+
return sel;
|
|
707
|
+
}
|
|
708
|
+
function parseComplex() {
|
|
709
|
+
skipSpaces();
|
|
710
|
+
var segments = [];
|
|
711
|
+
segments.push({
|
|
712
|
+
selector: parseCompound(),
|
|
713
|
+
combinator: null
|
|
714
|
+
});
|
|
715
|
+
while (pos < len) {
|
|
716
|
+
var beforeSkip = pos;
|
|
717
|
+
skipSpaces();
|
|
718
|
+
if (pos >= len || input.charAt(pos) === ",") break;
|
|
719
|
+
var combinator = " ";
|
|
720
|
+
if (input.charAt(pos) === ">") {
|
|
721
|
+
combinator = ">";
|
|
722
|
+
pos++;
|
|
723
|
+
skipSpaces();
|
|
724
|
+
} else if (pos === beforeSkip) break;
|
|
725
|
+
var nextCh = pos < len ? input.charAt(pos) : "";
|
|
726
|
+
if (!/[A-Za-z_#[:]/.test(nextCh)) break;
|
|
727
|
+
segments.push({
|
|
728
|
+
selector: parseCompound(),
|
|
729
|
+
combinator
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
return { segments };
|
|
733
|
+
}
|
|
734
|
+
var selectors = [];
|
|
735
|
+
selectors.push(parseComplex());
|
|
736
|
+
while (pos < len) {
|
|
737
|
+
skipSpaces();
|
|
738
|
+
if (pos >= len || input.charAt(pos) !== ",") break;
|
|
739
|
+
pos++;
|
|
740
|
+
selectors.push(parseComplex());
|
|
741
|
+
}
|
|
742
|
+
return { selectors };
|
|
743
|
+
}
|
|
744
|
+
/** compound 셀렉터가 단일 fiber 노드에 매칭되는지 검사 */
|
|
745
|
+
function matchesCompound(fiber, compound, TextComp, ImgComp) {
|
|
746
|
+
if (!fiber) return false;
|
|
747
|
+
var props = fiber.memoizedProps || {};
|
|
748
|
+
if (compound.type !== null) {
|
|
749
|
+
if (getFiberTypeName(fiber) !== compound.type) return false;
|
|
750
|
+
}
|
|
751
|
+
if (compound.displayName !== null) {
|
|
752
|
+
var t = fiber.type;
|
|
753
|
+
if (!t || typeof t.displayName !== "string" || t.displayName !== compound.displayName) return false;
|
|
754
|
+
}
|
|
755
|
+
if (compound.testID !== null && props.testID !== compound.testID) return false;
|
|
756
|
+
for (var i = 0; i < compound.attrs.length; i++) {
|
|
757
|
+
var attr = compound.attrs[i];
|
|
758
|
+
if (String(props[attr.name] || "") !== attr.value) return false;
|
|
759
|
+
}
|
|
760
|
+
if (compound.text !== null) {
|
|
761
|
+
if (collectText(fiber, TextComp).replace(/\s+/g, " ").trim().indexOf(compound.text) === -1) return false;
|
|
762
|
+
}
|
|
763
|
+
if (compound.hasPress && typeof props.onPress !== "function") return false;
|
|
764
|
+
if (compound.hasScroll) {
|
|
765
|
+
var sn = fiber.stateNode;
|
|
766
|
+
if (!sn || typeof sn.scrollTo !== "function" && typeof sn.scrollToOffset !== "function") return false;
|
|
767
|
+
}
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
/** 계층 셀렉터(A > B, A B) 매칭 — fiber.return을 상향 탐색 */
|
|
771
|
+
function matchesComplexSelector(fiber, complex, TextComp, ImgComp) {
|
|
772
|
+
var segs = complex.segments;
|
|
773
|
+
var last = segs.length - 1;
|
|
774
|
+
if (!matchesCompound(fiber, segs[last].selector, TextComp, ImgComp)) return false;
|
|
775
|
+
var current = fiber;
|
|
776
|
+
for (var i = last - 1; i >= 0; i--) {
|
|
777
|
+
var combinator = segs[i + 1].combinator;
|
|
778
|
+
var targetSel = segs[i].selector;
|
|
779
|
+
if (combinator === ">") {
|
|
780
|
+
current = current.return;
|
|
781
|
+
if (!current || !matchesCompound(current, targetSel, TextComp, ImgComp)) return false;
|
|
782
|
+
} else {
|
|
783
|
+
current = current.return;
|
|
784
|
+
while (current) {
|
|
785
|
+
if (matchesCompound(current, targetSel, TextComp, ImgComp)) break;
|
|
786
|
+
current = current.return;
|
|
787
|
+
}
|
|
788
|
+
if (!current) return false;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
/** testID 없는 fiber의 경로 기반 uid 계산 ("0.1.2" 형식) */
|
|
794
|
+
function getPathUid(fiber) {
|
|
795
|
+
var parts = [];
|
|
796
|
+
var cur = fiber;
|
|
797
|
+
while (cur && cur.return) {
|
|
798
|
+
var parent = cur.return;
|
|
799
|
+
var idx = 0;
|
|
800
|
+
var ch = parent.child;
|
|
801
|
+
while (ch) {
|
|
802
|
+
if (ch === cur) break;
|
|
803
|
+
ch = ch.sibling;
|
|
804
|
+
idx++;
|
|
805
|
+
}
|
|
806
|
+
parts.unshift(idx);
|
|
807
|
+
cur = parent;
|
|
808
|
+
}
|
|
809
|
+
parts.unshift(0);
|
|
810
|
+
return parts.join(".");
|
|
811
|
+
}
|
|
812
|
+
/** path("0.1.2")로 Fiber 트리에서 노드 찾기. getComponentTree와 동일한 인덱스 규칙. */
|
|
813
|
+
function getFiberByPath(root, pathStr) {
|
|
814
|
+
if (!root || typeof pathStr !== "string") return null;
|
|
815
|
+
var parts = pathStr.trim().split(".");
|
|
816
|
+
var fiber = root;
|
|
817
|
+
for (var i = 0; i < parts.length; i++) {
|
|
818
|
+
if (!fiber) return null;
|
|
819
|
+
var part = parts[i];
|
|
820
|
+
if (part === void 0) return null;
|
|
821
|
+
var idx = parseInt(part, 10);
|
|
822
|
+
if (i === 0) {
|
|
823
|
+
if (idx !== 0) return null;
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
var child = fiber.child;
|
|
827
|
+
var j = 0;
|
|
828
|
+
while (child && j < idx) {
|
|
829
|
+
child = child.sibling;
|
|
830
|
+
j++;
|
|
831
|
+
}
|
|
832
|
+
fiber = child;
|
|
833
|
+
}
|
|
834
|
+
return fiber;
|
|
835
|
+
}
|
|
836
|
+
/** uid가 경로 형식인지 ("0", "0.1", "0.1.2" 등) */
|
|
837
|
+
function isPathUid(uid) {
|
|
838
|
+
return typeof uid === "string" && /^\d+(\.\d+)*$/.test(uid.trim());
|
|
839
|
+
}
|
|
840
|
+
var init_query_selector = __esmMin(() => {
|
|
841
|
+
init_fiber_helpers();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
//#endregion
|
|
845
|
+
//#region src/runtime/screen-offset.ts
|
|
846
|
+
function resolveScreenOffset() {}
|
|
847
|
+
var screenOffsetX, screenOffsetY;
|
|
848
|
+
var init_screen_offset = __esmMin(() => {
|
|
849
|
+
screenOffsetX = 0;
|
|
850
|
+
screenOffsetY = 0;
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/runtime/mcp-measure.ts
|
|
855
|
+
/**
|
|
856
|
+
* getScreenInfo() → { screen, window, scale, fontScale, orientation }
|
|
857
|
+
*/
|
|
858
|
+
function getScreenInfo() {
|
|
859
|
+
try {
|
|
860
|
+
var rn = typeof require !== "undefined" && require("react-native");
|
|
861
|
+
if (!rn) return { error: "react-native not available" };
|
|
862
|
+
var screen = rn.Dimensions.get("screen");
|
|
863
|
+
var win = rn.Dimensions.get("window");
|
|
864
|
+
var pixelRatio = rn.PixelRatio ? rn.PixelRatio.get() : 1;
|
|
865
|
+
var fontScale = rn.PixelRatio ? rn.PixelRatio.getFontScale() : 1;
|
|
866
|
+
return {
|
|
867
|
+
screen: {
|
|
868
|
+
width: screen.width,
|
|
869
|
+
height: screen.height
|
|
870
|
+
},
|
|
871
|
+
window: {
|
|
872
|
+
width: win.width,
|
|
873
|
+
height: win.height
|
|
874
|
+
},
|
|
875
|
+
scale: pixelRatio,
|
|
876
|
+
fontScale,
|
|
877
|
+
orientation: win.width > win.height ? "landscape" : "portrait"
|
|
878
|
+
};
|
|
879
|
+
} catch (e) {
|
|
880
|
+
return { error: String(e) };
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* measureView(uid) → Promise<{ x, y, width, height, pageX, pageY }>
|
|
885
|
+
* uid: testID 또는 경로("0.1.2"). query_selector로 얻은 uid 그대로 사용 가능.
|
|
886
|
+
* Fiber에서 native node를 찾아 measureInWindow (Fabric) 또는 measure (Bridge)로 절대 좌표 반환.
|
|
887
|
+
* pageX/pageY: 화면 왼쪽 상단 기준 절대 좌표 (points).
|
|
888
|
+
*/
|
|
889
|
+
function measureView(uid) {
|
|
890
|
+
return new Promise(function(resolve, reject) {
|
|
891
|
+
try {
|
|
892
|
+
var root = getFiberRoot();
|
|
893
|
+
if (!root) return reject(/* @__PURE__ */ new Error("no fiber root"));
|
|
894
|
+
var found = null;
|
|
895
|
+
if (isPathUid(uid)) {
|
|
896
|
+
found = getFiberByPath(root, uid);
|
|
897
|
+
if (found && !found.stateNode) found = null;
|
|
898
|
+
}
|
|
899
|
+
if (!found) (function find(fiber) {
|
|
900
|
+
if (!fiber || found) return;
|
|
901
|
+
if (fiber.memoizedProps && fiber.memoizedProps.testID === uid && fiber.stateNode) {
|
|
902
|
+
found = fiber;
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
find(fiber.child);
|
|
906
|
+
if (!found) find(fiber.sibling);
|
|
907
|
+
})(root);
|
|
908
|
+
if (!found) return reject(/* @__PURE__ */ new Error("uid \"" + uid + "\" not found or has no native view"));
|
|
909
|
+
var g = typeof globalThis !== "undefined" ? globalThis : global;
|
|
910
|
+
var rn = typeof require !== "undefined" && require("react-native");
|
|
911
|
+
while (found) {
|
|
912
|
+
var node = found.stateNode;
|
|
913
|
+
if (g.nativeFabricUIManager && node) {
|
|
914
|
+
var shadowNode = node.node || node._internalInstanceHandle && node._internalInstanceHandle.stateNode && node._internalInstanceHandle.stateNode.node;
|
|
915
|
+
if (!shadowNode && node._viewInfo && node._viewInfo.shadowNodeWrapper) shadowNode = node._viewInfo.shadowNodeWrapper;
|
|
916
|
+
if (shadowNode) {
|
|
917
|
+
/* @__PURE__ */ resolveScreenOffset();
|
|
918
|
+
g.nativeFabricUIManager.measureInWindow(shadowNode, function(x, y, w, h) {
|
|
919
|
+
resolve({
|
|
920
|
+
x,
|
|
921
|
+
y,
|
|
922
|
+
width: w,
|
|
923
|
+
height: h,
|
|
924
|
+
pageX: x + screenOffsetX,
|
|
925
|
+
pageY: y + screenOffsetY
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (rn && rn.UIManager && rn.findNodeHandle && node) {
|
|
932
|
+
var handle = rn.findNodeHandle(node);
|
|
933
|
+
if (handle) {
|
|
934
|
+
rn.UIManager.measure(handle, function(x, y, w, h, pageX, pageY) {
|
|
935
|
+
resolve({
|
|
936
|
+
x,
|
|
937
|
+
y,
|
|
938
|
+
width: w,
|
|
939
|
+
height: h,
|
|
940
|
+
pageX,
|
|
941
|
+
pageY
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
found = found.return;
|
|
948
|
+
}
|
|
949
|
+
reject(/* @__PURE__ */ new Error("cannot measure: no native node"));
|
|
950
|
+
} catch (e) {
|
|
951
|
+
reject(e);
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* measureViewSync(uid) → { x, y, width, height, pageX, pageY } | null
|
|
957
|
+
* Fabric에서는 동기 호출 가능. Bridge에서는 null → measureView() 사용 권장.
|
|
958
|
+
*/
|
|
959
|
+
function measureViewSync(uid) {
|
|
960
|
+
try {
|
|
961
|
+
var root = getFiberRoot();
|
|
962
|
+
if (!root) return null;
|
|
963
|
+
var found = null;
|
|
964
|
+
if (isPathUid(uid)) found = getFiberByPath(root, uid);
|
|
965
|
+
if (!found) (function find(fiber) {
|
|
966
|
+
if (!fiber || found) return;
|
|
967
|
+
if (fiber.memoizedProps && fiber.memoizedProps.testID === uid && fiber.stateNode) {
|
|
968
|
+
found = fiber;
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
find(fiber.child);
|
|
972
|
+
if (!found) find(fiber.sibling);
|
|
973
|
+
})(root);
|
|
974
|
+
if (!found) return null;
|
|
975
|
+
var node = found.stateNode;
|
|
976
|
+
var g = typeof globalThis !== "undefined" ? globalThis : global;
|
|
977
|
+
if (g.nativeFabricUIManager && node) {
|
|
978
|
+
var shadowNode = node.node || node._internalInstanceHandle && node._internalInstanceHandle.stateNode && node._internalInstanceHandle.stateNode.node;
|
|
979
|
+
if (!shadowNode && node._viewInfo && node._viewInfo.shadowNodeWrapper) shadowNode = node._viewInfo.shadowNodeWrapper;
|
|
980
|
+
if (shadowNode) {
|
|
981
|
+
var result = null;
|
|
982
|
+
/* @__PURE__ */ resolveScreenOffset();
|
|
983
|
+
g.nativeFabricUIManager.measureInWindow(shadowNode, function(x, y, w, h) {
|
|
984
|
+
result = {
|
|
985
|
+
x,
|
|
986
|
+
y,
|
|
987
|
+
width: w,
|
|
988
|
+
height: h,
|
|
989
|
+
pageX: x + screenOffsetX,
|
|
990
|
+
pageY: y + screenOffsetY
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
return result;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return null;
|
|
997
|
+
} catch (e) {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
var init_mcp_measure = __esmMin(() => {
|
|
1002
|
+
init_fiber_helpers();
|
|
1003
|
+
init_query_selector();
|
|
1004
|
+
init_screen_offset();
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/runtime/mcp-webview.ts
|
|
1009
|
+
function registerWebView(ref, id) {
|
|
1010
|
+
if (ref && typeof id === "string") {
|
|
1011
|
+
_webViews[id] = ref;
|
|
1012
|
+
if (_webViewRefToId) try {
|
|
1013
|
+
_webViewRefToId.set(ref, id);
|
|
1014
|
+
} catch (e) {}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function unregisterWebView(id) {
|
|
1018
|
+
if (typeof id === "string") {
|
|
1019
|
+
var ref = _webViews[id];
|
|
1020
|
+
if (ref && _webViewRefToId) _webViewRefToId.delete(ref);
|
|
1021
|
+
delete _webViews[id];
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
/** ref에 해당하는 등록된 webViewId 반환 (query_selector로 찾은 WebView → webViewId용) */
|
|
1025
|
+
function getWebViewIdForRef(ref) {
|
|
1026
|
+
return _webViewRefToId && ref ? _webViewRefToId.get(ref) || null : null;
|
|
1027
|
+
}
|
|
1028
|
+
function clickInWebView(id, selector) {
|
|
1029
|
+
var ref = _webViews[id];
|
|
1030
|
+
if (!ref || typeof ref.injectJavaScript !== "function") return {
|
|
1031
|
+
ok: false,
|
|
1032
|
+
error: "WebView not found or injectJavaScript not available"
|
|
1033
|
+
};
|
|
1034
|
+
var script = "(function(){ var el = document.querySelector(" + JSON.stringify(selector) + "); if (el) el.click(); })();";
|
|
1035
|
+
ref.injectJavaScript(script);
|
|
1036
|
+
return { ok: true };
|
|
1037
|
+
}
|
|
1038
|
+
/** 등록된 WebView 내부에서 임의의 JavaScript를 실행 (동기, 반환값 없음) */
|
|
1039
|
+
function evaluateInWebView(id, script) {
|
|
1040
|
+
var ref = _webViews[id];
|
|
1041
|
+
if (!ref || typeof ref.injectJavaScript !== "function") return {
|
|
1042
|
+
ok: false,
|
|
1043
|
+
error: "WebView not found or injectJavaScript not available"
|
|
1044
|
+
};
|
|
1045
|
+
ref.injectJavaScript(script);
|
|
1046
|
+
return { ok: true };
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* WebView에서 스크립트 실행 후 postMessage로 결과 수신.
|
|
1050
|
+
* @returns Promise<{ ok: true, value: string } | { ok: false, error: string }>
|
|
1051
|
+
*/
|
|
1052
|
+
function evaluateInWebViewAsync(id, script) {
|
|
1053
|
+
var ref = _webViews[id];
|
|
1054
|
+
if (!ref || typeof ref.injectJavaScript !== "function") return Promise.resolve({
|
|
1055
|
+
ok: false,
|
|
1056
|
+
error: "WebView not found or injectJavaScript not available"
|
|
1057
|
+
});
|
|
1058
|
+
var requestId = "wv_" + ++_webViewEvalRequestId + "_" + Date.now();
|
|
1059
|
+
var wrapped = "(function(){ var __reqId=" + JSON.stringify(requestId) + "; var __script=" + JSON.stringify(script) + "; try { var __r=(function(){ return eval(__script); })(); var __v=typeof __r===\"string\" ? __r : (function(){ try { return JSON.stringify(__r); } catch(e){ return String(__r); } })(); window.ReactNativeWebView&&window.ReactNativeWebView.postMessage(JSON.stringify({__mcpEvalResult:true,requestId:__reqId,value:__v})); } catch(e) { window.ReactNativeWebView&&window.ReactNativeWebView.postMessage(JSON.stringify({__mcpEvalResult:true,requestId:__reqId,error:(e&&e.message)||String(e)})); } })();";
|
|
1060
|
+
return new Promise(function(resolve) {
|
|
1061
|
+
_webViewEvalPending[requestId] = {
|
|
1062
|
+
resolve,
|
|
1063
|
+
timeout: setTimeout(function() {
|
|
1064
|
+
if (_webViewEvalPending[requestId]) {
|
|
1065
|
+
delete _webViewEvalPending[requestId];
|
|
1066
|
+
resolve({
|
|
1067
|
+
ok: false,
|
|
1068
|
+
error: "WebView eval timeout (10s)"
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
}, 1e4)
|
|
1072
|
+
};
|
|
1073
|
+
ref.injectJavaScript(wrapped);
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* WebView onMessage에서 호출. postMessage로 온 __mcpEvalResult 수신 시 evaluateInWebViewAsync Promise resolve.
|
|
1078
|
+
* @returns true if the message was __mcpEvalResult (consumed), false otherwise.
|
|
1079
|
+
*/
|
|
1080
|
+
function handleWebViewMessage(data) {
|
|
1081
|
+
if (!data || typeof data !== "string") return false;
|
|
1082
|
+
try {
|
|
1083
|
+
var payload = JSON.parse(data);
|
|
1084
|
+
if (!payload || payload.__mcpEvalResult !== true || !payload.requestId) return false;
|
|
1085
|
+
var reqId = payload.requestId;
|
|
1086
|
+
var pending = _webViewEvalPending[reqId];
|
|
1087
|
+
if (!pending) return false;
|
|
1088
|
+
delete _webViewEvalPending[reqId];
|
|
1089
|
+
if (pending.timeout) clearTimeout(pending.timeout);
|
|
1090
|
+
if (payload.error != null) pending.resolve({
|
|
1091
|
+
ok: false,
|
|
1092
|
+
error: payload.error
|
|
1093
|
+
});
|
|
1094
|
+
else pending.resolve({
|
|
1095
|
+
ok: true,
|
|
1096
|
+
value: payload.value
|
|
1097
|
+
});
|
|
1098
|
+
return true;
|
|
1099
|
+
} catch (_) {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* WebView onMessage와 사용자 핸들러를 함께 쓰기 위한 래퍼.
|
|
1105
|
+
*/
|
|
1106
|
+
function createWebViewOnMessage(userHandler) {
|
|
1107
|
+
if (typeof userHandler !== "function") return function(e) {
|
|
1108
|
+
globalThis.__REACT_NATIVE_MCP__.handleWebViewMessage(e.nativeEvent.data);
|
|
1109
|
+
};
|
|
1110
|
+
return function(e) {
|
|
1111
|
+
var data = e && e.nativeEvent && e.nativeEvent.data;
|
|
1112
|
+
if (!globalThis.__REACT_NATIVE_MCP__.handleWebViewMessage(data)) userHandler(e);
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function getRegisteredWebViewIds() {
|
|
1116
|
+
return Object.keys(_webViews);
|
|
1117
|
+
}
|
|
1118
|
+
var _webViews, _webViewRefToId, _webViewEvalPending, _webViewEvalRequestId;
|
|
1119
|
+
var init_mcp_webview = __esmMin(() => {
|
|
1120
|
+
_webViews = {};
|
|
1121
|
+
_webViewRefToId = typeof WeakMap !== "undefined" ? /* @__PURE__ */ new WeakMap() : null;
|
|
1122
|
+
_webViewEvalPending = {};
|
|
1123
|
+
_webViewEvalRequestId = 0;
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
//#endregion
|
|
1127
|
+
//#region src/runtime/fiber-serialization.ts
|
|
1128
|
+
/** fiber 노드를 결과 객체로 직렬화 */
|
|
1129
|
+
function fiberToResult(fiber, TextComp, ImgComp) {
|
|
1130
|
+
var props = fiber.memoizedProps || {};
|
|
1131
|
+
var typeName = getFiberTypeName(fiber);
|
|
1132
|
+
var testID = typeof props.testID === "string" && props.testID.trim() ? props.testID.trim() : void 0;
|
|
1133
|
+
var text = collectText(fiber, TextComp).replace(/\s+/g, " ").trim() || void 0;
|
|
1134
|
+
var a11y = typeof props.accessibilityLabel === "string" && props.accessibilityLabel.trim() ? props.accessibilityLabel.trim() : void 0;
|
|
1135
|
+
var hasOnPress = typeof props.onPress === "function";
|
|
1136
|
+
var hasOnLongPress = typeof props.onLongPress === "function";
|
|
1137
|
+
var sn = fiber.stateNode;
|
|
1138
|
+
var hasScrollTo = !!(sn && (typeof sn.scrollTo === "function" || typeof sn.scrollToOffset === "function"));
|
|
1139
|
+
var uid = testID || getPathUid(fiber);
|
|
1140
|
+
var result = {
|
|
1141
|
+
uid,
|
|
1142
|
+
type: typeName
|
|
1143
|
+
};
|
|
1144
|
+
if (testID) result.testID = testID;
|
|
1145
|
+
if (text) result.text = text;
|
|
1146
|
+
if (a11y) result.accessibilityLabel = a11y;
|
|
1147
|
+
result.hasOnPress = hasOnPress;
|
|
1148
|
+
result.hasOnLongPress = hasOnLongPress;
|
|
1149
|
+
result.hasScrollTo = hasScrollTo;
|
|
1150
|
+
if (props.value !== void 0) result.value = props.value;
|
|
1151
|
+
if (props.disabled != null) result.disabled = !!props.disabled;
|
|
1152
|
+
if (props.editable !== void 0) result.editable = props.editable;
|
|
1153
|
+
var measure = null;
|
|
1154
|
+
try {
|
|
1155
|
+
measure = measureViewSync(uid);
|
|
1156
|
+
} catch (e) {}
|
|
1157
|
+
if (!measure && typeof fiber.type !== "string") {
|
|
1158
|
+
var hostChild = (function findHost(f) {
|
|
1159
|
+
if (!f) return null;
|
|
1160
|
+
if (typeof f.type === "string" && f.stateNode) return f;
|
|
1161
|
+
var c = f.child;
|
|
1162
|
+
while (c) {
|
|
1163
|
+
var h = findHost(c);
|
|
1164
|
+
if (h) return h;
|
|
1165
|
+
c = c.sibling;
|
|
1166
|
+
}
|
|
1167
|
+
return null;
|
|
1168
|
+
})(fiber.child);
|
|
1169
|
+
if (hostChild) {
|
|
1170
|
+
var hostUid = hostChild.memoizedProps && hostChild.memoizedProps.testID || getPathUid(hostChild);
|
|
1171
|
+
try {
|
|
1172
|
+
measure = measureViewSync(hostUid);
|
|
1173
|
+
} catch (e) {}
|
|
1174
|
+
if (!measure) result._measureUid = hostUid;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
result.measure = measure;
|
|
1178
|
+
if (typeName === "WebView" && sn && typeof sn.injectJavaScript === "function") {
|
|
1179
|
+
var wvId = getWebViewIdForRef(sn);
|
|
1180
|
+
if (wvId) result.webViewId = wvId;
|
|
1181
|
+
}
|
|
1182
|
+
return result;
|
|
1183
|
+
}
|
|
1184
|
+
var init_fiber_serialization = __esmMin(() => {
|
|
1185
|
+
init_fiber_helpers();
|
|
1186
|
+
init_query_selector();
|
|
1187
|
+
init_mcp_measure();
|
|
1188
|
+
init_mcp_webview();
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
//#endregion
|
|
1192
|
+
//#region src/runtime/mcp-registration.ts
|
|
1193
|
+
function registerComponent(name, component) {
|
|
1194
|
+
return require("react-native").AppRegistry.registerComponent(name, component);
|
|
1195
|
+
}
|
|
1196
|
+
function registerPressHandler(testID, handler) {
|
|
1197
|
+
if (typeof testID === "string" && typeof handler === "function") pressHandlers[testID] = handler;
|
|
1198
|
+
}
|
|
1199
|
+
function triggerPress(testID) {
|
|
1200
|
+
var h = pressHandlers[testID];
|
|
1201
|
+
if (typeof h === "function") {
|
|
1202
|
+
h();
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
var root = getFiberRoot();
|
|
1206
|
+
if (root) {
|
|
1207
|
+
var found = (function findByTestID(fiber) {
|
|
1208
|
+
if (!fiber) return null;
|
|
1209
|
+
if (fiber.memoizedProps && fiber.memoizedProps.testID === testID && typeof fiber.memoizedProps.onPress === "function") return fiber;
|
|
1210
|
+
return findByTestID(fiber.child) || findByTestID(fiber.sibling);
|
|
1211
|
+
})(root);
|
|
1212
|
+
if (found) {
|
|
1213
|
+
found.memoizedProps.onPress();
|
|
1214
|
+
return true;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
function triggerLongPress(testID) {
|
|
1220
|
+
var root = getFiberRoot();
|
|
1221
|
+
if (!root) return false;
|
|
1222
|
+
var found = (function find(fiber) {
|
|
1223
|
+
if (!fiber) return null;
|
|
1224
|
+
if (fiber.memoizedProps && fiber.memoizedProps.testID === testID && typeof fiber.memoizedProps.onLongPress === "function") return fiber;
|
|
1225
|
+
return find(fiber.child) || find(fiber.sibling);
|
|
1226
|
+
})(root);
|
|
1227
|
+
if (found) {
|
|
1228
|
+
found.memoizedProps.onLongPress();
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
function getRegisteredPressTestIDs() {
|
|
1234
|
+
return Object.keys(pressHandlers);
|
|
1235
|
+
}
|
|
1236
|
+
var init_mcp_registration = __esmMin(() => {
|
|
1237
|
+
init_fiber_helpers();
|
|
1238
|
+
init_shared();
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
//#endregion
|
|
1242
|
+
//#region src/runtime/mcp-introspection.ts
|
|
1243
|
+
/**
|
|
1244
|
+
* 클릭 가능 요소 목록 (uid + label). Fiber 트리에서 onPress 있는 모든 노드 수집.
|
|
1245
|
+
* _pressHandlers 레지스트리가 있으면 우선 사용, 없으면 Fiber 순회.
|
|
1246
|
+
*/
|
|
1247
|
+
function getClickables() {
|
|
1248
|
+
var ids = Object.keys(pressHandlers);
|
|
1249
|
+
if (ids.length > 0) {
|
|
1250
|
+
var root0 = getFiberRoot();
|
|
1251
|
+
var c0 = root0 ? getRNComponents() : null;
|
|
1252
|
+
return ids.map(function(id) {
|
|
1253
|
+
var label = "";
|
|
1254
|
+
if (root0 && c0) (function visit(fiber) {
|
|
1255
|
+
if (!fiber || label) return;
|
|
1256
|
+
if (fiber.memoizedProps && fiber.memoizedProps.testID === id) {
|
|
1257
|
+
label = collectText(fiber, c0.Text).replace(/\s+/g, " ").trim();
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
visit(fiber.child);
|
|
1261
|
+
visit(fiber.sibling);
|
|
1262
|
+
})(root0);
|
|
1263
|
+
return {
|
|
1264
|
+
uid: id,
|
|
1265
|
+
label
|
|
1266
|
+
};
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
try {
|
|
1270
|
+
var root = getFiberRoot();
|
|
1271
|
+
if (!root) return [];
|
|
1272
|
+
var c = getRNComponents();
|
|
1273
|
+
var out = [];
|
|
1274
|
+
function visit(fiber) {
|
|
1275
|
+
if (!fiber) return;
|
|
1276
|
+
var props = fiber.memoizedProps;
|
|
1277
|
+
if (typeof (props && props.onPress) === "function") {
|
|
1278
|
+
var testID = props && props.testID || void 0;
|
|
1279
|
+
var label = getLabel(fiber, c.Text, c.Image);
|
|
1280
|
+
out.push({
|
|
1281
|
+
uid: testID || "",
|
|
1282
|
+
label
|
|
1283
|
+
});
|
|
1284
|
+
visit(fiber.sibling);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
visit(fiber.child);
|
|
1288
|
+
visit(fiber.sibling);
|
|
1289
|
+
}
|
|
1290
|
+
visit(root);
|
|
1291
|
+
return out;
|
|
1292
|
+
} catch (e) {
|
|
1293
|
+
return [];
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Fiber 트리 전체에서 Text 노드 내용 수집. 버튼 여부와 무관하게 모든 보이는 텍스트.
|
|
1298
|
+
* 반환: [{ text, testID? }] — testID는 해당 Text의 조상 중 가장 가까운 testID.
|
|
1299
|
+
*/
|
|
1300
|
+
function getTextNodes() {
|
|
1301
|
+
try {
|
|
1302
|
+
var root = getFiberRoot();
|
|
1303
|
+
if (!root) return [];
|
|
1304
|
+
var c = getRNComponents();
|
|
1305
|
+
var TextComponent = c && c.Text;
|
|
1306
|
+
if (!TextComponent) return [];
|
|
1307
|
+
var out = [];
|
|
1308
|
+
function visit(fiber) {
|
|
1309
|
+
if (!fiber) return;
|
|
1310
|
+
if (fiber.type === TextComponent) {
|
|
1311
|
+
var text = getTextNodeContent(fiber, TextComponent);
|
|
1312
|
+
if (text) out.push({
|
|
1313
|
+
text: text.replace(/\s+/g, " "),
|
|
1314
|
+
testID: getAncestorTestID(fiber)
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
visit(fiber.child);
|
|
1318
|
+
visit(fiber.sibling);
|
|
1319
|
+
}
|
|
1320
|
+
visit(root);
|
|
1321
|
+
return out;
|
|
1322
|
+
} catch (e) {
|
|
1323
|
+
return [];
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Fiber 트리 전체를 컴포넌트 트리로 직렬화. querySelector 대체용 스냅샷.
|
|
1328
|
+
* 노드: { uid, type, testID?, accessibilityLabel?, text?, children? }
|
|
1329
|
+
* uid: testID 있으면 testID, 없으면 경로 "0.1.2". click(uid)는 testID일 때만 동작.
|
|
1330
|
+
* options: { maxDepth } (기본 무제한, 권장 20~30)
|
|
1331
|
+
*/
|
|
1332
|
+
function getComponentTree(options) {
|
|
1333
|
+
try {
|
|
1334
|
+
var root = getFiberRoot();
|
|
1335
|
+
if (!root) return null;
|
|
1336
|
+
var c = getRNComponents();
|
|
1337
|
+
var TextComponent = c && c.Text;
|
|
1338
|
+
var maxDepth = options && typeof options.maxDepth === "number" ? options.maxDepth : 999;
|
|
1339
|
+
function buildNode(fiber, path, depth) {
|
|
1340
|
+
if (!fiber || depth > maxDepth) return null;
|
|
1341
|
+
var props = fiber.memoizedProps || {};
|
|
1342
|
+
var testID = typeof props.testID === "string" && props.testID.trim() ? props.testID.trim() : void 0;
|
|
1343
|
+
var typeName = getFiberTypeName(fiber);
|
|
1344
|
+
var node = {
|
|
1345
|
+
uid: testID || path,
|
|
1346
|
+
type: typeName
|
|
1347
|
+
};
|
|
1348
|
+
if (testID) node.testID = testID;
|
|
1349
|
+
if (typeof props.accessibilityLabel === "string" && props.accessibilityLabel.trim()) node.accessibilityLabel = props.accessibilityLabel.trim();
|
|
1350
|
+
if (fiber.type === TextComponent) {
|
|
1351
|
+
var text = getTextNodeContent(fiber, TextComponent);
|
|
1352
|
+
if (text) node.text = text.replace(/\s+/g, " ");
|
|
1353
|
+
}
|
|
1354
|
+
var children = [];
|
|
1355
|
+
var child = fiber.child;
|
|
1356
|
+
var idx = 0;
|
|
1357
|
+
while (child) {
|
|
1358
|
+
var childPath = path + "." + idx;
|
|
1359
|
+
var childNode = buildNode(child, childPath, depth + 1);
|
|
1360
|
+
if (childNode) children.push(childNode);
|
|
1361
|
+
child = child.sibling;
|
|
1362
|
+
idx += 1;
|
|
1363
|
+
}
|
|
1364
|
+
if (children.length) node.children = children;
|
|
1365
|
+
return node;
|
|
1366
|
+
}
|
|
1367
|
+
return buildNode(root, "0", 0);
|
|
1368
|
+
} catch (e) {
|
|
1369
|
+
return null;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
var init_mcp_introspection = __esmMin(() => {
|
|
1373
|
+
init_fiber_helpers();
|
|
1374
|
+
init_shared();
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
//#endregion
|
|
1378
|
+
//#region src/runtime/mcp-actions.ts
|
|
1379
|
+
/**
|
|
1380
|
+
* Fiber 트리에서 라벨(텍스트)에 해당하는 onPress 노드들을 순서대로 수집한 뒤, index번째(0-based) 호출.
|
|
1381
|
+
* index 생략 시 0 (첫 번째). querySelectorAll()[index]와 유사.
|
|
1382
|
+
*/
|
|
1383
|
+
function pressByLabel(labelSubstring, index) {
|
|
1384
|
+
if (typeof labelSubstring !== "string" || !labelSubstring.trim()) return false;
|
|
1385
|
+
try {
|
|
1386
|
+
var root = getFiberRoot();
|
|
1387
|
+
if (!root) return false;
|
|
1388
|
+
var c = getRNComponents();
|
|
1389
|
+
var search = labelSubstring.trim();
|
|
1390
|
+
var matches = [];
|
|
1391
|
+
function visit(fiber) {
|
|
1392
|
+
if (!fiber) return;
|
|
1393
|
+
var props = fiber.memoizedProps;
|
|
1394
|
+
var onPress = props && props.onPress;
|
|
1395
|
+
if (typeof onPress === "function") {
|
|
1396
|
+
if (getLabel(fiber, c.Text, c.Image).indexOf(search) !== -1) matches.push(onPress);
|
|
1397
|
+
visit(fiber.sibling);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
visit(fiber.child);
|
|
1401
|
+
visit(fiber.sibling);
|
|
1402
|
+
}
|
|
1403
|
+
visit(root);
|
|
1404
|
+
var fn = matches[typeof index === "number" && index >= 0 ? index : 0];
|
|
1405
|
+
if (typeof fn === "function") {
|
|
1406
|
+
try {
|
|
1407
|
+
fn();
|
|
1408
|
+
} catch (e) {}
|
|
1409
|
+
return true;
|
|
1410
|
+
}
|
|
1411
|
+
return false;
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
return false;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Fiber 트리에서 라벨(텍스트)에 해당하는 onLongPress 노드들을 순서대로 수집한 뒤, index번째(0-based) 호출.
|
|
1418
|
+
*/
|
|
1419
|
+
function longPressByLabel(labelSubstring, index) {
|
|
1420
|
+
if (typeof labelSubstring !== "string" || !labelSubstring.trim()) return false;
|
|
1421
|
+
try {
|
|
1422
|
+
var root = getFiberRoot();
|
|
1423
|
+
if (!root) return false;
|
|
1424
|
+
var c = getRNComponents();
|
|
1425
|
+
var search = labelSubstring.trim();
|
|
1426
|
+
var matches = [];
|
|
1427
|
+
function visitLP(fiber) {
|
|
1428
|
+
if (!fiber) return;
|
|
1429
|
+
var props = fiber.memoizedProps;
|
|
1430
|
+
var onLP = props && props.onLongPress;
|
|
1431
|
+
if (typeof onLP === "function") {
|
|
1432
|
+
if (getLabel(fiber, c.Text, c.Image).indexOf(search) !== -1) matches.push(onLP);
|
|
1433
|
+
visitLP(fiber.sibling);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
visitLP(fiber.child);
|
|
1437
|
+
visitLP(fiber.sibling);
|
|
1438
|
+
}
|
|
1439
|
+
visitLP(root);
|
|
1440
|
+
var fn = matches[typeof index === "number" && index >= 0 ? index : 0];
|
|
1441
|
+
if (typeof fn === "function") {
|
|
1442
|
+
try {
|
|
1443
|
+
fn();
|
|
1444
|
+
} catch (e) {}
|
|
1445
|
+
return true;
|
|
1446
|
+
}
|
|
1447
|
+
return false;
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
return false;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* TextInput에 텍스트 입력. Fiber에서 testID 매칭 → onChangeText(text) 호출 + setNativeProps 동기화.
|
|
1454
|
+
*/
|
|
1455
|
+
function typeText(testID, text) {
|
|
1456
|
+
try {
|
|
1457
|
+
var root = getFiberRoot();
|
|
1458
|
+
if (!root) return {
|
|
1459
|
+
ok: false,
|
|
1460
|
+
error: "No Fiber root"
|
|
1461
|
+
};
|
|
1462
|
+
var found = (function find(fiber) {
|
|
1463
|
+
if (!fiber) return null;
|
|
1464
|
+
if (fiber.memoizedProps && fiber.memoizedProps.testID === testID && typeof fiber.memoizedProps.onChangeText === "function") return fiber;
|
|
1465
|
+
return find(fiber.child) || find(fiber.sibling);
|
|
1466
|
+
})(root);
|
|
1467
|
+
if (!found) return {
|
|
1468
|
+
ok: false,
|
|
1469
|
+
error: "TextInput not found: " + testID
|
|
1470
|
+
};
|
|
1471
|
+
found.memoizedProps.onChangeText(text);
|
|
1472
|
+
if (found.stateNode && typeof found.stateNode.setNativeProps === "function") found.stateNode.setNativeProps({ text });
|
|
1473
|
+
return { ok: true };
|
|
1474
|
+
} catch (e) {
|
|
1475
|
+
return {
|
|
1476
|
+
ok: false,
|
|
1477
|
+
error: String(e)
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
var init_mcp_actions = __esmMin(() => {
|
|
1482
|
+
init_fiber_helpers();
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
//#endregion
|
|
1486
|
+
//#region src/runtime/mcp-scroll.ts
|
|
1487
|
+
function registerScrollRef(testID, ref) {
|
|
1488
|
+
if (typeof testID === "string" && ref != null) _scrollRefs[testID] = ref;
|
|
1489
|
+
}
|
|
1490
|
+
function unregisterScrollRef(testID) {
|
|
1491
|
+
if (typeof testID === "string") delete _scrollRefs[testID];
|
|
1492
|
+
}
|
|
1493
|
+
function scrollTo(testID, options) {
|
|
1494
|
+
var ref = _scrollRefs[testID];
|
|
1495
|
+
if (!ref) {
|
|
1496
|
+
var root = getFiberRoot();
|
|
1497
|
+
if (root) ref = (function findScrollable(fiber) {
|
|
1498
|
+
if (!fiber) return null;
|
|
1499
|
+
if (fiber.memoizedProps && fiber.memoizedProps.testID === testID && fiber.stateNode) {
|
|
1500
|
+
var si = fiber.stateNode;
|
|
1501
|
+
if (typeof si.scrollTo === "function" || typeof si.scrollToOffset === "function") return si;
|
|
1502
|
+
}
|
|
1503
|
+
return findScrollable(fiber.child) || findScrollable(fiber.sibling);
|
|
1504
|
+
})(root);
|
|
1505
|
+
}
|
|
1506
|
+
if (!ref) return {
|
|
1507
|
+
ok: false,
|
|
1508
|
+
error: "ScrollView not found for testID: " + testID
|
|
1509
|
+
};
|
|
1510
|
+
var opts = typeof options === "object" && options !== null ? options : {};
|
|
1511
|
+
var x = opts.x || 0;
|
|
1512
|
+
var y = opts.y || 0;
|
|
1513
|
+
var animated = opts.animated !== false;
|
|
1514
|
+
try {
|
|
1515
|
+
if (typeof ref.scrollTo === "function") {
|
|
1516
|
+
ref.scrollTo({
|
|
1517
|
+
x,
|
|
1518
|
+
y,
|
|
1519
|
+
animated
|
|
1520
|
+
});
|
|
1521
|
+
return { ok: true };
|
|
1522
|
+
}
|
|
1523
|
+
if (typeof ref.scrollToOffset === "function") {
|
|
1524
|
+
ref.scrollToOffset({
|
|
1525
|
+
offset: y,
|
|
1526
|
+
animated
|
|
1527
|
+
});
|
|
1528
|
+
return { ok: true };
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
ok: false,
|
|
1532
|
+
error: "scrollTo/scrollToOffset not available on stateNode"
|
|
1533
|
+
};
|
|
1534
|
+
} catch (e) {
|
|
1535
|
+
return {
|
|
1536
|
+
ok: false,
|
|
1537
|
+
error: e && e.message ? e.message : String(e)
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
function getRegisteredScrollTestIDs() {
|
|
1542
|
+
return Object.keys(_scrollRefs);
|
|
1543
|
+
}
|
|
1544
|
+
var _scrollRefs;
|
|
1545
|
+
var init_mcp_scroll = __esmMin(() => {
|
|
1546
|
+
init_fiber_helpers();
|
|
1547
|
+
_scrollRefs = {};
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
//#endregion
|
|
1551
|
+
//#region src/runtime/mcp-console.ts
|
|
1552
|
+
/**
|
|
1553
|
+
* 콘솔 로그 조회. options: { level?, since?, limit? }
|
|
1554
|
+
* level 맵핑: 0=log, 1=info, 2=warn, 3=error
|
|
1555
|
+
*/
|
|
1556
|
+
function getConsoleLogs(options) {
|
|
1557
|
+
var opts = typeof options === "object" && options !== null ? options : {};
|
|
1558
|
+
var levelMap = {
|
|
1559
|
+
log: 0,
|
|
1560
|
+
info: 1,
|
|
1561
|
+
warn: 2,
|
|
1562
|
+
error: 3
|
|
1563
|
+
};
|
|
1564
|
+
var out = consoleLogs;
|
|
1565
|
+
if (opts.level != null) {
|
|
1566
|
+
var targetLevel = typeof opts.level === "string" ? levelMap[opts.level] : opts.level;
|
|
1567
|
+
if (targetLevel != null) out = out.filter(function(entry) {
|
|
1568
|
+
return entry.level === targetLevel;
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
if (typeof opts.since === "number") {
|
|
1572
|
+
var since = opts.since;
|
|
1573
|
+
out = out.filter(function(entry) {
|
|
1574
|
+
return entry.timestamp > since;
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
var limit = typeof opts.limit === "number" && opts.limit > 0 ? opts.limit : 100;
|
|
1578
|
+
if (out.length > limit) out = out.slice(out.length - limit);
|
|
1579
|
+
return out;
|
|
1580
|
+
}
|
|
1581
|
+
/** 콘솔 로그 버퍼 초기화 */
|
|
1582
|
+
function clearConsoleLogs() {
|
|
1583
|
+
resetConsoleLogs();
|
|
1584
|
+
}
|
|
1585
|
+
var init_mcp_console = __esmMin(() => {
|
|
1586
|
+
init_shared();
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
//#endregion
|
|
1590
|
+
//#region src/runtime/mcp-network.ts
|
|
1591
|
+
/**
|
|
1592
|
+
* 네트워크 요청 조회. options: { url?, method?, status?, since?, limit? }
|
|
1593
|
+
* url: substring 매칭, method: 정확 매칭, status: 정확 매칭
|
|
1594
|
+
*/
|
|
1595
|
+
function getNetworkRequests(options) {
|
|
1596
|
+
var opts = typeof options === "object" && options !== null ? options : {};
|
|
1597
|
+
var out = networkRequests;
|
|
1598
|
+
if (typeof opts.url === "string" && opts.url) {
|
|
1599
|
+
var urlFilter = opts.url;
|
|
1600
|
+
out = out.filter(function(entry) {
|
|
1601
|
+
return entry.url.indexOf(urlFilter) !== -1;
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
if (typeof opts.method === "string" && opts.method) {
|
|
1605
|
+
var methodFilter = opts.method.toUpperCase();
|
|
1606
|
+
out = out.filter(function(entry) {
|
|
1607
|
+
return entry.method === methodFilter;
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
if (typeof opts.status === "number") {
|
|
1611
|
+
var statusFilter = opts.status;
|
|
1612
|
+
out = out.filter(function(entry) {
|
|
1613
|
+
return entry.status === statusFilter;
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
if (typeof opts.since === "number") {
|
|
1617
|
+
var since = opts.since;
|
|
1618
|
+
out = out.filter(function(entry) {
|
|
1619
|
+
return entry.startTime > since;
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
var limit = typeof opts.limit === "number" && opts.limit > 0 ? opts.limit : 50;
|
|
1623
|
+
if (out.length > limit) out = out.slice(out.length - limit);
|
|
1624
|
+
return out;
|
|
1625
|
+
}
|
|
1626
|
+
/** 네트워크 요청 버퍼 초기화 */
|
|
1627
|
+
function clearNetworkRequests() {
|
|
1628
|
+
resetNetworkRequests();
|
|
1629
|
+
}
|
|
1630
|
+
var init_mcp_network = __esmMin(() => {
|
|
1631
|
+
init_shared();
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
//#endregion
|
|
1635
|
+
//#region src/runtime/mcp-state.ts
|
|
1636
|
+
/**
|
|
1637
|
+
* inspectState(selector) → 셀렉터로 찾은 컴포넌트의 state Hook 목록.
|
|
1638
|
+
* 반환: { component, hooks: [{ index, type, value }] } 또는 null.
|
|
1639
|
+
* FunctionComponent가 아닌 host fiber가 매칭되면 가장 가까운 조상 FunctionComponent로 이동.
|
|
1640
|
+
*/
|
|
1641
|
+
function inspectState(selector) {
|
|
1642
|
+
if (typeof selector !== "string" || !selector.trim()) return null;
|
|
1643
|
+
try {
|
|
1644
|
+
var root = getFiberRoot();
|
|
1645
|
+
if (!root) return null;
|
|
1646
|
+
var c = getRNComponents();
|
|
1647
|
+
var parsed;
|
|
1648
|
+
try {
|
|
1649
|
+
parsed = parseSelector(selector.trim());
|
|
1650
|
+
} catch (_parseErr) {
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
var foundFiber = null;
|
|
1654
|
+
for (var si = 0; si < parsed.selectors.length && !foundFiber; si++) {
|
|
1655
|
+
var complex = parsed.selectors[si];
|
|
1656
|
+
(function visit(fiber) {
|
|
1657
|
+
if (!fiber || foundFiber) return;
|
|
1658
|
+
if (matchesComplexSelector(fiber, complex, c.Text, c.Image)) {
|
|
1659
|
+
foundFiber = fiber;
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
visit(fiber.child);
|
|
1663
|
+
visit(fiber.sibling);
|
|
1664
|
+
})(root);
|
|
1665
|
+
}
|
|
1666
|
+
if (!foundFiber) return null;
|
|
1667
|
+
var target = foundFiber;
|
|
1668
|
+
if (target.tag !== 0 && target.tag !== 1) {
|
|
1669
|
+
var p = target.return;
|
|
1670
|
+
while (p) {
|
|
1671
|
+
if (p.tag === 0 || p.tag === 1) {
|
|
1672
|
+
target = p;
|
|
1673
|
+
break;
|
|
1674
|
+
}
|
|
1675
|
+
p = p.return;
|
|
1676
|
+
}
|
|
1677
|
+
if (target.tag !== 0 && target.tag !== 1) return null;
|
|
1678
|
+
}
|
|
1679
|
+
var hooks = parseHooks(target);
|
|
1680
|
+
return {
|
|
1681
|
+
component: getFiberTypeName(target),
|
|
1682
|
+
hooks: hooks.map(function(h) {
|
|
1683
|
+
return {
|
|
1684
|
+
index: h.index,
|
|
1685
|
+
type: h.type,
|
|
1686
|
+
value: safeClone(h.value)
|
|
1687
|
+
};
|
|
1688
|
+
})
|
|
1689
|
+
};
|
|
1690
|
+
} catch (e) {
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* getStateChanges(options) → 상태 변경 이력 조회.
|
|
1696
|
+
* options: { component?, since?, limit? }
|
|
1697
|
+
*/
|
|
1698
|
+
function getStateChanges(options) {
|
|
1699
|
+
var opts = typeof options === "object" && options !== null ? options : {};
|
|
1700
|
+
var out = stateChanges;
|
|
1701
|
+
if (typeof opts.component === "string" && opts.component) {
|
|
1702
|
+
var componentFilter = opts.component;
|
|
1703
|
+
out = out.filter(function(entry) {
|
|
1704
|
+
return entry.component === componentFilter;
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
if (typeof opts.since === "number") {
|
|
1708
|
+
var since = opts.since;
|
|
1709
|
+
out = out.filter(function(entry) {
|
|
1710
|
+
return entry.timestamp > since;
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
var limit = typeof opts.limit === "number" && opts.limit > 0 ? opts.limit : 100;
|
|
1714
|
+
if (out.length > limit) out = out.slice(out.length - limit);
|
|
1715
|
+
return out;
|
|
1716
|
+
}
|
|
1717
|
+
/** 상태 변경 버퍼 초기화 */
|
|
1718
|
+
function clearStateChanges() {
|
|
1719
|
+
resetStateChanges();
|
|
1720
|
+
}
|
|
1721
|
+
var init_mcp_state = __esmMin(() => {
|
|
1722
|
+
init_fiber_helpers();
|
|
1723
|
+
init_query_selector();
|
|
1724
|
+
init_state_hooks();
|
|
1725
|
+
init_shared();
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
//#endregion
|
|
1729
|
+
//#region src/runtime/mcp-render.ts
|
|
1730
|
+
/** 프로파일링 시작 */
|
|
1731
|
+
function startRenderProfile(options) {
|
|
1732
|
+
var opts = typeof options === "object" && options !== null ? options : {};
|
|
1733
|
+
resetRenderProfile();
|
|
1734
|
+
setRenderProfileActive(true);
|
|
1735
|
+
setRenderProfileStartTime(Date.now());
|
|
1736
|
+
if (Array.isArray(opts.components) && opts.components.length > 0) setRenderComponentFilter(opts.components);
|
|
1737
|
+
if (Array.isArray(opts.ignore) && opts.ignore.length > 0) setRenderIgnoreFilter(opts.ignore);
|
|
1738
|
+
return { started: true };
|
|
1739
|
+
}
|
|
1740
|
+
/** 수집된 데이터 집계 리포트 반환 */
|
|
1741
|
+
function getRenderReport() {
|
|
1742
|
+
var now = Date.now();
|
|
1743
|
+
var durationStr = ((renderProfileStartTime > 0 ? now - renderProfileStartTime : 0) / 1e3).toFixed(1) + "s";
|
|
1744
|
+
var commitIds = {};
|
|
1745
|
+
for (var i = 0; i < renderEntries.length; i++) commitIds[renderEntries[i].commitId] = true;
|
|
1746
|
+
var totalCommits = Object.keys(commitIds).length;
|
|
1747
|
+
var componentMap = {};
|
|
1748
|
+
for (var j = 0; j < renderEntries.length; j++) {
|
|
1749
|
+
var entry = renderEntries[j];
|
|
1750
|
+
var comp = componentMap[entry.component];
|
|
1751
|
+
if (!comp) {
|
|
1752
|
+
comp = {
|
|
1753
|
+
name: entry.component,
|
|
1754
|
+
renders: 0,
|
|
1755
|
+
mounts: 0,
|
|
1756
|
+
unnecessaryRenders: 0,
|
|
1757
|
+
triggers: {},
|
|
1758
|
+
isMemoized: entry.isMemoized,
|
|
1759
|
+
recentRenders: []
|
|
1760
|
+
};
|
|
1761
|
+
if (entry.nativeType) comp.nativeType = entry.nativeType;
|
|
1762
|
+
componentMap[entry.component] = comp;
|
|
1763
|
+
}
|
|
1764
|
+
comp.renders++;
|
|
1765
|
+
if (entry.type === "mount") comp.mounts++;
|
|
1766
|
+
else {
|
|
1767
|
+
comp.triggers[entry.trigger] = (comp.triggers[entry.trigger] || 0) + 1;
|
|
1768
|
+
if (entry.trigger === "parent") comp.unnecessaryRenders++;
|
|
1769
|
+
}
|
|
1770
|
+
if (entry.isMemoized) comp.isMemoized = true;
|
|
1771
|
+
comp.recentRenders.push(entry);
|
|
1772
|
+
if (comp.recentRenders.length > 5) comp.recentRenders.shift();
|
|
1773
|
+
}
|
|
1774
|
+
var components = [];
|
|
1775
|
+
for (var key in componentMap) if (componentMap.hasOwnProperty(key)) components.push(componentMap[key]);
|
|
1776
|
+
components.sort(function(a, b) {
|
|
1777
|
+
return b.renders - a.renders;
|
|
1778
|
+
});
|
|
1779
|
+
if (components.length > 20) components = components.slice(0, 20);
|
|
1780
|
+
var hotComponents = components.map(function(c) {
|
|
1781
|
+
var result = {
|
|
1782
|
+
name: c.name,
|
|
1783
|
+
renders: c.renders,
|
|
1784
|
+
mounts: c.mounts,
|
|
1785
|
+
unnecessaryRenders: c.unnecessaryRenders,
|
|
1786
|
+
triggers: c.triggers,
|
|
1787
|
+
isMemoized: c.isMemoized
|
|
1788
|
+
};
|
|
1789
|
+
if (c.nativeType) result.nativeType = c.nativeType;
|
|
1790
|
+
result.recentRenders = c.recentRenders.map(function(r) {
|
|
1791
|
+
var recent = {
|
|
1792
|
+
timestamp: r.timestamp,
|
|
1793
|
+
trigger: r.trigger,
|
|
1794
|
+
commitId: r.commitId,
|
|
1795
|
+
parent: r.parent
|
|
1796
|
+
};
|
|
1797
|
+
if (r.changes) recent.changes = r.changes;
|
|
1798
|
+
return recent;
|
|
1799
|
+
});
|
|
1800
|
+
return result;
|
|
1801
|
+
});
|
|
1802
|
+
return {
|
|
1803
|
+
profiling: renderProfileActive,
|
|
1804
|
+
startTime: renderProfileStartTime,
|
|
1805
|
+
endTime: now,
|
|
1806
|
+
duration: durationStr,
|
|
1807
|
+
totalCommits,
|
|
1808
|
+
totalRenders: renderEntries.length,
|
|
1809
|
+
hotComponents
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
/** 프로파일링 중지 + 데이터 초기화 */
|
|
1813
|
+
function clearRenderProfile() {
|
|
1814
|
+
resetRenderProfile();
|
|
1815
|
+
return { cleared: true };
|
|
1816
|
+
}
|
|
1817
|
+
var init_mcp_render = __esmMin(() => {
|
|
1818
|
+
init_shared();
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1821
|
+
//#endregion
|
|
1822
|
+
//#region src/runtime/mcp-query.ts
|
|
1823
|
+
/**
|
|
1824
|
+
* querySelectorAll(selector) → 매칭되는 모든 fiber 정보 배열.
|
|
1825
|
+
*/
|
|
1826
|
+
function querySelectorAll(selector) {
|
|
1827
|
+
if (typeof selector !== "string" || !selector.trim()) return [];
|
|
1828
|
+
try {
|
|
1829
|
+
var root = getFiberRoot();
|
|
1830
|
+
if (!root) return [];
|
|
1831
|
+
var c = getRNComponents();
|
|
1832
|
+
var parsed;
|
|
1833
|
+
try {
|
|
1834
|
+
parsed = parseSelector(selector.trim());
|
|
1835
|
+
} catch (parseErr) {
|
|
1836
|
+
return [];
|
|
1837
|
+
}
|
|
1838
|
+
var results = [];
|
|
1839
|
+
for (var si = 0; si < parsed.selectors.length; si++) {
|
|
1840
|
+
var complex = parsed.selectors[si];
|
|
1841
|
+
var lastSeg = complex.segments[complex.segments.length - 1];
|
|
1842
|
+
var nth = lastSeg.selector.nth;
|
|
1843
|
+
var matchCount = 0;
|
|
1844
|
+
(function visit(fiber) {
|
|
1845
|
+
if (!fiber) return;
|
|
1846
|
+
if (matchesComplexSelector(fiber, complex, c.Text, c.Image)) {
|
|
1847
|
+
if (nth === -1 || nth === -2) results.push(fiberToResult(fiber, c.Text, c.Image));
|
|
1848
|
+
else if (matchCount === nth) results.push(fiberToResult(fiber, c.Text, c.Image));
|
|
1849
|
+
matchCount++;
|
|
1850
|
+
}
|
|
1851
|
+
visit(fiber.child);
|
|
1852
|
+
visit(fiber.sibling);
|
|
1853
|
+
})(root);
|
|
1854
|
+
}
|
|
1855
|
+
if (lastSeg.selector.nth === -2 && results.length > 1) results = [results[results.length - 1]];
|
|
1856
|
+
var deduped = [];
|
|
1857
|
+
var seen = {};
|
|
1858
|
+
for (var di = 0; di < results.length; di++) {
|
|
1859
|
+
var r = results[di];
|
|
1860
|
+
var key = r.uid || "";
|
|
1861
|
+
if (!key || seen[key] === void 0) {
|
|
1862
|
+
seen[key] = deduped.length;
|
|
1863
|
+
deduped.push(r);
|
|
1864
|
+
} else {
|
|
1865
|
+
var idx = seen[key];
|
|
1866
|
+
if (idx !== void 0) {
|
|
1867
|
+
var prev = deduped[idx];
|
|
1868
|
+
if (r.hasScrollTo && !prev.hasScrollTo) deduped[idx] = r;
|
|
1869
|
+
else if (r.hasOnPress && !prev.hasOnPress) deduped[idx] = r;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return deduped;
|
|
1874
|
+
} catch (e) {
|
|
1875
|
+
return [];
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* querySelector(selector) → 첫 번째 매칭 fiber 정보 또는 null.
|
|
1880
|
+
*/
|
|
1881
|
+
function querySelector(selector) {
|
|
1882
|
+
if (typeof selector !== "string" || !selector.trim()) return null;
|
|
1883
|
+
try {
|
|
1884
|
+
var all = querySelectorAll(selector);
|
|
1885
|
+
return all.length > 0 ? all[0] : null;
|
|
1886
|
+
} catch (e) {
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* querySelectorWithMeasure(selector) → Promise<결과 | null>
|
|
1892
|
+
* Fabric: fiberToResult의 measureViewSync로 이미 measure 포함 → 즉시 반환.
|
|
1893
|
+
* Bridge: measure가 null인 경우 async measureView fallback.
|
|
1894
|
+
*/
|
|
1895
|
+
function querySelectorWithMeasure(selector) {
|
|
1896
|
+
var el = querySelector(selector);
|
|
1897
|
+
if (!el) return Promise.resolve(null);
|
|
1898
|
+
if (el.measure) return Promise.resolve(el);
|
|
1899
|
+
return measureView(el._measureUid || el.uid).then(function(m) {
|
|
1900
|
+
el.measure = m;
|
|
1901
|
+
return el;
|
|
1902
|
+
}).catch(function() {
|
|
1903
|
+
return el;
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* querySelectorAllWithMeasure(selector) → Promise<배열>
|
|
1908
|
+
* measure가 null인 요소만 비동기 보충.
|
|
1909
|
+
*/
|
|
1910
|
+
function querySelectorAllWithMeasure(selector) {
|
|
1911
|
+
var list = querySelectorAll(selector);
|
|
1912
|
+
if (!list.length) return Promise.resolve(list);
|
|
1913
|
+
var needsMeasure = [];
|
|
1914
|
+
for (var i = 0; i < list.length; i++) if (!list[i].measure) needsMeasure.push(i);
|
|
1915
|
+
if (!needsMeasure.length) return Promise.resolve(list);
|
|
1916
|
+
var chain = Promise.resolve();
|
|
1917
|
+
needsMeasure.forEach(function(idx) {
|
|
1918
|
+
chain = chain.then(function() {
|
|
1919
|
+
return measureView(list[idx]._measureUid || list[idx].uid).then(function(m) {
|
|
1920
|
+
list[idx].measure = m;
|
|
1921
|
+
}).catch(function() {});
|
|
1922
|
+
});
|
|
1923
|
+
});
|
|
1924
|
+
return chain.then(function() {
|
|
1925
|
+
return list;
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
var init_mcp_query = __esmMin(() => {
|
|
1929
|
+
init_fiber_helpers();
|
|
1930
|
+
init_query_selector();
|
|
1931
|
+
init_fiber_serialization();
|
|
1932
|
+
init_mcp_measure();
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
//#endregion
|
|
1936
|
+
//#region src/runtime/mcp-accessibility.ts
|
|
1937
|
+
/**
|
|
1938
|
+
* 접근성(a11y) 자동 감사. Fiber 트리 순회 후 규칙 위반 목록 반환.
|
|
1939
|
+
* 반환: [{ rule, selector, severity, message }]
|
|
1940
|
+
*/
|
|
1941
|
+
function getAccessibilityAudit(options) {
|
|
1942
|
+
try {
|
|
1943
|
+
var root = getFiberRoot();
|
|
1944
|
+
if (!root) return [];
|
|
1945
|
+
var c = getRNComponents();
|
|
1946
|
+
var TextComp = c && c.Text;
|
|
1947
|
+
var ImageComp = c && c.Image;
|
|
1948
|
+
var maxDepth = options && typeof options.maxDepth === "number" ? options.maxDepth : 999;
|
|
1949
|
+
var minTouchTarget = 44;
|
|
1950
|
+
var violations = [];
|
|
1951
|
+
function selectorFor(_fiber, typeName, testID, pathUid) {
|
|
1952
|
+
if (testID) return "#" + testID;
|
|
1953
|
+
return typeName + (pathUid ? "@" + pathUid : "");
|
|
1954
|
+
}
|
|
1955
|
+
function visit(fiber, path, depth) {
|
|
1956
|
+
if (!fiber || depth > maxDepth) return;
|
|
1957
|
+
var props = fiber.memoizedProps || {};
|
|
1958
|
+
var typeName = getFiberTypeName(fiber);
|
|
1959
|
+
var testID = typeof props.testID === "string" && props.testID.trim() ? props.testID.trim() : void 0;
|
|
1960
|
+
var pathUid = getPathUid(fiber);
|
|
1961
|
+
var hasOnPress = typeof props.onPress === "function";
|
|
1962
|
+
var hasOnLongPress = typeof props.onLongPress === "function";
|
|
1963
|
+
var accessibilityLabel = typeof props.accessibilityLabel === "string" && props.accessibilityLabel.trim() ? props.accessibilityLabel.trim() : "";
|
|
1964
|
+
var accessibilityRole = typeof props.accessibilityRole === "string" && props.accessibilityRole.trim() ? props.accessibilityRole.trim() : "";
|
|
1965
|
+
if (hasOnPress || hasOnLongPress) {
|
|
1966
|
+
if (!(accessibilityLabel || collectText(fiber, TextComp).replace(/\s+/g, " ").trim() || collectAccessibilityLabel(fiber, ImageComp))) violations.push({
|
|
1967
|
+
rule: "pressable-needs-label",
|
|
1968
|
+
selector: selectorFor(fiber, typeName, testID, pathUid),
|
|
1969
|
+
severity: "error",
|
|
1970
|
+
message: typeName + "에 onPress/onLongPress가 있으나 accessibilityLabel 또는 접근 가능한 텍스트가 없습니다."
|
|
1971
|
+
});
|
|
1972
|
+
if (!accessibilityRole) violations.push({
|
|
1973
|
+
rule: "missing-role",
|
|
1974
|
+
selector: selectorFor(fiber, typeName, testID, pathUid),
|
|
1975
|
+
severity: "warning",
|
|
1976
|
+
message: "인터랙티브 요소에 accessibilityRole이 없습니다."
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
if (fiber.type === ImageComp) {
|
|
1980
|
+
if (!accessibilityLabel) violations.push({
|
|
1981
|
+
rule: "image-needs-alt",
|
|
1982
|
+
selector: selectorFor(fiber, typeName, testID, pathUid),
|
|
1983
|
+
severity: "error",
|
|
1984
|
+
message: "Image에 accessibilityLabel(또는 alt)이 없습니다."
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
var child = fiber.child;
|
|
1988
|
+
var idx = 0;
|
|
1989
|
+
while (child) {
|
|
1990
|
+
visit(child, path + "." + idx, depth + 1);
|
|
1991
|
+
child = child.sibling;
|
|
1992
|
+
idx += 1;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
visit(root, "0", 0);
|
|
1996
|
+
var touchables = [];
|
|
1997
|
+
(function collectTouchables(fiber, path, depth) {
|
|
1998
|
+
if (!fiber || depth > maxDepth) return;
|
|
1999
|
+
var props = fiber.memoizedProps || {};
|
|
2000
|
+
var hasOnPress = typeof props.onPress === "function";
|
|
2001
|
+
var hasOnLongPress = typeof props.onLongPress === "function";
|
|
2002
|
+
if (hasOnPress || hasOnLongPress) {
|
|
2003
|
+
var testID = typeof props.testID === "string" && props.testID.trim() ? props.testID.trim() : void 0;
|
|
2004
|
+
touchables.push({
|
|
2005
|
+
fiber,
|
|
2006
|
+
typeName: getFiberTypeName(fiber),
|
|
2007
|
+
testID,
|
|
2008
|
+
pathUid: getPathUid(fiber)
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
var child = fiber.child;
|
|
2012
|
+
var idx = 0;
|
|
2013
|
+
while (child) {
|
|
2014
|
+
collectTouchables(child, path + "." + idx, depth + 1);
|
|
2015
|
+
child = child.sibling;
|
|
2016
|
+
idx += 1;
|
|
2017
|
+
}
|
|
2018
|
+
})(root, "0", 0);
|
|
2019
|
+
for (var i = 0; i < touchables.length; i++) {
|
|
2020
|
+
var t = touchables[i];
|
|
2021
|
+
if (t === void 0) continue;
|
|
2022
|
+
var item = t;
|
|
2023
|
+
var uid = item.testID || item.pathUid;
|
|
2024
|
+
var measure = null;
|
|
2025
|
+
try {
|
|
2026
|
+
measure = globalThis.__REACT_NATIVE_MCP__.measureViewSync(uid);
|
|
2027
|
+
} catch (e) {}
|
|
2028
|
+
if (!measure && typeof item.fiber.type !== "string") {
|
|
2029
|
+
var hostChild = (function findHost(f) {
|
|
2030
|
+
if (!f) return null;
|
|
2031
|
+
if (typeof f.type === "string" && f.stateNode) return f;
|
|
2032
|
+
var ch = f.child;
|
|
2033
|
+
while (ch) {
|
|
2034
|
+
var h = findHost(ch);
|
|
2035
|
+
if (h) return h;
|
|
2036
|
+
ch = ch.sibling;
|
|
2037
|
+
}
|
|
2038
|
+
return null;
|
|
2039
|
+
})(item.fiber.child);
|
|
2040
|
+
if (hostChild) {
|
|
2041
|
+
var hostUid = hostChild.memoizedProps && hostChild.memoizedProps.testID || getPathUid(hostChild);
|
|
2042
|
+
try {
|
|
2043
|
+
measure = measureViewSync(hostUid);
|
|
2044
|
+
} catch (e) {}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (measure && (measure.width < minTouchTarget || measure.height < minTouchTarget)) violations.push({
|
|
2048
|
+
rule: "touch-target-size",
|
|
2049
|
+
selector: item.testID ? "#" + item.testID : item.typeName + "@" + item.pathUid,
|
|
2050
|
+
severity: "warning",
|
|
2051
|
+
message: "터치 영역이 " + minTouchTarget + "x" + minTouchTarget + "pt 미만입니다 (" + Math.round(measure.width) + "x" + Math.round(measure.height) + "pt)"
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
return violations;
|
|
2055
|
+
} catch (e) {
|
|
2056
|
+
return [];
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
var init_mcp_accessibility = __esmMin(() => {
|
|
2060
|
+
init_fiber_helpers();
|
|
2061
|
+
init_mcp_measure();
|
|
2062
|
+
init_query_selector();
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
//#endregion
|
|
2066
|
+
//#region src/runtime/network-mock.ts
|
|
2067
|
+
function matchesMockRule(rule, method, url) {
|
|
2068
|
+
if (!rule.enabled) return false;
|
|
2069
|
+
if (rule.method && rule.method !== method) return false;
|
|
2070
|
+
if (rule.isRegex) try {
|
|
2071
|
+
return new RegExp(rule.urlPattern).test(url);
|
|
2072
|
+
} catch (_e) {
|
|
2073
|
+
return false;
|
|
2074
|
+
}
|
|
2075
|
+
return url.indexOf(rule.urlPattern) !== -1;
|
|
2076
|
+
}
|
|
2077
|
+
function findMatchingMock(method, url) {
|
|
2078
|
+
for (var i = 0; i < networkMockRules.length; i++) if (matchesMockRule(networkMockRules[i], method, url)) {
|
|
2079
|
+
networkMockRules[i].hitCount++;
|
|
2080
|
+
return networkMockRules[i];
|
|
2081
|
+
}
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
function addNetworkMock(opts) {
|
|
2085
|
+
var rule = {
|
|
2086
|
+
id: ++_nextMockId,
|
|
2087
|
+
urlPattern: opts.urlPattern,
|
|
2088
|
+
isRegex: !!opts.isRegex,
|
|
2089
|
+
method: opts.method ? opts.method.toUpperCase() : null,
|
|
2090
|
+
response: {
|
|
2091
|
+
status: opts.status != null ? opts.status : 200,
|
|
2092
|
+
statusText: opts.statusText || null,
|
|
2093
|
+
headers: opts.headers || {},
|
|
2094
|
+
body: opts.body != null ? String(opts.body) : "",
|
|
2095
|
+
delay: opts.delay != null ? opts.delay : 0
|
|
2096
|
+
},
|
|
2097
|
+
enabled: true,
|
|
2098
|
+
hitCount: 0
|
|
2099
|
+
};
|
|
2100
|
+
networkMockRules.push(rule);
|
|
2101
|
+
return rule;
|
|
2102
|
+
}
|
|
2103
|
+
function removeNetworkMock(id) {
|
|
2104
|
+
for (var i = 0; i < networkMockRules.length; i++) if (networkMockRules[i].id === id) {
|
|
2105
|
+
networkMockRules.splice(i, 1);
|
|
2106
|
+
return true;
|
|
2107
|
+
}
|
|
2108
|
+
return false;
|
|
2109
|
+
}
|
|
2110
|
+
function listNetworkMocks() {
|
|
2111
|
+
return networkMockRules.map(function(r) {
|
|
2112
|
+
return {
|
|
2113
|
+
id: r.id,
|
|
2114
|
+
urlPattern: r.urlPattern,
|
|
2115
|
+
isRegex: r.isRegex,
|
|
2116
|
+
method: r.method,
|
|
2117
|
+
status: r.response.status,
|
|
2118
|
+
enabled: r.enabled,
|
|
2119
|
+
hitCount: r.hitCount
|
|
2120
|
+
};
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
function clearNetworkMocks() {
|
|
2124
|
+
networkMockRules.length = 0;
|
|
2125
|
+
return true;
|
|
2126
|
+
}
|
|
2127
|
+
var _nextMockId;
|
|
2128
|
+
var init_network_mock = __esmMin(() => {
|
|
2129
|
+
init_shared();
|
|
2130
|
+
_nextMockId = 0;
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2133
|
+
//#endregion
|
|
2134
|
+
//#region src/runtime/mcp-object.ts
|
|
2135
|
+
var MCP;
|
|
2136
|
+
var init_mcp_object = __esmMin(() => {
|
|
2137
|
+
init_mcp_registration();
|
|
2138
|
+
init_mcp_introspection();
|
|
2139
|
+
init_mcp_actions();
|
|
2140
|
+
init_mcp_webview();
|
|
2141
|
+
init_mcp_scroll();
|
|
2142
|
+
init_mcp_console();
|
|
2143
|
+
init_mcp_network();
|
|
2144
|
+
init_network_mock();
|
|
2145
|
+
init_mcp_state();
|
|
2146
|
+
init_mcp_render();
|
|
2147
|
+
init_mcp_query();
|
|
2148
|
+
init_mcp_measure();
|
|
2149
|
+
init_mcp_accessibility();
|
|
2150
|
+
MCP = {
|
|
2151
|
+
registerComponent,
|
|
2152
|
+
registerPressHandler,
|
|
2153
|
+
triggerPress,
|
|
2154
|
+
triggerLongPress,
|
|
2155
|
+
getRegisteredPressTestIDs,
|
|
2156
|
+
getClickables,
|
|
2157
|
+
getTextNodes,
|
|
2158
|
+
getComponentTree,
|
|
2159
|
+
pressByLabel,
|
|
2160
|
+
longPressByLabel,
|
|
2161
|
+
typeText,
|
|
2162
|
+
registerWebView,
|
|
2163
|
+
unregisterWebView,
|
|
2164
|
+
getWebViewIdForRef,
|
|
2165
|
+
clickInWebView,
|
|
2166
|
+
evaluateInWebView,
|
|
2167
|
+
evaluateInWebViewAsync,
|
|
2168
|
+
handleWebViewMessage,
|
|
2169
|
+
createWebViewOnMessage,
|
|
2170
|
+
getRegisteredWebViewIds,
|
|
2171
|
+
registerScrollRef,
|
|
2172
|
+
unregisterScrollRef,
|
|
2173
|
+
scrollTo,
|
|
2174
|
+
getRegisteredScrollTestIDs,
|
|
2175
|
+
getConsoleLogs,
|
|
2176
|
+
clearConsoleLogs,
|
|
2177
|
+
getNetworkRequests,
|
|
2178
|
+
clearNetworkRequests,
|
|
2179
|
+
addNetworkMock,
|
|
2180
|
+
removeNetworkMock,
|
|
2181
|
+
listNetworkMocks,
|
|
2182
|
+
clearNetworkMocks,
|
|
2183
|
+
inspectState,
|
|
2184
|
+
getStateChanges,
|
|
2185
|
+
clearStateChanges,
|
|
2186
|
+
querySelector,
|
|
2187
|
+
querySelectorAll,
|
|
2188
|
+
querySelectorWithMeasure,
|
|
2189
|
+
querySelectorAllWithMeasure,
|
|
2190
|
+
getScreenInfo,
|
|
2191
|
+
measureView,
|
|
2192
|
+
measureViewSync,
|
|
2193
|
+
getAccessibilityAudit,
|
|
2194
|
+
startRenderProfile,
|
|
2195
|
+
getRenderReport,
|
|
2196
|
+
clearRenderProfile
|
|
2197
|
+
};
|
|
2198
|
+
if (typeof global !== "undefined") global.__REACT_NATIVE_MCP__ = MCP;
|
|
2199
|
+
if (typeof globalThis !== "undefined") globalThis.__REACT_NATIVE_MCP__ = MCP;
|
|
2200
|
+
});
|
|
2201
|
+
|
|
2202
|
+
//#endregion
|
|
2203
|
+
//#region src/runtime/console-hook.ts
|
|
2204
|
+
var _origNativeLoggingHook;
|
|
2205
|
+
var init_console_hook = __esmMin(() => {
|
|
2206
|
+
init_shared();
|
|
2207
|
+
_origNativeLoggingHook = typeof global !== "undefined" ? global.nativeLoggingHook : void 0;
|
|
2208
|
+
if (typeof global !== "undefined") global.nativeLoggingHook = function(msg, level) {
|
|
2209
|
+
pushConsoleLog({
|
|
2210
|
+
id: nextConsoleLogId(),
|
|
2211
|
+
message: msg,
|
|
2212
|
+
level,
|
|
2213
|
+
timestamp: Date.now()
|
|
2214
|
+
});
|
|
2215
|
+
if (typeof _origNativeLoggingHook === "function") _origNativeLoggingHook(msg, level);
|
|
2216
|
+
};
|
|
2217
|
+
});
|
|
2218
|
+
|
|
2219
|
+
//#endregion
|
|
2220
|
+
//#region src/runtime/network-helpers.ts
|
|
2221
|
+
function pushNetworkEntry(entry) {
|
|
2222
|
+
entry.id = nextNetworkRequestId();
|
|
2223
|
+
networkRequests.push(entry);
|
|
2224
|
+
if (networkRequests.length > NETWORK_BUFFER_SIZE) networkRequests.shift();
|
|
2225
|
+
}
|
|
2226
|
+
function truncateBody(body) {
|
|
2227
|
+
if (body == null) return null;
|
|
2228
|
+
var s = typeof body === "string" ? body : String(body);
|
|
2229
|
+
return s.length > NETWORK_BODY_LIMIT ? s.substring(0, NETWORK_BODY_LIMIT) : s;
|
|
2230
|
+
}
|
|
2231
|
+
var init_network_helpers = __esmMin(() => {
|
|
2232
|
+
init_shared();
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2235
|
+
//#endregion
|
|
2236
|
+
//#region src/runtime/xhr-patch.ts
|
|
2237
|
+
var init_xhr_patch = __esmMin(() => {
|
|
2238
|
+
init_network_helpers();
|
|
2239
|
+
init_network_mock();
|
|
2240
|
+
(function() {
|
|
2241
|
+
if (typeof XMLHttpRequest === "undefined") return;
|
|
2242
|
+
var XHR = XMLHttpRequest.prototype;
|
|
2243
|
+
var _origOpen = XHR.open;
|
|
2244
|
+
var _origSend = XHR.send;
|
|
2245
|
+
var _origSetRequestHeader = XHR.setRequestHeader;
|
|
2246
|
+
XHR.open = function(method, url) {
|
|
2247
|
+
this.__mcpNetworkEntry = {
|
|
2248
|
+
id: 0,
|
|
2249
|
+
method: (method || "GET").toUpperCase(),
|
|
2250
|
+
url: String(url || ""),
|
|
2251
|
+
requestHeaders: {},
|
|
2252
|
+
requestBody: null,
|
|
2253
|
+
status: null,
|
|
2254
|
+
statusText: null,
|
|
2255
|
+
responseHeaders: null,
|
|
2256
|
+
responseBody: null,
|
|
2257
|
+
startTime: Date.now(),
|
|
2258
|
+
duration: null,
|
|
2259
|
+
error: null,
|
|
2260
|
+
state: "pending"
|
|
2261
|
+
};
|
|
2262
|
+
return _origOpen.apply(this, arguments);
|
|
2263
|
+
};
|
|
2264
|
+
XHR.setRequestHeader = function(name, value) {
|
|
2265
|
+
if (this.__mcpNetworkEntry) this.__mcpNetworkEntry.requestHeaders[name] = value;
|
|
2266
|
+
return _origSetRequestHeader.apply(this, arguments);
|
|
2267
|
+
};
|
|
2268
|
+
XHR.send = function(body) {
|
|
2269
|
+
var entry = this.__mcpNetworkEntry;
|
|
2270
|
+
if (entry) {
|
|
2271
|
+
entry.requestBody = truncateBody(body);
|
|
2272
|
+
var mockRule = findMatchingMock(entry.method, entry.url);
|
|
2273
|
+
if (mockRule) {
|
|
2274
|
+
var xhr = this;
|
|
2275
|
+
var mockResp = mockRule.response;
|
|
2276
|
+
var deliverMock = function() {
|
|
2277
|
+
entry.status = mockResp.status;
|
|
2278
|
+
entry.statusText = mockResp.statusText || null;
|
|
2279
|
+
entry.responseHeaders = JSON.stringify(mockResp.headers);
|
|
2280
|
+
entry.responseBody = truncateBody(mockResp.body);
|
|
2281
|
+
entry.duration = Date.now() - entry.startTime;
|
|
2282
|
+
entry.state = "done";
|
|
2283
|
+
entry.mocked = true;
|
|
2284
|
+
pushNetworkEntry(entry);
|
|
2285
|
+
var fakeId = -1 - Date.now();
|
|
2286
|
+
try {
|
|
2287
|
+
xhr.__didCreateRequest(fakeId);
|
|
2288
|
+
xhr.__didReceiveResponse(fakeId, mockResp.status, mockResp.headers || {}, entry.url);
|
|
2289
|
+
if (mockResp.body) xhr.__didReceiveData(fakeId, mockResp.body);
|
|
2290
|
+
xhr.__didCompleteResponse(fakeId, "", false);
|
|
2291
|
+
} catch (_e) {}
|
|
2292
|
+
};
|
|
2293
|
+
setTimeout(deliverMock, mockResp.delay > 0 ? mockResp.delay : 0);
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
var xhr = this;
|
|
2297
|
+
xhr.addEventListener("load", function() {
|
|
2298
|
+
entry.status = xhr.status;
|
|
2299
|
+
entry.statusText = xhr.statusText || null;
|
|
2300
|
+
try {
|
|
2301
|
+
entry.responseHeaders = xhr.getAllResponseHeaders() || null;
|
|
2302
|
+
} catch (_e) {
|
|
2303
|
+
entry.responseHeaders = null;
|
|
2304
|
+
}
|
|
2305
|
+
try {
|
|
2306
|
+
entry.responseBody = truncateBody(xhr.responseText);
|
|
2307
|
+
} catch (_e) {
|
|
2308
|
+
entry.responseBody = null;
|
|
2309
|
+
}
|
|
2310
|
+
entry.duration = Date.now() - entry.startTime;
|
|
2311
|
+
entry.state = "done";
|
|
2312
|
+
pushNetworkEntry(entry);
|
|
2313
|
+
});
|
|
2314
|
+
xhr.addEventListener("error", function() {
|
|
2315
|
+
entry.duration = Date.now() - entry.startTime;
|
|
2316
|
+
entry.error = "Network error";
|
|
2317
|
+
entry.state = "error";
|
|
2318
|
+
pushNetworkEntry(entry);
|
|
2319
|
+
});
|
|
2320
|
+
xhr.addEventListener("timeout", function() {
|
|
2321
|
+
entry.duration = Date.now() - entry.startTime;
|
|
2322
|
+
entry.error = "Timeout";
|
|
2323
|
+
entry.state = "error";
|
|
2324
|
+
pushNetworkEntry(entry);
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
return _origSend.apply(this, arguments);
|
|
2328
|
+
};
|
|
2329
|
+
})();
|
|
2330
|
+
});
|
|
2331
|
+
|
|
2332
|
+
//#endregion
|
|
2333
|
+
//#region src/runtime/fetch-patch.ts
|
|
2334
|
+
var init_fetch_patch = __esmMin(() => {
|
|
2335
|
+
init_shared();
|
|
2336
|
+
init_network_helpers();
|
|
2337
|
+
init_network_mock();
|
|
2338
|
+
(function() {
|
|
2339
|
+
var g = typeof globalThis !== "undefined" ? globalThis : typeof global !== "undefined" ? global : null;
|
|
2340
|
+
if (!g || typeof g.fetch !== "function") return;
|
|
2341
|
+
var _origFetch = g.fetch;
|
|
2342
|
+
g.fetch = function(input, init) {
|
|
2343
|
+
var url = "";
|
|
2344
|
+
var method = "GET";
|
|
2345
|
+
var requestHeaders = {};
|
|
2346
|
+
var requestBody = null;
|
|
2347
|
+
if (typeof input === "string") url = input;
|
|
2348
|
+
else if (input && typeof input === "object" && typeof input.url === "string") {
|
|
2349
|
+
url = input.url;
|
|
2350
|
+
if (input.method) method = input.method.toUpperCase();
|
|
2351
|
+
if (input.headers) try {
|
|
2352
|
+
if (typeof input.headers.forEach === "function") input.headers.forEach(function(v, k) {
|
|
2353
|
+
requestHeaders[k] = v;
|
|
2354
|
+
});
|
|
2355
|
+
else if (typeof input.headers === "object") {
|
|
2356
|
+
var hk = Object.keys(input.headers);
|
|
2357
|
+
for (var i = 0; i < hk.length; i++) {
|
|
2358
|
+
var key = hk[i];
|
|
2359
|
+
if (key === void 0) continue;
|
|
2360
|
+
requestHeaders[key] = input.headers[key];
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
} catch (_e) {}
|
|
2364
|
+
if (input.body != null) requestBody = input.body;
|
|
2365
|
+
}
|
|
2366
|
+
if (init && typeof init === "object") {
|
|
2367
|
+
if (init.method) method = init.method.toUpperCase();
|
|
2368
|
+
if (init.headers) try {
|
|
2369
|
+
if (typeof init.headers.forEach === "function") init.headers.forEach(function(v, k) {
|
|
2370
|
+
requestHeaders[k] = v;
|
|
2371
|
+
});
|
|
2372
|
+
else if (typeof init.headers === "object") {
|
|
2373
|
+
var hk2 = Object.keys(init.headers);
|
|
2374
|
+
for (var j = 0; j < hk2.length; j++) {
|
|
2375
|
+
var key = hk2[j];
|
|
2376
|
+
if (key === void 0) continue;
|
|
2377
|
+
requestHeaders[key] = init.headers[key];
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
} catch (_e) {}
|
|
2381
|
+
if (init.body != null) requestBody = init.body;
|
|
2382
|
+
}
|
|
2383
|
+
var bodyStr = null;
|
|
2384
|
+
if (requestBody != null) {
|
|
2385
|
+
bodyStr = typeof requestBody === "string" ? requestBody : typeof requestBody.toString === "function" ? requestBody.toString() : String(requestBody);
|
|
2386
|
+
if (bodyStr != null && bodyStr.length > NETWORK_BODY_LIMIT) bodyStr = bodyStr.substring(0, NETWORK_BODY_LIMIT);
|
|
2387
|
+
}
|
|
2388
|
+
var entry = {
|
|
2389
|
+
id: 0,
|
|
2390
|
+
method,
|
|
2391
|
+
url,
|
|
2392
|
+
requestHeaders,
|
|
2393
|
+
requestBody: bodyStr,
|
|
2394
|
+
status: null,
|
|
2395
|
+
statusText: null,
|
|
2396
|
+
responseHeaders: null,
|
|
2397
|
+
responseBody: null,
|
|
2398
|
+
startTime: Date.now(),
|
|
2399
|
+
duration: null,
|
|
2400
|
+
error: null,
|
|
2401
|
+
state: "pending"
|
|
2402
|
+
};
|
|
2403
|
+
var mockRule = findMatchingMock(method, url);
|
|
2404
|
+
if (mockRule) {
|
|
2405
|
+
var mockResp = mockRule.response;
|
|
2406
|
+
var deliverMock = function() {
|
|
2407
|
+
entry.status = mockResp.status;
|
|
2408
|
+
entry.statusText = mockResp.statusText || null;
|
|
2409
|
+
entry.responseHeaders = JSON.stringify(mockResp.headers);
|
|
2410
|
+
entry.responseBody = truncateBody(mockResp.body);
|
|
2411
|
+
entry.duration = Date.now() - entry.startTime;
|
|
2412
|
+
entry.state = "done";
|
|
2413
|
+
entry.mocked = true;
|
|
2414
|
+
pushNetworkEntry(entry);
|
|
2415
|
+
var fakeResponse;
|
|
2416
|
+
try {
|
|
2417
|
+
fakeResponse = new Response(mockResp.body, {
|
|
2418
|
+
status: mockResp.status,
|
|
2419
|
+
statusText: mockResp.statusText || "",
|
|
2420
|
+
headers: mockResp.headers
|
|
2421
|
+
});
|
|
2422
|
+
} catch (_e) {
|
|
2423
|
+
var _body = mockResp.body;
|
|
2424
|
+
fakeResponse = {
|
|
2425
|
+
ok: mockResp.status >= 200 && mockResp.status < 300,
|
|
2426
|
+
status: mockResp.status,
|
|
2427
|
+
statusText: mockResp.statusText || "",
|
|
2428
|
+
headers: {
|
|
2429
|
+
get: function(k) {
|
|
2430
|
+
return mockResp.headers[k] || null;
|
|
2431
|
+
},
|
|
2432
|
+
forEach: function(cb) {
|
|
2433
|
+
for (var hk in mockResp.headers) cb(mockResp.headers[hk], hk);
|
|
2434
|
+
}
|
|
2435
|
+
},
|
|
2436
|
+
text: function() {
|
|
2437
|
+
return Promise.resolve(_body);
|
|
2438
|
+
},
|
|
2439
|
+
json: function() {
|
|
2440
|
+
try {
|
|
2441
|
+
return Promise.resolve(JSON.parse(_body));
|
|
2442
|
+
} catch (e) {
|
|
2443
|
+
return Promise.reject(/* @__PURE__ */ new SyntaxError("Invalid JSON: " + e.message));
|
|
2444
|
+
}
|
|
2445
|
+
},
|
|
2446
|
+
clone: function() {
|
|
2447
|
+
return fakeResponse;
|
|
2448
|
+
},
|
|
2449
|
+
url,
|
|
2450
|
+
type: "basic",
|
|
2451
|
+
redirected: false,
|
|
2452
|
+
bodyUsed: false
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
return fakeResponse;
|
|
2456
|
+
};
|
|
2457
|
+
if (mockResp.delay > 0) return new Promise(function(resolve) {
|
|
2458
|
+
setTimeout(function() {
|
|
2459
|
+
resolve(deliverMock());
|
|
2460
|
+
}, mockResp.delay);
|
|
2461
|
+
});
|
|
2462
|
+
return new Promise(function(resolve) {
|
|
2463
|
+
setTimeout(function() {
|
|
2464
|
+
resolve(deliverMock());
|
|
2465
|
+
}, 0);
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
return _origFetch.apply(this, arguments).then(function(response) {
|
|
2469
|
+
entry.status = response.status;
|
|
2470
|
+
entry.statusText = response.statusText || null;
|
|
2471
|
+
try {
|
|
2472
|
+
var headerObj = {};
|
|
2473
|
+
if (response.headers && typeof response.headers.forEach === "function") response.headers.forEach(function(v, k) {
|
|
2474
|
+
headerObj[k] = v;
|
|
2475
|
+
});
|
|
2476
|
+
entry.responseHeaders = JSON.stringify(headerObj);
|
|
2477
|
+
} catch (_e) {
|
|
2478
|
+
entry.responseHeaders = null;
|
|
2479
|
+
}
|
|
2480
|
+
entry.duration = Date.now() - entry.startTime;
|
|
2481
|
+
entry.state = "done";
|
|
2482
|
+
try {
|
|
2483
|
+
response.clone().text().then(function(text) {
|
|
2484
|
+
entry.responseBody = text && text.length > NETWORK_BODY_LIMIT ? text.substring(0, NETWORK_BODY_LIMIT) : text || null;
|
|
2485
|
+
pushNetworkEntry(entry);
|
|
2486
|
+
}).catch(function() {
|
|
2487
|
+
pushNetworkEntry(entry);
|
|
2488
|
+
});
|
|
2489
|
+
} catch (_e) {
|
|
2490
|
+
pushNetworkEntry(entry);
|
|
2491
|
+
}
|
|
2492
|
+
return response;
|
|
2493
|
+
}, function(err) {
|
|
2494
|
+
entry.duration = Date.now() - entry.startTime;
|
|
2495
|
+
entry.error = err && err.message ? err.message : "Network error";
|
|
2496
|
+
entry.state = "error";
|
|
2497
|
+
pushNetworkEntry(entry);
|
|
2498
|
+
throw err;
|
|
2499
|
+
});
|
|
2500
|
+
};
|
|
2501
|
+
})();
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
//#endregion
|
|
2505
|
+
//#region src/runtime/connection.ts
|
|
2506
|
+
function _shouldConnect() {
|
|
2507
|
+
if (_mcpEnabled) return true;
|
|
2508
|
+
if (typeof global !== "undefined" && global.__REACT_NATIVE_MCP_ENABLED__) return true;
|
|
2509
|
+
if (typeof globalThis !== "undefined" && globalThis.__REACT_NATIVE_MCP_ENABLED__) return true;
|
|
2510
|
+
return false;
|
|
2511
|
+
}
|
|
2512
|
+
function _stopHeartbeat() {
|
|
2513
|
+
if (_heartbeatTimer != null) {
|
|
2514
|
+
clearInterval(_heartbeatTimer);
|
|
2515
|
+
_heartbeatTimer = null;
|
|
2516
|
+
}
|
|
2517
|
+
if (_pongTimer != null) {
|
|
2518
|
+
clearTimeout(_pongTimer);
|
|
2519
|
+
_pongTimer = null;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
function _startHeartbeat() {
|
|
2523
|
+
_stopHeartbeat();
|
|
2524
|
+
_heartbeatTimer = setInterval(function() {
|
|
2525
|
+
if (!ws || ws.readyState !== 1) {
|
|
2526
|
+
_stopHeartbeat();
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
try {
|
|
2530
|
+
ws.send(JSON.stringify({ type: "ping" }));
|
|
2531
|
+
} catch (_e) {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
_pongTimer = setTimeout(function() {
|
|
2535
|
+
if (ws) try {
|
|
2536
|
+
ws.close();
|
|
2537
|
+
} catch (_e) {}
|
|
2538
|
+
}, PONG_TIMEOUT_MS);
|
|
2539
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
2540
|
+
}
|
|
2541
|
+
function connect() {
|
|
2542
|
+
if (!_shouldConnect()) return;
|
|
2543
|
+
if (ws && (ws.readyState === 0 || ws.readyState === 1)) return;
|
|
2544
|
+
if (ws) try {
|
|
2545
|
+
ws.close();
|
|
2546
|
+
} catch (_e) {}
|
|
2547
|
+
ws = new WebSocket(wsUrl);
|
|
2548
|
+
ws.onopen = function() {
|
|
2549
|
+
if (typeof console !== "undefined" && console.warn) console.warn("[MCP] Connected to server", wsUrl);
|
|
2550
|
+
reconnectDelay = 1e3;
|
|
2551
|
+
if (_reconnectTimer != null) clearTimeout(_reconnectTimer);
|
|
2552
|
+
_reconnectTimer = null;
|
|
2553
|
+
var platform = null;
|
|
2554
|
+
var deviceName = null;
|
|
2555
|
+
var origin = null;
|
|
2556
|
+
var pixelRatio = null;
|
|
2557
|
+
try {
|
|
2558
|
+
var rn = require("react-native");
|
|
2559
|
+
platform = rn.Platform && rn.Platform.OS;
|
|
2560
|
+
deviceName = rn.Platform && rn.Platform.constants && rn.Platform.constants.Model || null;
|
|
2561
|
+
if (rn.PixelRatio) pixelRatio = rn.PixelRatio.get();
|
|
2562
|
+
} catch (_e) {
|
|
2563
|
+
if (typeof console !== "undefined" && console.warn) console.warn("[MCP] Failed to read platform info:", _e && _e.message);
|
|
2564
|
+
}
|
|
2565
|
+
try {
|
|
2566
|
+
var _rn = require("react-native");
|
|
2567
|
+
var scriptURL = _rn.NativeModules && _rn.NativeModules.SourceCode && _rn.NativeModules.SourceCode.scriptURL;
|
|
2568
|
+
if (scriptURL && typeof scriptURL === "string") try {
|
|
2569
|
+
origin = new URL(scriptURL).origin;
|
|
2570
|
+
} catch (_ue) {
|
|
2571
|
+
var _match$;
|
|
2572
|
+
var match = scriptURL.match(/^(https?:\/\/[^/?#]+)/);
|
|
2573
|
+
if (match) origin = (_match$ = match[1]) !== null && _match$ !== void 0 ? _match$ : null;
|
|
2574
|
+
}
|
|
2575
|
+
} catch (_e2) {
|
|
2576
|
+
if (typeof console !== "undefined" && console.warn) console.warn("[MCP] Failed to read metro URL:", _e2 && _e2.message);
|
|
2577
|
+
}
|
|
2578
|
+
try {
|
|
2579
|
+
ws.send(JSON.stringify({
|
|
2580
|
+
type: "init",
|
|
2581
|
+
platform,
|
|
2582
|
+
deviceId: platform ? platform + "-1" : void 0,
|
|
2583
|
+
deviceName,
|
|
2584
|
+
metroBaseUrl: origin,
|
|
2585
|
+
pixelRatio
|
|
2586
|
+
}));
|
|
2587
|
+
} catch (_e3) {
|
|
2588
|
+
if (typeof console !== "undefined" && console.warn) console.warn("[MCP] Failed to send init:", _e3 && _e3.message);
|
|
2589
|
+
}
|
|
2590
|
+
_startHeartbeat();
|
|
2591
|
+
};
|
|
2592
|
+
ws.onmessage = function(ev) {
|
|
2593
|
+
try {
|
|
2594
|
+
var msg = JSON.parse(ev.data);
|
|
2595
|
+
if (msg.type === "pong") {
|
|
2596
|
+
if (_pongTimer != null) {
|
|
2597
|
+
clearTimeout(_pongTimer);
|
|
2598
|
+
_pongTimer = null;
|
|
2599
|
+
}
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
if (msg.method === "eval" && msg.id != null) {
|
|
2603
|
+
var result;
|
|
2604
|
+
var errMsg = null;
|
|
2605
|
+
try {
|
|
2606
|
+
result = eval(msg.params && msg.params.code != null ? msg.params.code : "undefined");
|
|
2607
|
+
} catch (e) {
|
|
2608
|
+
errMsg = e && e.message != null ? e.message : String(e);
|
|
2609
|
+
}
|
|
2610
|
+
function sendEvalResponse(res, err) {
|
|
2611
|
+
if (ws && ws.readyState === 1) ws.send(JSON.stringify(err != null ? {
|
|
2612
|
+
id: msg.id,
|
|
2613
|
+
error: err
|
|
2614
|
+
} : {
|
|
2615
|
+
id: msg.id,
|
|
2616
|
+
result: res
|
|
2617
|
+
}));
|
|
2618
|
+
}
|
|
2619
|
+
if (errMsg != null) sendEvalResponse(null, errMsg);
|
|
2620
|
+
else if (result != null && typeof result.then === "function") result.then(function(r) {
|
|
2621
|
+
sendEvalResponse(r, null);
|
|
2622
|
+
}, function(e) {
|
|
2623
|
+
sendEvalResponse(null, e && e.message != null ? e.message : String(e));
|
|
2624
|
+
});
|
|
2625
|
+
else sendEvalResponse(result, null);
|
|
2626
|
+
}
|
|
2627
|
+
} catch (_unused) {}
|
|
2628
|
+
};
|
|
2629
|
+
ws.onclose = function() {
|
|
2630
|
+
_stopHeartbeat();
|
|
2631
|
+
ws = null;
|
|
2632
|
+
_reconnectTimer = setTimeout(function() {
|
|
2633
|
+
connect();
|
|
2634
|
+
if (reconnectDelay < 3e4) reconnectDelay = Math.min(reconnectDelay * 1.5, 3e4);
|
|
2635
|
+
}, reconnectDelay);
|
|
2636
|
+
};
|
|
2637
|
+
ws.onerror = function() {};
|
|
2638
|
+
}
|
|
2639
|
+
var _isDevMode, wsUrl, ws, _reconnectTimer, reconnectDelay, _mcpEnabled, _heartbeatTimer, _pongTimer, HEARTBEAT_INTERVAL_MS, PONG_TIMEOUT_MS, _AppRegistry, _originalRun, PERIODIC_INTERVAL_MS;
|
|
2640
|
+
var init_connection = __esmMin(() => {
|
|
2641
|
+
init_mcp_object();
|
|
2642
|
+
_isDevMode = typeof globalThis !== "undefined" && typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__ || typeof process !== "undefined" && process.env && process.env.REACT_NATIVE_MCP_ENABLED === "true";
|
|
2643
|
+
if (_isDevMode && typeof console !== "undefined" && console.warn) console.warn("[MCP] runtime loaded, __REACT_NATIVE_MCP__ available");
|
|
2644
|
+
wsUrl = "ws://localhost:12300";
|
|
2645
|
+
ws = null;
|
|
2646
|
+
_reconnectTimer = null;
|
|
2647
|
+
reconnectDelay = 1e3;
|
|
2648
|
+
_mcpEnabled = _isDevMode;
|
|
2649
|
+
_heartbeatTimer = null;
|
|
2650
|
+
_pongTimer = null;
|
|
2651
|
+
HEARTBEAT_INTERVAL_MS = 3e4;
|
|
2652
|
+
PONG_TIMEOUT_MS = 1e4;
|
|
2653
|
+
/**
|
|
2654
|
+
* 릴리즈 빌드에서 MCP WebSocket 연결을 활성화한다.
|
|
2655
|
+
* 기본은 빌드 시 REACT_NATIVE_MCP_ENABLED 로 활성화(transformer가 global 주입).
|
|
2656
|
+
* 필요 시 앱에서 __REACT_NATIVE_MCP__.enable() 호출 가능.
|
|
2657
|
+
*/
|
|
2658
|
+
MCP.enable = function() {
|
|
2659
|
+
_mcpEnabled = true;
|
|
2660
|
+
connect();
|
|
2661
|
+
};
|
|
2662
|
+
if (_isDevMode) connect();
|
|
2663
|
+
_AppRegistry = require("react-native").AppRegistry;
|
|
2664
|
+
_originalRun = _AppRegistry.runApplication;
|
|
2665
|
+
_AppRegistry.runApplication = function() {
|
|
2666
|
+
if (_shouldConnect() && (!ws || ws.readyState !== 1)) connect();
|
|
2667
|
+
return _originalRun.apply(this, arguments);
|
|
2668
|
+
};
|
|
2669
|
+
(function() {
|
|
2670
|
+
try {
|
|
2671
|
+
var rn = require("react-native");
|
|
2672
|
+
if (rn && rn.AppState && typeof rn.AppState.addEventListener === "function") rn.AppState.addEventListener("change", function(nextState) {
|
|
2673
|
+
if (nextState === "active") {
|
|
2674
|
+
if (ws && ws.readyState === 1) _startHeartbeat();
|
|
2675
|
+
} else _stopHeartbeat();
|
|
2676
|
+
});
|
|
2677
|
+
} catch (_e) {}
|
|
2678
|
+
})();
|
|
2679
|
+
PERIODIC_INTERVAL_MS = 5e3;
|
|
2680
|
+
setInterval(function() {
|
|
2681
|
+
if (!_shouldConnect()) return;
|
|
2682
|
+
if (ws && ws.readyState === 1) return;
|
|
2683
|
+
connect();
|
|
2684
|
+
}, PERIODIC_INTERVAL_MS);
|
|
2685
|
+
});
|
|
2686
|
+
|
|
2687
|
+
//#endregion
|
|
2688
|
+
//#region src/runtime/index.ts
|
|
2689
|
+
/**
|
|
2690
|
+
* React Native 앱에 주입되는 MCP 런타임
|
|
2691
|
+
* - __REACT_NATIVE_MCP__.registerComponent → AppRegistry.registerComponent 위임
|
|
2692
|
+
* - __DEV__ 시 WebSocket으로 MCP 서버(12300)에 연결, eval 요청 처리
|
|
2693
|
+
*
|
|
2694
|
+
* Metro transformer가 진입점 상단에 require('@ohah/react-native-mcp-server/runtime') 주입
|
|
2695
|
+
* global은 모듈 로드 직후 최상단에서 설정해 ReferenceError 방지.
|
|
2696
|
+
*/
|
|
2697
|
+
var init_runtime = __esmMin(() => {
|
|
2698
|
+
init_devtools_hook();
|
|
2699
|
+
init_fiber_helpers();
|
|
2700
|
+
init_state_hooks();
|
|
2701
|
+
init_render_tracking();
|
|
2702
|
+
init_state_change_tracking();
|
|
2703
|
+
init_query_selector();
|
|
2704
|
+
init_fiber_serialization();
|
|
2705
|
+
init_mcp_registration();
|
|
2706
|
+
init_mcp_introspection();
|
|
2707
|
+
init_mcp_actions();
|
|
2708
|
+
init_mcp_scroll();
|
|
2709
|
+
init_mcp_state();
|
|
2710
|
+
init_mcp_query();
|
|
2711
|
+
init_mcp_measure();
|
|
2712
|
+
init_mcp_accessibility();
|
|
2713
|
+
init_mcp_object();
|
|
2714
|
+
init_console_hook();
|
|
2715
|
+
init_xhr_patch();
|
|
2716
|
+
init_fetch_patch();
|
|
2717
|
+
init_connection();
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2720
|
+
//#endregion
|
|
2721
|
+
init_runtime();
|
|
2722
|
+
})();
|