@yume-chan/adb-daemon-webusb
Advanced tools
Comparing version 0.0.0-next-20241129144018 to 0.0.0-next-20241130050937
# Change Log - @yume-chan/adb-daemon-webusb | ||
## 0.0.0-next-20241129144018 | ||
## 0.0.0-next-20241130050937 | ||
@@ -13,2 +13,3 @@ ### Major Changes | ||
- c68e216: Accept exclusionFilters in getDevices and DeviceObserver | ||
- db8466f: Accept standard `USBDeviceFilter` type and fill in default interface filters automatically | ||
@@ -21,6 +22,6 @@ - db8466f: Throw `DeviceBusyError` when interface can't be claimed | ||
- Updated dependencies [db8466f] | ||
- @yume-chan/stream-extra@0.0.0-next-20241129144018 | ||
- @yume-chan/struct@0.0.0-next-20241129144018 | ||
- @yume-chan/event@0.0.0-next-20241129144018 | ||
- @yume-chan/adb@0.0.0-next-20241129144018 | ||
- @yume-chan/stream-extra@0.0.0-next-20241130050937 | ||
- @yume-chan/struct@0.0.0-next-20241130050937 | ||
- @yume-chan/event@0.0.0-next-20241130050937 | ||
- @yume-chan/adb@0.0.0-next-20241130050937 | ||
@@ -27,0 +28,0 @@ This log was last generated on Tue, 18 Jun 2024 02:49:43 GMT and should not be manually modified. |
import type { AdbDaemonDevice, AdbPacketData, AdbPacketInit } from "@yume-chan/adb"; | ||
import type { Consumable, ReadableWritablePair, WritableStream } from "@yume-chan/stream-extra"; | ||
import { ReadableStream } from "@yume-chan/stream-extra"; | ||
import type { UsbInterfaceFilter } from "./utils.js"; | ||
import type { UsbInterfaceFilter, UsbInterfaceIdentifier } from "./utils.js"; | ||
/** | ||
* The default filter for ADB devices, as defined by Google. | ||
*/ | ||
export declare const ADB_DEFAULT_INTERFACE_FILTER: { | ||
export declare const AdbDefaultInterfaceFilter: { | ||
readonly classCode: 255; | ||
@@ -13,3 +13,3 @@ readonly subclassCode: 66; | ||
}; | ||
export declare function toAdbDeviceFilters(filters: USBDeviceFilter[] | undefined): (USBDeviceFilter & UsbInterfaceFilter)[]; | ||
export declare function mergeDefaultAdbInterfaceFilter(filters: USBDeviceFilter[] | undefined): (USBDeviceFilter & UsbInterfaceFilter)[]; | ||
export declare class AdbDaemonWebUsbConnection implements ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>> { | ||
@@ -35,3 +35,3 @@ #private; | ||
*/ | ||
constructor(device: USBDevice, filters: UsbInterfaceFilter[], usbManager: USB); | ||
constructor(device: USBDevice, interface_: UsbInterfaceIdentifier, usbManager: USB); | ||
/** | ||
@@ -38,0 +38,0 @@ * Open the device and create a new connection to the ADB Daemon. |
import { AdbPacketHeader, AdbPacketSerializeStream, unreachable, } from "@yume-chan/adb"; | ||
import { DuplexStreamFactory, MaybeConsumable, ReadableStream, pipeFrom, } from "@yume-chan/stream-extra"; | ||
import { EmptyUint8Array } from "@yume-chan/struct"; | ||
import { findUsbAlternateInterface, findUsbEndpoints, getSerialNumber, isErrorName, } from "./utils.js"; | ||
import { findUsbEndpoints, getSerialNumber, isErrorName } from "./utils.js"; | ||
/** | ||
* The default filter for ADB devices, as defined by Google. | ||
*/ | ||
export const ADB_DEFAULT_INTERFACE_FILTER = { | ||
export const AdbDefaultInterfaceFilter = { | ||
classCode: 0xff, | ||
@@ -13,5 +13,5 @@ subclassCode: 0x42, | ||
}; | ||
export function toAdbDeviceFilters(filters) { | ||
export function mergeDefaultAdbInterfaceFilter(filters) { | ||
if (!filters || filters.length === 0) { | ||
return [ADB_DEFAULT_INTERFACE_FILTER]; | ||
return [AdbDefaultInterfaceFilter]; | ||
} | ||
@@ -21,7 +21,5 @@ else { | ||
...filter, | ||
classCode: filter.classCode ?? ADB_DEFAULT_INTERFACE_FILTER.classCode, | ||
subclassCode: filter.subclassCode ?? | ||
ADB_DEFAULT_INTERFACE_FILTER.subclassCode, | ||
protocolCode: filter.protocolCode ?? | ||
ADB_DEFAULT_INTERFACE_FILTER.protocolCode, | ||
classCode: filter.classCode ?? AdbDefaultInterfaceFilter.classCode, | ||
subclassCode: filter.subclassCode ?? AdbDefaultInterfaceFilter.subclassCode, | ||
protocolCode: filter.protocolCode ?? AdbDefaultInterfaceFilter.protocolCode, | ||
})); | ||
@@ -172,3 +170,3 @@ } | ||
export class AdbDaemonWebUsbDevice { | ||
#filters; | ||
#interface; | ||
#usbManager; | ||
@@ -192,6 +190,6 @@ #raw; | ||
*/ | ||
constructor(device, filters, usbManager) { | ||
constructor(device, interface_, usbManager) { | ||
this.#raw = device; | ||
this.#serial = getSerialNumber(device); | ||
this.#filters = filters; | ||
this.#interface = interface_; | ||
this.#usbManager = usbManager; | ||
@@ -203,3 +201,3 @@ } | ||
} | ||
const { configuration, interface_, alternate } = findUsbAlternateInterface(this.#raw, this.#filters); | ||
const { configuration, interface_, alternate } = this.#interface; | ||
if (this.#raw.configuration?.configurationValue !== | ||
@@ -225,4 +223,3 @@ configuration.configurationValue) { | ||
} | ||
const { inEndpoint, outEndpoint } = findUsbEndpoints(alternate.endpoints); | ||
return [inEndpoint, outEndpoint]; | ||
return findUsbEndpoints(alternate.endpoints); | ||
} | ||
@@ -233,3 +230,3 @@ /** | ||
async connect() { | ||
const [inEndpoint, outEndpoint] = await this.#claimInterface(); | ||
const { inEndpoint, outEndpoint } = await this.#claimInterface(); | ||
return new AdbDaemonWebUsbConnection(this, inEndpoint, outEndpoint, this.#usbManager); | ||
@@ -236,0 +233,0 @@ } |
@@ -29,5 +29,5 @@ import { AdbDaemonWebUsbDevice } from "./device.js"; | ||
*/ | ||
getDevices(filters?: USBDeviceFilter[]): Promise<AdbDaemonWebUsbDevice[]>; | ||
trackDevices(filters?: USBDeviceFilter[]): AdbDaemonWebUsbDeviceObserver; | ||
getDevices(options?: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions): Promise<AdbDaemonWebUsbDevice[]>; | ||
trackDevices(options?: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions): AdbDaemonWebUsbDeviceObserver; | ||
} | ||
//# sourceMappingURL=manager.d.ts.map |
@@ -1,4 +0,4 @@ | ||
import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; | ||
import { AdbDaemonWebUsbDevice, mergeDefaultAdbInterfaceFilter, } from "./device.js"; | ||
import { AdbDaemonWebUsbDeviceObserver } from "./observer.js"; | ||
import { isErrorName, matchesFilters } from "./utils.js"; | ||
import { isErrorName, matchFilters } from "./utils.js"; | ||
export class AdbDaemonWebUsbDeviceManager { | ||
@@ -26,3 +26,3 @@ /** | ||
async requestDevice(options = {}) { | ||
const filters = toAdbDeviceFilters(options.filters); | ||
const filters = mergeDefaultAdbInterfaceFilter(options.filters); | ||
try { | ||
@@ -33,3 +33,9 @@ const device = await this.#usbManager.requestDevice({ | ||
}); | ||
return new AdbDaemonWebUsbDevice(device, filters, this.#usbManager); | ||
const interface_ = matchFilters(device, filters, options.exclusionFilters); | ||
if (!interface_) { | ||
// `#usbManager` doesn't support `exclusionFilters`, | ||
// selected device is invalid | ||
return undefined; | ||
} | ||
return new AdbDaemonWebUsbDevice(device, interface_, this.#usbManager); | ||
} | ||
@@ -44,13 +50,22 @@ catch (e) { | ||
} | ||
async getDevices(filters_) { | ||
const filters = toAdbDeviceFilters(filters_); | ||
/** | ||
* Get all connected and requested devices that match the specified filters. | ||
*/ | ||
async getDevices(options = {}) { | ||
const filters = mergeDefaultAdbInterfaceFilter(options.filters); | ||
const devices = await this.#usbManager.getDevices(); | ||
return devices | ||
.filter((device) => matchesFilters(device, filters)) | ||
.map((device) => new AdbDaemonWebUsbDevice(device, filters, this.#usbManager)); | ||
// filter map | ||
const result = []; | ||
for (const device of devices) { | ||
const interface_ = matchFilters(device, filters, options.exclusionFilters); | ||
if (interface_) { | ||
result.push(new AdbDaemonWebUsbDevice(device, interface_, this.#usbManager)); | ||
} | ||
} | ||
return result; | ||
} | ||
trackDevices(filters) { | ||
return new AdbDaemonWebUsbDeviceObserver(this.#usbManager, filters); | ||
trackDevices(options = {}) { | ||
return new AdbDaemonWebUsbDeviceObserver(this.#usbManager, options); | ||
} | ||
} | ||
//# sourceMappingURL=manager.js.map |
import type { DeviceObserver } from "@yume-chan/adb"; | ||
import { AdbDaemonWebUsbDevice } from "./device.js"; | ||
import type { AdbDaemonWebUsbDeviceManager } from "./manager.js"; | ||
/** | ||
@@ -14,5 +15,5 @@ * A watcher that listens for new WebUSB devices and notifies the callback when | ||
current: AdbDaemonWebUsbDevice[]; | ||
constructor(usb: USB, filters?: USBDeviceFilter[]); | ||
constructor(usb: USB, options?: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions); | ||
stop(): void; | ||
} | ||
//# sourceMappingURL=observer.d.ts.map |
import { EventEmitter } from "@yume-chan/event"; | ||
import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; | ||
import { matchesFilters } from "./utils.js"; | ||
import { AdbDaemonWebUsbDevice, mergeDefaultAdbInterfaceFilter, } from "./device.js"; | ||
import { matchFilters } from "./utils.js"; | ||
/** | ||
@@ -10,2 +10,3 @@ * A watcher that listens for new WebUSB devices and notifies the callback when | ||
#filters; | ||
#exclusionFilters; | ||
#usbManager; | ||
@@ -21,4 +22,5 @@ #onError = new EventEmitter(); | ||
current = []; | ||
constructor(usb, filters) { | ||
this.#filters = toAdbDeviceFilters(filters); | ||
constructor(usb, options = {}) { | ||
this.#filters = mergeDefaultAdbInterfaceFilter(options.filters); | ||
this.#exclusionFilters = options.exclusionFilters; | ||
this.#usbManager = usb; | ||
@@ -29,6 +31,7 @@ this.#usbManager.addEventListener("connect", this.#handleConnect); | ||
#handleConnect = (e) => { | ||
if (!matchesFilters(e.device, this.#filters)) { | ||
const interface_ = matchFilters(e.device, this.#filters, this.#exclusionFilters); | ||
if (!interface_) { | ||
return; | ||
} | ||
const device = new AdbDaemonWebUsbDevice(e.device, this.#filters, this.#usbManager); | ||
const device = new AdbDaemonWebUsbDevice(e.device, interface_, this.#usbManager); | ||
this.#onDeviceAdd.fire([device]); | ||
@@ -51,4 +54,8 @@ this.current.push(device); | ||
this.#usbManager.removeEventListener("disconnect", this.#handleDisconnect); | ||
this.#onError.dispose(); | ||
this.#onDeviceAdd.dispose(); | ||
this.#onDeviceRemove.dispose(); | ||
this.#onListChange.dispose(); | ||
} | ||
} | ||
//# sourceMappingURL=observer.js.map |
@@ -10,7 +10,9 @@ export declare function isErrorName(e: unknown, name: string): e is Error; | ||
export type UsbInterfaceFilter = PickNonNullable<USBDeviceFilter, "classCode" | "subclassCode" | "protocolCode">; | ||
export declare function findUsbAlternateInterface(device: USBDevice, filters: UsbInterfaceFilter[]): { | ||
export declare function isUsbInterfaceFilter(filter: USBDeviceFilter): filter is UsbInterfaceFilter; | ||
export interface UsbInterfaceIdentifier { | ||
configuration: USBConfiguration; | ||
interface_: USBInterface; | ||
alternate: USBAlternateInterface; | ||
}; | ||
} | ||
export declare function findUsbInterface(device: USBDevice, filter: UsbInterfaceFilter): UsbInterfaceIdentifier | undefined; | ||
export declare function getSerialNumber(device: USBDevice): string; | ||
@@ -26,3 +28,6 @@ /** | ||
}; | ||
export declare function matchesFilters(device: USBDevice, filters: (USBDeviceFilter & UsbInterfaceFilter)[]): boolean; | ||
export declare function matchFilter(device: USBDevice, filter: USBDeviceFilter & UsbInterfaceFilter): UsbInterfaceIdentifier | false; | ||
export declare function matchFilter(device: USBDevice, filter: USBDeviceFilter): boolean; | ||
export declare function matchFilters(device: USBDevice, filters: (USBDeviceFilter & UsbInterfaceFilter)[], exclusionFilters?: USBDeviceFilter[]): UsbInterfaceIdentifier | false; | ||
export declare function matchFilters(device: USBDevice, filters: USBDeviceFilter[], exclusionFilters?: USBDeviceFilter[]): boolean; | ||
//# sourceMappingURL=utils.d.ts.map |
@@ -7,12 +7,17 @@ export function isErrorName(e, name) { | ||
} | ||
function alternateMatchesFilter(alternate, filters) { | ||
return filters.some((filter) => alternate.interfaceClass === filter.classCode && | ||
export function isUsbInterfaceFilter(filter) { | ||
return (filter.classCode !== undefined && | ||
filter.subclassCode !== undefined && | ||
filter.protocolCode !== undefined); | ||
} | ||
function matchUsbInterfaceFilter(alternate, filter) { | ||
return (alternate.interfaceClass === filter.classCode && | ||
alternate.interfaceSubclass === filter.subclassCode && | ||
alternate.interfaceProtocol === filter.protocolCode); | ||
} | ||
export function findUsbAlternateInterface(device, filters) { | ||
export function findUsbInterface(device, filter) { | ||
for (const configuration of device.configurations) { | ||
for (const interface_ of configuration.interfaces) { | ||
for (const alternate of interface_.alternates) { | ||
if (alternateMatchesFilter(alternate, filters)) { | ||
if (matchUsbInterfaceFilter(alternate, filter)) { | ||
return { configuration, interface_, alternate }; | ||
@@ -23,3 +28,3 @@ } | ||
} | ||
throw new TypeError("No matched alternate interface found"); | ||
return undefined; | ||
} | ||
@@ -70,23 +75,30 @@ function padNumber(value) { | ||
} | ||
export function matchesFilters(device, filters) { | ||
export function matchFilter(device, filter) { | ||
if (filter.vendorId !== undefined && device.vendorId !== filter.vendorId) { | ||
return false; | ||
} | ||
if (filter.productId !== undefined && | ||
device.productId !== filter.productId) { | ||
return false; | ||
} | ||
if (filter.serialNumber !== undefined && | ||
getSerialNumber(device) !== filter.serialNumber) { | ||
return false; | ||
} | ||
if (isUsbInterfaceFilter(filter)) { | ||
return findUsbInterface(device, filter) || false; | ||
} | ||
return true; | ||
} | ||
export function matchFilters(device, filters, exclusionFilters) { | ||
if (exclusionFilters && exclusionFilters.length > 0) { | ||
if (matchFilters(device, exclusionFilters)) { | ||
return false; | ||
} | ||
} | ||
for (const filter of filters) { | ||
if (filter.vendorId !== undefined && | ||
device.vendorId !== filter.vendorId) { | ||
continue; | ||
const result = matchFilter(device, filter); | ||
if (result) { | ||
return result; | ||
} | ||
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; | ||
} | ||
} | ||
@@ -93,0 +105,0 @@ return false; |
{ | ||
"name": "@yume-chan/adb-daemon-webusb", | ||
"version": "0.0.0-next-20241129144018", | ||
"version": "0.0.0-next-20241130050937", | ||
"description": "Adb daemon transport connection for `@yume-chan/adb` using WebUSB API.", | ||
@@ -30,6 +30,6 @@ "keywords": [ | ||
"@types/w3c-web-usb": "^1.0.10", | ||
"@yume-chan/adb": "^0.0.0-next-20241129144018", | ||
"@yume-chan/event": "^0.0.0-next-20241129144018", | ||
"@yume-chan/stream-extra": "^0.0.0-next-20241129144018", | ||
"@yume-chan/struct": "^0.0.0-next-20241129144018" | ||
"@yume-chan/event": "^0.0.0-next-20241130050937", | ||
"@yume-chan/adb": "^0.0.0-next-20241130050937", | ||
"@yume-chan/stream-extra": "^0.0.0-next-20241130050937", | ||
"@yume-chan/struct": "^0.0.0-next-20241130050937" | ||
}, | ||
@@ -40,4 +40,4 @@ "devDependencies": { | ||
"typescript": "^5.7.2", | ||
"@yume-chan/eslint-config": "^1.0.0", | ||
"@yume-chan/test-runner": "^1.0.0", | ||
"@yume-chan/eslint-config": "^1.0.0", | ||
"@yume-chan/tsconfig": "^1.0.0" | ||
@@ -44,0 +44,0 @@ }, |
@@ -25,9 +25,4 @@ import type { | ||
import type { UsbInterfaceFilter } from "./utils.js"; | ||
import { | ||
findUsbAlternateInterface, | ||
findUsbEndpoints, | ||
getSerialNumber, | ||
isErrorName, | ||
} from "./utils.js"; | ||
import type { UsbInterfaceFilter, UsbInterfaceIdentifier } from "./utils.js"; | ||
import { findUsbEndpoints, getSerialNumber, isErrorName } from "./utils.js"; | ||
@@ -37,3 +32,3 @@ /** | ||
*/ | ||
export const ADB_DEFAULT_INTERFACE_FILTER = { | ||
export const AdbDefaultInterfaceFilter = { | ||
classCode: 0xff, | ||
@@ -44,18 +39,15 @@ subclassCode: 0x42, | ||
export function toAdbDeviceFilters( | ||
export function mergeDefaultAdbInterfaceFilter( | ||
filters: USBDeviceFilter[] | undefined, | ||
): (USBDeviceFilter & UsbInterfaceFilter)[] { | ||
if (!filters || filters.length === 0) { | ||
return [ADB_DEFAULT_INTERFACE_FILTER]; | ||
return [AdbDefaultInterfaceFilter]; | ||
} else { | ||
return filters.map((filter) => ({ | ||
...filter, | ||
classCode: | ||
filter.classCode ?? ADB_DEFAULT_INTERFACE_FILTER.classCode, | ||
classCode: filter.classCode ?? AdbDefaultInterfaceFilter.classCode, | ||
subclassCode: | ||
filter.subclassCode ?? | ||
ADB_DEFAULT_INTERFACE_FILTER.subclassCode, | ||
filter.subclassCode ?? AdbDefaultInterfaceFilter.subclassCode, | ||
protocolCode: | ||
filter.protocolCode ?? | ||
ADB_DEFAULT_INTERFACE_FILTER.protocolCode, | ||
filter.protocolCode ?? AdbDefaultInterfaceFilter.protocolCode, | ||
})); | ||
@@ -268,3 +260,3 @@ } | ||
export class AdbDaemonWebUsbDevice implements AdbDaemonDevice { | ||
#filters: UsbInterfaceFilter[]; | ||
#interface: UsbInterfaceIdentifier; | ||
#usbManager: USB; | ||
@@ -294,3 +286,3 @@ | ||
device: USBDevice, | ||
filters: UsbInterfaceFilter[], | ||
interface_: UsbInterfaceIdentifier, | ||
usbManager: USB, | ||
@@ -300,7 +292,10 @@ ) { | ||
this.#serial = getSerialNumber(device); | ||
this.#filters = filters; | ||
this.#interface = interface_; | ||
this.#usbManager = usbManager; | ||
} | ||
async #claimInterface(): Promise<[USBEndpoint, USBEndpoint]> { | ||
async #claimInterface(): Promise<{ | ||
inEndpoint: USBEndpoint; | ||
outEndpoint: USBEndpoint; | ||
}> { | ||
if (!this.#raw.opened) { | ||
@@ -310,4 +305,3 @@ await this.#raw.open(); | ||
const { configuration, interface_, alternate } = | ||
findUsbAlternateInterface(this.#raw, this.#filters); | ||
const { configuration, interface_, alternate } = this.#interface; | ||
@@ -346,6 +340,3 @@ if ( | ||
const { inEndpoint, outEndpoint } = findUsbEndpoints( | ||
alternate.endpoints, | ||
); | ||
return [inEndpoint, outEndpoint]; | ||
return findUsbEndpoints(alternate.endpoints); | ||
} | ||
@@ -357,3 +348,3 @@ | ||
async connect(): Promise<AdbDaemonWebUsbConnection> { | ||
const [inEndpoint, outEndpoint] = await this.#claimInterface(); | ||
const { inEndpoint, outEndpoint } = await this.#claimInterface(); | ||
return new AdbDaemonWebUsbConnection( | ||
@@ -360,0 +351,0 @@ this, |
@@ -1,4 +0,7 @@ | ||
import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; | ||
import { | ||
AdbDaemonWebUsbDevice, | ||
mergeDefaultAdbInterfaceFilter, | ||
} from "./device.js"; | ||
import { AdbDaemonWebUsbDeviceObserver } from "./observer.js"; | ||
import { isErrorName, matchesFilters } from "./utils.js"; | ||
import { isErrorName, matchFilters } from "./utils.js"; | ||
@@ -40,3 +43,3 @@ export namespace AdbDaemonWebUsbDeviceManager { | ||
): Promise<AdbDaemonWebUsbDevice | undefined> { | ||
const filters = toAdbDeviceFilters(options.filters); | ||
const filters = mergeDefaultAdbInterfaceFilter(options.filters); | ||
@@ -48,3 +51,19 @@ try { | ||
}); | ||
return new AdbDaemonWebUsbDevice(device, filters, this.#usbManager); | ||
const interface_ = matchFilters( | ||
device, | ||
filters, | ||
options.exclusionFilters, | ||
); | ||
if (!interface_) { | ||
// `#usbManager` doesn't support `exclusionFilters`, | ||
// selected device is invalid | ||
return undefined; | ||
} | ||
return new AdbDaemonWebUsbDevice( | ||
device, | ||
interface_, | ||
this.#usbManager, | ||
); | ||
} catch (e) { | ||
@@ -63,24 +82,35 @@ // No device selected | ||
*/ | ||
getDevices(filters?: USBDeviceFilter[]): Promise<AdbDaemonWebUsbDevice[]>; | ||
async getDevices( | ||
filters_: USBDeviceFilter[] | undefined, | ||
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {}, | ||
): Promise<AdbDaemonWebUsbDevice[]> { | ||
const filters = toAdbDeviceFilters(filters_); | ||
const filters = mergeDefaultAdbInterfaceFilter(options.filters); | ||
const devices = await this.#usbManager.getDevices(); | ||
return devices | ||
.filter((device) => matchesFilters(device, filters)) | ||
.map( | ||
(device) => | ||
// filter map | ||
const result: AdbDaemonWebUsbDevice[] = []; | ||
for (const device of devices) { | ||
const interface_ = matchFilters( | ||
device, | ||
filters, | ||
options.exclusionFilters, | ||
); | ||
if (interface_) { | ||
result.push( | ||
new AdbDaemonWebUsbDevice( | ||
device, | ||
filters, | ||
interface_, | ||
this.#usbManager, | ||
), | ||
); | ||
); | ||
} | ||
} | ||
return result; | ||
} | ||
trackDevices(filters?: USBDeviceFilter[]): AdbDaemonWebUsbDeviceObserver { | ||
return new AdbDaemonWebUsbDeviceObserver(this.#usbManager, filters); | ||
trackDevices( | ||
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {}, | ||
): AdbDaemonWebUsbDeviceObserver { | ||
return new AdbDaemonWebUsbDeviceObserver(this.#usbManager, options); | ||
} | ||
} |
import type { DeviceObserver } from "@yume-chan/adb"; | ||
import { EventEmitter } from "@yume-chan/event"; | ||
import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js"; | ||
import { | ||
AdbDaemonWebUsbDevice, | ||
mergeDefaultAdbInterfaceFilter, | ||
} from "./device.js"; | ||
import type { AdbDaemonWebUsbDeviceManager } from "./manager.js"; | ||
import type { UsbInterfaceFilter } from "./utils.js"; | ||
import { matchesFilters } from "./utils.js"; | ||
import { matchFilters } from "./utils.js"; | ||
@@ -15,3 +19,4 @@ /** | ||
{ | ||
#filters: UsbInterfaceFilter[]; | ||
#filters: (USBDeviceFilter & UsbInterfaceFilter)[]; | ||
#exclusionFilters?: USBDeviceFilter[] | undefined; | ||
#usbManager: USB; | ||
@@ -33,4 +38,8 @@ | ||
constructor(usb: USB, filters?: USBDeviceFilter[]) { | ||
this.#filters = toAdbDeviceFilters(filters); | ||
constructor( | ||
usb: USB, | ||
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {}, | ||
) { | ||
this.#filters = mergeDefaultAdbInterfaceFilter(options.filters); | ||
this.#exclusionFilters = options.exclusionFilters; | ||
this.#usbManager = usb; | ||
@@ -43,3 +52,8 @@ | ||
#handleConnect = (e: USBConnectionEvent) => { | ||
if (!matchesFilters(e.device, this.#filters)) { | ||
const interface_ = matchFilters( | ||
e.device, | ||
this.#filters, | ||
this.#exclusionFilters, | ||
); | ||
if (!interface_) { | ||
return; | ||
@@ -50,3 +64,3 @@ } | ||
e.device, | ||
this.#filters, | ||
interface_, | ||
this.#usbManager, | ||
@@ -78,3 +92,8 @@ ); | ||
); | ||
this.#onError.dispose(); | ||
this.#onDeviceAdd.dispose(); | ||
this.#onDeviceRemove.dispose(); | ||
this.#onListChange.dispose(); | ||
} | ||
} |
123
src/utils.ts
@@ -23,22 +23,37 @@ export function isErrorName(e: unknown, name: string): e is Error { | ||
function alternateMatchesFilter( | ||
export function isUsbInterfaceFilter( | ||
filter: USBDeviceFilter, | ||
): filter is UsbInterfaceFilter { | ||
return ( | ||
filter.classCode !== undefined && | ||
filter.subclassCode !== undefined && | ||
filter.protocolCode !== undefined | ||
); | ||
} | ||
function matchUsbInterfaceFilter( | ||
alternate: USBAlternateInterface, | ||
filters: UsbInterfaceFilter[], | ||
filter: UsbInterfaceFilter, | ||
) { | ||
return filters.some( | ||
(filter) => | ||
alternate.interfaceClass === filter.classCode && | ||
alternate.interfaceSubclass === filter.subclassCode && | ||
alternate.interfaceProtocol === filter.protocolCode, | ||
return ( | ||
alternate.interfaceClass === filter.classCode && | ||
alternate.interfaceSubclass === filter.subclassCode && | ||
alternate.interfaceProtocol === filter.protocolCode | ||
); | ||
} | ||
export function findUsbAlternateInterface( | ||
export interface UsbInterfaceIdentifier { | ||
configuration: USBConfiguration; | ||
interface_: USBInterface; | ||
alternate: USBAlternateInterface; | ||
} | ||
export function findUsbInterface( | ||
device: USBDevice, | ||
filters: UsbInterfaceFilter[], | ||
) { | ||
filter: UsbInterfaceFilter, | ||
): UsbInterfaceIdentifier | undefined { | ||
for (const configuration of device.configurations) { | ||
for (const interface_ of configuration.interfaces) { | ||
for (const alternate of interface_.alternates) { | ||
if (alternateMatchesFilter(alternate, filters)) { | ||
if (matchUsbInterfaceFilter(alternate, filter)) { | ||
return { configuration, interface_, alternate }; | ||
@@ -49,4 +64,3 @@ } | ||
} | ||
throw new TypeError("No matched alternate interface found"); | ||
return undefined; | ||
} | ||
@@ -105,31 +119,64 @@ | ||
export function matchesFilters( | ||
export function matchFilter( | ||
device: USBDevice, | ||
filter: USBDeviceFilter & UsbInterfaceFilter, | ||
): UsbInterfaceIdentifier | false; | ||
export function matchFilter( | ||
device: USBDevice, | ||
filter: USBDeviceFilter, | ||
): boolean; | ||
export function matchFilter( | ||
device: USBDevice, | ||
filter: USBDeviceFilter, | ||
): UsbInterfaceIdentifier | boolean { | ||
if (filter.vendorId !== undefined && device.vendorId !== filter.vendorId) { | ||
return false; | ||
} | ||
if ( | ||
filter.productId !== undefined && | ||
device.productId !== filter.productId | ||
) { | ||
return false; | ||
} | ||
if ( | ||
filter.serialNumber !== undefined && | ||
getSerialNumber(device) !== filter.serialNumber | ||
) { | ||
return false; | ||
} | ||
if (isUsbInterfaceFilter(filter)) { | ||
return findUsbInterface(device, filter) || false; | ||
} | ||
return true; | ||
} | ||
export function matchFilters( | ||
device: USBDevice, | ||
filters: (USBDeviceFilter & UsbInterfaceFilter)[], | ||
) { | ||
for (const filter of filters) { | ||
if ( | ||
filter.vendorId !== undefined && | ||
device.vendorId !== filter.vendorId | ||
) { | ||
continue; | ||
exclusionFilters?: USBDeviceFilter[], | ||
): UsbInterfaceIdentifier | false; | ||
export function matchFilters( | ||
device: USBDevice, | ||
filters: USBDeviceFilter[], | ||
exclusionFilters?: USBDeviceFilter[], | ||
): boolean; | ||
export function matchFilters( | ||
device: USBDevice, | ||
filters: USBDeviceFilter[], | ||
exclusionFilters?: USBDeviceFilter[], | ||
): UsbInterfaceIdentifier | boolean { | ||
if (exclusionFilters && exclusionFilters.length > 0) { | ||
if (matchFilters(device, exclusionFilters)) { | ||
return false; | ||
} | ||
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; | ||
for (const filter of filters) { | ||
const result = matchFilter(device, filter); | ||
if (result) { | ||
return result; | ||
} | ||
@@ -136,0 +183,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
105767
1266