@blitzdev/iphone-mcp 0.1.7

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/dist/child-env.d.ts +1 -0
  4. package/dist/child-env.js +61 -0
  5. package/dist/child-env.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +318 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/device-client.d.ts +2 -0
  10. package/dist/device-client.js +15 -0
  11. package/dist/device-client.js.map +1 -0
  12. package/dist/execution-context.d.ts +33 -0
  13. package/dist/execution-context.js +66 -0
  14. package/dist/execution-context.js.map +1 -0
  15. package/dist/idb/ax-scan-client.d.ts +27 -0
  16. package/dist/idb/ax-scan-client.js +244 -0
  17. package/dist/idb/ax-scan-client.js.map +1 -0
  18. package/dist/idb/idb-client.d.ts +34 -0
  19. package/dist/idb/idb-client.js +288 -0
  20. package/dist/idb/idb-client.js.map +1 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +15 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/logger.d.ts +3 -0
  25. package/dist/logger.js +6 -0
  26. package/dist/logger.js.map +1 -0
  27. package/dist/mcp-server.d.ts +2 -0
  28. package/dist/mcp-server.js +649 -0
  29. package/dist/mcp-server.js.map +1 -0
  30. package/dist/types.d.ts +75 -0
  31. package/dist/types.js +7 -0
  32. package/dist/types.js.map +1 -0
  33. package/dist/ui-filters.d.ts +11 -0
  34. package/dist/ui-filters.js +100 -0
  35. package/dist/ui-filters.js.map +1 -0
  36. package/dist/viewer/server.d.ts +5 -0
  37. package/dist/viewer/server.js +233 -0
  38. package/dist/viewer/server.js.map +1 -0
  39. package/dist/wda/device-discovery.d.ts +12 -0
  40. package/dist/wda/device-discovery.js +91 -0
  41. package/dist/wda/device-discovery.js.map +1 -0
  42. package/dist/wda/wda-client.d.ts +38 -0
  43. package/dist/wda/wda-client.js +314 -0
  44. package/dist/wda/wda-client.js.map +1 -0
  45. package/dist/wda/wda-manager.d.ts +29 -0
  46. package/dist/wda/wda-manager.js +263 -0
  47. package/dist/wda/wda-manager.js.map +1 -0
  48. package/dist/wda/wda-scan.d.ts +3 -0
  49. package/dist/wda/wda-scan.js +41 -0
  50. package/dist/wda/wda-scan.js.map +1 -0
  51. package/package.json +40 -0
  52. package/src/idb/ax-scan/Makefile +30 -0
  53. package/src/idb/ax-scan/ax-scan.m +168 -0
