Loading libraries/adb-daemon-webusb/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ "dependencies": { "@types/w3c-web-usb": "^1.0.10", "@yume-chan/adb": "workspace:^", "@yume-chan/event": "workspace:^", "@yume-chan/stream-extra": "workspace:^", "@yume-chan/struct": "workspace:^" }, Loading libraries/adb-daemon-webusb/src/index.ts +1 −1 Original line number Diff line number Diff line export * from "./device.js"; export * from "./manager.js"; export * from "./utils.js"; export * from "./watcher.js"; export * from "./observer.js"; libraries/adb-daemon-webusb/src/manager.ts +7 −35 Original line number Diff line number Diff line import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; import { findUsbAlternateInterface, getSerialNumber, isErrorName, } from "./utils.js"; import { AdbDaemonWebUsbDeviceObserver } from "./observer.js"; import { isErrorName, matchesFilters } from "./utils.js"; export namespace AdbDaemonWebUsbDeviceManager { export interface RequestDeviceOptions { Loading Loading @@ -76,36 +73,7 @@ export class AdbDaemonWebUsbDeviceManager { const devices = await this.#usbManager.getDevices(); return devices .filter((device) => { for (const filter of filters) { if ( filter.vendorId !== undefined && device.vendorId !== filter.vendorId ) { continue; } if ( filter.productId !== undefined && device.productId !== filter.productId ) { continue; } if ( filter.serialNumber !== undefined && getSerialNumber(device) !== filter.serialNumber ) { continue; } try { findUsbAlternateInterface(device, filters); return true; } catch { continue; } } return false; }) .filter((device) => matchesFilters(device, filters)) .map( (device) => new AdbDaemonWebUsbDevice( Loading @@ -115,4 +83,8 @@ export class AdbDaemonWebUsbDeviceManager { ), ); } trackDevices(filters?: USBDeviceFilter[]): AdbDaemonWebUsbDeviceObserver { return new AdbDaemonWebUsbDeviceObserver(this.#usbManager, filters); } } libraries/adb-daemon-webusb/src/observer.ts 0 → 100644 +85 −0 Original line number Diff line number Diff line import type { DeviceObserver } from "@yume-chan/adb"; import { EventEmitter } from "@yume-chan/event"; import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; import type { UsbInterfaceFilter } from "./utils.js"; import { matchesFilters } from "./utils.js"; /** * A watcher that listens for new WebUSB devices and notifies the callback when * a new device is connected or disconnected. * * [Online Documentation](https://docs.tangoapp.dev/tango/daemon/usb/watch-devices/) */ export class AdbDaemonWebUsbDeviceObserver implements DeviceObserver<AdbDaemonWebUsbDevice> { #filters: UsbInterfaceFilter[]; #usbManager: USB; #deviceAdded = new EventEmitter<AdbDaemonWebUsbDevice[]>(); deviceAdded = this.#deviceAdded.event; #deviceRemoved = new EventEmitter<AdbDaemonWebUsbDevice[]>(); deviceRemoved = this.#deviceRemoved.event; #deviceChanged = new EventEmitter<AdbDaemonWebUsbDevice[]>(); deviceChanged = this.#deviceChanged.event; #listChanged = new EventEmitter<AdbDaemonWebUsbDevice[]>(); listChanged = this.#listChanged.event; current: AdbDaemonWebUsbDevice[] = []; constructor(usb: USB, filters?: USBDeviceFilter[]) { this.#filters = toAdbDeviceFilters(filters); this.#usbManager = usb; this.#usbManager.addEventListener("connect", this.#handleConnect); this.#usbManager.addEventListener("disconnect", this.#handleDisconnect); } dispose(): void { this.#usbManager.removeEventListener("connect", this.#handleConnect); this.#usbManager.removeEventListener( "disconnect", this.#handleDisconnect, ); } #handleConnect = (e: USBConnectionEvent) => { if (!matchesFilters(e.device, this.#filters)) { return; } const device = new AdbDaemonWebUsbDevice( e.device, this.#filters, this.#usbManager, ); this.#deviceAdded.fire([device]); this.current.push(device); this.#listChanged.fire(this.current); }; #handleDisconnect = (e: USBConnectionEvent) => { const index = this.current.findIndex( (device) => device.raw === e.device, ); if (index !== -1) { const device = this.current[index]!; this.#deviceRemoved.fire([device]); this.current[index] = this.current[this.current.length - 1]!; this.current.length -= 1; this.#listChanged.fire(this.current); } }; stop(): void { this.#usbManager.removeEventListener("connect", this.#handleConnect); this.#usbManager.removeEventListener( "disconnect", this.#handleDisconnect, ); } } libraries/adb-daemon-webusb/src/utils.ts +34 −0 Original line number Diff line number Diff line Loading @@ -99,3 +99,37 @@ export function findUsbEndpoints(endpoints: USBEndpoint[]) { } throw new Error("unreachable"); } export function matchesFilters( device: USBDevice, filters: (USBDeviceFilter & UsbInterfaceFilter)[], ) { for (const filter of filters) { if ( filter.vendorId !== undefined && device.vendorId !== filter.vendorId ) { continue; } if ( filter.productId !== undefined && device.productId !== filter.productId ) { continue; } if ( filter.serialNumber !== undefined && getSerialNumber(device) !== filter.serialNumber ) { continue; } try { findUsbAlternateInterface(device, filters); return true; } catch { continue; } } return false; } Loading
libraries/adb-daemon-webusb/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ "dependencies": { "@types/w3c-web-usb": "^1.0.10", "@yume-chan/adb": "workspace:^", "@yume-chan/event": "workspace:^", "@yume-chan/stream-extra": "workspace:^", "@yume-chan/struct": "workspace:^" }, Loading
libraries/adb-daemon-webusb/src/index.ts +1 −1 Original line number Diff line number Diff line export * from "./device.js"; export * from "./manager.js"; export * from "./utils.js"; export * from "./watcher.js"; export * from "./observer.js";
libraries/adb-daemon-webusb/src/manager.ts +7 −35 Original line number Diff line number Diff line import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; import { findUsbAlternateInterface, getSerialNumber, isErrorName, } from "./utils.js"; import { AdbDaemonWebUsbDeviceObserver } from "./observer.js"; import { isErrorName, matchesFilters } from "./utils.js"; export namespace AdbDaemonWebUsbDeviceManager { export interface RequestDeviceOptions { Loading Loading @@ -76,36 +73,7 @@ export class AdbDaemonWebUsbDeviceManager { const devices = await this.#usbManager.getDevices(); return devices .filter((device) => { for (const filter of filters) { if ( filter.vendorId !== undefined && device.vendorId !== filter.vendorId ) { continue; } if ( filter.productId !== undefined && device.productId !== filter.productId ) { continue; } if ( filter.serialNumber !== undefined && getSerialNumber(device) !== filter.serialNumber ) { continue; } try { findUsbAlternateInterface(device, filters); return true; } catch { continue; } } return false; }) .filter((device) => matchesFilters(device, filters)) .map( (device) => new AdbDaemonWebUsbDevice( Loading @@ -115,4 +83,8 @@ export class AdbDaemonWebUsbDeviceManager { ), ); } trackDevices(filters?: USBDeviceFilter[]): AdbDaemonWebUsbDeviceObserver { return new AdbDaemonWebUsbDeviceObserver(this.#usbManager, filters); } }
libraries/adb-daemon-webusb/src/observer.ts 0 → 100644 +85 −0 Original line number Diff line number Diff line import type { DeviceObserver } from "@yume-chan/adb"; import { EventEmitter } from "@yume-chan/event"; import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; import type { UsbInterfaceFilter } from "./utils.js"; import { matchesFilters } from "./utils.js"; /** * A watcher that listens for new WebUSB devices and notifies the callback when * a new device is connected or disconnected. * * [Online Documentation](https://docs.tangoapp.dev/tango/daemon/usb/watch-devices/) */ export class AdbDaemonWebUsbDeviceObserver implements DeviceObserver<AdbDaemonWebUsbDevice> { #filters: UsbInterfaceFilter[]; #usbManager: USB; #deviceAdded = new EventEmitter<AdbDaemonWebUsbDevice[]>(); deviceAdded = this.#deviceAdded.event; #deviceRemoved = new EventEmitter<AdbDaemonWebUsbDevice[]>(); deviceRemoved = this.#deviceRemoved.event; #deviceChanged = new EventEmitter<AdbDaemonWebUsbDevice[]>(); deviceChanged = this.#deviceChanged.event; #listChanged = new EventEmitter<AdbDaemonWebUsbDevice[]>(); listChanged = this.#listChanged.event; current: AdbDaemonWebUsbDevice[] = []; constructor(usb: USB, filters?: USBDeviceFilter[]) { this.#filters = toAdbDeviceFilters(filters); this.#usbManager = usb; this.#usbManager.addEventListener("connect", this.#handleConnect); this.#usbManager.addEventListener("disconnect", this.#handleDisconnect); } dispose(): void { this.#usbManager.removeEventListener("connect", this.#handleConnect); this.#usbManager.removeEventListener( "disconnect", this.#handleDisconnect, ); } #handleConnect = (e: USBConnectionEvent) => { if (!matchesFilters(e.device, this.#filters)) { return; } const device = new AdbDaemonWebUsbDevice( e.device, this.#filters, this.#usbManager, ); this.#deviceAdded.fire([device]); this.current.push(device); this.#listChanged.fire(this.current); }; #handleDisconnect = (e: USBConnectionEvent) => { const index = this.current.findIndex( (device) => device.raw === e.device, ); if (index !== -1) { const device = this.current[index]!; this.#deviceRemoved.fire([device]); this.current[index] = this.current[this.current.length - 1]!; this.current.length -= 1; this.#listChanged.fire(this.current); } }; stop(): void { this.#usbManager.removeEventListener("connect", this.#handleConnect); this.#usbManager.removeEventListener( "disconnect", this.#handleDisconnect, ); } }
libraries/adb-daemon-webusb/src/utils.ts +34 −0 Original line number Diff line number Diff line Loading @@ -99,3 +99,37 @@ export function findUsbEndpoints(endpoints: USBEndpoint[]) { } throw new Error("unreachable"); } export function matchesFilters( device: USBDevice, filters: (USBDeviceFilter & UsbInterfaceFilter)[], ) { for (const filter of filters) { if ( filter.vendorId !== undefined && device.vendorId !== filter.vendorId ) { continue; } if ( filter.productId !== undefined && device.productId !== filter.productId ) { continue; } if ( filter.serialNumber !== undefined && getSerialNumber(device) !== filter.serialNumber ) { continue; } try { findUsbAlternateInterface(device, filters); return true; } catch { continue; } } return false; }