@yume-chan/adb
Version:
TypeScript implementation of Android Debug Bridge (ADB) protocol.
171 lines (137 loc) • 4.5 kB
text/typescript
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();
}
}