@@ -0,0 +1,38 @@
1
+ export declare class WDAClient {
2
+ private static instances;
3
+ private baseUrl;
4
+ private sessionId;
5
+ private udid;
6
+ private port;
7
+ private constructor();
8
+ static getInstance(udid: string, port?: number, host?: string): WDAClient;
9
+ static getExistingInstance(udid: string, port?: number): WDAClient | null;
10
+ static removeInstance(udid: string, port?: number): void;
11
+ private request;
12
+ isReachable(): Promise<boolean>;
13
+ createSession(): Promise<void>;
14
+ destroySession(): Promise<void>;
15
+ private ensureSession;
16
+ tap(x: number, y: number, duration?: number): Promise<void>;
17
+ swipe(fromX: number, fromY: number, toX: number, toY: number, duration?: number): Promise<void>;
18
+ inputText(text: string): Promise<void>;
19
+ pressButton(button: string, duration?: number): Promise<void>;
20
+ pressKey(key: number | string, _duration?: number): Promise<void>;
21
+ pressKeySequence(keySequence: (number | string)[]): Promise<void>;
22
+ screenshot(): Promise<Buffer>;
23
+ getWindowSize(): Promise<{
24
+ width: number;
25
+ height: number;
26
+ }>;
27
+ getSource(): Promise<string>;
28
+ describeAll(nested?: boolean): Promise<unknown>;
29
+ describePoint(x: number, y: number, nested?: boolean): Promise<unknown>;
30
+ parseAccessibilityXml(xml: string, nested?: boolean): Record<string, unknown>[];
31
+ private parseXmlAttributes;
32
+ private normalizeElementType;
33
+ private normalizeElement;
34
+ private findElementAtPoint;
35
+ activateApp(bundleId: string): Promise<void>;
36
+ shutdown(): Promise<void>;
37
+ }
38
+ export declare function getWDAClient(udid: string, port?: number): WDAClient;
@@ -0,0 +1,314 @@
1
+ import { log } from '../logger.js';
2
+ export class WDAClient {
3
+ static instances = new Map();
4
+ baseUrl;
5
+ sessionId = null;
6
+ udid;
7
+ port;
8
+ constructor(udid, port = 8100, host) {
9
+ this.udid = udid;
10
+ this.port = port;
11
+ if (host) {
12
+ const hostPart = host.includes(':') ? `[${host}]` : host;
13
+ this.baseUrl = `http://${hostPart}:${port}`;
14
+ }
15
+ else {
16
+ this.baseUrl = `http://localhost:${port}`;
17
+ }
18
+ }
19
+ static getInstance(udid, port = 8100, host) {
20
+ const key = `${udid}:${port}`;
21
+ const existing = WDAClient.instances.get(key);
22
+ if (existing) {
23
+ if (host) {
24
+ const hostPart = host.includes(':') ? `[${host}]` : host;
25
+ existing.baseUrl = `http://${hostPart}:${port}`;
26
+ }
27
+ return existing;
28
+ }
29
+ const instance = new WDAClient(udid, port, host);
30
+ WDAClient.instances.set(key, instance);
31
+ return instance;
32
+ }
33
+ static getExistingInstance(udid, port = 8100) {
34
+ return WDAClient.instances.get(`${udid}:${port}`) ?? null;
35
+ }
36
+ static removeInstance(udid, port = 8100) {
37
+ WDAClient.instances.delete(`${udid}:${port}`);
38
+ }
39
+ async request(method, path, body, _retried) {
40
+ const url = `${this.baseUrl}${path}`;
41
+ const options = {
42
+ method,
43
+ headers: { 'Content-Type': 'application/json' },
44
+ };
45
+ if (body)
46
+ options.body = JSON.stringify(body);
47
+ const response = await fetch(url, options);
48
+ if (!response.ok) {
49
+ const text = await response.text();
50
+ if (response.status === 404 && (text.includes('invalid session id') || text.includes('Session does not exist'))) {
51
+ log('WDAClient', 'warn', `Session ${this.sessionId} is stale, clearing and retrying`);
52
+ this.sessionId = null;
53
+ if (!_retried && path.includes('/session/')) {
54
+ await this.createSession();
55
+ const newPath = path.replace(/\/session\/[^/]+/, `/session/${this.sessionId}`);
56
+ return this.request(method, newPath, body, true);
57
+ }
58
+ }
59
+ let shortMessage = text;
60
+ try {
61
+ const parsed = JSON.parse(text);
62
+ if (parsed?.value?.message) {
63
+ const msg = parsed.value.message;
64
+ const nsDesc = msg.match(/NSLocalizedDescription=([^,}]+)/);
65
+ shortMessage = nsDesc ? nsDesc[1] : msg.split('UserInfo=')[0].trim();
66
+ }
67
+ }
68
+ catch { /* not JSON, use raw text */ }
69
+ log('WDAClient', 'error', `WDA ${method} ${path} -> ${response.status}: ${text}`);
70
+ throw new Error(shortMessage);
71
+ }
72
+ return response.json();
73
+ }
74
+ async isReachable() {
75
+ try {
76
+ const status = await this.request('GET', '/status');
77
+ return status.value?.ready === true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ async createSession() {
84
+ if (this.sessionId)
85
+ return;
86
+ log('WDAClient', 'log', `Creating WDA session for ${this.udid}...`);
87
+ const response = await this.request('POST', '/session', {
88
+ capabilities: { alwaysMatch: {}, firstMatch: [{}] },
89
+ });
90
+ this.sessionId = response.value?.sessionId ?? response.sessionId;
91
+ log('WDAClient', 'log', `WDA session created: ${this.sessionId}`);
92
+ }
93
+ async destroySession() {
94
+ if (!this.sessionId)
95
+ return;
96
+ try {
97
+ await this.request('DELETE', `/session/${this.sessionId}`);
98
+ }
99
+ catch (e) {
100
+ log('WDAClient', 'warn', `Failed to destroy WDA session: ${e}`);
101
+ }
102
+ this.sessionId = null;
103
+ }
104
+ async ensureSession() {
105
+ if (!this.sessionId)
106
+ await this.createSession();
107
+ return this.sessionId;
108
+ }
109
+ async tap(x, y, duration) {
110
+ const sessionId = await this.ensureSession();
111
+ log('WDAClient', 'log', `Tap at (${x}, ${y}) duration=${duration}`);
112
+ if (duration && duration > 0) {
113
+ await this.request('POST', `/session/${sessionId}/wda/touchAndHold`, { x, y, duration });
114
+ }
115
+ else {
116
+ await this.request('POST', `/session/${sessionId}/actions`, {
117
+ actions: [{
118
+ type: 'pointer',
119
+ id: 'finger1',
120
+ parameters: { pointerType: 'touch' },
121
+ actions: [
122
+ { type: 'pointerMove', duration: 0, x, y },
123
+ { type: 'pointerDown', button: 0 },
124
+ { type: 'pause', duration: 100 },
125
+ { type: 'pointerUp', button: 0 },
126
+ ],
127
+ }],
128
+ });
129
+ }
130
+ }
131
+ async swipe(fromX, fromY, toX, toY, duration) {
132
+ const sessionId = await this.ensureSession();
133
+ const moveDuration = Math.round((duration ?? 0.3) * 1000);
134
+ log('WDAClient', 'log', `Swipe from (${fromX}, ${fromY}) to (${toX}, ${toY}) duration=${moveDuration}ms`);
135
+ await this.request('POST', `/session/${sessionId}/actions`, {
136
+ actions: [{
137
+ type: 'pointer',
138
+ id: 'finger1',
139
+ parameters: { pointerType: 'touch' },
140
+ actions: [
141
+ { type: 'pointerMove', duration: 0, x: fromX, y: fromY },
142
+ { type: 'pointerDown', button: 0 },
143
+ { type: 'pointerMove', duration: moveDuration, x: toX, y: toY, origin: 'viewport' },
144
+ { type: 'pointerUp', button: 0 },
145
+ ],
146
+ }],
147
+ });
148
+ }
149
+ async inputText(text) {
150
+ const sessionId = await this.ensureSession();
151
+ log('WDAClient', 'log', `Input text: "${text.slice(0, 50)}"`);
152
+ await this.request('POST', `/session/${sessionId}/wda/keys`, { value: [...text] });
153
+ }
154
+ async pressButton(button, duration) {
155
+ const sessionId = await this.ensureSession();
156
+ log('WDAClient', 'log', `Press button: ${button} duration=${duration}`);
157
+ const buttonMap = {
158
+ HOME: 'home',
159
+ LOCK: 'lock',
160
+ SIDE_BUTTON: 'lock',
161
+ VOLUME_UP: 'volumeUp',
162
+ VOLUME_DOWN: 'volumeDown',
163
+ };
164
+ const wdaButton = buttonMap[button] ?? button.toLowerCase();
165
+ await this.request('POST', `/session/${sessionId}/wda/pressButton`, {
166
+ name: wdaButton,
167
+ ...(duration ? { duration } : {}),
168
+ });
169
+ }
170
+ async pressKey(key, _duration) {
171
+ if (typeof key === 'string') {
172
+ await this.inputText(key);
173
+ return;
174
+ }
175
+ log('WDAClient', 'warn', `pressKey with HID keycode ${key} not supported on physical device`);
176
+ }
177
+ async pressKeySequence(keySequence) {
178
+ const text = keySequence.filter((k) => typeof k === 'string').join('');
179
+ if (text)
180
+ await this.inputText(text);
181
+ }
182
+ async screenshot() {
183
+ let sessionId = await this.ensureSession();
184
+ try {
185
+ const response = await this.request('GET', `/session/${sessionId}/screenshot`);
186
+ return Buffer.from(response.value, 'base64');
187
+ }
188
+ catch (e) {
189
+ if (!this.sessionId) {
190
+ sessionId = await this.ensureSession();
191
+ const response = await this.request('GET', `/session/${sessionId}/screenshot`);
192
+ return Buffer.from(response.value, 'base64');
193
+ }
194
+ throw e;
195
+ }
196
+ }
197
+ async getWindowSize() {
198
+ const sessionId = await this.ensureSession();
199
+ const response = await this.request('GET', `/session/${sessionId}/window/size`);
200
+ return response.value;
201
+ }
202
+ async getSource() {
203
+ const sessionId = await this.ensureSession();
204
+ const response = await this.request('GET', `/session/${sessionId}/source`);
205
+ return response.value;
206
+ }
207
+ async describeAll(nested) {
208
+ const xml = await this.getSource();
209
+ return this.parseAccessibilityXml(xml, nested);
210
+ }
211
+ async describePoint(x, y, nested) {
212
+ const xml = await this.getSource();
213
+ return this.findElementAtPoint(this.parseAccessibilityXml(xml, nested ?? true), x, y);
214
+ }
215
+ parseAccessibilityXml(xml, nested) {
216
+ const elements = [];
217
+ const elementRegex = /<(XCUIElementType\w+)\s+([^>]*?)(\s*\/>|>)/g;
218
+ let match;
219
+ while ((match = elementRegex.exec(xml)) !== null) {
220
+ const attrs = this.parseXmlAttributes(match[2]);
221
+ const elementType = this.normalizeElementType(match[1]);
222
+ const element = {
223
+ type: elementType,
224
+ AXLabel: attrs.label ?? null,
225
+ AXValue: attrs.value ?? null,
226
+ title: attrs.name ?? null,
227
+ frame: {
228
+ x: Number(attrs.x ?? 0),
229
+ y: Number(attrs.y ?? 0),
230
+ width: Number(attrs.width ?? 0),
231
+ height: Number(attrs.height ?? 0),
232
+ },
233
+ AXUniqueId: attrs.accessible === 'true' ? attrs.name : null,
234
+ enabled: attrs.enabled === 'true',
235
+ visible: attrs.visible === 'true',
236
+ };
237
+ if (!nested) {
238
+ if (element.visible && (element.enabled || elementType === 'StaticText')) {
239
+ elements.push(element);
240
+ }
241
+ }
242
+ else {
243
+ elements.push(element);
244
+ }
245
+ }
246
+ return elements;
247
+ }
248
+ parseXmlAttributes(attrString) {
249
+ const attrs = {};
250
+ const attrRegex = /(\w+)="([^"]*)"/g;
251
+ let match;
252
+ while ((match = attrRegex.exec(attrString)) !== null) {
253
+ attrs[match[1]] = match[2];
254
+ }
255
+ return attrs;
256
+ }
257
+ normalizeElementType(wdaType) {
258
+ return wdaType.replace('XCUIElementType', '');
259
+ }
260
+ normalizeElement(raw) {
261
+ return {
262
+ type: this.normalizeElementType(raw.type ?? ''),
263
+ AXLabel: raw.label ?? null,
264
+ AXValue: raw.value ?? null,
265
+ frame: raw.rect ?? { x: 0, y: 0, width: 0, height: 0 },
266
+ enabled: raw.isEnabled ?? true,
267
+ visible: raw.isVisible ?? true,
268
+ };
269
+ }
270
+ findElementAtPoint(tree, x, y) {
271
+ if (!tree || typeof tree !== 'object')
272
+ return null;
273
+ const elements = Array.isArray(tree) ? tree : tree.children ?? [];
274
+ let best = null;
275
+ let bestArea = Infinity;
276
+ for (const el of elements) {
277
+ const r = el.rect ?? { x: 0, y: 0, width: 0, height: 0 };
278
+ if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height) {
279
+ const area = r.width * r.height;
280
+ if (area < bestArea) {
281
+ bestArea = area;
282
+ best = el;
283
+ }
284
+ }
285
+ if (el.children) {
286
+ const child = this.findElementAtPoint({ children: el.children }, x, y);
287
+ if (child) {
288
+ const cr = child.rect;
289
+ if (cr) {
290
+ const childArea = cr.width * cr.height;
291
+ if (childArea < bestArea) {
292
+ bestArea = childArea;
293
+ best = child;
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ return best ? this.normalizeElement(best) : null;
300
+ }
301
+ async activateApp(bundleId) {
302
+ const sessionId = await this.ensureSession();
303
+ await this.request('POST', `/session/${sessionId}/wda/apps/activate`, { bundleId });
304
+ }
305
+ async shutdown() {
306
+ await this.destroySession();
307
+ WDAClient.removeInstance(this.udid, this.port);
308
+ log('WDAClient', 'log', `[${this.udid}] Shutdown complete`);
309
+ }
310
+ }
311
+ export function getWDAClient(udid, port = 8100) {
312
+ return WDAClient.getInstance(udid, port);
313
+ }
314
+ //# sourceMappingURL=wda-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wda-client.js","sourceRoot":"","sources":["../../src/wda/wda-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { WDAClient } from './wda-client.js';
2
+ export type WDASetupStep = 'connecting' | 'building_wda' | 'installing_wda' | 'establishing_connection' | 'ready';
3
+ export interface WDASetupProgress {
4
+ step: WDASetupStep;
5
+ message: string;
6
+ error?: string;
7
+ }
8
+ type ProgressCallback = (progress: WDASetupProgress) => void;
9
+ export declare class WDAManager {
10
+ private static instance;
11
+ private wdaProcesses;
12
+ static getInstance(): WDAManager;
13
+ private getWdaProjectPath;
14
+ private ensureWdaSource;
15
+ private getDerivedDataPath;
16
+ private detectTeamId;
17
+ getTunnelAddress(udid: string): Promise<string>;
18
+ isWDARunning(udid: string, port?: number): Promise<boolean>;
19
+ setupDevice(udid: string, onProgress?: ProgressCallback, port?: number): Promise<WDAClient>;
20
+ quickConnect(udid: string, onProgress?: ProgressCallback, port?: number): Promise<WDAClient>;
21
+ private verifyDeviceConnected;
22
+ private buildWDA;
23
+ private launchWDA;
24
+ private waitForWDA;
25
+ teardownDevice(udid: string): Promise<void>;
26
+ shutdownAll(): Promise<void>;
27
+ }
28
+ export declare const wdaManager: WDAManager;
29
+ export {};
@@ -0,0 +1,263 @@
1
+ import { spawn, exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { existsSync, mkdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { homedir } from 'os';
6
+ import { log } from '../logger.js';
7
+ import { childEnv } from '../child-env.js';
8
+ import { WDAClient } from './wda-client.js';
9
+ const execAsync = promisify(exec);
10
+ export class WDAManager {
11
+ static instance = null;
12
+ wdaProcesses = new Map();
13
+ static getInstance() {
14
+ if (!WDAManager.instance) {
15
+ WDAManager.instance = new WDAManager();
16
+ }
17
+ return WDAManager.instance;
18
+ }
19
+ getWdaProjectPath() {
20
+ const mcpPath = join(homedir(), '.blitz-iphone-mcp', 'wda-build', 'WebDriverAgent');
21
+ if (existsSync(join(mcpPath, 'WebDriverAgent.xcodeproj')))
22
+ return mcpPath;
23
+ const blitzPath = join(homedir(), '.blitz', 'wda-build', 'WebDriverAgent');
24
+ if (existsSync(join(blitzPath, 'WebDriverAgent.xcodeproj')))
25
+ return blitzPath;
26
+ throw new Error('WebDriverAgent not found. Run `npx @blitzdev/iphone-mcp --setup` to install it.');
27
+ }
28
+ async ensureWdaSource() {
29
+ try {
30
+ return this.getWdaProjectPath();
31
+ }
32
+ catch {
33
+ const targetDir = join(homedir(), '.blitz-iphone-mcp', 'wda-build');
34
+ mkdirSync(targetDir, { recursive: true });
35
+ const wdaDir = join(targetDir, 'WebDriverAgent');
36
+ log('WDAManager', 'log', 'Cloning WebDriverAgent from GitHub...');
37
+ await execAsync(`git clone --depth 1 https://github.com/appium/WebDriverAgent.git "${wdaDir}"`, { timeout: 120_000, env: childEnv() });
38
+ log('WDAManager', 'log', 'WebDriverAgent cloned successfully');
39
+ return wdaDir;
40
+ }
41
+ }
42
+ getDerivedDataPath() {
43
+ const dir = join(homedir(), '.blitz-iphone-mcp', 'wda-build');
44
+ if (!existsSync(dir))
45
+ mkdirSync(dir, { recursive: true });
46
+ return dir;
47
+ }
48
+ async detectTeamId() {
49
+ try {
50
+ const { stdout } = await execAsync('defaults read com.apple.dt.Xcode IDEProvisioningTeams', { env: childEnv() });
51
+ const match = stdout.match(/teamID\s*=\s*([A-Z0-9]{10})/);
52
+ if (match)
53
+ return match[1];
54
+ }
55
+ catch {
56
+ // Xcode prefs not available
57
+ }
58
+ throw new Error('No development team found. Sign in to Xcode with your Apple ID first (Xcode -> Settings -> Accounts).');
59
+ }
60
+ async getTunnelAddress(udid) {
61
+ try {
62
+ const { stdout } = await execAsync('xcrun devicectl list devices --json-output /dev/stdout 2>/dev/null', { env: childEnv() });
63
+ const jsonStart = stdout.indexOf('{');
64
+ if (jsonStart < 0)
65
+ throw new Error('No JSON in devicectl output');
66
+ const json = JSON.parse(stdout.substring(jsonStart));
67
+ const devices = json?.result?.devices ?? [];
68
+ for (const d of devices) {
69
+ const devUdid = d.hardwareProperties?.udid;
70
+ if (devUdid !== udid)
71
+ continue;
72
+ const tunnelIP = d.connectionProperties?.tunnelIPAddress;
73
+ if (tunnelIP) {
74
+ log('WDAManager', 'log', `Tunnel IP for ${udid}: ${tunnelIP}`);
75
+ return tunnelIP;
76
+ }
77
+ }
78
+ }
79
+ catch (e) {
80
+ log('WDAManager', 'error', `Failed to get tunnel address: ${e.message}`);
81
+ }
82
+ throw new Error('No CoreDevice tunnel found. Ensure iPhone is connected via USB and trusted.');
83
+ }
84
+ async isWDARunning(udid, port = 8100) {
85
+ try {
86
+ const tunnelIP = await this.getTunnelAddress(udid);
87
+ const client = WDAClient.getInstance(udid, port, tunnelIP);
88
+ return client.isReachable();
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ async setupDevice(udid, onProgress, port = 8100) {
95
+ const report = (step, message) => {
96
+ log('WDAManager', 'log', `[${udid}] ${step}: ${message}`);
97
+ onProgress?.({ step, message });
98
+ };
99
+ report('connecting', 'Verifying device connection...');
100
+ await this.verifyDeviceConnected(udid);
101
+ report('connecting', 'Device connected.');
102
+ if (await this.isWDARunning(udid, port)) {
103
+ report('establishing_connection', 'WDA already running, creating session...');
104
+ const tunnelIP = await this.getTunnelAddress(udid);
105
+ const client = WDAClient.getInstance(udid, port, tunnelIP);
106
+ await client.createSession();
107
+ report('ready', 'Connected to device.');
108
+ return client;
109
+ }
110
+ report('building_wda', 'Building WebDriverAgent...');
111
+ await this.buildWDA(udid);
112
+ report('building_wda', 'Build complete.');
113
+ report('installing_wda', 'Launching WebDriverAgent on device...');
114
+ await this.launchWDA(udid);
115
+ report('installing_wda', 'WebDriverAgent launched.');
116
+ report('establishing_connection', 'Waiting for WebDriverAgent...');
117
+ const tunnelIP = await this.getTunnelAddress(udid);
118
+ await this.waitForWDA(udid, port, tunnelIP);
119
+ const client = WDAClient.getInstance(udid, port, tunnelIP);
120
+ await client.createSession();
121
+ report('ready', 'Connected to device.');
122
+ return client;
123
+ }
124
+ async quickConnect(udid, onProgress, port = 8100) {
125
+ const report = (step, message) => {
126
+ log('WDAManager', 'log', `[${udid}] ${step}: ${message}`);
127
+ onProgress?.({ step, message });
128
+ };
129
+ report('establishing_connection', 'Connecting to WebDriverAgent...');
130
+ const tunnelIP = await this.getTunnelAddress(udid);
131
+ await this.waitForWDA(udid, port, tunnelIP);
132
+ const client = WDAClient.getInstance(udid, port, tunnelIP);
133
+ await client.createSession();
134
+ report('ready', 'Connected to device.');
135
+ return client;
136
+ }
137
+ async verifyDeviceConnected(udid) {
138
+ try {
139
+ const { stdout } = await execAsync('xcrun devicectl list devices --json-output /dev/stdout 2>/dev/null', { env: childEnv() });
140
+ const jsonStart = stdout.indexOf('{');
141
+ if (jsonStart < 0)
142
+ throw new Error(`Device ${udid} not found. Connect your iPhone via USB.`);
143
+ const json = JSON.parse(stdout.substring(jsonStart));
144
+ const devices = json?.result?.devices ?? [];
145
+ const found = devices.some((d) => d.hardwareProperties?.udid === udid || d.identifier === udid);
146
+ if (!found)
147
+ throw new Error(`Device ${udid} not found. Connect your iPhone via USB.`);
148
+ }
149
+ catch (e) {
150
+ if (e.message.includes('not found'))
151
+ throw e;
152
+ throw new Error(`Failed to verify device connection: ${e.message}`);
153
+ }
154
+ }
155
+ async buildWDA(udid) {
156
+ const wdaPath = await this.ensureWdaSource();
157
+ const derivedData = this.getDerivedDataPath();
158
+ const teamId = await this.detectTeamId();
159
+ const args = [
160
+ 'build-for-testing',
161
+ '-project', join(wdaPath, 'WebDriverAgent.xcodeproj'),
162
+ '-scheme', 'WebDriverAgentRunner',
163
+ '-destination', `id=${udid}`,
164
+ '-derivedDataPath', derivedData,
165
+ '-allowProvisioningUpdates',
166
+ `DEVELOPMENT_TEAM=${teamId}`,
167
+ ];
168
+ log('WDAManager', 'log', `Building WDA: xcodebuild ${args.join(' ')}`);
169
+ return new Promise((resolve, reject) => {
170
+ const proc = spawn('xcodebuild', args, { stdio: ['pipe', 'pipe', 'pipe'], env: childEnv() });
171
+ let stderr = '';
172
+ proc.stderr?.on('data', (data) => { stderr += data.toString(); });
173
+ proc.on('close', (code) => {
174
+ if (code === 0) {
175
+ log('WDAManager', 'log', 'WDA build succeeded');
176
+ resolve();
177
+ }
178
+ else {
179
+ log('WDAManager', 'error', `WDA build failed (code ${code}): ${stderr.slice(-500)}`);
180
+ if (stderr.includes('Developer Mode')) {
181
+ reject(new Error('Enable Developer Mode on your iPhone: Settings -> Privacy & Security -> Developer Mode'));
182
+ }
183
+ else if (stderr.includes('provisioning')) {
184
+ reject(new Error('Provisioning error. Ensure your Apple ID is signed into Xcode.'));
185
+ }
186
+ else if (stderr.includes('Trust This Computer')) {
187
+ reject(new Error('Tap "Trust This Computer" on your iPhone.'));
188
+ }
189
+ else {
190
+ reject(new Error(`WDA build failed with code ${code}`));
191
+ }
192
+ }
193
+ });
194
+ proc.on('error', (err) => {
195
+ reject(new Error(`Failed to start xcodebuild: ${err.message}`));
196
+ });
197
+ });
198
+ }
199
+ async launchWDA(udid) {
200
+ const existing = this.wdaProcesses.get(udid);
201
+ if (existing) {
202
+ existing.kill('SIGTERM');
203
+ this.wdaProcesses.delete(udid);
204
+ }
205
+ const wdaPath = this.getWdaProjectPath();
206
+ const derivedData = this.getDerivedDataPath();
207
+ const args = [
208
+ 'test-without-building',
209
+ '-project', join(wdaPath, 'WebDriverAgent.xcodeproj'),
210
+ '-scheme', 'WebDriverAgentRunner',
211
+ '-destination', `id=${udid}`,
212
+ '-derivedDataPath', derivedData,
213
+ ];
214
+ log('WDAManager', 'log', `Launching WDA: xcodebuild ${args.join(' ')}`);
215
+ const proc = spawn('xcodebuild', args, { stdio: ['pipe', 'pipe', 'pipe'], env: childEnv() });
216
+ this.wdaProcesses.set(udid, proc);
217
+ proc.stderr?.on('data', (data) => {
218
+ const text = data.toString();
219
+ if (text.includes('ServerURLHere')) {
220
+ log('WDAManager', 'log', `WDA server started on device ${udid}`);
221
+ }
222
+ });
223
+ proc.on('exit', (code, signal) => {
224
+ log('WDAManager', 'log', `WDA process exited for ${udid}: code=${code} signal=${signal}`);
225
+ this.wdaProcesses.delete(udid);
226
+ });
227
+ proc.on('error', (err) => {
228
+ log('WDAManager', 'error', `WDA process error for ${udid}: ${err.message}`);
229
+ this.wdaProcesses.delete(udid);
230
+ });
231
+ await new Promise(resolve => setTimeout(resolve, 3000));
232
+ }
233
+ async waitForWDA(udid, port, tunnelIP, maxRetries = 30) {
234
+ const client = WDAClient.getInstance(udid, port, tunnelIP);
235
+ for (let i = 0; i < maxRetries; i++) {
236
+ if (await client.isReachable()) {
237
+ log('WDAManager', 'log', `WDA reachable on attempt ${i + 1}`);
238
+ return;
239
+ }
240
+ await new Promise(resolve => setTimeout(resolve, 1000));
241
+ }
242
+ throw new Error('WebDriverAgent did not become reachable in time. Check that your iPhone is unlocked.');
243
+ }
244
+ async teardownDevice(udid) {
245
+ const wdaProc = this.wdaProcesses.get(udid);
246
+ if (wdaProc) {
247
+ wdaProc.kill('SIGTERM');
248
+ this.wdaProcesses.delete(udid);
249
+ }
250
+ const client = WDAClient.getInstance(udid);
251
+ await client.shutdown();
252
+ log('WDAManager', 'log', `[${udid}] Device torn down`);
253
+ }
254
+ async shutdownAll() {
255
+ for (const [udid, proc] of this.wdaProcesses) {
256
+ proc.kill('SIGTERM');
257
+ log('WDAManager', 'log', `Killed WDA process for ${udid}`);
258
+ }
259
+ this.wdaProcesses.clear();
260
+ }
261
+ }
262
+ export const wdaManager = WDAManager.getInstance();
263
+ //# sourceMappingURL=wda-manager.js.map