consortium
Version:
Remote control and session sharing CLI for AI coding agents
2,072 lines • 58.1 kB
JavaScript
import { b as PromiseResolver, P as PushReadableStream, W as WritableStream, R as ReadableStream, A as AbortController, C as Consumable, t as tryCancel, a as tryClose } from './consumable-GBIKq8FA.mjs';
import { M as MaybeConsumableWritableStream } from './writable-C9MDBwYu.mjs';
import { C as ConcatBufferStream, a as ConcatStringStream, D as DistributionStream, B as BufferCombiner } from './distribution-DG6IW3GT.mjs';
import { T as TextDecoderStream } from './encoding-Dra722yg.mjs';
import { B as BufferedReadableStream, d as StructEmptyError, s as struct, b as buffer, S as StructDeserializeStream, E as EmptyUint8Array, e as bipedal } from './struct-deserialize-DqeMe-dl.mjs';
import { u as u32, b as u8, a as encodeUtf8, g as getUint32LittleEndian, d as decodeUtf8, s as string, c as u64, e as extend, T as TextDecoder, f as getUint64LittleEndian } from './string-BC-7T2zc.mjs';
import { a as AdbServiceBase, u as unreachable, N as NOOP, b as AdbReverseService, s as sequenceEqual, w as write4HexDigits, h as hexToNumber, E as EventEmitter, S as StickyEventEmitter } from './reverse-BeYKDdbE.mjs';
export { c as AdbReverseError, d as AdbReverseErrorResponse, A as AdbReverseNotSupportedError } from './reverse-BeYKDdbE.mjs';
import './utils-CoFf4cMv.mjs';
const Version = struct({ version: u32 }, { littleEndian: true });
const AdbFrameBufferV1 = struct({
bpp: u32,
size: u32,
width: u32,
height: u32,
red_offset: u32,
red_length: u32,
blue_offset: u32,
blue_length: u32,
green_offset: u32,
green_length: u32,
alpha_offset: u32,
alpha_length: u32,
data: buffer("size")
}, { littleEndian: true });
const AdbFrameBufferV2 = struct({
bpp: u32,
colorSpace: u32,
size: u32,
width: u32,
height: u32,
red_offset: u32,
red_length: u32,
blue_offset: u32,
blue_length: u32,
green_offset: u32,
green_length: u32,
alpha_offset: u32,
alpha_length: u32,
data: buffer("size")
}, { littleEndian: true });
class AdbFrameBufferError extends Error {
constructor(message, options) {
super(message, options);
}
}
class AdbFrameBufferUnsupportedVersionError extends AdbFrameBufferError {
constructor(version) {
super(`Unsupported FrameBuffer version ${version}`);
}
}
class AdbFrameBufferForbiddenError extends AdbFrameBufferError {
constructor() {
super("FrameBuffer is disabled by current app");
}
}
async function framebuffer(adb) {
const socket = await adb.createSocket("framebuffer:");
const stream = new BufferedReadableStream(socket.readable);
let version;
try {
({ version } = await Version.deserialize(stream));
} catch (e) {
if (e instanceof StructEmptyError) {
throw new AdbFrameBufferForbiddenError();
}
throw e;
}
switch (version) {
case 1:
return await AdbFrameBufferV1.deserialize(stream);
case 2:
return await AdbFrameBufferV2.deserialize(stream);
default:
throw new AdbFrameBufferUnsupportedVersionError(version);
}
}
class AdbPower extends AdbServiceBase {
reboot(mode = "") {
return this.adb.createSocketAndWait(`reboot:${mode}`);
}
bootloader() {
return this.reboot("bootloader");
}
fastboot() {
return this.reboot("fastboot");
}
recovery() {
return this.reboot("recovery");
}
sideload() {
return this.reboot("sideload");
}
/**
* Reboot to Qualcomm Emergency Download (EDL) Mode.
*
* Only works on some Qualcomm devices.
*/
qualcommEdlMode() {
return this.reboot("edl");
}
powerOff() {
return this.adb.subprocess.noneProtocol.spawnWaitText(["reboot", "-p"]);
}
powerButton(longPress = false) {
const args = ["input", "keyevent"];
if (longPress) {
args.push("--longpress");
}
args.push("POWER");
return this.adb.subprocess.noneProtocol.spawnWaitText(args);
}
/**
* Reboot to Samsung Odin download mode.
*
* Only works on Samsung devices.
*/
samsungOdin() {
return this.reboot("download");
}
}
class AutoResetEvent {
#set;
#queue = [];
constructor(initialSet = false) {
this.#set = initialSet;
}
wait() {
if (!this.#set) {
this.#set = true;
if (this.#queue.length === 0) {
return Promise.resolve();
}
}
const resolver = new PromiseResolver();
this.#queue.push(resolver);
return resolver.promise;
}
notifyOne() {
if (this.#queue.length !== 0) {
this.#queue.pop().resolve();
} else {
this.#set = false;
}
}
dispose() {
for (const item of this.#queue) {
item.reject(new Error("The AutoResetEvent has been disposed"));
}
this.#queue.length = 0;
}
}
const { setInterval, clearInterval } = globalThis;
class Ref {
#intervalId;
constructor(options) {
if (!options?.unref) {
this.ref();
}
}
ref() {
this.#intervalId = setInterval(() => {
}, 60 * 1e3);
}
unref() {
if (this.#intervalId) {
clearInterval(this.#intervalId);
this.#intervalId = void 0;
}
}
}
class AdbNoneProtocolProcessImpl {
#socket;
get stdin() {
return this.#socket.writable;
}
get output() {
return this.#socket.readable;
}
#exited;
get exited() {
return this.#exited;
}
constructor(socket, signal) {
this.#socket = socket;
if (signal) {
const exited = new PromiseResolver();
this.#socket.closed.then(() => exited.resolve(void 0), (e) => exited.reject(e));
signal.addEventListener("abort", () => {
exited.reject(signal.reason);
this.#socket.close();
});
this.#exited = exited.promise;
} else {
this.#exited = this.#socket.closed;
}
}
kill() {
return this.#socket.close();
}
}
class AdbNoneProtocolPtyProcess {
#socket;
#writer;
#input;
get input() {
return this.#input;
}
get output() {
return this.#socket.readable;
}
get exited() {
return this.#socket.closed;
}
constructor(socket) {
this.#socket = socket;
this.#writer = this.#socket.writable.getWriter();
this.#input = new MaybeConsumableWritableStream({
write: (chunk) => this.#writer.write(chunk)
});
}
sigint() {
return this.#writer.write(new Uint8Array([3]));
}
kill() {
return this.#socket.close();
}
}
function escapeArg(s) {
let result = "";
result += `'`;
let base = 0;
while (true) {
const found = s.indexOf(`'`, base);
if (found === -1) {
result += s.substring(base);
break;
}
result += s.substring(base, found);
result += String.raw`'\''`;
base = found + 1;
}
result += `'`;
return result;
}
function splitCommand(command) {
const result = [];
let quote;
let isEscaped = false;
let start = 0;
for (let i = 0, len = command.length; i < len; i += 1) {
if (isEscaped) {
isEscaped = false;
continue;
}
const char = command.charAt(i);
switch (char) {
case " ":
if (!quote && i !== start) {
result.push(command.substring(start, i));
start = i + 1;
}
break;
case "'":
case '"':
if (!quote) {
quote = char;
} else if (char === quote) {
quote = void 0;
}
break;
case "\\":
isEscaped = true;
break;
}
}
if (start < command.length) {
result.push(command.substring(start));
}
return result;
}
class AdbNoneProtocolSpawner {
#spawn;
constructor(spawn) {
this.#spawn = spawn;
}
spawn(command, signal) {
signal?.throwIfAborted();
if (typeof command === "string") {
command = splitCommand(command);
}
return this.#spawn(command, signal);
}
async spawnWait(command) {
const process = await this.spawn(command);
return await process.output.pipeThrough(new ConcatBufferStream());
}
async spawnWaitText(command) {
const process = await this.spawn(command);
return await process.output.pipeThrough(new TextDecoderStream()).pipeThrough(new ConcatStringStream());
}
}
class AdbNoneProtocolSubprocessService extends AdbNoneProtocolSpawner {
#adb;
get adb() {
return this.#adb;
}
constructor(adb) {
super(async (command, signal) => {
const socket = await this.#adb.createSocket(`exec:${command.join(" ")}`);
if (signal?.aborted) {
await socket.close();
throw signal.reason;
}
return new AdbNoneProtocolProcessImpl(socket, signal);
});
this.#adb = adb;
}
async pty(command) {
if (command === void 0) {
command = "";
} else if (Array.isArray(command)) {
command = command.join(" ");
}
return new AdbNoneProtocolPtyProcess(
// https://github.com/microsoft/typescript/issues/17002
await this.#adb.createSocket(`shell:${command}`)
);
}
}
const AdbFeature = {
ShellV2: "shell_v2",
Cmd: "cmd",
StatV2: "stat_v2",
ListV2: "ls_v2",
FixedPushMkdir: "fixed_push_mkdir",
Abb: "abb",
AbbExec: "abb_exec",
SendReceiveV2: "sendrecv_v2"};
const AdbShellProtocolId = {
Stdin: 0,
Stdout: 1,
Stderr: 2,
Exit: 3,
CloseStdin: 4,
WindowSizeChange: 5
};
const AdbShellProtocolPacket = struct({
id: u8(),
data: buffer(u32)
}, { littleEndian: true });
class AdbShellProtocolProcessImpl {
#socket;
#writer;
#stdin;
get stdin() {
return this.#stdin;
}
#stdout;
get stdout() {
return this.#stdout;
}
#stderr;
get stderr() {
return this.#stderr;
}
#exited;
get exited() {
return this.#exited;
}
constructor(socket, signal) {
this.#socket = socket;
let stdoutController;
let stderrController;
this.#stdout = new PushReadableStream((controller) => {
stdoutController = controller;
});
this.#stderr = new PushReadableStream((controller) => {
stderrController = controller;
});
const exited = new PromiseResolver();
this.#exited = exited.promise;
socket.readable.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket)).pipeTo(new WritableStream({
write: async (chunk) => {
switch (chunk.id) {
case AdbShellProtocolId.Exit:
exited.resolve(chunk.data[0]);
break;
case AdbShellProtocolId.Stdout:
await stdoutController.enqueue(chunk.data);
break;
case AdbShellProtocolId.Stderr:
await stderrController.enqueue(chunk.data);
break;
}
}
})).then(() => {
stdoutController.close();
stderrController.close();
exited.reject(new Error("Socket ended without exit message"));
}, (e) => {
stdoutController.error(e);
stderrController.error(e);
exited.reject(e);
});
if (signal) {
signal.addEventListener("abort", () => {
exited.reject(signal.reason);
this.#socket.close();
});
}
this.#writer = this.#socket.writable.getWriter();
this.#stdin = new MaybeConsumableWritableStream({
write: async (chunk) => {
await this.#writer.write(AdbShellProtocolPacket.serialize({
id: AdbShellProtocolId.Stdin,
data: chunk
}));
},
close: () => (
// Only shell protocol + raw mode supports closing stdin
this.#writer.write(AdbShellProtocolPacket.serialize({
id: AdbShellProtocolId.CloseStdin,
data: EmptyUint8Array
}))
)
});
}
kill() {
return this.#socket.close();
}
}
class AdbShellProtocolPtyProcess {
#socket;
#writer;
#input;
get input() {
return this.#input;
}
#stdout;
get output() {
return this.#stdout;
}
#exited = new PromiseResolver();
get exited() {
return this.#exited.promise;
}
constructor(socket) {
this.#socket = socket;
let stdoutController;
this.#stdout = new PushReadableStream((controller) => {
stdoutController = controller;
});
socket.readable.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket)).pipeTo(new WritableStream({
write: async (chunk) => {
switch (chunk.id) {
case AdbShellProtocolId.Exit:
this.#exited.resolve(chunk.data[0]);
break;
case AdbShellProtocolId.Stdout:
await stdoutController.enqueue(chunk.data);
break;
}
}
})).then(() => {
stdoutController.close();
this.#exited.reject(new Error("Socket ended without exit message"));
}, (e) => {
stdoutController.error(e);
this.#exited.reject(e);
});
this.#writer = this.#socket.writable.getWriter();
this.#input = new MaybeConsumableWritableStream({
write: (chunk) => this.#writeStdin(chunk)
});
}
#writeStdin(chunk) {
return this.#writer.write(AdbShellProtocolPacket.serialize({
id: AdbShellProtocolId.Stdin,
data: chunk
}));
}
async resize(rows, cols) {
await this.#writer.write(AdbShellProtocolPacket.serialize({
id: AdbShellProtocolId.WindowSizeChange,
// The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
// However, according to https://linux.die.net/man/4/tty_ioctl
// `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine.
data: encodeUtf8(`${rows}x${cols},0x0\0`)
}));
}
sigint() {
return this.#writeStdin(new Uint8Array([3]));
}
kill() {
return this.#socket.close();
}
}
class AdbShellProtocolSpawner {
#spawn;
constructor(spawn) {
this.#spawn = spawn;
}
spawn(command, signal) {
signal?.throwIfAborted();
if (typeof command === "string") {
command = splitCommand(command);
}
return this.#spawn(command, signal);
}
async spawnWait(command) {
const process = await this.spawn(command);
const [stdout, stderr, exitCode] = await Promise.all([
process.stdout.pipeThrough(new ConcatBufferStream()),
process.stderr.pipeThrough(new ConcatBufferStream()),
process.exited
]);
return { stdout, stderr, exitCode };
}
async spawnWaitText(command) {
const process = await this.spawn(command);
const [stdout, stderr, exitCode] = await Promise.all([
process.stdout.pipeThrough(new TextDecoderStream()).pipeThrough(new ConcatStringStream()),
process.stderr.pipeThrough(new TextDecoderStream()).pipeThrough(new ConcatStringStream()),
process.exited
]);
return { stdout, stderr, exitCode };
}
}
class AdbShellProtocolSubprocessService extends AdbShellProtocolSpawner {
#adb;
get adb() {
return this.#adb;
}
get isSupported() {
return this.#adb.canUseFeature(AdbFeature.ShellV2);
}
constructor(adb) {
super(async (command, signal) => {
const socket = await this.#adb.createSocket(`shell,v2,raw:${command.join(" ")}`);
if (signal?.aborted) {
await socket.close();
throw signal.reason;
}
return new AdbShellProtocolProcessImpl(socket, signal);
});
this.#adb = adb;
}
async pty(options) {
let service = "shell,v2,pty";
if (options?.terminalType) {
service += `,TERM=` + options.terminalType;
}
service += ":";
if (options) {
if (typeof options.command === "string") {
service += options.command;
} else if (Array.isArray(options.command)) {
service += options.command.join(" ");
}
}
return new AdbShellProtocolPtyProcess(await this.#adb.createSocket(service));
}
}
class AdbSubprocessService {
#adb;
get adb() {
return this.#adb;
}
#noneProtocol;
get noneProtocol() {
return this.#noneProtocol;
}
#shellProtocol;
get shellProtocol() {
return this.#shellProtocol;
}
constructor(adb) {
this.#adb = adb;
this.#noneProtocol = new AdbNoneProtocolSubprocessService(adb);
if (adb.canUseFeature(AdbFeature.ShellV2)) {
this.#shellProtocol = new AdbShellProtocolSubprocessService(adb);
}
}
}
function encodeAsciiUnchecked(value) {
const result = new Uint8Array(value.length);
for (let i = 0; i < value.length; i += 1) {
result[i] = value.charCodeAt(i);
}
return result;
}
// @__NO_SIDE_EFFECTS__
function adbSyncEncodeId(value) {
const buffer = encodeAsciiUnchecked(value);
return getUint32LittleEndian(buffer, 0);
}
const AdbSyncResponseId = {
Entry: /* @__PURE__ */ adbSyncEncodeId("DENT"),
Entry2: /* @__PURE__ */ adbSyncEncodeId("DNT2"),
Lstat: /* @__PURE__ */ adbSyncEncodeId("STAT"),
Stat: /* @__PURE__ */ adbSyncEncodeId("STA2"),
Lstat2: /* @__PURE__ */ adbSyncEncodeId("LST2"),
Done: /* @__PURE__ */ adbSyncEncodeId("DONE"),
Data: /* @__PURE__ */ adbSyncEncodeId("DATA"),
Ok: /* @__PURE__ */ adbSyncEncodeId("OKAY"),
Fail: /* @__PURE__ */ adbSyncEncodeId("FAIL")
};
class AdbSyncError extends Error {
}
const AdbSyncFailResponse = struct({ message: string(u32) }, {
littleEndian: true,
postDeserialize(value) {
throw new AdbSyncError(value.message);
}
});
async function adbSyncReadResponse(stream, id, type) {
if (typeof id === "string") {
id = /* @__PURE__ */ adbSyncEncodeId(id);
}
const buffer = await stream.readExactly(4);
switch (getUint32LittleEndian(buffer, 0)) {
case AdbSyncResponseId.Fail:
await AdbSyncFailResponse.deserialize(stream);
throw new Error("Unreachable");
case id:
return await type.deserialize(stream);
default:
throw new Error(`Expected '${id}', but got '${decodeUtf8(buffer)}'`);
}
}
async function* adbSyncReadResponses(stream, id, type) {
if (typeof id === "string") {
id = /* @__PURE__ */ adbSyncEncodeId(id);
}
while (true) {
const buffer = await stream.readExactly(4);
switch (getUint32LittleEndian(buffer, 0)) {
case AdbSyncResponseId.Fail:
await AdbSyncFailResponse.deserialize(stream);
unreachable();
case AdbSyncResponseId.Done:
await stream.readExactly(type.size);
return;
case id:
yield await type.deserialize(stream);
break;
default:
throw new Error(`Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${decodeUtf8(buffer)}'`);
}
}
}
const AdbSyncRequestId = {
List: adbSyncEncodeId("LIST"),
ListV2: adbSyncEncodeId("LIS2"),
Send: adbSyncEncodeId("SEND"),
SendV2: adbSyncEncodeId("SND2"),
Lstat: adbSyncEncodeId("STAT"),
Stat: adbSyncEncodeId("STA2"),
LstatV2: adbSyncEncodeId("LST2"),
Data: adbSyncEncodeId("DATA"),
Done: adbSyncEncodeId("DONE"),
Receive: adbSyncEncodeId("RECV")
};
const AdbSyncNumberRequest = struct({ id: u32, arg: u32 }, { littleEndian: true });
async function adbSyncWriteRequest(writable, id, value) {
if (typeof id === "string") {
id = adbSyncEncodeId(id);
}
if (typeof value === "number") {
await writable.write(AdbSyncNumberRequest.serialize({ id, arg: value }));
return;
}
if (typeof value === "string") {
value = encodeUtf8(value);
}
await writable.write(AdbSyncNumberRequest.serialize({ id, arg: value.length }));
await writable.write(value);
}
const LinuxFileType = {
File: 8};
const AdbSyncLstatResponse = struct({ mode: u32, size: u32, mtime: u32 }, {
littleEndian: true,
extra: {
get type() {
return this.mode >> 12;
},
get permission() {
return this.mode & 4095;
}
},
postDeserialize(value) {
if (value.mode === 0 && value.size === 0 && value.mtime === 0) {
throw new Error("lstat error");
}
return value;
}
});
const AdbSyncStatErrorCode = {
SUCCESS: 0,
EACCES: 13,
EEXIST: 17,
EFAULT: 14,
EFBIG: 27,
EINTR: 4,
EINVAL: 22,
EIO: 5,
EISDIR: 21,
ELOOP: 40,
EMFILE: 24,
ENAMETOOLONG: 36,
ENFILE: 23,
ENOENT: 2,
ENOMEM: 12,
ENOSPC: 28,
ENOTDIR: 20,
EOVERFLOW: 75,
EPERM: 1,
EROFS: 30,
ETXTBSY: 26
};
const AdbSyncStatErrorName = /* @__PURE__ */ (() => Object.fromEntries(Object.entries(AdbSyncStatErrorCode).map(([key, value]) => [
value,
key
])))();
const AdbSyncStatResponse = struct({
error: u32(),
dev: u64,
ino: u64,
mode: u32,
nlink: u32,
uid: u32,
gid: u32,
size: u64,
atime: u64,
mtime: u64,
ctime: u64
}, {
littleEndian: true,
extra: {
get type() {
return this.mode >> 12;
},
get permission() {
return this.mode & 4095;
}
},
postDeserialize(value) {
if (value.error) {
throw new Error(AdbSyncStatErrorName[value.error]);
}
return value;
}
});
async function adbSyncLstat(socket, path, v2) {
const locked = await socket.lock();
try {
if (v2) {
await adbSyncWriteRequest(locked, AdbSyncRequestId.LstatV2, path);
return await adbSyncReadResponse(locked, AdbSyncResponseId.Lstat2, AdbSyncStatResponse);
} else {
await adbSyncWriteRequest(locked, AdbSyncRequestId.Lstat, path);
const response = await adbSyncReadResponse(locked, AdbSyncResponseId.Lstat, AdbSyncLstatResponse);
return {
mode: response.mode,
// Convert to `BigInt` to make it compatible with `AdbSyncStatResponse`
size: BigInt(response.size),
mtime: BigInt(response.mtime),
get type() {
return response.type;
},
get permission() {
return response.permission;
}
};
}
} finally {
locked.release();
}
}
async function adbSyncStat(socket, path) {
const locked = await socket.lock();
try {
await adbSyncWriteRequest(locked, AdbSyncRequestId.Stat, path);
return await adbSyncReadResponse(locked, AdbSyncResponseId.Stat, AdbSyncStatResponse);
} finally {
locked.release();
}
}
const AdbSyncEntryResponse = extend(AdbSyncLstatResponse, {
name: string(u32)
});
const AdbSyncEntry2Response = extend(AdbSyncStatResponse, {
name: string(u32)
});
async function* adbSyncOpenDirV2(socket, path) {
const locked = await socket.lock();
try {
await adbSyncWriteRequest(locked, AdbSyncRequestId.ListV2, path);
for await (const item of adbSyncReadResponses(locked, AdbSyncResponseId.Entry2, AdbSyncEntry2Response)) {
if (item.error !== AdbSyncStatErrorCode.SUCCESS) {
continue;
}
yield item;
}
} finally {
locked.release();
}
}
async function* adbSyncOpenDirV1(socket, path) {
const locked = await socket.lock();
try {
await adbSyncWriteRequest(locked, AdbSyncRequestId.List, path);
for await (const item of adbSyncReadResponses(locked, AdbSyncResponseId.Entry, AdbSyncEntryResponse)) {
yield item;
}
} finally {
locked.release();
}
}
async function* adbSyncOpenDir(socket, path, v2) {
if (v2) {
yield* adbSyncOpenDirV2(socket, path);
} else {
for await (const item of adbSyncOpenDirV1(socket, path)) {
yield {
mode: item.mode,
size: BigInt(item.size),
mtime: BigInt(item.mtime),
get type() {
return item.type;
},
get permission() {
return item.permission;
},
name: item.name
};
}
}
}
const AdbSyncDataResponse = struct({ data: buffer(u32) }, { littleEndian: true });
async function* adbSyncPullGenerator(socket, path) {
const locked = await socket.lock();
let done = false;
try {
await adbSyncWriteRequest(locked, AdbSyncRequestId.Receive, path);
for await (const packet of adbSyncReadResponses(locked, AdbSyncResponseId.Data, AdbSyncDataResponse)) {
yield packet.data;
}
done = true;
} catch (e) {
done = true;
throw e;
} finally {
if (!done) {
for await (const packet of adbSyncReadResponses(locked, AdbSyncResponseId.Data, AdbSyncDataResponse)) {
}
}
locked.release();
}
}
function adbSyncPull(socket, path) {
return ReadableStream.from(adbSyncPullGenerator(socket, path));
}
const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024;
const AdbSyncOkResponse = struct({ unused: u32 }, { littleEndian: true });
async function pipeFileData(locked, file, packetSize, mtime) {
const abortController = new AbortController();
file.pipeThrough(new DistributionStream(packetSize, true)).pipeTo(new MaybeConsumableWritableStream({
write(chunk) {
return adbSyncWriteRequest(locked, AdbSyncRequestId.Data, chunk);
}
}), { signal: abortController.signal }).then(async () => {
await adbSyncWriteRequest(locked, AdbSyncRequestId.Done, mtime);
await locked.flush();
}, NOOP);
await adbSyncReadResponse(locked, AdbSyncResponseId.Ok, AdbSyncOkResponse).catch((e) => {
abortController.abort();
throw e;
});
}
async function adbSyncPushV1({ socket, filename, file, type = LinuxFileType.File, permission = 438, mtime = Date.now() / 1e3 | 0, packetSize = ADB_SYNC_MAX_PACKET_SIZE }) {
const locked = await socket.lock();
try {
const mode = type << 12 | permission;
const pathAndMode = `${filename},${mode.toString()}`;
await adbSyncWriteRequest(locked, AdbSyncRequestId.Send, pathAndMode);
await pipeFileData(locked, file, packetSize, mtime);
} finally {
locked.release();
}
}
const AdbSyncSendV2Flags = {
None: 0,
Brotli: 1,
/**
* 2
*/
Lz4: 1 << 1,
/**
* 4
*/
Zstd: 1 << 2,
DryRun: 2147483648
};
const AdbSyncSendV2Request = struct({ id: u32, mode: u32, flags: u32() }, { littleEndian: true });
async function adbSyncPushV2({ socket, filename, file, type = LinuxFileType.File, permission = 438, mtime = Date.now() / 1e3 | 0, packetSize = ADB_SYNC_MAX_PACKET_SIZE, dryRun = false }) {
const locked = await socket.lock();
try {
await adbSyncWriteRequest(locked, AdbSyncRequestId.SendV2, filename);
const mode = type << 12 | permission;
let flags = AdbSyncSendV2Flags.None;
if (dryRun) {
flags |= AdbSyncSendV2Flags.DryRun;
}
await locked.write(AdbSyncSendV2Request.serialize({
id: AdbSyncRequestId.SendV2,
mode,
flags
}));
await pipeFileData(locked, file, packetSize, mtime);
} finally {
locked.release();
}
}
function adbSyncPush(options) {
if (options.v2) {
return adbSyncPushV2(options);
}
if (options.dryRun) {
throw new Error("dryRun is not supported in v1");
}
return adbSyncPushV1(options);
}
class AdbSyncSocketLocked {
#writer;
#readable;
#socketLock;
#writeLock = new AutoResetEvent();
#combiner;
get position() {
return this.#readable.position;
}
constructor(writer, readable, bufferSize, lock) {
this.#writer = writer;
this.#readable = readable;
this.#socketLock = lock;
this.#combiner = new BufferCombiner(bufferSize);
}
#write(buffer) {
return Consumable.WritableStream.write(this.#writer, buffer);
}
async flush() {
try {
await this.#writeLock.wait();
const buffer = this.#combiner.flush();
if (buffer) {
await this.#write(buffer);
}
} finally {
this.#writeLock.notifyOne();
}
}
async write(data) {
try {
await this.#writeLock.wait();
for (const buffer of this.#combiner.push(data)) {
await this.#write(buffer);
}
} finally {
this.#writeLock.notifyOne();
}
}
async readExactly(length) {
await this.flush();
return await this.#readable.readExactly(length);
}
release() {
this.#combiner.flush();
this.#socketLock.notifyOne();
}
async close() {
await this.#readable.cancel();
}
}
class AdbSyncSocket {
#lock = new AutoResetEvent();
#socket;
#locked;
constructor(socket, bufferSize) {
this.#socket = socket;
this.#locked = new AdbSyncSocketLocked(socket.writable.getWriter(), new BufferedReadableStream(socket.readable), bufferSize, this.#lock);
}
async lock() {
await this.#lock.wait();
return this.#locked;
}
async close() {
await this.#locked.close();
await this.#socket.close();
}
}
function dirname(path) {
const end = path.lastIndexOf("/");
if (end === -1) {
throw new Error(`Invalid path`);
}
if (end === 0) {
return "/";
}
return path.substring(0, end);
}
class AdbSync {
_adb;
_socket;
#supportsStat;
#supportsListV2;
#fixedPushMkdir;
#supportsSendReceiveV2;
#needPushMkdirWorkaround;
get supportsStat() {
return this.#supportsStat;
}
get supportsListV2() {
return this.#supportsListV2;
}
get fixedPushMkdir() {
return this.#fixedPushMkdir;
}
get supportsSendReceiveV2() {
return this.#supportsSendReceiveV2;
}
get needPushMkdirWorkaround() {
return this.#needPushMkdirWorkaround;
}
constructor(adb, socket) {
this._adb = adb;
this._socket = new AdbSyncSocket(socket, adb.maxPayloadSize);
this.#supportsStat = adb.canUseFeature(AdbFeature.StatV2);
this.#supportsListV2 = adb.canUseFeature(AdbFeature.ListV2);
this.#fixedPushMkdir = adb.canUseFeature(AdbFeature.FixedPushMkdir);
this.#supportsSendReceiveV2 = adb.canUseFeature(AdbFeature.SendReceiveV2);
this.#needPushMkdirWorkaround = this._adb.canUseFeature(AdbFeature.ShellV2) && !this.fixedPushMkdir;
}
/**
* Gets information of a file or folder.
*
* If `path` points to a symbolic link, the returned information is about the link itself (with `type` being `LinuxFileType.Link`).
*/
async lstat(path) {
return await adbSyncLstat(this._socket, path, this.#supportsStat);
}
/**
* Gets the information of a file or folder.
*
* If `path` points to a symbolic link, it will be resolved and the returned information is about the target (with `type` being `LinuxFileType.File` or `LinuxFileType.Directory`).
*/
async stat(path) {
if (!this.#supportsStat) {
throw new Error("Not supported");
}
return await adbSyncStat(this._socket, path);
}
/**
* Checks if `path` is a directory, or a symbolic link to a directory.
*
* This uses `lstat` internally, thus works on all Android versions.
*/
async isDirectory(path) {
try {
await this.lstat(path + "/");
return true;
} catch {
return false;
}
}
opendir(path) {
return adbSyncOpenDir(this._socket, path, this.supportsListV2);
}
async readdir(path) {
const results = [];
for await (const entry of this.opendir(path)) {
results.push(entry);
}
return results;
}
/**
* Reads the content of a file on device.
*
* @param filename The full path of the file on device to read.
* @returns A `ReadableStream` that contains the file content.
*/
read(filename) {
return adbSyncPull(this._socket, filename);
}
/**
* Writes a file on device. If the file name already exists, it will be overwritten.
*
* @param options The content and options of the file to write.
*/
async write(options) {
if (this.needPushMkdirWorkaround) {
await this._adb.subprocess.noneProtocol.spawnWait([
"mkdir",
"-p",
escapeArg(dirname(options.filename))
]);
}
await adbSyncPush({
v2: this.supportsSendReceiveV2,
socket: this._socket,
...options
});
}
lockSocket() {
return this._socket.lock();
}
dispose() {
return this._socket.close();
}
}
function parsePort(value) {
if (!value || value === "0") {
return void 0;
}
return Number.parseInt(value, 10);
}
class AdbTcpIpService extends AdbServiceBase {
async getListenAddresses() {
const serviceListenAddresses = await this.adb.getProp("service.adb.listen_addrs");
const servicePort = await this.adb.getProp("service.adb.tcp.port");
const persistPort = await this.adb.getProp("persist.adb.tcp.port");
return {
serviceListenAddresses: serviceListenAddresses != "" ? serviceListenAddresses.split(",") : [],
servicePort: parsePort(servicePort),
persistPort: parsePort(persistPort)
};
}
async setPort(port) {
if (port <= 0) {
throw new TypeError(`Invalid port ${port}`);
}
const output = await this.adb.createSocketAndWait(`tcpip:${port}`);
if (output !== `restarting in TCP mode port: ${port}
`) {
throw new Error(output);
}
return output;
}
async disable() {
const output = await this.adb.createSocketAndWait("usb:");
if (output !== "restarting in USB mode\n") {
throw new Error(output);
}
return output;
}
}
class Adb {
#transport;
get transport() {
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;
}
get clientFeatures() {
return this.#transport.clientFeatures;
}
get deviceFeatures() {
return this.banner.features;
}
subprocess;
power;
reverse;
tcpip;
constructor(transport) {
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) {
return this.clientFeatures.includes(feature) && this.deviceFeatures.includes(feature);
}
/**
* Creates a new ADB Socket to the specified service or socket address.
*/
async createSocket(service) {
return this.#transport.connect(service);
}
async createSocketAndWait(service) {
const socket = await this.createSocket(service);
return await socket.readable.pipeThrough(new TextDecoderStream()).pipeThrough(new ConcatStringStream());
}
getProp(key) {
return this.subprocess.noneProtocol.spawnWaitText(["getprop", key]).then((output) => output.trim());
}
rm(filenames, options) {
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));
}
args.push("</dev/null");
return this.subprocess.noneProtocol.spawnWaitText(args);
}
async sync() {
const socket = await this.createSocket("sync:");
return new AdbSync(this, socket);
}
async framebuffer() {
return framebuffer(this);
}
async close() {
await this.#transport.close();
}
}
const AdbBannerKey = {
Product: "ro.product.name",
Model: "ro.product.model",
Device: "ro.product.device",
Features: "features"
};
class AdbBanner {
static parse(banner) {
let state;
let product;
let model;
let device;
let features = [];
const pieces = banner.split("::");
if (pieces.length > 1) {
state = pieces[0].trim() || void 0;
const props = pieces[1];
for (const prop of props.split(";")) {
if (!prop) {
continue;
}
const keyValue = prop.split("=");
if (keyValue.length !== 2) {
continue;
}
const [key, value] = keyValue;
switch (key) {
case AdbBannerKey.Product:
product = value;
break;
case AdbBannerKey.Model:
model = value;
break;
case AdbBannerKey.Device:
device = value;
break;
case AdbBannerKey.Features:
features = value.split(",");
break;
}
}
}
return new AdbBanner(state, product, model, device, features);
}
#state;
get state() {
return this.#state;
}
#product;
get product() {
return this.#product;
}
#model;
get model() {
return this.#model;
}
#device;
get device() {
return this.#device;
}
#features = [];
get features() {
return this.#features;
}
// eslint-disable-next-line @typescript-eslint/max-params
constructor(state, product, model, device, features) {
this.#state = state;
this.#product = product;
this.#model = model;
this.#device = device;
this.#features = features;
}
}
class MDnsCommands {
#client;
constructor(client) {
this.#client = client;
}
async check() {
const connection = await this.#client.createConnection("host:mdns:check");
try {
const response = await connection.readString();
return !response.startsWith("ERROR:");
} finally {
await connection.dispose();
}
}
async getServices() {
const connection = await this.#client.createConnection("host:mdns:services");
try {
const response = await connection.readString();
return response.split("\n").filter(Boolean).map((line) => {
const parts = line.split(" ");
return {
name: parts[0],
service: parts[1],
address: parts[2]
};
});
} finally {
await connection.dispose();
}
}
}
const OKAY = encodeUtf8("OKAY");
const FAIL = encodeUtf8("FAIL");
class AdbServerStream {
#connection;
#buffered;
#writer;
constructor(connection) {
this.#connection = connection;
this.#buffered = new BufferedReadableStream(connection.readable);
this.#writer = connection.writable.getWriter();
}
readExactly(length) {
return this.#buffered.readExactly(length);
}
readString = bipedal(function* (then) {
const data = yield* then(this.readExactly(4));
const length = hexToNumber(data);
if (length === 0) {
return "";
} else {
const decoder = new TextDecoder();
let result = "";
const iterator = this.#buffered.iterateExactly(length);
while (true) {
const { done, value } = iterator.next();
if (done) {
break;
}
result += decoder.decode(yield* then(value), { stream: true });
}
result += decoder.decode();
return result;
}
});
async readOkay() {
const response = await this.readExactly(4);
if (sequenceEqual(response, OKAY)) {
return;
}
if (sequenceEqual(response, FAIL)) {
const reason = await this.readString();
throw new Error(reason);
}
throw new Error(`Unexpected response: ${decodeUtf8(response)}`);
}
async writeString(value) {
const encoded = encodeUtf8(value);
const buffer = new Uint8Array(4 + encoded.length);
write4HexDigits(buffer, 0, encoded.length);
buffer.set(encoded, 4);
await this.#writer.write(buffer);
}
release() {
this.#writer.releaseLock();
return {
readable: this.#buffered.release(),
writable: this.#connection.writable,
closed: this.#connection.closed,
close: () => this.#connection.close()
};
}
async dispose() {
void tryCancel(this.#buffered);
void tryClose(this.#writer);
await this.#connection.close();
}
}
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = "NetworkError";
}
}
class UnauthorizedError extends Error {
constructor(message) {
super(message);
this.name = "UnauthorizedError";
}
}
class AlreadyConnectedError extends Error {
constructor(message) {
super(message);
this.name = "AlreadyConnectedError";
}
}
class WirelessCommands {
#client;
constructor(client) {
this.#client = client;
}
/**
* `adb pair <password> <address>`
*/
async pair(address, password) {
const connection = await this.#client.createConnection(`host:pair:${password}:${address}`);
try {
const response = await connection.readExactly(4);
if (sequenceEqual(response, FAIL)) {
throw new Error(await connection.readString());
}
const length = hexToNumber(response);
await connection.readExactly(length);
} finally {
await connection.dispose();
}
}
/**
* `adb connect <address>`
*/
async connect(address) {
const connection = await this.#client.createConnection(`host:connect:${address}`);
try {
const response = await connection.readString();
switch (response) {
case `already connected to ${address}`:
throw new AlreadyConnectedError(response);
case `failed to connect to ${address}`:
// `adb pair` mode not authorized
case `failed to authenticate to ${address}`:
throw new UnauthorizedError(response);
case `connected to ${address}`:
return;
default:
throw new NetworkError(response);
}
} finally {
await connection.dispose();
}
}
/**
* `adb disconnect <address>`
*/
async disconnect(address) {
const connection = await this.#client.createConnection(`host:disconnect:${address}`);
try {
await connection.readString();
} finally {
await connection.dispose();
}
}
}
function unorderedRemove(array, index) {
if (index < 0 || index >= array.length) {
return;
}
array[index] = array[array.length - 1];
array.length -= 1;
}
function filterDeviceStates(devices, states) {
return devices.filter((device) => states.includes(device.state));
}
class AdbServerDeviceObserverOwner {
current = [];
#client;
#stream;
#observers = [];
constructor(client) {
this.#client = client;
}
async #receive(stream) {
const response = await stream.readString();
const next = AdbServerClient.parseDeviceList(response);
const removed = this.current.slice();
const added = [];
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.#observers) {
const filtered = filterDeviceStates(added, observer.includeStates);
if (filtered.length) {
observer.onDeviceAdd.fire(filtered);
}
}
}
if (removed.length) {
for (const observer of this.#observers) {
const filtered = filterDeviceStates(removed, observer.includeStates);
if (filtered.length) {
observer.onDeviceRemove.fire(removed);
}
}
}
for (const observer of this.#observers) {
const filtered = filterDeviceStates(this.current, observer.includeStates);
observer.onListChange.fire(filtered);
}
}
async #receiveLoop(stream) {
try {
while (true) {
await this.#receive(stream);
}
} catch (e) {
this.#stream = void 0;
for (const observer of this.#observers) {
observer.onError.fire(e);
}
}
}
async #connect() {
const stream = await this.#client.createConnection(
"host:track-devices-l",
// Each individual observer will ref depending on their options
{ unref: true }
);
await this.#receive(stream);
void this.#receiveLoop(stream);
return stream;
}
async #handleObserverStop(stream) {
if (this.#observers.length === 0) {
this.#stream = void 0;
await stream.dispose();
}
}
async createObserver(options) {
options?.signal?.throwIfAborted();
let current = [];
const onDeviceAdd = new EventEmitter();
const onDeviceRemove = new EventEmitter();
const onListChange = new StickyEventEmitter();
const onError = new StickyEventEmitter();
const includeStates = options?.includeStates ?? [
"device",
"unauthorized"
];
const observer = {
includeStates,
onDeviceAdd,
onDeviceRemove,
onListChange,
onError
};
this.#observers.push(observer);
onListChange.event((value) => current = value);
let stream;
if (!this.#stream) {
this.#stream = this.#connect();
try {
stream = await this.#stream;
} catch (e) {
this.#stream = void 0;
throw e;
}
} else {
stream = await this.#stream;
onListChange.fire(filterDeviceStates(this.current, includeStates));
}
const ref = new Ref(options);
const stop = async () => {
unorderedRemove(this.#observers, this.#observers.indexOf(observer));
await this.#handleObserverStop(stream);
ref.unref();
};
if (options?.signal) {
if (options.signal.aborted) {
await stop();
throw options.signal.reason;
}
options.signal.addEventListener("abort", () => void stop());
}
return {
onDeviceAdd: onDeviceAdd.event,
onDeviceRemove: onDeviceRemove.event,
onListChange: onListChange.event,
onError: onError.event,
get current() {
return current;
},
stop
};
}
}
const ADB_SERVER_DEFAULT_FEATURES = /* @__PURE__ */ (() => [
AdbFeature.ShellV2,
AdbFeature.Cmd,
AdbFeature.StatV2,
AdbFeature.ListV2,
AdbFeature.FixedPushMkdir,
"apex",
AdbFeature.Abb,
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
// No special handling required.
"fixed_push_symlink_timestamp",
AdbFeature.AbbExec,
"remount_shell",
"track_app",
AdbFeature.SendReceiveV2,
"sendrecv_v2_brotli",
"sendrecv_v2_lz4",
"sendrecv_v2_zstd",
"sendrecv_v2_dry_run_send"
])();
class AdbServerTransport {
#client;
serial;
transportId;
maxPayloadSize = 1 * 1024 * 1024;
banner;
#sockets = [];
#closed = new PromiseResolver();
#disconnected;
get disconnected() {
return this.#disconnected;
}
get clientFeatures() {
return ADB_SERVER_DEFAULT_FEATURES;
}
// eslint-disable-next-line @typescript-eslint/max-params
constructor(client, serial, banner, transportId, disconnected) {
this.#client = client;
this.serial = serial;
this.banner = banner;
this.transportId = transportId;
this.#disconnected = Promise.race([this.#closed.promise, disconnected]);
}
async connect(service) {
const socket = await this.#client.createDeviceConnection({ transportId: this.transportId }, service);
this.#sockets.push(socket);
return socket;
}
async addReverseTunnel(handler, address) {
return await this.#client.connector.addReverseTunnel(handler, address);
}
async removeReverseTunnel(address) {
await this.#client.connector.removeReverseTunnel(address);
}
async clearReverseTunnels() {
await this.#client.connector.clearReverseTunnels();
}
async close() {
for (const socket of this.#sockets) {
await socket.close();
}
this.#sockets.length = 0;
this.#closed.resolve();
}
}
class AdbServerClient {
static NetworkError = NetworkError;
static UnauthorizedError = UnauthorizedError;
static AlreadyConnectedError = AlreadyConnectedError;
static parseDeviceList(value, includeStates = [
"device",
"unauthorized"
]) {
const devices = [];
for (const line of value.split("\n")) {
if (!line) {
continue;
}
const parts = line.split(" ").filter(Boolean);
const serial = parts[0];
const state = parts[1];
if (!includeStates.includes(state)) {
continue;
}
let product;
let model;
let device;
let transportId;
for (let i = 2; i < parts.length; i += 1) {
const [key, value2] = parts[i].split(":");
switch (key) {
case "product":
product = value2;
break;
case "model":
model = value2;
break;
case "device":
device = value2;
break;
case "transport_id":
transportId = BigInt(value2);
break;
}
}
if (!transportId) {
throw new Error(`No transport id for device ${serial}`);
}
devices.push({
serial,
state,
authenticating: state === "unauthorized",
product,
model,
device,
transportId
});
}
return devices;
}
static formatDeviceService(device, command) {
if (!device) {
return `host:${command}`;
}
if ("transportId" in device) {
return `host-transport-id:${device.transportId}:${command}`;
}
if ("serial" in device) {
return `host-serial:${device.serial}:${command}`;
}
if ("usb" in device) {
return `host-usb:${command}`;
}
if ("tcp" in device) {
return `host-local:${command}`;
}
throw new TypeError("Invalid device selector");
}
connector;
wireless = new WirelessCommands(this);
mDns = new MDnsCommands(this);
#observerOwner = new AdbServerDeviceObserverOwner(this);
constructor(connector) {
this.connector = connector;
}
async createConnection(request, options) {
const connection = await this.connector.connect(options);
const stream = new AdbServerStream(connection);
try {
await stream.writeString(request);
} catch (e) {
await stream.dispose();
throw e;
}
try {
await raceSignal(() => stream.readOkay(), options?.signal);
return stream;
} catch (e) {
await stream.dispose();
throw e;
}
}
/**
* `adb version`
*/
async getVersion() {
const connection = await this.createConnection("host:version");
try {
const length = hexToNumber(await connection.readExactly(4));
const version = hexToNumber(await connection.readExactly(length));
return version;
} finally {
await connection.dispose();
}
}
async validateVersion(minimalVersion) {
const version = await this.getVersion();
if (version < minimalVersion) {
throw new Error(`adb server version (${version}) doesn't match this client (${minimalVersion})`);
}
}
/**
* `adb kill-server`
*/
async killServer() {
const connection = await this.createConnection("host:kill");
await connection.dispose();
}
/**
* `adb host-features`
*/
async getServerFeatures() {
const connection = await this.createConnection("host:host-features");
try {
const response = await connection.readString();
return response.split(",");
} finally {
await connection.dispose();
}
}
/**
* Get a list of connected devices from ADB Server.
*
* Equivalent ADB Command: `adb devices -l`
*/
async getDevices(includeStates = [
"device",
"unauthorized"
]) {
const connection = await this.createConnection("host:devices-l");
try {
const response = await connection.readString();
return AdbServerClient.parseDeviceList(response, includeStates);
} finally {
await connection.dispose();
}
}
/**
* Monitors device list changes.
*/
async trackDevices(options) {
return this.#observerOwner.createObserver(options);
}
/**
* `adb -s <device> reconnect` or `adb reconnect offline`
*/
async reconnectDevice(device) {
const connection = await this.createConnection(device === "offline" ? "host:reconnect-offline" : AdbServerClient.formatDeviceService(device, "reconnect"));
try {
await connection.readString();
} finally {
await connection.dispose();
}
}
/**
* Gets the features supported by the device.
* The transport ID of the selected device is also returned,
* so the caller can execute other commands against the same device.
* @param device The device selector
* @returns The transport ID of the selected device, and the features supported by the device.
*/
async getDeviceFeatures(device) {
const connection = await this.createDeviceConnection(device, "host:features");
const stream = new AdbServerStream(connection);
try {
const featuresString = await stream.readString();
const features = featuresString.split(",");
return { transportId: connection.transportId, features };
} finally {
await stream.dispose();
}
}
/**
* Creates a connection that will forward the service to device.
* @param device The device selector
* @param service The service to forward
* @returns An `AdbServerClient.Socket` that can be used to communicate with the service
*/
async createDeviceConnection(device, service) {
let switchService;
let transportId;
if (!device) {
await this.validateVersion(41);
switchService = `host:tport:any`;
} else if ("transportId" in device) {
switchService = `host:transport-id:${device.transportId}`;
transportId = device.transportId;
} else if ("serial" in device) {
await this.validateVersion(41);
switchService = `host:tport:serial:${device.serial}`;
} else if ("usb" in device) {
await this.validateVersion(41);
switchService = `host:tport:usb`;
} else if ("tcp" in device) {
await this.validateVersion(41);
switchService = `host:tport:local`;
} else {
throw new TypeError("Invalid device selector");
}
const connection = await this.createConnection(switchService);
try {
await connection.writeString(service);
} catch (e) {
await connection.dispose();
throw e;
}
try {
if (transportId === void 0) {
const array = await connection.readExactly(8);
transportId = getUint64LittleEndian(array, 0);
}
await connection.readOkay();
const socket = connection.release();
return {
transportId,
service,
readable: socket.readable,
writable: socket.writable,
get closed() {
return socket.closed;
},
async close() {
await socket.close();
}
};
} catch (e) {
await connection.dispose();
throw e;
}
}
async #waitForUnchecked(device, state, options) {
let type;
if (!device) {
type = "any";
} else if ("transportId" in device) {
type = "any";
} else if ("serial" in device) {
type = "any";
} else if ("usb" in device) {
type = "usb";
} else if ("tcp" in device) {
type = "local";
} else {
throw new TypeError("Invalid device selector");
}
const service = AdbServerClient.formatDeviceService(device, `wait-for-${type}-${state}`);
const connection = await this.createConnection(service, options);
try {
await connection.readOkay();
} finally {
await connection.dispose();
}
}
/**
* Wait for a device to be connected or disconnected.
*
* `adb wait-for-<state>`
*
* @param device The device selector
* @param state The state to wait for
* @param options The options
* @returns A promise that resolves when the condition is met.
*/
async waitFor(device, state, options) {
if (state === "disconnect") {
await this.validateVersion(41);
}
return this.#waitForUnchecked(device, state, options);
}
async waitForDisconnect(transportId, options) {
const serverVersion = await this.getVersion();
if (serverVersion >= 41) {
return this.#waitForUnchecked({ transportId }, "disconnect", options);
} else {
const observer = await this.trackDevices(options);
return new Promise((resolve, reject) => {
observer.onDeviceRemove((devices) => {
if (devices.some((device) => device.transportId === transportId)) {
observer.stop();
resolve();
}
});
observer.onError((e) => {
observer.stop();
reject(e);
});
});
}
}
/**
* Creates an ADB Transport for the specified device.
*/
async createTransport(device) {
const { transportId, features } = await this.getDeviceFeatures(device);
const devices = await this.getDevices();
const info = devices.find((device2) => device2.transportId === transportId);
const banner = new AdbBanner(info?.state, info?.product, info?.model, info?.device, features);
const waitAbortController = new AbortController();
const disconnected = this.waitForDisconnect(transportId, {
unref: true,
signal: waitAbortController.signal
});
const transport = new AdbServerTransport(this, info?.serial ?? "", banner, transportId, disconnected);
void transport.disconnected.finally(() => waitAbortController.abort());
return transport;
}
async createAdb(device) {
const transport = await this.createTransport(device);
return new Adb(transport);
}
}
async function raceSignal(callback, ...signals) {
const abortPromise = new PromiseResolver();
function abort() {
abortPromise.reject(this.reason);
}
try {
for (const signal of signals) {
if (!signal) {
continue;
}
if (signal.aborted) {
throw signal.reason;
}
signal.addEventListener("abort", abort);
}
return await Promise.race([callback(), abortPromise.promise]);
} finally {
for (const signal of signals) {
if (!signal) {
continue;
}
signal.removeEventListener("abort", abort);
}
}
}
export { ADB_SERVER_DEFAULT_FEATURES, ADB_SYNC_MAX_PACKET_SIZE, Adb, AdbBanner, AdbBannerKey, AdbFeature, AdbFrameBufferError, AdbFrameBufferForbiddenError, AdbFrameBufferUnsupportedVersionError, AdbFrameBufferV1, AdbFrameBufferV2, AdbNoneProtocolProcessImpl, AdbNoneProtocolPtyProcess, AdbNoneProtocolSpawner, AdbNoneProtocolSubprocessService, AdbPower, AdbReverseService, AdbServerClient, AdbServerDeviceObserverOwner, AdbServerStream, AdbServerTransport, AdbServiceBase, AdbShellProtocolId, AdbShellProtocolPacket, AdbShellProtocolProcessImpl, AdbShellProtocolPtyProcess, AdbShellProtocolSpawner, AdbShellProtocolSubprocessService, AdbSubprocessService, AdbSync, AdbSyncDataResponse, AdbSyncEntry2Response, AdbSyncEntryResponse, AdbSyncError, AdbSyncFailResponse, AdbSyncLstatResponse, AdbSyncNumberRequest, AdbSyncOkResponse, AdbSyncRequestId, AdbSyncResponseId, AdbSyncSendV2Flags, AdbSyncSendV2Request, AdbSyncSocket, AdbSyncSocketLocked, AdbSyncStatErrorCode, AdbSyncStatResponse, AdbTcpIpService, AutoResetEvent, FAIL, LinuxFileType, NOOP, Ref, adbSyncEncodeId, adbSyncLstat, adbSyncOpenDir, adbSyncOpenDirV1, adbSyncOpenDirV2, adbSyncPull, adbSyncPullGenerator, adbSyncPush, adbSyncPushV1, adbSyncPushV2, adbSyncReadResponse, adbSyncReadResponses, adbSyncStat, adbSyncWriteRequest, decodeUtf8, dirname, encodeUtf8, escapeArg, framebuffer, hexToNumber, raceSignal, sequenceEqual, splitCommand, unorderedRemove, unreachable, write4HexDigits };