UNPKG

@yume-chan/adb

Version:

TypeScript implementation of Android Debug Bridge (ADB) protocol.

171 lines (137 loc) 4.5 kB
import type { MaybePromiseLike } from "@yume-chan/async"; import type { MaybeConsumable, ReadableWritablePair, } from "@yume-chan/stream-extra"; import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra"; import type { AdbBanner } from "./banner.js"; import type { AdbFrameBuffer } from "./commands/index.js"; import { AdbPower, AdbReverseService, AdbSubprocessService, AdbSync, AdbTcpIpService, escapeArg, framebuffer, } from "./commands/index.js"; import type { AdbFeature } from "./features.js"; export interface Closeable { close(): MaybePromiseLike<void>; } /** * Represents an ADB socket. */ export interface AdbSocket extends ReadableWritablePair<Uint8Array, MaybeConsumable<Uint8Array>>, Closeable { get service(): string; get closed(): Promise<undefined>; } export type AdbIncomingSocketHandler = ( socket: AdbSocket, ) => MaybePromiseLike<void>; export interface AdbTransport extends Closeable { readonly serial: string; readonly maxPayloadSize: number; readonly banner: AdbBanner; readonly disconnected: Promise<void>; readonly clientFeatures: readonly AdbFeature[]; connect(service: string): MaybePromiseLike<AdbSocket>; addReverseTunnel( handler: AdbIncomingSocketHandler, address?: string, ): MaybePromiseLike<string>; removeReverseTunnel(address: string): MaybePromiseLike<void>; clearReverseTunnels(): MaybePromiseLike<void>; } export class Adb implements Closeable { readonly #transport: AdbTransport; get transport(): AdbTransport { return this.#transport; } get serial() { return this.#transport.serial; } get maxPayloadSize() { return this.#transport.maxPayloadSize; } get banner() { return this.#transport.banner; } get disconnected() { return this.#transport.disconnected; } public get clientFeatures() { return this.#transport.clientFeatures; } public get deviceFeatures() { return this.banner.features; } readonly subprocess: AdbSubprocessService; readonly power: AdbPower; readonly reverse: AdbReverseService; readonly tcpip: AdbTcpIpService; constructor(transport: AdbTransport) { this.#transport = transport; this.subprocess = new AdbSubprocessService(this); this.power = new AdbPower(this); this.reverse = new AdbReverseService(this); this.tcpip = new AdbTcpIpService(this); } canUseFeature(feature: AdbFeature): boolean { return ( this.clientFeatures.includes(feature) && this.deviceFeatures.includes(feature) ); } /** * Creates a new ADB Socket to the specified service or socket address. */ async createSocket(service: string): Promise<AdbSocket> { return this.#transport.connect(service); } async createSocketAndWait(service: string): Promise<string> { const socket = await this.createSocket(service); return await socket.readable .pipeThrough(new TextDecoderStream()) .pipeThrough(new ConcatStringStream()); } getProp(key: string): Promise<string> { return this.subprocess.noneProtocol .spawnWaitText(["getprop", key]) .then((output) => output.trim()); } rm( filenames: string | string[], options?: { recursive?: boolean; force?: boolean }, ): Promise<string> { const args = ["rm"]; if (options?.recursive) { args.push("-r"); } if (options?.force) { args.push("-f"); } if (Array.isArray(filenames)) { for (const filename of filenames) { args.push(escapeArg(filename)); } } else { args.push(escapeArg(filenames)); } // https://android.googlesource.com/platform/packages/modules/adb/+/1a0fb8846d4e6b671c8aa7f137a8c21d7b248716/client/adb_install.cpp#984 args.push("</dev/null"); return this.subprocess.noneProtocol.spawnWaitText(args); } async sync(): Promise<AdbSync> { const socket = await this.createSocket("sync:"); return new AdbSync(this, socket); } async framebuffer(): Promise<AdbFrameBuffer> { return framebuffer(this); } async close(): Promise<void> { await this.#transport.close(); } }