@mcesystems/apple-kit 1.0.0

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 (47) hide show
  1. package/README.md +250 -0
  2. package/dist/index.js +680 -0
  3. package/dist/index.js.map +7 -0
  4. package/dist/types/index.d.ts +11 -0
  5. package/dist/types/index.d.ts.map +1 -0
  6. package/dist/types/logic/appleDeviceKit.d.ts +68 -0
  7. package/dist/types/logic/appleDeviceKit.d.ts.map +1 -0
  8. package/dist/types/logic/devicesMonitor.d.ts +45 -0
  9. package/dist/types/logic/devicesMonitor.d.ts.map +1 -0
  10. package/dist/types/types.d.ts +127 -0
  11. package/dist/types/types.d.ts.map +1 -0
  12. package/dist/types/utils/debug.d.ts +9 -0
  13. package/dist/types/utils/debug.d.ts.map +1 -0
  14. package/dist/types/utils/exec.d.ts +14 -0
  15. package/dist/types/utils/exec.d.ts.map +1 -0
  16. package/dist/types/utils/idevicePath.d.ts +19 -0
  17. package/dist/types/utils/idevicePath.d.ts.map +1 -0
  18. package/package.json +63 -0
  19. package/resources/bin/windows/bz2.dll +0 -0
  20. package/resources/bin/windows/getopt.dll +0 -0
  21. package/resources/bin/windows/iconv-2.dll +0 -0
  22. package/resources/bin/windows/idevice_id.exe +0 -0
  23. package/resources/bin/windows/ideviceactivation.exe +0 -0
  24. package/resources/bin/windows/idevicedebug.exe +0 -0
  25. package/resources/bin/windows/ideviceinfo.exe +0 -0
  26. package/resources/bin/windows/ideviceinstaller.exe +0 -0
  27. package/resources/bin/windows/idevicepair.exe +0 -0
  28. package/resources/bin/windows/imobiledevice.dll +0 -0
  29. package/resources/bin/windows/iproxy.exe +0 -0
  30. package/resources/bin/windows/libcrypto-1_1-x64.dll +0 -0
  31. package/resources/bin/windows/libcurl.dll +0 -0
  32. package/resources/bin/windows/libssl-1_1-x64.dll +0 -0
  33. package/resources/bin/windows/libusb-1.0.dll +0 -0
  34. package/resources/bin/windows/libusb0.dll +0 -0
  35. package/resources/bin/windows/libxml2.dll +0 -0
  36. package/resources/bin/windows/lzma.dll +0 -0
  37. package/resources/bin/windows/pcre.dll +0 -0
  38. package/resources/bin/windows/pcreposix.dll +0 -0
  39. package/resources/bin/windows/plist.dll +0 -0
  40. package/resources/bin/windows/pthreadVC3.dll +0 -0
  41. package/resources/bin/windows/readline.dll +0 -0
  42. package/resources/bin/windows/usbmuxd.dll +0 -0
  43. package/resources/bin/windows/usbmuxd.exe +0 -0
  44. package/resources/bin/windows/vcruntime140.dll +0 -0
  45. package/resources/bin/windows/zip.dll +0 -0
  46. package/resources/bin/windows/zlib1.dll +0 -0
  47. package/resources/licenses/LGPL-2.1.txt +33 -0
