@yume-chan/adb
Version:
TypeScript implementation of Android Debug Bridge (ADB) protocol.
173 lines (142 loc) • 5.13 kB
text/typescript
import { EventEmitter, StickyEventEmitter } from "@yume-chan/event";
import { Ref } from "../utils/index.js";
import { AdbServerClient } from "./client.js";
import type { AdbServerStream } from "./stream.js";
export function unorderedRemove<T>(array: T[], index: number) {
if (index < 0 || index >= array.length) {
return;
}
array[index] = array[array.length - 1]!;
array.length -= 1;
}
export class AdbServerDeviceObserverOwner {
current: readonly AdbServerClient.Device[] = [];
readonly
onDeviceAdd: EventEmitter<readonly AdbServerClient.Device[]>;
onDeviceRemove: EventEmitter<readonly AdbServerClient.Device[]>;
onListChange: EventEmitter<readonly AdbServerClient.Device[]>;
onError: EventEmitter<Error>;
}[] = [];
constructor(client: AdbServerClient) {
this.
}
async
const response = await stream.readString();
const next = AdbServerClient.parseDeviceList(response);
const removed = this.current.slice();
const added: AdbServerClient.Device[] = [];
for (const nextDevice of next) {
const index = removed.findIndex(
(device) => device.transportId === nextDevice.transportId,
);
if (index === -1) {
added.push(nextDevice);
continue;
}
unorderedRemove(removed, index);
}
this.current = next;
if (added.length) {
for (const observer of this.
observer.onDeviceAdd.fire(added);
}
}
if (removed.length) {
for (const observer of this.
observer.onDeviceRemove.fire(removed);
}
}
for (const observer of this.
observer.onListChange.fire(this.current);
}
}
async
try {
while (true) {
await this.
}
} catch (e) {
this.
for (const observer of this.
observer.onError.fire(e as Error);
}
}
}
async
const stream = await this.
"host:track-devices-l",
// Each individual observer will ref depending on their options
{ unref: true },
);
// Set `current` and `onListChange` value before returning
await this.
// Then start receive loop
void this.
return stream;
}
async
if (this.
this.
await stream.dispose();
}
}
async createObserver(
options?: AdbServerClient.ServerConnectionOptions,
): Promise<AdbServerClient.DeviceObserver> {
options?.signal?.throwIfAborted();
const onDeviceAdd = new EventEmitter<
readonly AdbServerClient.Device[]
>();
const onDeviceRemove = new EventEmitter<
readonly AdbServerClient.Device[]
>();
const onListChange = new StickyEventEmitter<
readonly AdbServerClient.Device[]
>();
const onError = new StickyEventEmitter<Error>();
const observer = { onDeviceAdd, onDeviceRemove, onListChange, onError };
// Register `observer` before `#connect`.
// So `#handleObserverStop` knows if there is any observer.
this.
let stream: AdbServerStream;
if (!this.
this.
try {
stream = await this.
} catch (e) {
this.
throw e;
}
} else {
stream = await this.
onListChange.fire(this.current);
}
const ref = new Ref(options);
const stop = async () => {
unorderedRemove(this.
await this.
ref.unref();
};
if (options?.signal) {
if (options.signal.aborted) {
await stop();
throw options.signal.reason;
}
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;
},
stop,
};
}
}