@mcesystems/usbmuxd-instance-manager 1.0.72

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/dist/index.js ADDED
@@ -0,0 +1,680 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ InstanceManager: () => InstanceManager,
34
+ UsbmuxdService: () => UsbmuxdService
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/UsbmuxdService.ts
39
+ var import_node_events2 = require("node:events");
40
+ var import_tool_debug_g42 = require("@mcesystems/tool-debug-g4");
41
+ var import_usb_device_listener = __toESM(require("@mcesystems/usb-device-listener"));
42
+
43
+ // src/InstanceManager.ts
44
+ var import_node_child_process = require("node:child_process");
45
+ var import_node_events = require("node:events");
46
+ var import_node_util = require("node:util");
47
+ var import_tool_debug_g4 = require("@mcesystems/tool-debug-g4");
48
+ var { logInfo, logWarning } = (0, import_tool_debug_g4.createLoggers)("usbmuxd-instance-manager");
49
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
50
+ var USBIPD_PATH = '"C:\\Program Files\\usbipd-win\\usbipd.exe"';
51
+ function parseUsbipdList(output) {
52
+ const devices = [];
53
+ const lines = output.split(/\r?\n/);
54
+ for (const line of lines) {
55
+ const match = line.match(/^(\d+-\d+)\s+([0-9a-f]{4}):([0-9a-f]{4})\s+(.+)$/i);
56
+ if (match) {
57
+ const rest = match[4].trim();
58
+ const stateMatch = rest.match(/^(.+?)\s{2,}(\S.*)$/);
59
+ const description = stateMatch ? stateMatch[1].trim() : rest;
60
+ const state = stateMatch ? stateMatch[2].trim() : "Unknown";
61
+ devices.push({
62
+ busId: match[1],
63
+ vid: match[2].toUpperCase(),
64
+ pid: match[3].toUpperCase(),
65
+ description,
66
+ state
67
+ });
68
+ }
69
+ }
70
+ return devices;
71
+ }
72
+ var DEFAULT_CONFIG = {
73
+ batchSize: 4,
74
+ basePort: 27015,
75
+ maxInstances: 20,
76
+ usbmuxdPath: "usbmuxd",
77
+ // Path inside WSL2
78
+ wslDistribution: "alpine-usbmuxd-build",
79
+ // Alpine WSL2 distribution name
80
+ verboseLogging: true,
81
+ appleVendorId: "05AC"
82
+ };
83
+ var InstanceManager = class extends import_node_events.EventEmitter {
84
+ config;
85
+ instances = /* @__PURE__ */ new Map();
86
+ deviceMappings = /* @__PURE__ */ new Map();
87
+ processes = /* @__PURE__ */ new Map();
88
+ nextInstanceId = 1;
89
+ startedAt = null;
90
+ isRunning = false;
91
+ /** Tracks which devices have been attached to WSL */
92
+ attachedDevices = /* @__PURE__ */ new Set();
93
+ /** Cached WSL IP address for connecting from Windows */
94
+ wslIpAddress = null;
95
+ constructor(config = {}) {
96
+ super();
97
+ this.config = { ...DEFAULT_CONFIG, ...config };
98
+ }
99
+ /**
100
+ * Detect the WSL2 IP address for the configured distribution
101
+ * This IP is needed to connect from Windows to services inside WSL
102
+ */
103
+ async detectWslIpAddress() {
104
+ if (this.wslIpAddress) {
105
+ return this.wslIpAddress;
106
+ }
107
+ const distro = this.config.wslDistribution || "alpine-usbmuxd-build";
108
+ try {
109
+ const { stdout } = await execAsync(`wsl -d ${distro} -- ip -4 addr show eth0`);
110
+ const match = stdout.match(/inet\s+(\d+\.\d+\.\d+\.\d+)/);
111
+ if (match) {
112
+ this.wslIpAddress = match[1];
113
+ logInfo(`Detected WSL IP address: ${this.wslIpAddress}`);
114
+ return this.wslIpAddress;
115
+ }
116
+ } catch (error) {
117
+ logWarning(`Failed to detect WSL IP via ip addr: ${error}`);
118
+ }
119
+ try {
120
+ const { stdout } = await execAsync(`wsl -d ${distro} -- hostname -I`);
121
+ const ip = stdout.trim().split(/\s+/)[0];
122
+ if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
123
+ this.wslIpAddress = ip;
124
+ logInfo(`Detected WSL IP address (hostname): ${this.wslIpAddress}`);
125
+ return this.wslIpAddress;
126
+ }
127
+ } catch (error) {
128
+ logWarning(`Failed to detect WSL IP via hostname: ${error}`);
129
+ }
130
+ logWarning("Could not detect WSL IP, falling back to localhost");
131
+ this.wslIpAddress = "127.0.0.1";
132
+ return this.wslIpAddress;
133
+ }
134
+ /**
135
+ * Start the instance manager
136
+ */
137
+ start() {
138
+ if (this.isRunning) {
139
+ throw new Error("Instance manager is already running");
140
+ }
141
+ this.isRunning = true;
142
+ this.startedAt = /* @__PURE__ */ new Date();
143
+ this.emit("started");
144
+ }
145
+ /**
146
+ * Stop the instance manager and all instances
147
+ */
148
+ async stop() {
149
+ if (!this.isRunning) {
150
+ return;
151
+ }
152
+ this.isRunning = false;
153
+ const detachPromises = Array.from(this.attachedDevices).map(
154
+ (busId) => this.detachDeviceFromWsl(busId)
155
+ );
156
+ await Promise.all(detachPromises);
157
+ this.attachedDevices.clear();
158
+ const stopPromises = Array.from(this.instances.keys()).map((id) => this.stopInstance(id));
159
+ await Promise.all(stopPromises);
160
+ this.instances.clear();
161
+ this.deviceMappings.clear();
162
+ this.processes.clear();
163
+ this.emit("stopped");
164
+ }
165
+ /**
166
+ * Find the usbipd bus ID for a device by matching VID/PID
167
+ */
168
+ async findBusIdForDevice(device) {
169
+ try {
170
+ const { stdout } = await execAsync(`${USBIPD_PATH} list`);
171
+ const usbipdDevices = parseUsbipdList(stdout);
172
+ const deviceVid = device.vid.toString(16).toUpperCase().padStart(4, "0");
173
+ const devicePid = device.pid.toString(16).toUpperCase().padStart(4, "0");
174
+ logInfo(`Looking for device with VID:PID ${deviceVid}:${devicePid}`);
175
+ logInfo(`Found ${usbipdDevices.length} devices from usbipd list`);
176
+ const match = usbipdDevices.find((d) => d.vid === deviceVid && d.pid === devicePid);
177
+ if (match) {
178
+ logInfo(
179
+ `Found usbipd bus ID ${match.busId} for device ${device.deviceId} (${deviceVid}:${devicePid})`
180
+ );
181
+ return match.busId;
182
+ }
183
+ for (const d of usbipdDevices) {
184
+ logInfo(` usbipd device: ${d.busId} ${d.vid}:${d.pid} "${d.description}"`);
185
+ }
186
+ logWarning(
187
+ `Could not find usbipd bus ID for device ${device.deviceId} (${deviceVid}:${devicePid})`
188
+ );
189
+ return null;
190
+ } catch (error) {
191
+ logWarning(`Failed to run usbipd list: ${error}`);
192
+ return null;
193
+ }
194
+ }
195
+ /**
196
+ * Ensure the WSL distribution is running
197
+ */
198
+ async ensureWslRunning() {
199
+ const distro = this.config.wslDistribution;
200
+ try {
201
+ if (distro) {
202
+ logInfo(`Starting WSL distribution: ${distro}...`);
203
+ await execAsync(`wsl -d ${distro} -- echo "WSL started"`);
204
+ } else {
205
+ logInfo("Starting default WSL distribution...");
206
+ await execAsync(`wsl -- echo "WSL started"`);
207
+ }
208
+ return true;
209
+ } catch (error) {
210
+ logWarning(`Failed to start WSL: ${error}`);
211
+ return false;
212
+ }
213
+ }
214
+ /**
215
+ * Attach a device to WSL via usbipd
216
+ * Note: This requires administrator privileges
217
+ */
218
+ async attachDeviceToWsl(busId) {
219
+ const distro = this.config.wslDistribution;
220
+ try {
221
+ await this.ensureWslRunning();
222
+ logInfo(`Binding device ${busId}...`);
223
+ await execAsync(`${USBIPD_PATH} bind --busid ${busId} --force`);
224
+ if (distro) {
225
+ logInfo(`Attaching device ${busId} to WSL distribution ${distro}...`);
226
+ await execAsync(`${USBIPD_PATH} attach --wsl=${distro} --busid=${busId}`);
227
+ } else {
228
+ logInfo(`Attaching device ${busId} to default WSL...`);
229
+ await execAsync(`${USBIPD_PATH} attach --wsl --busid=${busId}`);
230
+ }
231
+ logInfo(`Device ${busId} attached to WSL successfully`);
232
+ return true;
233
+ } catch (error) {
234
+ logWarning(`Failed to attach device ${busId} to WSL: ${error}`);
235
+ return false;
236
+ }
237
+ }
238
+ /**
239
+ * Detach a device from WSL via usbipd
240
+ */
241
+ async detachDeviceFromWsl(busId) {
242
+ try {
243
+ await execAsync(`${USBIPD_PATH} detach --busid=${busId}`);
244
+ logInfo(`Device ${busId} detached from WSL`);
245
+ } catch (error) {
246
+ logWarning(`Failed to detach device ${busId}: ${error}`);
247
+ }
248
+ }
249
+ /**
250
+ * Handle device connection
251
+ * Attaches device to WSL, then assigns to an existing instance or creates a new one
252
+ */
253
+ async onDeviceConnected(device) {
254
+ if (this.deviceMappings.has(device.deviceId)) {
255
+ logWarning(`Device ${device.deviceId} is already connected`);
256
+ return;
257
+ }
258
+ const busId = await this.findBusIdForDevice(device);
259
+ if (!busId) {
260
+ logWarning(`Cannot attach device ${device.deviceId} - bus ID not found`);
261
+ }
262
+ if (busId && !this.attachedDevices.has(busId)) {
263
+ const attached = await this.attachDeviceToWsl(busId);
264
+ if (attached) {
265
+ this.attachedDevices.add(busId);
266
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
267
+ }
268
+ }
269
+ let targetInstance = this.findInstanceWithCapacity();
270
+ if (!targetInstance) {
271
+ if (this.instances.size >= this.config.maxInstances) {
272
+ throw new Error(`Maximum number of instances (${this.config.maxInstances}) reached`);
273
+ }
274
+ targetInstance = await this.createInstance();
275
+ }
276
+ targetInstance.deviceUdids.push(device.deviceId);
277
+ const mapping = {
278
+ udid: device.deviceId,
279
+ instanceId: targetInstance.id,
280
+ host: targetInstance.host,
281
+ port: targetInstance.port,
282
+ addedAt: /* @__PURE__ */ new Date(),
283
+ busId: busId ?? void 0
284
+ };
285
+ this.deviceMappings.set(device.deviceId, mapping);
286
+ this.emit("device-assigned", {
287
+ device,
288
+ instance: targetInstance,
289
+ mapping
290
+ });
291
+ }
292
+ /**
293
+ * Pair a device with the usbmuxd host.
294
+ * This is required once per device before most commands will work.
295
+ * The pairing record is stored in WSL and persists across restarts.
296
+ *
297
+ * @param udid Device UDID to pair
298
+ * @param goIosPath Optional path to go-ios binary (defaults to "ios")
299
+ * @returns true if pairing succeeded, false otherwise
300
+ */
301
+ async pairDevice(udid, goIosPath = "ios") {
302
+ const mapping = this.deviceMappings.get(udid);
303
+ if (!mapping) {
304
+ logWarning(`Cannot pair device ${udid} - not found in mappings`);
305
+ return false;
306
+ }
307
+ try {
308
+ const socketAddress = `${mapping.host}:${mapping.port}`;
309
+ logInfo(`Pairing device ${udid} via ${socketAddress}...`);
310
+ const { stderr } = await execAsync(`"${goIosPath}" pair --udid=${udid}`, {
311
+ env: { ...process.env, USBMUXD_SOCKET_ADDRESS: socketAddress }
312
+ });
313
+ if (stderr?.includes("error")) {
314
+ logWarning(`Pairing warning for ${udid}: ${stderr}`);
315
+ }
316
+ logInfo(`Device ${udid} paired successfully`);
317
+ this.emit("device-paired", { udid, mapping });
318
+ return true;
319
+ } catch (error) {
320
+ logWarning(`Failed to pair device ${udid}: ${error}`);
321
+ return false;
322
+ }
323
+ }
324
+ /**
325
+ * Handle device disconnection
326
+ * Detaches from WSL, removes device from instance, and stops instance if empty
327
+ */
328
+ async onDeviceDisconnected(device) {
329
+ const mapping = this.deviceMappings.get(device.deviceId);
330
+ if (!mapping) {
331
+ logWarning(`Device ${device.deviceId} was not tracked`);
332
+ return;
333
+ }
334
+ if (mapping.busId) {
335
+ await this.detachDeviceFromWsl(mapping.busId);
336
+ this.attachedDevices.delete(mapping.busId);
337
+ }
338
+ const instance = this.instances.get(mapping.instanceId);
339
+ if (!instance) {
340
+ logWarning(`Instance ${mapping.instanceId} not found`);
341
+ this.deviceMappings.delete(device.deviceId);
342
+ return;
343
+ }
344
+ const deviceIndex = instance.deviceUdids.indexOf(device.deviceId);
345
+ if (deviceIndex > -1) {
346
+ instance.deviceUdids.splice(deviceIndex, 1);
347
+ }
348
+ this.deviceMappings.delete(device.deviceId);
349
+ this.emit("device-removed", {
350
+ device,
351
+ instance
352
+ });
353
+ if (instance.deviceUdids.length === 0) {
354
+ await this.stopInstance(instance.id);
355
+ }
356
+ }
357
+ /**
358
+ * Find an instance with available capacity
359
+ */
360
+ findInstanceWithCapacity() {
361
+ for (const instance of this.instances.values()) {
362
+ if (instance.deviceUdids.length < this.config.batchSize) {
363
+ return instance;
364
+ }
365
+ }
366
+ return null;
367
+ }
368
+ /**
369
+ * Create a new usbmuxd instance
370
+ */
371
+ async createInstance() {
372
+ const instanceId = this.nextInstanceId++;
373
+ const port = this.config.basePort + instanceId - 1;
374
+ const host = await this.detectWslIpAddress();
375
+ const usbmuxdArgs = [
376
+ "-f",
377
+ // Foreground
378
+ "-v",
379
+ // Verbose (if enabled)
380
+ "-S",
381
+ `0.0.0.0:${port}`,
382
+ // Listen on all interfaces (for Windows → WSL2)
383
+ "--pidfile",
384
+ "NONE"
385
+ ];
386
+ if (!this.config.verboseLogging) {
387
+ usbmuxdArgs.splice(1, 1);
388
+ }
389
+ const wslArgs = [
390
+ "-d",
391
+ this.config.wslDistribution || "alpine-usbmuxd-build",
392
+ this.config.usbmuxdPath,
393
+ ...usbmuxdArgs
394
+ ];
395
+ const process2 = (0, import_node_child_process.spawn)("wsl", wslArgs, {
396
+ stdio: ["ignore", "pipe", "pipe"],
397
+ windowsHide: false
398
+ // Show console for debugging
399
+ });
400
+ process2.stdout?.on("data", (data) => {
401
+ this.emit("instance-log", {
402
+ instanceId,
403
+ level: "info",
404
+ message: data.toString().trim()
405
+ });
406
+ });
407
+ process2.stderr?.on("data", (data) => {
408
+ this.emit("instance-log", {
409
+ instanceId,
410
+ level: "error",
411
+ message: data.toString().trim()
412
+ });
413
+ });
414
+ process2.on("exit", (code, signal) => {
415
+ this.emit("instance-exited", {
416
+ instanceId,
417
+ code,
418
+ signal
419
+ });
420
+ if (this.instances.has(instanceId)) {
421
+ this.instances.delete(instanceId);
422
+ this.processes.delete(instanceId);
423
+ }
424
+ });
425
+ const pid = process2.pid;
426
+ if (pid === void 0) {
427
+ process2.kill("SIGKILL");
428
+ throw new Error("Failed to get PID for usbmuxd instance");
429
+ }
430
+ const instance = {
431
+ id: instanceId,
432
+ host,
433
+ port,
434
+ pid,
435
+ deviceUdids: [],
436
+ startedAt: /* @__PURE__ */ new Date()
437
+ };
438
+ this.instances.set(instanceId, instance);
439
+ this.processes.set(instanceId, process2);
440
+ this.emit("instance-started", instance);
441
+ await new Promise((resolve) => setTimeout(resolve, 500));
442
+ return instance;
443
+ }
444
+ /**
445
+ * Stop a specific instance
446
+ */
447
+ async stopInstance(instanceId) {
448
+ const instance = this.instances.get(instanceId);
449
+ const process2 = this.processes.get(instanceId);
450
+ if (!instance || !process2) {
451
+ return;
452
+ }
453
+ process2.kill("SIGTERM");
454
+ await new Promise((resolve) => {
455
+ const timeout = setTimeout(() => {
456
+ if (!process2.killed) {
457
+ process2.kill("SIGKILL");
458
+ }
459
+ resolve();
460
+ }, 5e3);
461
+ process2.once("exit", () => {
462
+ clearTimeout(timeout);
463
+ resolve();
464
+ });
465
+ });
466
+ this.instances.delete(instanceId);
467
+ this.processes.delete(instanceId);
468
+ for (const [udid, mapping] of this.deviceMappings.entries()) {
469
+ if (mapping.instanceId === instanceId) {
470
+ this.deviceMappings.delete(udid);
471
+ }
472
+ }
473
+ this.emit("instance-stopped", instance);
474
+ }
475
+ /**
476
+ * Get device-to-port mapping for a specific UDID
477
+ */
478
+ getDevicePort(udid) {
479
+ const mapping = this.deviceMappings.get(udid);
480
+ return mapping ? mapping.port : null;
481
+ }
482
+ /**
483
+ * Get all device mappings
484
+ */
485
+ getDeviceMappings() {
486
+ return Array.from(this.deviceMappings.values());
487
+ }
488
+ /**
489
+ * Get all running instances
490
+ */
491
+ getInstances() {
492
+ return Array.from(this.instances.values());
493
+ }
494
+ /**
495
+ * Get manager statistics
496
+ */
497
+ getStats() {
498
+ const now = Date.now();
499
+ const startTime = this.startedAt?.getTime() || now;
500
+ const uptimeSeconds = Math.floor((now - startTime) / 1e3);
501
+ return {
502
+ instanceCount: this.instances.size,
503
+ deviceCount: this.deviceMappings.size,
504
+ uptimeSeconds,
505
+ startedAt: this.startedAt || /* @__PURE__ */ new Date()
506
+ };
507
+ }
508
+ /**
509
+ * Get current configuration
510
+ */
511
+ getConfig() {
512
+ return { ...this.config };
513
+ }
514
+ };
515
+
516
+ // src/UsbmuxdService.ts
517
+ var { logInfo: logInfo2, logError } = (0, import_tool_debug_g42.createLoggers)("usbmuxd-instance-manager");
518
+ var UsbmuxdService = class extends import_node_events2.EventEmitter {
519
+ manager;
520
+ usbListener;
521
+ isListening = false;
522
+ constructor(config = {}) {
523
+ super();
524
+ this.manager = new InstanceManager(config);
525
+ this.usbListener = new import_usb_device_listener.default();
526
+ this.setupEventHandlers();
527
+ }
528
+ /**
529
+ * Forward manager events to service subscribers (no logging; CLI/examples handle logging).
530
+ */
531
+ setupEventHandlers() {
532
+ this.manager.on("instance-started", (instance) => {
533
+ this.emit("instance-started", instance);
534
+ });
535
+ this.manager.on("instance-stopped", (instance) => {
536
+ this.emit("instance-stopped", instance);
537
+ });
538
+ this.manager.on(
539
+ "device-assigned",
540
+ (payload) => {
541
+ this.emit("device-assigned", payload);
542
+ }
543
+ );
544
+ this.manager.on(
545
+ "device-removed",
546
+ (payload) => {
547
+ this.emit("device-removed", payload);
548
+ }
549
+ );
550
+ this.manager.on(
551
+ "instance-log",
552
+ (payload) => {
553
+ this.emit("instance-log", payload);
554
+ }
555
+ );
556
+ this.manager.on(
557
+ "instance-exited",
558
+ (payload) => {
559
+ this.emit("instance-exited", payload);
560
+ }
561
+ );
562
+ this.manager.on("device-paired", (payload) => {
563
+ this.emit("device-paired", payload);
564
+ });
565
+ }
566
+ /**
567
+ * Start the service
568
+ * Begins monitoring for iOS devices
569
+ */
570
+ start() {
571
+ if (this.isListening) {
572
+ throw new Error("Service is already running");
573
+ }
574
+ const config = this.manager.getConfig();
575
+ const appleVid = Number.parseInt(config.appleVendorId, 16);
576
+ logInfo2("Starting service...");
577
+ logInfo2(`Batch size: ${config.batchSize} devices per instance`);
578
+ logInfo2(`Base port: ${config.basePort}`);
579
+ logInfo2(`Max instances: ${config.maxInstances}`);
580
+ logInfo2(`usbmuxd path: ${config.usbmuxdPath}`);
581
+ logInfo2(`Monitoring Apple devices (VID: ${config.appleVendorId})`);
582
+ this.usbListener.onDeviceAdd(async (device) => {
583
+ if (device.vid !== appleVid) {
584
+ return;
585
+ }
586
+ logInfo2(`Apple device connected: ${device.deviceId} (${device.deviceName || "Unknown"})`);
587
+ try {
588
+ await this.manager.onDeviceConnected(device);
589
+ } catch (error) {
590
+ logError("Error handling device connection:", error);
591
+ }
592
+ });
593
+ this.usbListener.onDeviceRemove(async (device) => {
594
+ if (device.vid !== appleVid) {
595
+ return;
596
+ }
597
+ logInfo2(`Apple device disconnected: ${device.deviceId}`);
598
+ try {
599
+ await this.manager.onDeviceDisconnected(device);
600
+ } catch (error) {
601
+ logError("Error handling device disconnection:", error);
602
+ }
603
+ });
604
+ try {
605
+ this.usbListener.startListening({
606
+ targetDevices: []
607
+ // Monitor all devices, we'll filter by VID
608
+ });
609
+ this.isListening = true;
610
+ this.manager.start();
611
+ logInfo2("Service started successfully");
612
+ logInfo2("Waiting for iOS devices...");
613
+ } catch (error) {
614
+ logError("Failed to start USB listener:", error);
615
+ throw error;
616
+ }
617
+ }
618
+ /**
619
+ * Stop the service
620
+ * Stops monitoring and terminates all instances
621
+ */
622
+ async stop() {
623
+ if (!this.isListening) {
624
+ return;
625
+ }
626
+ logInfo2("Stopping service...");
627
+ this.usbListener.stopListening();
628
+ this.isListening = false;
629
+ await this.manager.stop();
630
+ logInfo2("Service stopped");
631
+ }
632
+ /**
633
+ * Get device port mapping
634
+ */
635
+ getDevicePort(udid) {
636
+ return this.manager.getDevicePort(udid);
637
+ }
638
+ /**
639
+ * Get all device mappings
640
+ */
641
+ getDeviceMappings() {
642
+ return this.manager.getDeviceMappings();
643
+ }
644
+ /**
645
+ * Get all running instances
646
+ */
647
+ getInstances() {
648
+ return this.manager.getInstances();
649
+ }
650
+ /**
651
+ * Get service statistics
652
+ */
653
+ getStats() {
654
+ return this.manager.getStats();
655
+ }
656
+ /**
657
+ * Get current configuration
658
+ */
659
+ getConfig() {
660
+ return this.manager.getConfig();
661
+ }
662
+ /**
663
+ * Pair a device with the usbmuxd host.
664
+ * This is required once per device before most commands will work.
665
+ * The pairing record is stored in WSL and persists across restarts.
666
+ *
667
+ * @param udid Device UDID to pair
668
+ * @param goIosPath Optional path to go-ios binary
669
+ * @returns true if pairing succeeded, false otherwise
670
+ */
671
+ async pairDevice(udid, goIosPath) {
672
+ return this.manager.pairDevice(udid, goIosPath);
673
+ }
674
+ };
675
+ // Annotate the CommonJS export names for ESM import in node:
676
+ 0 && (module.exports = {
677
+ InstanceManager,
678
+ UsbmuxdService
679
+ });
680
+ //# sourceMappingURL=index.js.map