package/dist/index.js ADDED
@@ -0,0 +1,680 @@
1
+ // src/logic/appleDeviceKit.ts
2
+ import { spawn } from "node:child_process";
3
+
4
+ // src/utils/debug.ts
5
+ import createDebug from "debug";
6
+ var debug = createDebug("apple-kit");
7
+ var debugTask = createDebug("apple-kit:task");
8
+ function logInfo(message) {
9
+ debug(message);
10
+ }
11
+ function logTask(message) {
12
+ debugTask(message);
13
+ }
14
+
15
+ // src/utils/exec.ts
16
+ import { exec as execCallback } from "node:child_process";
17
+ import { promisify } from "node:util";
18
+
19
+ // src/utils/idevicePath.ts
20
+ import { existsSync } from "node:fs";
21
+ import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ var __filename = fileURLToPath(import.meta.url);
24
+ var __dirname = dirname(__filename);
25
+ function getResourcesBinPath() {
26
+ const packageRoot = dirname(dirname(__dirname));
27
+ const platform = process.platform;
28
+ let platformDir;
29
+ switch (platform) {
30
+ case "win32":
31
+ platformDir = "windows";
32
+ break;
33
+ case "darwin":
34
+ platformDir = "darwin";
35
+ break;
36
+ case "linux":
37
+ platformDir = "linux";
38
+ break;
39
+ default:
40
+ throw new Error(`Unsupported platform: ${platform}`);
41
+ }
42
+ return join(packageRoot, "resources", "bin", platformDir);
43
+ }
44
+ function getIDeviceToolPath(toolName) {
45
+ const binPath = getResourcesBinPath();
46
+ const ext = process.platform === "win32" ? ".exe" : "";
47
+ const toolPath = join(binPath, `${toolName}${ext}`);
48
+ if (existsSync(toolPath)) {
49
+ return toolPath;
50
+ }
51
+ return null;
52
+ }
53
+ function getIDeviceBinPath() {
54
+ return getResourcesBinPath();
55
+ }
56
+
57
+ // src/utils/exec.ts
58
+ var execAsync = promisify(execCallback);
59
+ async function execIDevice(command, options = {}) {
60
+ const binPath = getIDeviceBinPath();
61
+ const env = {
62
+ ...process.env,
63
+ PATH: `${binPath};${process.env.PATH}`
64
+ };
65
+ const result = await execAsync(command, {
66
+ ...options,
67
+ env,
68
+ windowsHide: true,
69
+ encoding: "utf8"
70
+ });
71
+ return {
72
+ stdout: result.stdout.toString(),
73
+ stderr: result.stderr.toString()
74
+ };
75
+ }
76
+ async function runIDeviceTool(toolPath, args = [], options = {}) {
77
+ const command = `"${toolPath}" ${args.map((a) => `"${a}"`).join(" ")}`;
78
+ return execIDevice(command, options);
79
+ }
80
+
81
+ // src/logic/appleDeviceKit.ts
82
+ function parsePlistOutput(output) {
83
+ const result = {};
84
+ const lines = output.split("\n");
85
+ for (const line of lines) {
86
+ const colonIndex = line.indexOf(":");
87
+ if (colonIndex > 0) {
88
+ const key = line.substring(0, colonIndex).trim();
89
+ const value = line.substring(colonIndex + 1).trim();
90
+ result[key] = value;
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ function parseDeviceList(output) {
96
+ const devices = [];
97
+ const lines = output.trim().split("\n");
98
+ for (const line of lines) {
99
+ const trimmed = line.trim();
100
+ if (trimmed) {
101
+ const match = trimmed.match(/^([A-Fa-f0-9-]+)(?:\s+\((\w+)\))?/);
102
+ if (match) {
103
+ devices.push({
104
+ udid: match[1],
105
+ connectionType: match[2] === "Network" ? 2 : 1
106
+ });
107
+ }
108
+ }
109
+ }
110
+ return devices;
111
+ }
112
+ function parseAppList(output) {
113
+ const apps = [];
114
+ const lines = output.trim().split("\n");
115
+ for (const line of lines) {
116
+ const match = line.match(/^([^,]+),\s*([^,]+),\s*"?([^"]+)"?/);
117
+ if (match) {
118
+ apps.push({
119
+ bundleId: match[1].trim(),
120
+ version: match[2].trim(),
121
+ displayName: match[3].trim(),
122
+ bundleVersion: ""
123
+ });
124
+ }
125
+ }
126
+ return apps;
127
+ }
128
+ var AppleDeviceKit = class {
129
+ constructor(udid, port) {
130
+ this.port = port;
131
+ this.deviceId = udid;
132
+ logInfo(`AppleDeviceKit initialized for device: ${this.deviceId}`);
133
+ }
134
+ deviceId;
135
+ // ==================== Device Info ====================
136
+ /**
137
+ * Get detailed device information
138
+ */
139
+ async getDeviceInfo() {
140
+ logTask(`Getting device info for ${this.deviceId}`);
141
+ const toolPath = getIDeviceToolPath("ideviceinfo");
142
+ if (!toolPath) {
143
+ throw new Error("ideviceinfo tool not found");
144
+ }
145
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId]);
146
+ const props = parsePlistOutput(stdout);
147
+ return {
148
+ deviceName: props.DeviceName || "",
149
+ productType: props.ProductType || "",
150
+ productVersion: props.ProductVersion || "",
151
+ buildVersion: props.BuildVersion || "",
152
+ serialNumber: props.SerialNumber || "",
153
+ udid: props.UniqueDeviceID || this.deviceId,
154
+ wifiAddress: props.WiFiAddress || "",
155
+ bluetoothAddress: props.BluetoothAddress || "",
156
+ phoneNumber: props.PhoneNumber || "",
157
+ cpuArchitecture: props.CPUArchitecture || "",
158
+ hardwareModel: props.HardwareModel || "",
159
+ modelNumber: props.ModelNumber || "",
160
+ regionInfo: props.RegionInfo || "",
161
+ timeZone: props.TimeZone || "",
162
+ uniqueChipID: props.UniqueChipID || "",
163
+ isPaired: true
164
+ // If we can get info, device is paired
165
+ };
166
+ }
167
+ // ==================== Trust/Pairing ====================
168
+ /**
169
+ * Check if device is paired/trusted with this computer
170
+ */
171
+ async isPaired() {
172
+ logTask(`Checking pairing status for ${this.deviceId}`);
173
+ const toolPath = getIDeviceToolPath("idevicepair");
174
+ if (!toolPath) {
175
+ throw new Error("idevicepair tool not found");
176
+ }
177
+ try {
178
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "validate"]);
179
+ return stdout.toLowerCase().includes("success");
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+ /**
185
+ * Wait for device to be paired
186
+ * Polls the pairing status until successful or timeout
187
+ *
188
+ * @param timeout Timeout in milliseconds (default: 120000)
189
+ * @param pollInterval Poll interval in milliseconds (default: 1000)
190
+ */
191
+ async waitForPairing(timeout = 12e4, pollInterval = 1e3) {
192
+ logTask(`Waiting for pairing on device ${this.deviceId}`);
193
+ const startTime = Date.now();
194
+ while (Date.now() - startTime < timeout) {
195
+ if (await this.isPaired()) {
196
+ logInfo(`Device ${this.deviceId} is now paired`);
197
+ return true;
198
+ }
199
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
200
+ }
201
+ throw new Error(`Timeout waiting for device pairing after ${timeout}ms`);
202
+ }
203
+ /**
204
+ * Attempt to pair/trust the device
205
+ * User must accept the trust dialog on the device
206
+ */
207
+ async pair() {
208
+ logTask(`Initiating pairing for device ${this.deviceId}`);
209
+ const toolPath = getIDeviceToolPath("idevicepair");
210
+ if (!toolPath) {
211
+ throw new Error("idevicepair tool not found");
212
+ }
213
+ try {
214
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "pair"]);
215
+ return stdout.toLowerCase().includes("success");
216
+ } catch (error) {
217
+ const errorMsg = error instanceof Error ? error.message : String(error);
218
+ if (errorMsg.includes("Please accept the trust dialog")) {
219
+ return false;
220
+ }
221
+ throw error;
222
+ }
223
+ }
224
+ /**
225
+ * Unpair/untrust the device
226
+ */
227
+ async unpair() {
228
+ logTask(`Unpairing device ${this.deviceId}`);
229
+ const toolPath = getIDeviceToolPath("idevicepair");
230
+ if (!toolPath) {
231
+ throw new Error("idevicepair tool not found");
232
+ }
233
+ try {
234
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "unpair"]);
235
+ return stdout.toLowerCase().includes("success");
236
+ } catch {
237
+ return false;
238
+ }
239
+ }
240
+ // ==================== App Installation ====================
241
+ /**
242
+ * Install an IPA file on the device (install agent)
243
+ *
244
+ * @param ipaPath Path to the IPA file
245
+ */
246
+ async installApp(ipaPath) {
247
+ logTask(`Installing app ${ipaPath} on device ${this.deviceId}`);
248
+ if (!await this.isPaired()) {
249
+ await this.waitForPairing(1e4);
250
+ }
251
+ const toolPath = getIDeviceToolPath("ideviceinstaller");
252
+ if (!toolPath) {
253
+ throw new Error("ideviceinstaller tool not found");
254
+ }
255
+ await runIDeviceTool(toolPath, ["-u", this.deviceId, "-i", ipaPath]);
256
+ }
257
+ /**
258
+ * Uninstall an app by bundle ID (uninstall agent)
259
+ *
260
+ * @param bundleId Application bundle identifier
261
+ */
262
+ async uninstallApp(bundleId) {
263
+ logTask(`Uninstalling app ${bundleId} from device ${this.deviceId}`);
264
+ if (!await this.isPaired()) {
265
+ await this.waitForPairing(1e4);
266
+ }
267
+ const toolPath = getIDeviceToolPath("ideviceinstaller");
268
+ if (!toolPath) {
269
+ throw new Error("ideviceinstaller tool not found");
270
+ }
271
+ await runIDeviceTool(toolPath, ["-u", this.deviceId, "-U", bundleId]);
272
+ }
273
+ /**
274
+ * Check if an app is installed on the device
275
+ *
276
+ * @param bundleId Application bundle identifier
277
+ */
278
+ async isAppInstalled(bundleId) {
279
+ logTask(`Checking if app ${bundleId} is installed on device ${this.deviceId}`);
280
+ const apps = await this.listApps();
281
+ return apps.some((app) => app.bundleId === bundleId);
282
+ }
283
+ /**
284
+ * List all installed user applications
285
+ */
286
+ async listApps() {
287
+ logTask(`Listing apps on device ${this.deviceId}`);
288
+ if (!await this.isPaired()) {
289
+ await this.waitForPairing(1e4);
290
+ }
291
+ const toolPath = getIDeviceToolPath("ideviceinstaller");
292
+ if (!toolPath) {
293
+ throw new Error("ideviceinstaller tool not found");
294
+ }
295
+ try {
296
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "-l"]);
297
+ return parseAppList(stdout);
298
+ } catch {
299
+ return [];
300
+ }
301
+ }
302
+ // ==================== Launch Application ====================
303
+ /**
304
+ * Launch an application on the device
305
+ *
306
+ * @param bundleId Application bundle identifier to launch
307
+ * @param args Optional arguments to pass to the app
308
+ */
309
+ async launchApp(bundleId, args = []) {
310
+ logTask(`Launching app ${bundleId} on device ${this.deviceId}`);
311
+ if (!await this.isPaired()) {
312
+ await this.waitForPairing(1e4);
313
+ }
314
+ const toolPath = getIDeviceToolPath("idevicedebug");
315
+ if (!toolPath) {
316
+ throw new Error("idevicedebug tool not found");
317
+ }
318
+ const toolArgs = ["-u", this.deviceId, "run", bundleId, ...args];
319
+ await runIDeviceTool(toolPath, toolArgs);
320
+ }
321
+ // ==================== Port Forwarding ====================
322
+ /**
323
+ * Start port forwarding from local port to device port
324
+ *
325
+ * @param localPort Local port to listen on
326
+ * @param devicePort Device port to forward to
327
+ * @returns Handle to stop the port forwarding
328
+ */
329
+ startPortForward(localPort, devicePort) {
330
+ logTask(`Starting port forward ${localPort} -> ${devicePort} for device ${this.deviceId}`);
331
+ const toolPath = getIDeviceToolPath("iproxy");
332
+ if (!toolPath) {
333
+ throw new Error("iproxy tool not found");
334
+ }
335
+ const binPath = getIDeviceBinPath();
336
+ const env = {
337
+ ...process.env,
338
+ PATH: `${binPath};${process.env.PATH}`
339
+ };
340
+ const proc = spawn(toolPath, [
341
+ localPort.toString(),
342
+ devicePort.toString(),
343
+ "-u",
344
+ this.deviceId
345
+ ], {
346
+ env,
347
+ windowsHide: true,
348
+ stdio: "pipe"
349
+ });
350
+ proc.on("error", (err) => {
351
+ logInfo(`Port forward error: ${err.message}`);
352
+ });
353
+ return {
354
+ stop: () => {
355
+ proc.kill();
356
+ },
357
+ localPort,
358
+ devicePort,
359
+ process: proc
360
+ };
361
+ }
362
+ /**
363
+ * Start port forwarding and wait for it to be ready
364
+ *
365
+ * @param localPort Local port to listen on
366
+ * @param devicePort Device port to forward to
367
+ * @param _timeout Timeout in milliseconds (reserved for future use)
368
+ */
369
+ async startPortForwardAsync(localPort, devicePort, _timeout = 5e3) {
370
+ const handle = this.startPortForward(localPort, devicePort);
371
+ await new Promise((resolve) => setTimeout(resolve, 500));
372
+ if (handle.process.killed || handle.process.exitCode !== null) {
373
+ throw new Error("Port forwarding failed to start");
374
+ }
375
+ return handle;
376
+ }
377
+ // ==================== Activation ====================
378
+ /**
379
+ * Get the activation state of the device
380
+ */
381
+ async getActivationState() {
382
+ logTask(`Getting activation state for device ${this.deviceId}`);
383
+ const toolPath = getIDeviceToolPath("ideviceinfo");
384
+ if (!toolPath) {
385
+ throw new Error("ideviceinfo tool not found");
386
+ }
387
+ try {
388
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "-k", "ActivationState"]);
389
+ const state = stdout.trim();
390
+ return {
391
+ isActivated: state === "Activated",
392
+ activationState: state
393
+ };
394
+ } catch {
395
+ return {
396
+ isActivated: false,
397
+ activationState: "Unknown"
398
+ };
399
+ }
400
+ }
401
+ /**
402
+ * Activate the device
403
+ * Note: This requires a valid activation record or Apple server access
404
+ */
405
+ async activate() {
406
+ logTask(`Activating device ${this.deviceId}`);
407
+ const toolPath = getIDeviceToolPath("ideviceactivation");
408
+ if (!toolPath) {
409
+ throw new Error("ideviceactivation tool not found");
410
+ }
411
+ try {
412
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "activate"]);
413
+ return stdout.toLowerCase().includes("success") || stdout.toLowerCase().includes("activated");
414
+ } catch (error) {
415
+ const errorMsg = error instanceof Error ? error.message : String(error);
416
+ throw new Error(`Activation failed: ${errorMsg}`);
417
+ }
418
+ }
419
+ /**
420
+ * Deactivate the device
421
+ */
422
+ async deactivate() {
423
+ logTask(`Deactivating device ${this.deviceId}`);
424
+ const toolPath = getIDeviceToolPath("ideviceactivation");
425
+ if (!toolPath) {
426
+ throw new Error("ideviceactivation tool not found");
427
+ }
428
+ try {
429
+ const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "deactivate"]);
430
+ return stdout.toLowerCase().includes("success");
431
+ } catch {
432
+ return false;
433
+ }
434
+ }
435
+ /**
436
+ * Get activation state as string
437
+ */
438
+ async getActivationStateString() {
439
+ const state = await this.getActivationState();
440
+ return state.activationState;
441
+ }
442
+ // ==================== Device Identifiers ====================
443
+ /**
444
+ * Get the device UDID
445
+ */
446
+ getDeviceId() {
447
+ return this.deviceId;
448
+ }
449
+ /**
450
+ * Get the logical port number
451
+ */
452
+ getPort() {
453
+ return this.port;
454
+ }
455
+ // ==================== Static Methods ====================
456
+ /**
457
+ * Static method to list all connected iOS devices
458
+ */
459
+ static async listDevices() {
460
+ const toolPath = getIDeviceToolPath("idevice_id");
461
+ if (!toolPath) {
462
+ throw new Error("idevice_id tool not found");
463
+ }
464
+ try {
465
+ const { stdout } = await runIDeviceTool(toolPath, ["-l"]);
466
+ return parseDeviceList(stdout);
467
+ } catch {
468
+ return [];
469
+ }
470
+ }
471
+ };
472
+
473
+ // src/logic/devicesMonitor.ts
474
+ import EventEmitter from "node:events";
475
+ import usbDeviceListener from "@mcesystems/usb-device-listener";
476
+ var APPLE_VID = 1452;
477
+ var DevicesMonitor = class {
478
+ constructor(config = {}, identifyAlreadyConnected = false) {
479
+ this.config = config;
480
+ this.identifyAlreadyConnected = identifyAlreadyConnected;
481
+ }
482
+ kits = /* @__PURE__ */ new Map();
483
+ eventEmitter;
484
+ /**
485
+ * Start tracking iOS device connections
486
+ *
487
+ * @returns EventEmitter that emits:
488
+ * - 'added': (deviceKit: AppleDeviceKit) when a device is connected
489
+ * - 'removed': (deviceId: string, port: number) when a device is disconnected
490
+ */
491
+ async startTracking() {
492
+ logInfo("Starting iOS devices monitor");
493
+ const listenerConfig = {
494
+ logicalPortMap: this.config.logicalPortMap,
495
+ // Filter for Apple devices only
496
+ listenOnlyDevices: [
497
+ { vid: APPLE_VID.toString(16).toUpperCase().padStart(4, "0"), pid: "" }
498
+ ]
499
+ };
500
+ usbDeviceListener.startListening(listenerConfig);
501
+ this.eventEmitter = new EventEmitter();
502
+ if (this.identifyAlreadyConnected || this.config.identifyAlreadyConnected) {
503
+ await this.identifyConnectedDevices();
504
+ }
505
+ usbDeviceListener.onDeviceAdd((device) => {
506
+ if (device.vid !== APPLE_VID) {
507
+ return;
508
+ }
509
+ logInfo(`Apple device connected: ${device.deviceId}`);
510
+ this.findAndEmitDevice(device);
511
+ });
512
+ usbDeviceListener.onDeviceRemove((device) => {
513
+ if (device.vid !== APPLE_VID) {
514
+ return;
515
+ }
516
+ logInfo(`Apple device disconnected: ${device.deviceId}`);
517
+ for (const [udid, kit] of this.kits.entries()) {
518
+ if (kit.getPort() === device.logicalPort) {
519
+ this.kits.delete(udid);
520
+ this.eventEmitter?.emit("removed", udid, device.logicalPort);
521
+ break;
522
+ }
523
+ }
524
+ });
525
+ return this.eventEmitter;
526
+ }
527
+ /**
528
+ * Stop tracking device connections
529
+ */
530
+ async stopTracking() {
531
+ if (!this.eventEmitter) {
532
+ return;
533
+ }
534
+ logInfo("Stopping iOS devices monitor");
535
+ this.eventEmitter.removeAllListeners();
536
+ this.kits.clear();
537
+ this.eventEmitter = void 0;
538
+ usbDeviceListener.stopListening();
539
+ }
540
+ /**
541
+ * Get all currently tracked device kits
542
+ */
543
+ getKits() {
544
+ return new Map(this.kits);
545
+ }
546
+ /**
547
+ * Get a specific device kit by UDID
548
+ */
549
+ getKit(udid) {
550
+ return this.kits.get(udid);
551
+ }
552
+ /**
553
+ * Find connected iOS device and emit event
554
+ */
555
+ async findAndEmitDevice(usbDevice) {
556
+ const iOSDevices = await AppleDeviceKit.listDevices();
557
+ if (iOSDevices.length === 0) {
558
+ setTimeout(() => this.findAndEmitDevice(usbDevice), 500);
559
+ return;
560
+ }
561
+ for (const iDevice of iOSDevices) {
562
+ if (this.kits.has(iDevice.udid)) {
563
+ continue;
564
+ }
565
+ const port = usbDevice.logicalPort ?? 0;
566
+ const kit = new AppleDeviceKit(iDevice.udid, port);
567
+ this.kits.set(iDevice.udid, kit);
568
+ setTimeout(() => {
569
+ this.eventEmitter?.emit("added", kit);
570
+ }, 0);
571
+ break;
572
+ }
573
+ }
574
+ /**
575
+ * Identify already connected iOS devices
576
+ */
577
+ async identifyConnectedDevices() {
578
+ logInfo("Identifying already connected iOS devices");
579
+ const iOSDevices = await AppleDeviceKit.listDevices();
580
+ const usbDevices = usbDeviceListener.listDevices().filter((d) => d.vid === APPLE_VID);
581
+ for (let i = 0; i < iOSDevices.length; i++) {
582
+ const iDevice = iOSDevices[i];
583
+ const usbDevice = usbDevices[i];
584
+ if (this.kits.has(iDevice.udid)) {
585
+ continue;
586
+ }
587
+ const port = usbDevice?.logicalPort ?? 0;
588
+ const kit = new AppleDeviceKit(iDevice.udid, port);
589
+ this.kits.set(iDevice.udid, kit);
590
+ setTimeout(() => {
591
+ this.eventEmitter?.emit("added", kit);
592
+ }, 0);
593
+ }
594
+ }
595
+ };
596
+
597
+ // src/utils/usbmuxd.ts
598
+ import { spawn as spawn2 } from "node:child_process";
599
+ var usbmuxdProcess = null;
600
+ function startUsbmuxd(foreground = false) {
601
+ if (usbmuxdProcess && !usbmuxdProcess.killed) {
602
+ logInfo("usbmuxd is already running");
603
+ return {
604
+ stop: () => stopUsbmuxd(),
605
+ isRunning: () => !usbmuxdProcess?.killed,
606
+ process: usbmuxdProcess
607
+ };
608
+ }
609
+ const toolPath = getIDeviceToolPath("usbmuxd");
610
+ if (!toolPath) {
611
+ throw new Error("usbmuxd executable not found");
612
+ }
613
+ const binPath = getIDeviceBinPath();
614
+ const env = {
615
+ ...process.env,
616
+ PATH: `${binPath};${process.env.PATH}`
617
+ };
618
+ const args = [];
619
+ if (foreground) {
620
+ args.push("-f");
621
+ }
622
+ args.push("-v");
623
+ logInfo(`Starting usbmuxd: ${toolPath}`);
624
+ usbmuxdProcess = spawn2(toolPath, args, {
625
+ env,
626
+ windowsHide: true,
627
+ stdio: "pipe",
628
+ detached: !foreground
629
+ });
630
+ usbmuxdProcess.stdout?.on("data", (data) => {
631
+ logInfo(`usbmuxd: ${data.toString().trim()}`);
632
+ });
633
+ usbmuxdProcess.stderr?.on("data", (data) => {
634
+ logInfo(`usbmuxd error: ${data.toString().trim()}`);
635
+ });
636
+ usbmuxdProcess.on("error", (err) => {
637
+ logInfo(`usbmuxd failed to start: ${err.message}`);
638
+ });
639
+ usbmuxdProcess.on("exit", (code) => {
640
+ logInfo(`usbmuxd exited with code ${code}`);
641
+ usbmuxdProcess = null;
642
+ });
643
+ if (!foreground) {
644
+ usbmuxdProcess.unref();
645
+ }
646
+ return {
647
+ stop: () => stopUsbmuxd(),
648
+ isRunning: () => usbmuxdProcess !== null && !usbmuxdProcess.killed,
649
+ process: usbmuxdProcess
650
+ };
651
+ }
652
+ function stopUsbmuxd() {
653
+ if (usbmuxdProcess && !usbmuxdProcess.killed) {
654
+ logInfo("Stopping usbmuxd");
655
+ usbmuxdProcess.kill();
656
+ usbmuxdProcess = null;
657
+ }
658
+ }
659
+ function isUsbmuxdRunning() {
660
+ return usbmuxdProcess !== null && !usbmuxdProcess.killed;
661
+ }
662
+ function ensureUsbmuxd() {
663
+ if (isUsbmuxdRunning()) {
664
+ return {
665
+ stop: () => stopUsbmuxd(),
666
+ isRunning: () => isUsbmuxdRunning(),
667
+ process: usbmuxdProcess
668
+ };
669
+ }
670
+ return startUsbmuxd();
671
+ }
672
+ export {
673
+ AppleDeviceKit,
674
+ DevicesMonitor,
675
+ ensureUsbmuxd,
676
+ isUsbmuxdRunning,
677
+ startUsbmuxd,
678
+ stopUsbmuxd
679
+ };
680
+ //# sourceMappingURL=index.js.map