mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
feat(webusb): allow filters
to be empty arrays
This commit is contained in:
parent
10ed1848f5
commit
af0acb577a
15 changed files with 321 additions and 82 deletions
15
common/config/rush/pnpm-lock.yaml
generated
15
common/config/rush/pnpm-lock.yaml
generated
|
@ -130,15 +130,30 @@ importers:
|
|||
specifier: workspace:^0.0.24
|
||||
version: link:../struct
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.4
|
||||
version: 30.0.0-alpha.5
|
||||
'@types/node':
|
||||
specifier: ^20.14.9
|
||||
version: 20.14.9
|
||||
'@yume-chan/eslint-config':
|
||||
specifier: workspace:^1.0.0
|
||||
version: link:../../toolchain/eslint-config
|
||||
'@yume-chan/tsconfig':
|
||||
specifier: workspace:^1.0.0
|
||||
version: link:../../toolchain/tsconfig
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
jest:
|
||||
specifier: ^30.0.0-alpha.4
|
||||
version: 30.0.0-alpha.5(@types/node@20.14.9)
|
||||
prettier:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
ts-jest:
|
||||
specifier: ^29.1.5
|
||||
version: 29.1.5(@babel/core@7.24.7)(@jest/types@29.6.3)(jest@30.0.0-alpha.5)(typescript@5.5.2)
|
||||
typescript:
|
||||
specifier: ^5.5.2
|
||||
version: 5.5.2
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
|
||||
{
|
||||
"pnpmShrinkwrapHash": "4268d369f3d5b02e089540e98cb1fa9074cba8c9",
|
||||
"pnpmShrinkwrapHash": "e045703b662b53e585b409888c662653aa4196da",
|
||||
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
<a href="https://github.com/yume-chan/ya-webadb/releases">
|
||||
<img alt="GitHub release" src="https://img.shields.io/github/v/release/yume-chan/ya-webadb?logo=github">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/package/@yume-chan/adb-credential-web">
|
||||
<img alt="Package Size" src="https://img.shields.io/bundlephobia/minzip/%40yume-chan%2Fadb-credential-web">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@yume-chan/adb">
|
||||
<img alt="npm" src="https://img.shields.io/npm/dm/%40yume-chan/adb?logo=npm">
|
||||
<img alt="npm" src="https://img.shields.io/npm/dm/%40yume-chan/adb-credential-web?logo=npm">
|
||||
</a>
|
||||
<a href="https://discord.gg/26k3ttC2PN">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/1120215514732564502?logo=discord&logoColor=%23ffffff&label=Discord">
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
<a href="https://github.com/yume-chan/ya-webadb/releases">
|
||||
<img alt="GitHub release" src="https://img.shields.io/github/v/release/yume-chan/ya-webadb?logo=github">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@yume-chan/adb">
|
||||
<img alt="npm" src="https://img.shields.io/npm/dm/%40yume-chan/adb?logo=npm">
|
||||
<a href="https://bundlephobia.com/package/@yume-chan/adb-daemon-webusb">
|
||||
<img alt="Package Size" src="https://img.shields.io/bundlephobia/minzip/%40yume-chan%2Fadb-daemon-webusb">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@yume-chan/adb-daemon-webusb">
|
||||
<img alt="npm" src="https://img.shields.io/npm/dm/%40yume-chan/adb-daemon-webusb?logo=npm">
|
||||
</a>
|
||||
<a href="https://discord.gg/26k3ttC2PN">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/1120215514732564502?logo=discord&logoColor=%23ffffff&label=Discord">
|
||||
|
|
14
libraries/adb-daemon-webusb/jest.config.js
Normal file
14
libraries/adb-daemon-webusb/jest.config.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
export default {
|
||||
preset: "ts-jest/presets/default-esm",
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"ts-jest",
|
||||
{ tsconfig: "tsconfig.test.json", useESM: true },
|
||||
],
|
||||
},
|
||||
moduleNameMapper: {
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
||||
},
|
||||
};
|
|
@ -28,7 +28,8 @@
|
|||
"build": "tsc -b tsconfig.build.json",
|
||||
"build:watch": "tsc -b tsconfig.build.json",
|
||||
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4",
|
||||
"prepublishOnly": "npm run build"
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" TS_JEST_DISABLE_VER_CHECKER=true jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/w3c-web-usb": "^1.0.10",
|
||||
|
@ -37,9 +38,14 @@
|
|||
"@yume-chan/struct": "workspace:^0.0.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.4",
|
||||
"@types/node": "^20.14.9",
|
||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^30.0.0-alpha.4",
|
||||
"prettier": "^3.3.2",
|
||||
"ts-jest": "^29.1.5",
|
||||
"typescript": "^5.5.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,10 @@ import {
|
|||
import type { ExactReadable } from "@yume-chan/struct";
|
||||
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
||||
|
||||
import type { AdbDeviceFilter } from "./utils.js";
|
||||
import type { UsbInterfaceFilter } from "./utils.js";
|
||||
import {
|
||||
findUsbAlternateInterface,
|
||||
findUsbEndpoints,
|
||||
getSerialNumber,
|
||||
isErrorName,
|
||||
} from "./utils.js";
|
||||
|
@ -32,49 +33,30 @@ import {
|
|||
/**
|
||||
* The default filter for ADB devices, as defined by Google.
|
||||
*/
|
||||
export const ADB_DEFAULT_DEVICE_FILTER = {
|
||||
export const ADB_DEFAULT_INTERFACE_FILTER = {
|
||||
classCode: 0xff,
|
||||
subclassCode: 0x42,
|
||||
protocolCode: 1,
|
||||
} as const satisfies AdbDeviceFilter;
|
||||
} as const satisfies UsbInterfaceFilter;
|
||||
|
||||
/**
|
||||
* Find the first pair of input and output endpoints from an alternate interface.
|
||||
*
|
||||
* ADB interface only has two endpoints, one for input and one for output.
|
||||
*/
|
||||
function findUsbEndpoints(endpoints: USBEndpoint[]) {
|
||||
if (endpoints.length === 0) {
|
||||
throw new TypeError("No endpoints given");
|
||||
export function toAdbDeviceFilters(
|
||||
filters: USBDeviceFilter[] | undefined,
|
||||
): (USBDeviceFilter & UsbInterfaceFilter)[] {
|
||||
if (!filters || filters.length === 0) {
|
||||
return [ADB_DEFAULT_INTERFACE_FILTER];
|
||||
} else {
|
||||
return filters.map((filter) => ({
|
||||
...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,
|
||||
}));
|
||||
}
|
||||
|
||||
let inEndpoint: USBEndpoint | undefined;
|
||||
let outEndpoint: USBEndpoint | undefined;
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
switch (endpoint.direction) {
|
||||
case "in":
|
||||
inEndpoint = endpoint;
|
||||
if (outEndpoint) {
|
||||
return { inEndpoint, outEndpoint };
|
||||
}
|
||||
break;
|
||||
case "out":
|
||||
outEndpoint = endpoint;
|
||||
if (inEndpoint) {
|
||||
return { inEndpoint, outEndpoint };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inEndpoint) {
|
||||
throw new TypeError("No input endpoint found.");
|
||||
}
|
||||
if (!outEndpoint) {
|
||||
throw new TypeError("No output endpoint found.");
|
||||
}
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
|
||||
class Uint8ArrayExactReadable implements ExactReadable {
|
||||
|
@ -282,7 +264,7 @@ export class AdbDaemonWebUsbConnection
|
|||
}
|
||||
|
||||
export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
|
||||
#filters: AdbDeviceFilter[];
|
||||
#filters: UsbInterfaceFilter[];
|
||||
#usbManager: USB;
|
||||
|
||||
#raw: USBDevice;
|
||||
|
@ -307,7 +289,7 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
|
|||
*/
|
||||
constructor(
|
||||
device: USBDevice,
|
||||
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
|
||||
filters: UsbInterfaceFilter[],
|
||||
usbManager: USB,
|
||||
) {
|
||||
this.#raw = device;
|
||||
|
|
167
libraries/adb-daemon-webusb/src/manager.spec.ts
Normal file
167
libraries/adb-daemon-webusb/src/manager.spec.ts
Normal file
|
@ -0,0 +1,167 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
import { describe, expect, it, jest } from "@jest/globals";
|
||||
|
||||
import { ADB_DEFAULT_INTERFACE_FILTER, AdbDaemonWebUsbDevice } from "./device";
|
||||
import { AdbDaemonWebUsbDeviceManager } from "./manager.js";
|
||||
|
||||
class MockUsb implements USB {
|
||||
onconnect: (ev: USBConnectionEvent) => void = jest.fn();
|
||||
ondisconnect: (ev: USBConnectionEvent) => void = jest.fn();
|
||||
|
||||
getDevices: () => Promise<USBDevice[]> = jest.fn(async () => []);
|
||||
requestDevice: (options?: USBDeviceRequestOptions) => Promise<USBDevice> =
|
||||
jest.fn(async () => ({ serialNumber: "abcdefgh" }) as never);
|
||||
|
||||
addEventListener(
|
||||
type: "connect" | "disconnect",
|
||||
listener: (this: this, ev: USBConnectionEvent) => void,
|
||||
useCapture?: boolean,
|
||||
): void;
|
||||
addEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject | null,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
addEventListener(
|
||||
_type: unknown,
|
||||
_listener: unknown,
|
||||
_options?: unknown,
|
||||
): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
removeEventListener(
|
||||
type: "connect" | "disconnect",
|
||||
callback: (this: this, ev: USBConnectionEvent) => void,
|
||||
useCapture?: boolean,
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: string,
|
||||
callback: EventListenerOrEventListenerObject | null,
|
||||
options?: EventListenerOptions | boolean,
|
||||
): void;
|
||||
removeEventListener(
|
||||
_type: unknown,
|
||||
_callback: unknown,
|
||||
_options?: unknown,
|
||||
): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
dispatchEvent(_event: Event): boolean {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
describe("AdbDaemonWebUsbDeviceManager", () => {
|
||||
describe("requestDevice", () => {
|
||||
it("should accept 0 args", async () => {
|
||||
const usb = new MockUsb();
|
||||
const manager = new AdbDaemonWebUsbDeviceManager(usb);
|
||||
await expect(manager.requestDevice()).resolves.toBeInstanceOf(
|
||||
AdbDaemonWebUsbDevice,
|
||||
);
|
||||
expect(usb.requestDevice).toHaveBeenCalledTimes(1);
|
||||
expect(usb.requestDevice).toHaveBeenCalledWith({
|
||||
filters: [ADB_DEFAULT_INTERFACE_FILTER],
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept undefined filters", async () => {
|
||||
const usb = new MockUsb();
|
||||
const manager = new AdbDaemonWebUsbDeviceManager(usb);
|
||||
await expect(
|
||||
manager.requestDevice({ filters: undefined }),
|
||||
).resolves.toBeInstanceOf(AdbDaemonWebUsbDevice);
|
||||
expect(usb.requestDevice).toHaveBeenCalledTimes(1);
|
||||
expect(usb.requestDevice).toHaveBeenCalledWith({
|
||||
filters: [ADB_DEFAULT_INTERFACE_FILTER],
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept empty filters array", async () => {
|
||||
const usb = new MockUsb();
|
||||
const manager = new AdbDaemonWebUsbDeviceManager(usb);
|
||||
await expect(
|
||||
manager.requestDevice({ filters: [] }),
|
||||
).resolves.toBeInstanceOf(AdbDaemonWebUsbDevice);
|
||||
expect(usb.requestDevice).toHaveBeenCalledTimes(1);
|
||||
expect(usb.requestDevice).toHaveBeenCalledWith({
|
||||
filters: [ADB_DEFAULT_INTERFACE_FILTER],
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept empty filter object", async () => {
|
||||
const usb = new MockUsb();
|
||||
const manager = new AdbDaemonWebUsbDeviceManager(usb);
|
||||
await expect(
|
||||
manager.requestDevice({ filters: [{}] }),
|
||||
).resolves.toBeInstanceOf(AdbDaemonWebUsbDevice);
|
||||
expect(usb.requestDevice).toHaveBeenCalledTimes(1);
|
||||
expect(usb.requestDevice).toHaveBeenCalledWith({
|
||||
filters: [ADB_DEFAULT_INTERFACE_FILTER],
|
||||
});
|
||||
});
|
||||
|
||||
it("should merge missing fields with default values", async () => {
|
||||
const usb = new MockUsb();
|
||||
const manager = new AdbDaemonWebUsbDeviceManager(usb);
|
||||
const filter: USBDeviceFilter = { vendorId: 0x1234 };
|
||||
await expect(
|
||||
manager.requestDevice({ filters: [filter] }),
|
||||
).resolves.toBeInstanceOf(AdbDaemonWebUsbDevice);
|
||||
expect(usb.requestDevice).toHaveBeenCalledTimes(1);
|
||||
expect(usb.requestDevice).toHaveBeenCalledWith({
|
||||
filters: [
|
||||
{
|
||||
...ADB_DEFAULT_INTERFACE_FILTER,
|
||||
...filter,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should merge undefined fields with default values", async () => {
|
||||
const usb = new MockUsb();
|
||||
const manager = new AdbDaemonWebUsbDeviceManager(usb);
|
||||
const filter: USBDeviceFilter = {
|
||||
classCode: undefined,
|
||||
vendorId: 0x1234,
|
||||
};
|
||||
await expect(
|
||||
manager.requestDevice({ filters: [filter] }),
|
||||
).resolves.toBeInstanceOf(AdbDaemonWebUsbDevice);
|
||||
expect(usb.requestDevice).toHaveBeenCalledTimes(1);
|
||||
expect(usb.requestDevice).toHaveBeenCalledWith({
|
||||
filters: [
|
||||
{
|
||||
...filter,
|
||||
...ADB_DEFAULT_INTERFACE_FILTER,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept multiple filters", async () => {
|
||||
const usb = new MockUsb();
|
||||
const manager = new AdbDaemonWebUsbDeviceManager(usb);
|
||||
const filter1: USBDeviceFilter = { vendorId: 0x1234 };
|
||||
const filter2: USBDeviceFilter = { classCode: 0xaa };
|
||||
await expect(
|
||||
manager.requestDevice({ filters: [filter1, filter2] }),
|
||||
).resolves.toBeInstanceOf(AdbDaemonWebUsbDevice);
|
||||
expect(usb.requestDevice).toHaveBeenCalledTimes(1);
|
||||
expect(usb.requestDevice).toHaveBeenCalledWith({
|
||||
filters: [
|
||||
{
|
||||
...ADB_DEFAULT_INTERFACE_FILTER,
|
||||
...filter1,
|
||||
},
|
||||
{
|
||||
...ADB_DEFAULT_INTERFACE_FILTER,
|
||||
...filter2,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,4 @@
|
|||
import { ADB_DEFAULT_DEVICE_FILTER, AdbDaemonWebUsbDevice } from "./device.js";
|
||||
import type { AdbDeviceFilter } from "./utils.js";
|
||||
import { AdbDaemonWebUsbDevice, toAdbDeviceFilters } from "./device.js";
|
||||
import {
|
||||
findUsbAlternateInterface,
|
||||
getSerialNumber,
|
||||
|
@ -8,7 +7,7 @@ import {
|
|||
|
||||
export namespace AdbDaemonWebUsbDeviceManager {
|
||||
export interface RequestDeviceOptions {
|
||||
filters?: AdbDeviceFilter[] | undefined;
|
||||
filters?: USBDeviceFilter[] | undefined;
|
||||
exclusionFilters?: USBDeviceFilter[] | undefined;
|
||||
}
|
||||
}
|
||||
|
@ -44,28 +43,21 @@ export class AdbDaemonWebUsbDeviceManager {
|
|||
* It must have `classCode`, `subclassCode` and `protocolCode` fields for selecting the ADB interface,
|
||||
* but might also have `vendorId`, `productId` or `serialNumber` fields to limit the displayed device list.
|
||||
*
|
||||
* Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
|
||||
* Defaults to {@link ADB_DEFAULT_INTERFACE_FILTER}.
|
||||
* @returns An {@link AdbDaemonWebUsbDevice} instance if the user selected a device,
|
||||
* or `undefined` if the user cancelled the device picker.
|
||||
*/
|
||||
async requestDevice(
|
||||
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {},
|
||||
): Promise<AdbDaemonWebUsbDevice | undefined> {
|
||||
if (!options.filters) {
|
||||
options.filters = [ADB_DEFAULT_DEVICE_FILTER];
|
||||
} else if (options.filters.length === 0) {
|
||||
throw new TypeError("filters must not be empty");
|
||||
}
|
||||
const filters = toAdbDeviceFilters(options.filters);
|
||||
|
||||
try {
|
||||
const device = await this.#usbManager.requestDevice(
|
||||
options as USBDeviceRequestOptions,
|
||||
);
|
||||
return new AdbDaemonWebUsbDevice(
|
||||
device,
|
||||
options.filters,
|
||||
this.#usbManager,
|
||||
);
|
||||
const device = await this.#usbManager.requestDevice({
|
||||
filters,
|
||||
exclusionFilters: options.exclusionFilters,
|
||||
});
|
||||
return new AdbDaemonWebUsbDevice(device, filters, this.#usbManager);
|
||||
} catch (e) {
|
||||
// No device selected
|
||||
if (isErrorName(e, "NotFoundError")) {
|
||||
|
@ -85,34 +77,35 @@ export class AdbDaemonWebUsbDeviceManager {
|
|||
* It must have `classCode`, `subclassCode` and `protocolCode` fields for selecting the ADB interface,
|
||||
* but might also have `vendorId`, `productId` or `serialNumber` fields to limit the device list.
|
||||
*
|
||||
* Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
|
||||
* Defaults to {@link ADB_DEFAULT_INTERFACE_FILTER}.
|
||||
* @returns An array of {@link AdbDaemonWebUsbDevice} instances for all connected and authenticated devices.
|
||||
*/
|
||||
getDevices(
|
||||
filters?: USBDeviceFilter[] | undefined,
|
||||
): Promise<AdbDaemonWebUsbDevice[]>;
|
||||
async getDevices(
|
||||
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
|
||||
filters_: USBDeviceFilter[] | undefined,
|
||||
): Promise<AdbDaemonWebUsbDevice[]> {
|
||||
if (filters.length === 0) {
|
||||
throw new TypeError("filters must not be empty");
|
||||
}
|
||||
const filters = toAdbDeviceFilters(filters_);
|
||||
|
||||
const devices = await this.#usbManager.getDevices();
|
||||
return devices
|
||||
.filter((device) => {
|
||||
for (const filter of filters) {
|
||||
if (
|
||||
"vendorId" in filter &&
|
||||
filter.vendorId !== undefined &&
|
||||
device.vendorId !== filter.vendorId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
"productId" in filter &&
|
||||
filter.productId !== undefined &&
|
||||
device.productId !== filter.productId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
"serialNumber" in filter &&
|
||||
filter.serialNumber !== undefined &&
|
||||
getSerialNumber(device) !== filter.serialNumber
|
||||
) {
|
||||
continue;
|
||||
|
|
|
@ -7,18 +7,22 @@ export function isErrorName(e: unknown, name: string): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
export type PickNonNullable<T, K extends keyof T> = {
|
||||
[P in K]-?: NonNullable<T[P]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* `classCode`, `subclassCode` and `protocolCode` are required
|
||||
* for selecting correct USB configuration and interface.
|
||||
*/
|
||||
export type AdbDeviceFilter = USBDeviceFilter &
|
||||
Required<
|
||||
Pick<USBDeviceFilter, "classCode" | "subclassCode" | "protocolCode">
|
||||
>;
|
||||
export type UsbInterfaceFilter = PickNonNullable<
|
||||
USBDeviceFilter,
|
||||
"classCode" | "subclassCode" | "protocolCode"
|
||||
>;
|
||||
|
||||
function alternateMatchesFilter(
|
||||
alternate: USBAlternateInterface,
|
||||
filters: AdbDeviceFilter[],
|
||||
filters: UsbInterfaceFilter[],
|
||||
) {
|
||||
return filters.some(
|
||||
(filter) =>
|
||||
|
@ -30,7 +34,7 @@ function alternateMatchesFilter(
|
|||
|
||||
export function findUsbAlternateInterface(
|
||||
device: USBDevice,
|
||||
filters: AdbDeviceFilter[],
|
||||
filters: UsbInterfaceFilter[],
|
||||
) {
|
||||
for (const configuration of device.configurations) {
|
||||
for (const interface_ of configuration.interfaces) {
|
||||
|
@ -56,3 +60,42 @@ export function getSerialNumber(device: USBDevice) {
|
|||
|
||||
return padNumber(device.vendorId) + "x" + padNumber(device.productId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first pair of input and output endpoints from an alternate interface.
|
||||
*
|
||||
* ADB interface only has two endpoints, one for input and one for output.
|
||||
*/
|
||||
export function findUsbEndpoints(endpoints: USBEndpoint[]) {
|
||||
if (endpoints.length === 0) {
|
||||
throw new TypeError("No endpoints given");
|
||||
}
|
||||
|
||||
let inEndpoint: USBEndpoint | undefined;
|
||||
let outEndpoint: USBEndpoint | undefined;
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
switch (endpoint.direction) {
|
||||
case "in":
|
||||
inEndpoint = endpoint;
|
||||
if (outEndpoint) {
|
||||
return { inEndpoint, outEndpoint };
|
||||
}
|
||||
break;
|
||||
case "out":
|
||||
outEndpoint = endpoint;
|
||||
if (inEndpoint) {
|
||||
return { inEndpoint, outEndpoint };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inEndpoint) {
|
||||
throw new TypeError("No input endpoint found.");
|
||||
}
|
||||
if (!outEndpoint) {
|
||||
throw new TypeError("No output endpoint found.");
|
||||
}
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
|
|
10
libraries/adb-daemon-webusb/tsconfig.test.json
Normal file
10
libraries/adb-daemon-webusb/tsconfig.test.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"w3c-web-usb",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"exclude": []
|
||||
}
|
|
@ -15,6 +15,9 @@
|
|||
<a href="https://github.com/yume-chan/ya-webadb/releases">
|
||||
<img alt="GitHub release" src="https://img.shields.io/github/v/release/yume-chan/ya-webadb?logo=github">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/package/@yume-chan/adb">
|
||||
<img alt="Package Size" src="https://img.shields.io/bundlephobia/minzip/%40yume-chan%2Fadb">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@yume-chan/adb">
|
||||
<img alt="npm" src="https://img.shields.io/npm/dm/%40yume-chan/adb?logo=npm">
|
||||
</a>
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
"scripts": {
|
||||
"build": "tsc -b tsconfig.build.json",
|
||||
"build:watch": "tsc -b tsconfig.build.json",
|
||||
"test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" TS_JEST_DISABLE_VER_CHECKER=true jest --coverage",
|
||||
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4",
|
||||
"prepublishOnly": "npm run build"
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" TS_JEST_DISABLE_VER_CHECKER=true jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/async": "^2.2.0",
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
{
|
||||
"path": "../event/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../no-data-view/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../stream-extra/tsconfig.build.json"
|
||||
},
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
{
|
||||
"references": [
|
||||
{
|
||||
"path": "../no-data-view/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.test.json"
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue