@ceraph/react-native-mcp 0.3.3 → 0.4.5

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.
Files changed (132) hide show
  1. package/README.md +335 -68
  2. package/dist/babel-plugin/index.cjs +1 -0
  3. package/dist/babel-plugin/index.js +1 -0
  4. package/dist/cli.d.ts +3 -1
  5. package/dist/cli.js +1 -47
  6. package/dist/index.d.ts +106 -1
  7. package/dist/index.js +2 -1651
  8. package/dist/shim/async-storage-ops.d.ts +26 -0
  9. package/dist/shim/async-storage-ops.js +1 -0
  10. package/dist/shim/boot.d.ts +9 -0
  11. package/dist/shim/boot.js +1 -141
  12. package/dist/shim/camera.js +1 -62
  13. package/dist/shim/command-poll.d.ts +18 -0
  14. package/dist/shim/command-poll.js +1 -0
  15. package/dist/shim/config.js +1 -56
  16. package/dist/shim/console-capture.d.ts +16 -0
  17. package/dist/shim/console-capture.js +1 -0
  18. package/dist/shim/deep-link.js +1 -25
  19. package/dist/shim/dev-guard.js +1 -3
  20. package/dist/shim/dev-host.d.ts +1 -0
  21. package/dist/shim/dev-host.js +1 -0
  22. package/dist/shim/error-handler.js +1 -66
  23. package/dist/shim/fetch-interceptor.js +1 -93
  24. package/dist/shim/index.d.ts +3 -0
  25. package/dist/shim/index.js +1 -6
  26. package/dist/shim/keep-awake.js +1 -118
  27. package/dist/shim/network-ownership.d.ts +4 -0
  28. package/dist/shim/network-ownership.js +1 -0
  29. package/dist/shim/optimistic-observer.d.ts +29 -0
  30. package/dist/shim/optimistic-observer.js +1 -0
  31. package/dist/shim/reload.js +1 -76
  32. package/dist/shim/reset.d.ts +30 -0
  33. package/dist/shim/reset.js +1 -0
  34. package/dist/shim/signal-capture.d.ts +8 -0
  35. package/dist/shim/signal-capture.js +1 -15
  36. package/dist/shim/signal-transport.d.ts +14 -1
  37. package/dist/shim/signal-transport.js +1 -43
  38. package/dist/shim/xhr-interceptor.d.ts +39 -0
  39. package/dist/shim/xhr-interceptor.js +1 -0
  40. package/package.json +40 -11
  41. package/dist/app-lifecycle.d.ts +0 -50
  42. package/dist/app-lifecycle.js +0 -487
  43. package/dist/camera-image-writer.d.ts +0 -43
  44. package/dist/camera-image-writer.js +0 -280
  45. package/dist/camera-registry-sync.d.ts +0 -18
  46. package/dist/camera-registry-sync.js +0 -117
  47. package/dist/device-autonomy.d.ts +0 -30
  48. package/dist/device-autonomy.js +0 -117
  49. package/dist/error-parser.d.ts +0 -51
  50. package/dist/error-parser.js +0 -275
  51. package/dist/expo-manager.d.ts +0 -62
  52. package/dist/expo-manager.js +0 -447
  53. package/dist/init/ast-camera.d.ts +0 -29
  54. package/dist/init/ast-camera.js +0 -267
  55. package/dist/init/ast-layout.d.ts +0 -15
  56. package/dist/init/ast-layout.js +0 -167
  57. package/dist/init/claude-hook-constants.d.ts +0 -9
  58. package/dist/init/claude-hook-constants.js +0 -91
  59. package/dist/init/lan-ip.d.ts +0 -11
  60. package/dist/init/lan-ip.js +0 -51
  61. package/dist/init/monorepo.d.ts +0 -13
  62. package/dist/init/monorepo.js +0 -185
  63. package/dist/init/oauth.d.ts +0 -52
  64. package/dist/init/oauth.js +0 -220
  65. package/dist/init/package-manager.d.ts +0 -11
  66. package/dist/init/package-manager.js +0 -60
  67. package/dist/init/prompt.d.ts +0 -12
  68. package/dist/init/prompt.js +0 -68
  69. package/dist/init/shell-profile.d.ts +0 -22
  70. package/dist/init/shell-profile.js +0 -85
  71. package/dist/init/steps.d.ts +0 -135
  72. package/dist/init/steps.js +0 -399
  73. package/dist/init/url-scheme.d.ts +0 -42
  74. package/dist/init/url-scheme.js +0 -187
  75. package/dist/init/walkthrough.d.ts +0 -76
  76. package/dist/init/walkthrough.js +0 -340
  77. package/dist/init.d.ts +0 -8
  78. package/dist/init.js +0 -395
  79. package/dist/iproxy-manager.d.ts +0 -32
  80. package/dist/iproxy-manager.js +0 -216
  81. package/dist/mac-caffeinate.d.ts +0 -10
  82. package/dist/mac-caffeinate.js +0 -56
  83. package/dist/permission-interceptor.d.ts +0 -29
  84. package/dist/permission-interceptor.js +0 -185
  85. package/dist/prebuild-detector.d.ts +0 -19
  86. package/dist/prebuild-detector.js +0 -174
  87. package/dist/preflight.d.ts +0 -34
  88. package/dist/preflight.js +0 -847
  89. package/dist/screen.d.ts +0 -184
  90. package/dist/screen.js +0 -931
  91. package/dist/signal-listener.d.ts +0 -27
  92. package/dist/signal-listener.js +0 -135
  93. package/dist/simulator-boot.d.ts +0 -52
  94. package/dist/simulator-boot.js +0 -227
  95. package/dist/target.d.ts +0 -48
  96. package/dist/target.js +0 -267
  97. package/dist/uninstall/cli-runner.d.ts +0 -32
  98. package/dist/uninstall/cli-runner.js +0 -223
  99. package/dist/uninstall/footprint.d.ts +0 -40
  100. package/dist/uninstall/footprint.js +0 -288
  101. package/dist/uninstall/mcp-tools.d.ts +0 -14
  102. package/dist/uninstall/mcp-tools.js +0 -175
  103. package/dist/uninstall/revert-auth.d.ts +0 -22
  104. package/dist/uninstall/revert-auth.js +0 -31
  105. package/dist/uninstall/revert-boot.d.ts +0 -24
  106. package/dist/uninstall/revert-boot.js +0 -242
  107. package/dist/uninstall/revert-camera.d.ts +0 -12
  108. package/dist/uninstall/revert-camera.js +0 -199
  109. package/dist/uninstall/revert-ceraph-dir.d.ts +0 -27
  110. package/dist/uninstall/revert-ceraph-dir.js +0 -38
  111. package/dist/uninstall/revert-claude-hooks.d.ts +0 -19
  112. package/dist/uninstall/revert-claude-hooks.js +0 -191
  113. package/dist/uninstall/revert-gitignore.d.ts +0 -17
  114. package/dist/uninstall/revert-gitignore.js +0 -43
  115. package/dist/uninstall/revert-mcp-clients.d.ts +0 -57
  116. package/dist/uninstall/revert-mcp-clients.js +0 -194
  117. package/dist/uninstall/revert-package.d.ts +0 -34
  118. package/dist/uninstall/revert-package.js +0 -98
  119. package/dist/uninstall/revert-scheme.d.ts +0 -36
  120. package/dist/uninstall/revert-scheme.js +0 -139
  121. package/dist/uninstall/revert-signal-host-env.d.ts +0 -31
  122. package/dist/uninstall/revert-signal-host-env.js +0 -61
  123. package/dist/uninstall/walkthrough.d.ts +0 -80
  124. package/dist/uninstall/walkthrough.js +0 -1244
  125. package/dist/utils/atomic-write.d.ts +0 -1
  126. package/dist/utils/atomic-write.js +0 -30
  127. package/dist/wait-for-device.d.ts +0 -68
  128. package/dist/wait-for-device.js +0 -368
  129. package/dist/wda-manager.d.ts +0 -38
  130. package/dist/wda-manager.js +0 -186
  131. package/dist/wda-simulator.d.ts +0 -28
  132. package/dist/wda-simulator.js +0 -257
