From 0406d7b7c9f17f4837ddeba424094fcc8fe5caf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D1=8F?= =?UTF-8?q?=D1=81=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 14 Apr 2026 23:06:06 +0500 Subject: [PATCH 1/4] Filter non-iOS USB serials in iOS observer --- lib/units/ios-provider/IOSObserver.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/units/ios-provider/IOSObserver.ts b/lib/units/ios-provider/IOSObserver.ts index 7e769ba09..21e1ca963 100644 --- a/lib/units/ios-provider/IOSObserver.ts +++ b/lib/units/ios-provider/IOSObserver.ts @@ -80,12 +80,30 @@ export default class IOSObserver extends EventEmitter { return serial } + private isValidIosUsbSerial(rawSerial: string): boolean { + // Raw iOS UDID often comes as 24 hex chars from usb-hotplug. + const raw24 = /^[0-9A-Fa-f]{24}$/ + // Some tools expose 40-char lower/upper hex. + const raw40 = /^[0-9A-Fa-f]{40}$/ + // After formatting 24-char UDID becomes XXXXXXXX-XXXXXXXXXXXXXXXX. + const formatted8x16 = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{16}$/ + // Simulator UUID format (kept for completeness in case usb source changes). + const simUuid = /^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$/ + + const formatted = this.formatUDID(rawSerial) + return raw24.test(rawSerial) || + raw40.test(rawSerial) || + formatted8x16.test(formatted) || + simUuid.test(formatted) + } + listen = (): void => { new Promise(async() => { if (!this.usbListenerStarted) { const currentDevices = usb.listDevices() for (const device of currentDevices) { if (!device.serialNumber || device.vendorId !== 1452) continue + if (!this.isValidIosUsbSerial(device.serialNumber)) continue this.emit('attached', this.formatUDID(device.serialNumber), false) } @@ -93,6 +111,9 @@ export default class IOSObserver extends EventEmitter { if (!event.serialNumber) { return } + if (!this.isValidIosUsbSerial(event.serialNumber)) { + return + } if (event.eventType === 'Connected' && event.device?.vendorId === 1452) { this.emit('attached', this.formatUDID(event.serialNumber), false) @@ -117,4 +138,3 @@ export default class IOSObserver extends EventEmitter { clearTimeout(this.listnerInterval) } } - From 2b12e738eeec7332ffb51fef4d1bc7240ed213d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D1=8F?= =?UTF-8?q?=D1=81=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 29 May 2026 23:58:25 +0500 Subject: [PATCH 2/4] Use USB device class filter for iOS observer --- lib/units/ios-provider/IOSObserver.ts | 33 ++++++++++++--------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/units/ios-provider/IOSObserver.ts b/lib/units/ios-provider/IOSObserver.ts index 21e1ca963..80d5ee437 100644 --- a/lib/units/ios-provider/IOSObserver.ts +++ b/lib/units/ios-provider/IOSObserver.ts @@ -80,21 +80,16 @@ export default class IOSObserver extends EventEmitter { return serial } - private isValidIosUsbSerial(rawSerial: string): boolean { - // Raw iOS UDID often comes as 24 hex chars from usb-hotplug. - const raw24 = /^[0-9A-Fa-f]{24}$/ - // Some tools expose 40-char lower/upper hex. - const raw40 = /^[0-9A-Fa-f]{40}$/ - // After formatting 24-char UDID becomes XXXXXXXX-XXXXXXXXXXXXXXXX. - const formatted8x16 = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{16}$/ - // Simulator UUID format (kept for completeness in case usb source changes). - const simUuid = /^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$/ - - const formatted = this.formatUDID(rawSerial) - return raw24.test(rawSerial) || - raw40.test(rawSerial) || - formatted8x16.test(formatted) || - simUuid.test(formatted) + private isIosUsbDevice(vendorId?: number, deviceClass?: number): boolean { + const APPLE_VENDOR_ID = 1452 + const HID_DEVICE_CLASS = 0x03 + + if (vendorId !== APPLE_VENDOR_ID) { + return false + } + + // Apple devices may report class 0 (defined by interfaces), so we only exclude explicit HID. + return deviceClass !== HID_DEVICE_CLASS } listen = (): void => { @@ -102,8 +97,8 @@ export default class IOSObserver extends EventEmitter { if (!this.usbListenerStarted) { const currentDevices = usb.listDevices() for (const device of currentDevices) { - if (!device.serialNumber || device.vendorId !== 1452) continue - if (!this.isValidIosUsbSerial(device.serialNumber)) continue + if (!device.serialNumber) continue + if (!this.isIosUsbDevice(device.vendorId, device.deviceClass)) continue this.emit('attached', this.formatUDID(device.serialNumber), false) } @@ -111,11 +106,11 @@ export default class IOSObserver extends EventEmitter { if (!event.serialNumber) { return } - if (!this.isValidIosUsbSerial(event.serialNumber)) { + if (!this.isIosUsbDevice(event.device?.vendorId, event.device?.deviceClass)) { return } - if (event.eventType === 'Connected' && event.device?.vendorId === 1452) { + if (event.eventType === 'Connected') { this.emit('attached', this.formatUDID(event.serialNumber), false) } else { this.emit('detached', this.formatUDID(event.serialNumber), false) From 176f155b2a49b8b240b0f3d12e089f4e105eb02c Mon Sep 17 00:00:00 2001 From: "e.khalilov" Date: Mon, 1 Jun 2026 16:36:06 +0300 Subject: [PATCH 3/4] hid checking by device interfaces --- lib/units/ios-provider/IOSObserver.ts | 26 ++++++++------------------ package.json | 2 +- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/units/ios-provider/IOSObserver.ts b/lib/units/ios-provider/IOSObserver.ts index 80d5ee437..4d0ecacf4 100644 --- a/lib/units/ios-provider/IOSObserver.ts +++ b/lib/units/ios-provider/IOSObserver.ts @@ -80,25 +80,12 @@ export default class IOSObserver extends EventEmitter { return serial } - private isIosUsbDevice(vendorId?: number, deviceClass?: number): boolean { - const APPLE_VENDOR_ID = 1452 - const HID_DEVICE_CLASS = 0x03 - - if (vendorId !== APPLE_VENDOR_ID) { - return false - } - - // Apple devices may report class 0 (defined by interfaces), so we only exclude explicit HID. - return deviceClass !== HID_DEVICE_CLASS - } - listen = (): void => { new Promise(async() => { if (!this.usbListenerStarted) { const currentDevices = usb.listDevices() for (const device of currentDevices) { - if (!device.serialNumber) continue - if (!this.isIosUsbDevice(device.vendorId, device.deviceClass)) continue + if (!device.serialNumber || device.interfaces.length) continue this.emit('attached', this.formatUDID(device.serialNumber), false) } @@ -106,14 +93,17 @@ export default class IOSObserver extends EventEmitter { if (!event.serialNumber) { return } - if (!this.isIosUsbDevice(event.device?.vendorId, event.device?.deviceClass)) { + + if (event.eventType === 'Disconnected') { + this.emit('detached', this.formatUDID(event.serialNumber), false) return } - if (event.eventType === 'Connected') { + if (event.device + && !event.device.interfaces.length + && event.device.vendorId === 1452 + ) { this.emit('attached', this.formatUDID(event.serialNumber), false) - } else { - this.emit('detached', this.formatUDID(event.serialNumber), false) } }) diff --git a/package.json b/package.json index d436d76ef..4ed977558 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "tsx": "4.20.3", "underscore.string": "3.3.6", "url-join": "1.1.0", - "usb-hotplug": "^0.1.6", + "usb-hotplug": "^1.0.0", "utf-8-validate": "5.0.9", "uuid": "^11.0.3", "webpack": "^5.88.1", From 65da0222b9441c4970e4ba6891ef63a7b1127ffd Mon Sep 17 00:00:00 2001 From: "e.khalilov" Date: Mon, 1 Jun 2026 16:47:13 +0300 Subject: [PATCH 4/4] vendor check --- lib/units/ios-provider/IOSObserver.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/units/ios-provider/IOSObserver.ts b/lib/units/ios-provider/IOSObserver.ts index 4d0ecacf4..71e06c453 100644 --- a/lib/units/ios-provider/IOSObserver.ts +++ b/lib/units/ios-provider/IOSObserver.ts @@ -10,6 +10,7 @@ interface IOSSimEvents { } export default class IOSObserver extends EventEmitter { + private readonly APPLE_VENDOR_ID = 1452 private sims = new Set() private usbListenerStarted = false @@ -85,7 +86,12 @@ export default class IOSObserver extends EventEmitter { if (!this.usbListenerStarted) { const currentDevices = usb.listDevices() for (const device of currentDevices) { - if (!device.serialNumber || device.interfaces.length) continue + if (!device.serialNumber + || device.interfaces.length + || device.vendorId !== this.APPLE_VENDOR_ID + ) { + continue + } this.emit('attached', this.formatUDID(device.serialNumber), false) } @@ -101,7 +107,7 @@ export default class IOSObserver extends EventEmitter { if (event.device && !event.device.interfaces.length - && event.device.vendorId === 1452 + && event.device.vendorId === this.APPLE_VENDOR_ID ) { this.emit('attached', this.formatUDID(event.serialNumber), false) }