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