package/dist/screen.js DELETED
@@ -1,931 +0,0 @@
1
- import { DEFAULT_DEVICE_WDA_BASE_URL, TargetResolver } from "./target.js";
2
- const WDA_TIMEOUT_MS = 5000;
3
- export class ScreenManager {
4
- sessionId = null;
5
- pixelRatio = null;
6
- targetResolver;
7
- sessionBaseUrl = null;
8
- constructor(opts = {}) {
9
- this.targetResolver = opts.targetResolver ?? new TargetResolver();
10
- }
11
- async snapshotTarget() {
12
- try {
13
- const info = await this.targetResolver.resolve();
14
- return { baseUrl: info.baseUrl, ready: info.wdaReady };
15
- }
16
- catch {
17
- return { baseUrl: DEFAULT_DEVICE_WDA_BASE_URL, ready: true };
18
- }
19
- }
20
- async wdaFetchAt(baseUrl, path, options = {}, timeoutMs = WDA_TIMEOUT_MS) {
21
- const url = `${baseUrl}${path}`;
22
- const controller = new AbortController();
23
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
24
- try {
25
- const response = await fetch(url, {
26
- ...options,
27
- signal: controller.signal,
28
- headers: {
29
- "Content-Type": "application/json",
30
- ...options.headers,
31
- },
32
- });
33
- return response;
34
- }
35
- finally {
36
- clearTimeout(timeout);
37
- }
38
- }
39
- async wdaFetch(path, options = {}, timeoutMs = WDA_TIMEOUT_MS) {
40
- const snapshot = await this.snapshotTarget();
41
- if (!snapshot.ready) {
42
- throw new Error("WebDriverAgent is not ready for the selected target. If you " +
43
- "set CERAPH_TARGET=simulator (or are using a simulator with " +
44
- "auto-detection), call `rn_wda_start` first to build and " +
45
- "launch WDA against the simulator.");
46
- }
47
- const baseUrl = this.sessionBaseUrl ?? snapshot.baseUrl;
48
- return this.wdaFetchAt(baseUrl, path, options, timeoutMs);
49
- }
50
- async isAvailable() {
51
- const snapshot = await this.snapshotTarget();
52
- if (!snapshot.ready)
53
- return false;
54
- try {
55
- const res = await this.wdaFetchAt(snapshot.baseUrl, "/status");
56
- return res.ok;
57
- }
58
- catch {
59
- return false;
60
- }
61
- }
62
- async ensureSession() {
63
- const snapshot = await this.snapshotTarget();
64
- if (!snapshot.ready) {
65
- throw new Error("WebDriverAgent is not ready for the selected target. If you " +
66
- "set CERAPH_TARGET=simulator (or are using a simulator with " +
67
- "auto-detection), call `rn_wda_start` first to build and " +
68
- "launch WDA against the simulator.");
69
- }
70
- const currentBaseUrl = snapshot.baseUrl;
71
- if (this.sessionBaseUrl && this.sessionBaseUrl !== currentBaseUrl) {
72
- this.sessionId = null;
73
- this.pixelRatio = null;
74
- }
75
- this.sessionBaseUrl = currentBaseUrl;
76
- if (this.sessionId) {
77
- try {
78
- const res = await this.wdaFetchAt(currentBaseUrl, `/session/${this.sessionId}`);
79
- if (res.ok)
80
- return this.sessionId;
81
- }
82
- catch {
83
- }
84
- this.sessionId = null;
85
- }
86
- try {
87
- const statusRes = await this.wdaFetchAt(currentBaseUrl, "/status");
88
- if (statusRes.ok) {
89
- const statusData = (await statusRes.json());
90
- const existingId = statusData.sessionId || statusData.value?.sessionId;
91
- if (existingId) {
92
- this.sessionId = existingId;
93
- return existingId;
94
- }
95
- }
96
- }
97
- catch {
98
- }
99
- const res = await this.wdaFetchAt(currentBaseUrl, "/session", {
100
- method: "POST",
101
- body: JSON.stringify({
102
- capabilities: {
103
- alwaysMatch: {},
104
- },
105
- }),
106
- });
107
- if (!res.ok) {
108
- const text = await res.text().catch(() => "unknown error");
109
- throw new Error(`Failed to create WDA session: ${res.status} ${text}`);
110
- }
111
- const data = (await res.json());
112
- const sessionId = data.sessionId || data.value?.sessionId;
113
- if (!sessionId) {
114
- throw new Error("WDA session creation succeeded but no sessionId returned");
115
- }
116
- this.sessionId = sessionId;
117
- return sessionId;
118
- }
119
- async getPixelRatio() {
120
- if (this.pixelRatio)
121
- return this.pixelRatio;
122
- const sessionId = await this.ensureSession();
123
- const windowRes = await this.wdaFetch(`/session/${sessionId}/window/size`);
124
- if (!windowRes.ok) {
125
- throw new Error(`Failed to get window size: ${windowRes.status}`);
126
- }
127
- const windowData = (await windowRes.json());
128
- const logicalWidth = windowData.value.width;
129
- const screenshotRes = await this.wdaFetch(`/session/${sessionId}/screenshot`);
130
- if (!screenshotRes.ok) {
131
- this.pixelRatio = 3;
132
- return this.pixelRatio;
133
- }
134
- const screenshotData = (await screenshotRes.json());
135
- const base64 = screenshotData.value;
136
- const pngBuffer = Buffer.from(base64, "base64");
137
- if (pngBuffer.length < 24) {
138
- this.pixelRatio = 3;
139
- return this.pixelRatio;
140
- }
141
- const imageWidth = pngBuffer.readUInt32BE(16);
142
- this.pixelRatio = Math.round(imageWidth / logicalWidth);
143
- if (this.pixelRatio < 1 || this.pixelRatio > 4) {
144
- this.pixelRatio = 3;
145
- }
146
- return this.pixelRatio;
147
- }
148
- async getWindowSize() {
149
- const sessionId = await this.ensureSession();
150
- const res = await this.wdaFetch(`/session/${sessionId}/window/size`);
151
- if (!res.ok) {
152
- throw new Error(`Failed to get window size: ${res.status}`);
153
- }
154
- const data = (await res.json());
155
- return data.value;
156
- }
157
- async tap(x, y, fromScreenshot = true) {
158
- try {
159
- const sessionId = await this.ensureSession();
160
- const ratio = await this.getPixelRatio();
161
- const correctedX = fromScreenshot ? x / ratio : x;
162
- const correctedY = fromScreenshot ? y / ratio : y;
163
- const res = await this.wdaFetch(`/session/${sessionId}/wda/tap/0`, {
164
- method: "POST",
165
- body: JSON.stringify({ x: correctedX, y: correctedY }),
166
- });
167
- if (!res.ok) {
168
- const text = await res.text().catch(() => "unknown");
169
- return {
170
- success: false,
171
- tappedAt: { x: correctedX, y: correctedY },
172
- pixelRatio: ratio,
173
- correction: fromScreenshot
174
- ? `Divided by ${ratio}x: (${x},${y}) -> (${correctedX.toFixed(1)},${correctedY.toFixed(1)})`
175
- : "No correction (device coordinates)",
176
- error: `WDA tap failed: ${res.status} ${text}`,
177
- };
178
- }
179
- return {
180
- success: true,
181
- tappedAt: { x: correctedX, y: correctedY },
182
- pixelRatio: ratio,
183
- correction: fromScreenshot
184
- ? `Divided by ${ratio}x: (${x},${y}) -> (${correctedX.toFixed(1)},${correctedY.toFixed(1)})`
185
- : "No correction (device coordinates)",
186
- };
187
- }
188
- catch (err) {
189
- return {
190
- success: false,
191
- tappedAt: { x, y },
192
- pixelRatio: this.pixelRatio ?? 0,
193
- correction: "Failed before correction could be applied",
194
- error: err instanceof Error ? err.message : "Unknown error during tap",
195
- };
196
- }
197
- }
198
- async findAndTap(query) {
199
- try {
200
- const sessionId = await this.ensureSession();
201
- const sourceRes = await this.wdaFetch(`/session/${sessionId}/source?format=json`);
202
- if (!sourceRes.ok) {
203
- return {
204
- success: false,
205
- error: `Failed to get element tree: ${sourceRes.status}`,
206
- };
207
- }
208
- const sourceData = (await sourceRes.json());
209
- const root = sourceData.value;
210
- const matches = this.searchElements(root, query);
211
- if (matches.length === 0) {
212
- const visible = this.collectVisibleElements(root, 50);
213
- return {
214
- success: false,
215
- availableElements: visible,
216
- error: `No element found matching query: ${JSON.stringify(query)}`,
217
- };
218
- }
219
- const index = query.index ?? 0;
220
- if (index >= matches.length) {
221
- return {
222
- success: false,
223
- error: `Index ${index} out of range. Found ${matches.length} matching element(s).`,
224
- availableElements: matches.map((m) => ({
225
- type: m.type ?? "unknown",
226
- label: m.label ?? "",
227
- text: String(m.value ?? m.label ?? ""),
228
- })),
229
- };
230
- }
231
- const element = matches[index];
232
- const rect = element.rect;
233
- if (!rect) {
234
- return {
235
- success: false,
236
- error: "Matched element has no bounds/rect information.",
237
- };
238
- }
239
- const centerX = rect.x + rect.width / 2;
240
- const centerY = rect.y + rect.height / 2;
241
- const tapRes = await this.wdaFetch(`/session/${sessionId}/wda/tap/0`, {
242
- method: "POST",
243
- body: JSON.stringify({ x: centerX, y: centerY }),
244
- });
245
- if (!tapRes.ok) {
246
- const text = await tapRes.text().catch(() => "unknown");
247
- return {
248
- success: false,
249
- error: `WDA tap failed: ${tapRes.status} ${text}`,
250
- element: {
251
- type: element.type ?? "unknown",
252
- label: element.label ?? element.name ?? "",
253
- bounds: rect,
254
- },
255
- tappedAt: { x: centerX, y: centerY },
256
- };
257
- }
258
- return {
259
- success: true,
260
- element: {
261
- type: element.type ?? "unknown",
262
- label: element.label ?? element.name ?? "",
263
- bounds: rect,
264
- },
265
- tappedAt: { x: centerX, y: centerY },
266
- };
267
- }
268
- catch (err) {
269
- return {
270
- success: false,
271
- error: err instanceof Error
272
- ? err.message
273
- : "Unknown error during findAndTap",
274
- };
275
- }
276
- }
277
- async findElement(query) {
278
- try {
279
- const sessionId = await this.ensureSession();
280
- const sourceRes = await this.wdaFetch(`/session/${sessionId}/source?format=json`);
281
- if (!sourceRes.ok) {
282
- return {
283
- success: false,
284
- error: `Failed to get element tree: ${sourceRes.status}`,
285
- };
286
- }
287
- const sourceData = (await sourceRes.json());
288
- const matches = this.searchElements(sourceData.value, query);
289
- if (matches.length === 0) {
290
- return { success: false, matchCount: 0 };
291
- }
292
- const index = query.index ?? 0;
293
- if (index >= matches.length) {
294
- return { success: false, matchCount: matches.length };
295
- }
296
- return { success: true, element: matches[index], matchCount: matches.length };
297
- }
298
- catch (err) {
299
- return {
300
- success: false,
301
- error: err instanceof Error ? err.message : "Unknown error during findElement",
302
- };
303
- }
304
- }
305
- async swipe(opts) {
306
- try {
307
- const sessionId = await this.ensureSession();
308
- const windowSize = await this.getWindowSize();
309
- const ratio = await this.getPixelRatio();
310
- const fromScreenshot = (opts.coordinateSource ?? "device") === "screenshot";
311
- let fromX = opts.from?.x ?? windowSize.width / 2;
312
- let fromY = opts.from?.y ?? windowSize.height / 2;
313
- if (fromScreenshot) {
314
- fromX = fromX / ratio;
315
- fromY = fromY / ratio;
316
- }
317
- const axis = opts.direction === "up" || opts.direction === "down"
318
- ? windowSize.height
319
- : windowSize.width;
320
- const distance = opts.distancePx ?? Math.floor(axis * 0.6);
321
- let toX = fromX;
322
- let toY = fromY;
323
- switch (opts.direction) {
324
- case "up":
325
- toY = fromY - distance;
326
- break;
327
- case "down":
328
- toY = fromY + distance;
329
- break;
330
- case "left":
331
- toX = fromX - distance;
332
- break;
333
- case "right":
334
- toX = fromX + distance;
335
- break;
336
- }
337
- toX = Math.max(0, Math.min(windowSize.width - 1, toX));
338
- toY = Math.max(0, Math.min(windowSize.height - 1, toY));
339
- fromX = Math.max(0, Math.min(windowSize.width - 1, fromX));
340
- fromY = Math.max(0, Math.min(windowSize.height - 1, fromY));
341
- const durationMs = opts.durationMs ?? 300;
342
- const res = await this.wdaFetch(`/session/${sessionId}/wda/dragfromtoforduration`, {
343
- method: "POST",
344
- body: JSON.stringify({
345
- fromX,
346
- fromY,
347
- toX,
348
- toY,
349
- duration: durationMs / 1000,
350
- }),
351
- });
352
- if (!res.ok) {
353
- const text = await res.text().catch(() => "unknown");
354
- return {
355
- success: false,
356
- from: { x: fromX, y: fromY },
357
- to: { x: toX, y: toY },
358
- durationMs,
359
- error: `WDA swipe failed: ${res.status} ${text}`,
360
- };
361
- }
362
- return {
363
- success: true,
364
- from: { x: fromX, y: fromY },
365
- to: { x: toX, y: toY },
366
- durationMs,
367
- };
368
- }
369
- catch (err) {
370
- return {
371
- success: false,
372
- from: { x: 0, y: 0 },
373
- to: { x: 0, y: 0 },
374
- durationMs: opts.durationMs ?? 300,
375
- error: err instanceof Error ? err.message : "Unknown error during swipe",
376
- };
377
- }
378
- }
379
- async scrollToElement(query, opts = {}) {
380
- const maxSwipes = opts.maxSwipes ?? 10;
381
- const direction = opts.direction ?? "up";
382
- for (let i = 0; i <= maxSwipes; i++) {
383
- const found = await this.findElement(query);
384
- if (found.success && found.element?.rect) {
385
- return {
386
- success: true,
387
- swipes: i,
388
- element: {
389
- type: found.element.type ?? "unknown",
390
- label: found.element.label ?? found.element.name ?? "",
391
- bounds: found.element.rect,
392
- },
393
- };
394
- }
395
- if (i === maxSwipes)
396
- break;
397
- const swipeRes = await this.swipe({
398
- direction,
399
- distancePx: opts.distancePx,
400
- });
401
- if (!swipeRes.success) {
402
- return {
403
- success: false,
404
- swipes: i,
405
- error: swipeRes.error ?? "Swipe failed during scrollToElement",
406
- };
407
- }
408
- }
409
- return {
410
- success: false,
411
- swipes: maxSwipes,
412
- error: `Element not found after ${maxSwipes} swipes: ${JSON.stringify(query)}`,
413
- };
414
- }
415
- async longPress(x, y, durationMs = 1000, fromScreenshot = false) {
416
- try {
417
- const sessionId = await this.ensureSession();
418
- let pressX = x;
419
- let pressY = y;
420
- if (fromScreenshot) {
421
- const ratio = await this.getPixelRatio();
422
- pressX = x / ratio;
423
- pressY = y / ratio;
424
- }
425
- const res = await this.wdaFetch(`/session/${sessionId}/wda/touchAndHold`, {
426
- method: "POST",
427
- body: JSON.stringify({
428
- x: pressX,
429
- y: pressY,
430
- duration: durationMs / 1000,
431
- }),
432
- });
433
- if (!res.ok) {
434
- const text = await res.text().catch(() => "unknown");
435
- return {
436
- success: false,
437
- error: `WDA touchAndHold failed: ${res.status} ${text}`,
438
- };
439
- }
440
- return { success: true, details: { x: pressX, y: pressY, durationMs } };
441
- }
442
- catch (err) {
443
- return {
444
- success: false,
445
- error: err instanceof Error ? err.message : "Unknown error during longPress",
446
- };
447
- }
448
- }
449
- async longPressElement(query, durationMs = 1000) {
450
- const found = await this.findElement(query);
451
- if (!found.success || !found.element?.rect) {
452
- return {
453
- success: false,
454
- error: found.error ?? `No element found matching ${JSON.stringify(query)}`,
455
- };
456
- }
457
- const rect = found.element.rect;
458
- const centerX = rect.x + rect.width / 2;
459
- const centerY = rect.y + rect.height / 2;
460
- const res = await this.longPress(centerX, centerY, durationMs, false);
461
- return {
462
- ...res,
463
- element: {
464
- type: found.element.type ?? "unknown",
465
- label: found.element.label ?? found.element.name ?? "",
466
- bounds: rect,
467
- },
468
- };
469
- }
470
- async type(text, opts = {}) {
471
- try {
472
- const sessionId = await this.ensureSession();
473
- const chars = Array.from(text);
474
- const res = await this.wdaFetch(`/session/${sessionId}/wda/keys`, {
475
- method: "POST",
476
- body: JSON.stringify({ value: chars }),
477
- });
478
- if (!res.ok) {
479
- const t = await res.text().catch(() => "unknown");
480
- return {
481
- success: false,
482
- error: `WDA keys failed: ${res.status} ${t}`,
483
- };
484
- }
485
- if (opts.hideKeyboardAfter) {
486
- await this.wdaFetch(`/session/${sessionId}/wda/keys`, {
487
- method: "POST",
488
- body: JSON.stringify({ value: ["\n"] }),
489
- }).catch(() => undefined);
490
- }
491
- return { success: true };
492
- }
493
- catch (err) {
494
- return {
495
- success: false,
496
- error: err instanceof Error ? err.message : "Unknown error during type",
497
- };
498
- }
499
- }
500
- async clearText(query) {
501
- try {
502
- const found = await this.findElement(query);
503
- if (!found.success || !found.element?.rect) {
504
- return {
505
- success: false,
506
- error: found.error ?? `No element found matching ${JSON.stringify(query)}`,
507
- };
508
- }
509
- const rect = found.element.rect;
510
- const currentValue = String(found.element.value ?? "");
511
- const tapRes = await this.tap(rect.x + rect.width / 2, rect.y + rect.height / 2, false);
512
- if (!tapRes.success) {
513
- return {
514
- success: false,
515
- error: tapRes.error ?? "Failed to focus element before clearing",
516
- };
517
- }
518
- const backspaces = new Array(Math.max(1, currentValue.length)).fill("\uE003");
519
- const sessionId = await this.ensureSession();
520
- const res = await this.wdaFetch(`/session/${sessionId}/wda/keys`, {
521
- method: "POST",
522
- body: JSON.stringify({ value: backspaces }),
523
- });
524
- if (!res.ok) {
525
- const t = await res.text().catch(() => "unknown");
526
- return {
527
- success: false,
528
- error: `WDA keys (backspace) failed: ${res.status} ${t}`,
529
- };
530
- }
531
- return { success: true, details: { cleared: currentValue.length } };
532
- }
533
- catch (err) {
534
- return {
535
- success: false,
536
- error: err instanceof Error ? err.message : "Unknown error during clearText",
537
- };
538
- }
539
- }
540
- async pressKey(key) {
541
- try {
542
- const sessionId = await this.ensureSession();
543
- if (key === "lock") {
544
- const res = await this.wdaFetch(`/session/${sessionId}/wda/lock`, {
545
- method: "POST",
546
- body: JSON.stringify({}),
547
- });
548
- if (!res.ok) {
549
- const t = await res.text().catch(() => "unknown");
550
- return { success: false, error: `WDA lock failed: ${res.status} ${t}` };
551
- }
552
- return { success: true };
553
- }
554
- const name = key === "home" ? "home" : key === "volumeUp" ? "volumeup" : "volumedown";
555
- const res = await this.wdaFetch(`/session/${sessionId}/wda/pressButton`, {
556
- method: "POST",
557
- body: JSON.stringify({ name }),
558
- });
559
- if (!res.ok) {
560
- const t = await res.text().catch(() => "unknown");
561
- return {
562
- success: false,
563
- error: `WDA pressButton failed: ${res.status} ${t}`,
564
- };
565
- }
566
- return { success: true };
567
- }
568
- catch (err) {
569
- return {
570
- success: false,
571
- error: err instanceof Error ? err.message : "Unknown error during pressKey",
572
- };
573
- }
574
- }
575
- async unlock() {
576
- try {
577
- const sessionId = await this.ensureSession();
578
- const res = await this.wdaFetch(`/session/${sessionId}/wda/unlock`, {
579
- method: "POST",
580
- body: JSON.stringify({}),
581
- });
582
- if (!res.ok) {
583
- const t = await res.text().catch(() => "unknown");
584
- return { success: false, error: `WDA unlock failed: ${res.status} ${t}` };
585
- }
586
- return { success: true };
587
- }
588
- catch (err) {
589
- return {
590
- success: false,
591
- error: err instanceof Error ? err.message : "Unknown error during unlock",
592
- };
593
- }
594
- }
595
- async isLocked() {
596
- try {
597
- const sessionId = await this.ensureSession();
598
- const res = await this.wdaFetch(`/session/${sessionId}/wda/locked`);
599
- if (!res.ok)
600
- return null;
601
- const data = (await res.json());
602
- return typeof data.value === "boolean" ? data.value : null;
603
- }
604
- catch {
605
- return null;
606
- }
607
- }
608
- async screenshot() {
609
- try {
610
- const sessionId = await this.ensureSession();
611
- const res = await this.wdaFetch(`/session/${sessionId}/screenshot`);
612
- if (!res.ok) {
613
- const t = await res.text().catch(() => "unknown");
614
- return { success: false, error: `WDA screenshot failed: ${res.status} ${t}` };
615
- }
616
- const data = (await res.json());
617
- return { success: true, base64: data.value };
618
- }
619
- catch (err) {
620
- return {
621
- success: false,
622
- error: err instanceof Error ? err.message : "Unknown error during screenshot",
623
- };
624
- }
625
- }
626
- async getSource() {
627
- try {
628
- const sessionId = await this.ensureSession();
629
- const res = await this.wdaFetch(`/session/${sessionId}/source?format=json`);
630
- if (!res.ok) {
631
- const t = await res.text().catch(() => "unknown");
632
- return {
633
- success: false,
634
- error: `WDA source failed: ${res.status} ${t}`,
635
- };
636
- }
637
- const data = (await res.json());
638
- return { success: true, source: data.value };
639
- }
640
- catch (err) {
641
- return {
642
- success: false,
643
- error: err instanceof Error ? err.message : "Unknown error during getSource",
644
- };
645
- }
646
- }
647
- async waitFor(query, opts = {}) {
648
- const timeoutMs = opts.timeoutMs ?? 5000;
649
- const pollIntervalMs = opts.pollIntervalMs ?? 500;
650
- const disappear = opts.disappear ?? false;
651
- const start = Date.now();
652
- while (Date.now() - start < timeoutMs) {
653
- const found = await this.findElement(query);
654
- const matched = found.success && !!found.element;
655
- if (!disappear && matched) {
656
- const rect = found.element.rect;
657
- return {
658
- success: true,
659
- found: true,
660
- elapsedMs: Date.now() - start,
661
- element: rect
662
- ? {
663
- type: found.element.type ?? "unknown",
664
- label: found.element.label ?? found.element.name ?? "",
665
- bounds: rect,
666
- }
667
- : undefined,
668
- };
669
- }
670
- if (disappear && !matched) {
671
- return {
672
- success: true,
673
- found: false,
674
- elapsedMs: Date.now() - start,
675
- };
676
- }
677
- await new Promise((r) => setTimeout(r, pollIntervalMs));
678
- }
679
- return {
680
- success: false,
681
- found: !disappear,
682
- elapsedMs: Date.now() - start,
683
- error: disappear
684
- ? `Element still visible after ${timeoutMs}ms: ${JSON.stringify(query)}`
685
- : `Element not visible after ${timeoutMs}ms: ${JSON.stringify(query)}`,
686
- };
687
- }
688
- async assertVisible(query) {
689
- const found = await this.findElement(query);
690
- if (found.error) {
691
- return { success: false, visible: false, error: found.error };
692
- }
693
- if (found.success && found.element?.rect) {
694
- return {
695
- success: true,
696
- visible: true,
697
- element: {
698
- type: found.element.type ?? "unknown",
699
- label: found.element.label ?? found.element.name ?? "",
700
- bounds: found.element.rect,
701
- },
702
- };
703
- }
704
- return { success: true, visible: false };
705
- }
706
- async assertNotVisible(query) {
707
- const found = await this.findElement(query);
708
- if (found.error) {
709
- return { success: false, visible: false, error: found.error };
710
- }
711
- const visible = found.success && !!found.element;
712
- return { success: true, visible };
713
- }
714
- async getOrientation() {
715
- try {
716
- const sessionId = await this.ensureSession();
717
- const res = await this.wdaFetch(`/session/${sessionId}/orientation`);
718
- if (!res.ok)
719
- return null;
720
- const data = (await res.json());
721
- return data.value ?? null;
722
- }
723
- catch {
724
- return null;
725
- }
726
- }
727
- async setOrientation(orientation) {
728
- try {
729
- const sessionId = await this.ensureSession();
730
- const res = await this.wdaFetch(`/session/${sessionId}/orientation`, {
731
- method: "POST",
732
- body: JSON.stringify({
733
- orientation: orientation.toUpperCase(),
734
- }),
735
- });
736
- if (!res.ok) {
737
- const t = await res.text().catch(() => "unknown");
738
- return {
739
- success: false,
740
- error: `WDA setOrientation failed: ${res.status} ${t}`,
741
- };
742
- }
743
- return { success: true };
744
- }
745
- catch (err) {
746
- return {
747
- success: false,
748
- error: err instanceof Error
749
- ? err.message
750
- : "Unknown error during setOrientation",
751
- };
752
- }
753
- }
754
- async getActiveAppInfo() {
755
- try {
756
- const sessionId = await this.ensureSession();
757
- const res = await this.wdaFetch(`/session/${sessionId}/wda/activeAppInfo`);
758
- if (!res.ok) {
759
- const t = await res.text().catch(() => "unknown");
760
- return {
761
- success: false,
762
- error: `WDA activeAppInfo failed: ${res.status} ${t}`,
763
- };
764
- }
765
- const data = (await res.json());
766
- return {
767
- success: true,
768
- bundleId: data.value?.bundleId,
769
- pid: data.value?.pid,
770
- name: data.value?.name,
771
- };
772
- }
773
- catch (err) {
774
- return {
775
- success: false,
776
- error: err instanceof Error
777
- ? err.message
778
- : "Unknown error during getActiveAppInfo",
779
- };
780
- }
781
- }
782
- async activateApp(bundleId) {
783
- try {
784
- const sessionId = await this.ensureSession();
785
- const res = await this.wdaFetch(`/session/${sessionId}/wda/apps/launch`, {
786
- method: "POST",
787
- body: JSON.stringify({ bundleId }),
788
- });
789
- if (!res.ok) {
790
- const t = await res.text().catch(() => "unknown");
791
- return {
792
- success: false,
793
- error: `WDA apps/launch failed: ${res.status} ${t}`,
794
- };
795
- }
796
- return { success: true };
797
- }
798
- catch (err) {
799
- return {
800
- success: false,
801
- error: err instanceof Error
802
- ? err.message
803
- : "Unknown error during activateApp",
804
- };
805
- }
806
- }
807
- async openUrl(url) {
808
- try {
809
- const sessionId = await this.ensureSession();
810
- const res = await this.wdaFetch(`/session/${sessionId}/url`, {
811
- method: "POST",
812
- body: JSON.stringify({ url }),
813
- });
814
- if (!res.ok) {
815
- const t = await res.text().catch(() => "unknown");
816
- return {
817
- success: false,
818
- error: `WDA openUrl failed: ${res.status} ${t}`,
819
- };
820
- }
821
- return { success: true };
822
- }
823
- catch (err) {
824
- return {
825
- success: false,
826
- error: err instanceof Error ? err.message : "Unknown error during openUrl",
827
- };
828
- }
829
- }
830
- async terminateAppViaWDA(bundleId) {
831
- try {
832
- const sessionId = await this.ensureSession();
833
- const res = await this.wdaFetch(`/session/${sessionId}/wda/apps/terminate`, {
834
- method: "POST",
835
- body: JSON.stringify({ bundleId }),
836
- });
837
- if (!res.ok) {
838
- const t = await res.text().catch(() => "unknown");
839
- return {
840
- success: false,
841
- error: `WDA apps/terminate failed: ${res.status} ${t}`,
842
- };
843
- }
844
- return { success: true };
845
- }
846
- catch (err) {
847
- return {
848
- success: false,
849
- error: err instanceof Error
850
- ? err.message
851
- : "Unknown error during terminateAppViaWDA",
852
- };
853
- }
854
- }
855
- async pingStatus() {
856
- try {
857
- const res = await this.wdaFetch("/status", {}, 2000);
858
- return res.ok;
859
- }
860
- catch {
861
- return false;
862
- }
863
- }
864
- searchElements(element, query) {
865
- const results = [];
866
- if (this.elementMatches(element, query)) {
867
- results.push(element);
868
- }
869
- const children = element.children;
870
- if (Array.isArray(children)) {
871
- for (const child of children) {
872
- results.push(...this.searchElements(child, query));
873
- }
874
- }
875
- return results;
876
- }
877
- elementMatches(element, query) {
878
- if (query.text !== undefined) {
879
- const searchText = query.text.toLowerCase();
880
- const fields = [element.value, element.label, element.name]
881
- .filter(Boolean)
882
- .map(v => String(v).toLowerCase());
883
- if (!fields.some(f => f.includes(searchText))) {
884
- return false;
885
- }
886
- }
887
- if (query.accessibilityLabel !== undefined) {
888
- const label = String(element.label ?? element.name ?? "");
889
- if (!label
890
- .toLowerCase()
891
- .includes(query.accessibilityLabel.toLowerCase())) {
892
- return false;
893
- }
894
- }
895
- if (query.type !== undefined) {
896
- const elemType = String(element.type ?? "");
897
- if (!elemType.toLowerCase().includes(query.type.toLowerCase())) {
898
- return false;
899
- }
900
- }
901
- return (query.text !== undefined ||
902
- query.accessibilityLabel !== undefined ||
903
- query.type !== undefined);
904
- }
905
- collectVisibleElements(element, limit) {
906
- const results = [];
907
- if (limit <= 0)
908
- return results;
909
- const label = String(element.label ?? element.name ?? "");
910
- const text = String(element.value ?? "");
911
- const type = String(element.type ?? "");
912
- if (label || text) {
913
- results.push({ type, label, text });
914
- }
915
- const children = element.children;
916
- if (Array.isArray(children)) {
917
- for (const child of children) {
918
- const remaining = limit - results.length;
919
- if (remaining <= 0)
920
- break;
921
- results.push(...this.collectVisibleElements(child, remaining));
922
- }
923
- }
924
- return results;
925
- }
926
- reset() {
927
- this.sessionId = null;
928
- this.pixelRatio = null;
929
- this.sessionBaseUrl = null;
930
- }
931
- }