mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
feat(adb): add state filters to AdbServerClient.prototype.getDevices
and AdbServerClient.prototype.trackDevices
This commit is contained in:
parent
1d3d3c8864
commit
a835eb81b5
3 changed files with 87 additions and 25 deletions
5
.changeset/neat-taxes-pick.md
Normal file
5
.changeset/neat-taxes-pick.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@yume-chan/adb": minor
|
||||
---
|
||||
|
||||
Add state filters to `AdbServerClient.prototype.getDevices` and `AdbServerClient.prototype.trackDevices`
|
|
@ -37,7 +37,13 @@ export class AdbServerClient {
|
|||
static UnauthorizedError = _UnauthorizedError;
|
||||
static AlreadyConnectedError = _AlreadyConnectedError;
|
||||
|
||||
static parseDeviceList(value: string): AdbServerClient.Device[] {
|
||||
static parseDeviceList(
|
||||
value: string,
|
||||
includeStates: AdbServerClient.ConnectionState[] = [
|
||||
"device",
|
||||
"unauthorized",
|
||||
],
|
||||
): AdbServerClient.Device[] {
|
||||
const devices: AdbServerClient.Device[] = [];
|
||||
for (const line of value.split("\n")) {
|
||||
if (!line) {
|
||||
|
@ -46,12 +52,8 @@ export class AdbServerClient {
|
|||
|
||||
const parts = line.split(" ").filter(Boolean);
|
||||
const serial = parts[0]!;
|
||||
const state = parts[1]!;
|
||||
if (
|
||||
state !== "unauthorized" &&
|
||||
state !== "offline" &&
|
||||
state !== "device"
|
||||
) {
|
||||
const state = parts[1]! as AdbServerClient.ConnectionState;
|
||||
if (!includeStates.includes(state)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -82,6 +84,7 @@ export class AdbServerClient {
|
|||
devices.push({
|
||||
serial,
|
||||
state,
|
||||
authenticating: state === "unauthorized",
|
||||
product,
|
||||
model,
|
||||
device,
|
||||
|
@ -197,11 +200,16 @@ export class AdbServerClient {
|
|||
*
|
||||
* Equivalent ADB Command: `adb devices -l`
|
||||
*/
|
||||
async getDevices(): Promise<AdbServerClient.Device[]> {
|
||||
async getDevices(
|
||||
includeStates: AdbServerClient.ConnectionState[] = [
|
||||
"device",
|
||||
"unauthorized",
|
||||
],
|
||||
): Promise<AdbServerClient.Device[]> {
|
||||
const connection = await this.createConnection("host:devices-l");
|
||||
try {
|
||||
const response = await connection.readString();
|
||||
return AdbServerClient.parseDeviceList(response);
|
||||
return AdbServerClient.parseDeviceList(response, includeStates);
|
||||
} finally {
|
||||
await connection.dispose();
|
||||
}
|
||||
|
@ -211,7 +219,7 @@ export class AdbServerClient {
|
|||
* Monitors device list changes.
|
||||
*/
|
||||
async trackDevices(
|
||||
options?: AdbServerClient.ServerConnectionOptions,
|
||||
options?: AdbServerDeviceObserverOwner.Options,
|
||||
): Promise<AdbServerClient.DeviceObserver> {
|
||||
return this.#observerOwner.createObserver(options);
|
||||
}
|
||||
|
@ -551,6 +559,8 @@ export namespace AdbServerClient {
|
|||
export interface Device {
|
||||
serial: string;
|
||||
state: ConnectionState;
|
||||
/** @deprecated Use {@link state} instead */
|
||||
authenticating: boolean;
|
||||
product?: string | undefined;
|
||||
model?: string | undefined;
|
||||
device?: string | undefined;
|
||||
|
|
|
@ -13,17 +13,28 @@ export function unorderedRemove<T>(array: T[], index: number) {
|
|||
array.length -= 1;
|
||||
}
|
||||
|
||||
interface Observer {
|
||||
includeStates: AdbServerClient.ConnectionState[];
|
||||
onDeviceAdd: EventEmitter<readonly AdbServerClient.Device[]>;
|
||||
onDeviceRemove: EventEmitter<readonly AdbServerClient.Device[]>;
|
||||
onListChange: EventEmitter<readonly AdbServerClient.Device[]>;
|
||||
onError: EventEmitter<Error>;
|
||||
}
|
||||
|
||||
function filterDeviceStates(
|
||||
devices: readonly AdbServerClient.Device[],
|
||||
states: AdbServerClient.ConnectionState[],
|
||||
) {
|
||||
return devices.filter((device) => states.includes(device.state));
|
||||
}
|
||||
|
||||
export class AdbServerDeviceObserverOwner {
|
||||
current: readonly AdbServerClient.Device[] = [];
|
||||
|
||||
readonly #client: AdbServerClient;
|
||||
|
||||
#stream: Promise<AdbServerStream> | undefined;
|
||||
#observers: {
|
||||
onDeviceAdd: EventEmitter<readonly AdbServerClient.Device[]>;
|
||||
onDeviceRemove: EventEmitter<readonly AdbServerClient.Device[]>;
|
||||
onListChange: EventEmitter<readonly AdbServerClient.Device[]>;
|
||||
onError: EventEmitter<Error>;
|
||||
}[] = [];
|
||||
#observers: Observer[] = [];
|
||||
|
||||
constructor(client: AdbServerClient) {
|
||||
this.#client = client;
|
||||
|
@ -52,17 +63,33 @@ export class AdbServerDeviceObserverOwner {
|
|||
|
||||
if (added.length) {
|
||||
for (const observer of this.#observers) {
|
||||
observer.onDeviceAdd.fire(added);
|
||||
const filtered = filterDeviceStates(
|
||||
added,
|
||||
observer.includeStates,
|
||||
);
|
||||
if (filtered.length) {
|
||||
observer.onDeviceAdd.fire(filtered);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed.length) {
|
||||
for (const observer of this.#observers) {
|
||||
observer.onDeviceRemove.fire(removed);
|
||||
const filtered = filterDeviceStates(
|
||||
added,
|
||||
observer.includeStates,
|
||||
);
|
||||
if (filtered.length) {
|
||||
observer.onDeviceRemove.fire(removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const observer of this.#observers) {
|
||||
observer.onListChange.fire(this.current);
|
||||
const filtered = filterDeviceStates(
|
||||
this.current,
|
||||
observer.includeStates,
|
||||
);
|
||||
observer.onListChange.fire(filtered);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,10 +131,11 @@ export class AdbServerDeviceObserverOwner {
|
|||
}
|
||||
|
||||
async createObserver(
|
||||
options?: AdbServerClient.ServerConnectionOptions,
|
||||
options?: AdbServerDeviceObserverOwner.Options,
|
||||
): Promise<AdbServerClient.DeviceObserver> {
|
||||
options?.signal?.throwIfAborted();
|
||||
|
||||
let current: readonly AdbServerClient.Device[] = [];
|
||||
const onDeviceAdd = new EventEmitter<
|
||||
readonly AdbServerClient.Device[]
|
||||
>();
|
||||
|
@ -119,13 +147,27 @@ export class AdbServerDeviceObserverOwner {
|
|||
>();
|
||||
const onError = new StickyEventEmitter<Error>();
|
||||
|
||||
const observer = { onDeviceAdd, onDeviceRemove, onListChange, onError };
|
||||
const includeStates = options?.includeStates ?? [
|
||||
"device",
|
||||
"unauthorized",
|
||||
];
|
||||
const observer = {
|
||||
includeStates,
|
||||
onDeviceAdd,
|
||||
onDeviceRemove,
|
||||
onListChange,
|
||||
onError,
|
||||
} satisfies Observer;
|
||||
// Register `observer` before `#connect`.
|
||||
// So `#handleObserverStop` knows if there is any observer.
|
||||
this.#observers.push(observer);
|
||||
|
||||
// Read the filtered `current` value from `onListChange` event
|
||||
onListChange.event((value) => (current = value));
|
||||
|
||||
let stream: AdbServerStream;
|
||||
if (!this.#stream) {
|
||||
// `#connect` will initialize `onListChange` and `current`
|
||||
this.#stream = this.#connect();
|
||||
|
||||
try {
|
||||
|
@ -136,7 +178,8 @@ export class AdbServerDeviceObserverOwner {
|
|||
}
|
||||
} else {
|
||||
stream = await this.#stream;
|
||||
onListChange.fire(this.current);
|
||||
// Initialize `onListChange` and `current` ourselves
|
||||
onListChange.fire(filterDeviceStates(this.current, includeStates));
|
||||
}
|
||||
|
||||
const ref = new Ref(options);
|
||||
|
@ -156,17 +199,21 @@ export class AdbServerDeviceObserverOwner {
|
|||
options.signal.addEventListener("abort", () => void stop());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const _this = this;
|
||||
return {
|
||||
onDeviceAdd: onDeviceAdd.event,
|
||||
onDeviceRemove: onDeviceRemove.event,
|
||||
onListChange: onListChange.event,
|
||||
onError: onError.event,
|
||||
get current() {
|
||||
return _this.current;
|
||||
return current;
|
||||
},
|
||||
stop,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace AdbServerDeviceObserverOwner {
|
||||
export interface Options extends AdbServerClient.ServerConnectionOptions {
|
||||
includeStates?: AdbServerClient.ConnectionState[];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue