UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

2,153 lines • 61.1 kB
'use strict';

var consumable = require('./consumable-jpudCE9S.cjs');
var writable = require('./writable-CMUWv_PX.cjs');
var distribution = require('./distribution-BezPr1ur.cjs');
var encoding = require('./encoding-Crs7CKEn.cjs');
var structDeserialize = require('./struct-deserialize-BJQW83nw.cjs');
var string = require('./string-CdxeepiN.cjs');
var reverse = require('./reverse-EOXSx2wD.cjs');
require('./utils-vmOBBBS0.cjs');

const Version = structDeserialize.struct({ version: string.u32 }, { littleEndian: true });
const AdbFrameBufferV1 = structDeserialize.struct({
  bpp: string.u32,
  size: string.u32,
  width: string.u32,
  height: string.u32,
  red_offset: string.u32,
  red_length: string.u32,
  blue_offset: string.u32,
  blue_length: string.u32,
  green_offset: string.u32,
  green_length: string.u32,
  alpha_offset: string.u32,
  alpha_length: string.u32,
  data: structDeserialize.buffer("size")
}, { littleEndian: true });
const AdbFrameBufferV2 = structDeserialize.struct({
  bpp: string.u32,
  colorSpace: string.u32,
  size: string.u32,
  width: string.u32,
  height: string.u32,
  red_offset: string.u32,
  red_length: string.u32,
  blue_offset: string.u32,
  blue_length: string.u32,
  green_offset: string.u32,
  green_length: string.u32,
  alpha_offset: string.u32,
  alpha_length: string.u32,
  data: structDeserialize.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 structDeserialize.BufferedReadableStream(socket.readable);
  let version;
  try {
    ({ version } = await Version.deserialize(stream));
  } catch (e) {
    if (e instanceof structDeserialize.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 reverse.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 consumable.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 consumable.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 writable.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 distribution.ConcatBufferStream());
  }
  async spawnWaitText(command) {
    const process = await this.spawn(command);
    return await process.output.pipeThrough(new encoding.TextDecoderStream()).pipeThrough(new distribution.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 = structDeserialize.struct({
  id: string.u8(),
  data: structDeserialize.buffer(string.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 consumable.PushReadableStream((controller) => {
      stdoutController = controller;
    });
    this.#stderr = new consumable.PushReadableStream((controller) => {
      stderrController = controller;
    });
    const exited = new consumable.PromiseResolver();
    this.#exited = exited.promise;
    socket.readable.pipeThrough(new structDeserialize.StructDeserializeStream(AdbShellProtocolPacket)).pipeTo(new consumable.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 writable.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: structDeserialize.EmptyUint8Array
        }))
      )
    });
  }
  kill() {
    return this.#socket.close();
  }
}

class AdbShellProtocolPtyProcess {
  #socket;
  #writer;
  #input;
  get input() {
    return this.#input;
  }
  #stdout;
  get output() {
    return this.#stdout;
  }
  #exited = new consumable.PromiseResolver();
  get exited() {
    return this.#exited.promise;
  }
  constructor(socket) {
    this.#socket = socket;
    let stdoutController;
    this.#stdout = new consumable.PushReadableStream((controller) => {
      stdoutController = controller;
    });
    socket.readable.pipeThrough(new structDeserialize.StructDeserializeStream(AdbShellProtocolPacket)).pipeTo(new consumable.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 writable.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: string.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 distribution.ConcatBufferStream()),
      process.stderr.pipeThrough(new distribution.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 encoding.TextDecoderStream()).pipeThrough(new distribution.ConcatStringStream()),
      process.stderr.pipeThrough(new encoding.TextDecoderStream()).pipeThrough(new distribution.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 string.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 = structDeserialize.struct({ message: string.string(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 (string.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 '${string.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 (string.getUint32LittleEndian(buffer, 0)) {
      case AdbSyncResponseId.Fail:
        await AdbSyncFailResponse.deserialize(stream);
        reverse.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 '${string.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 = structDeserialize.struct({ id: string.u32, arg: string.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 = string.encodeUtf8(value);
  }
  await writable.write(AdbSyncNumberRequest.serialize({ id, arg: value.length }));
  await writable.write(value);
}

const LinuxFileType = {
  File: 8};
const AdbSyncLstatResponse = structDeserialize.struct({ mode: string.u32, size: string.u32, mtime: string.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 = structDeserialize.struct({
  error: string.u32(),
  dev: string.u64,
  ino: string.u64,
  mode: string.u32,
  nlink: string.u32,
  uid: string.u32,
  gid: string.u32,
  size: string.u64,
  atime: string.u64,
  mtime: string.u64,
  ctime: string.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 = string.extend(AdbSyncLstatResponse, {
  name: string.string(string.u32)
});
const AdbSyncEntry2Response = string.extend(AdbSyncStatResponse, {
  name: string.string(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 = structDeserialize.struct({ data: structDeserialize.buffer(string.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 consumable.ReadableStream.from(adbSyncPullGenerator(socket, path));
}

const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024;
const AdbSyncOkResponse = structDeserialize.struct({ unused: string.u32 }, { littleEndian: true });
async function pipeFileData(locked, file, packetSize, mtime) {
  const abortController = new consumable.AbortController();
  file.pipeThrough(new distribution.DistributionStream(packetSize, true)).pipeTo(new writable.MaybeConsumableWritableStream({
    write(chunk) {
      return adbSyncWriteRequest(locked, AdbSyncRequestId.Data, chunk);
    }
  }), { signal: abortController.signal }).then(async () => {
    await adbSyncWriteRequest(locked, AdbSyncRequestId.Done, mtime);
    await locked.flush();
  }, reverse.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 = structDeserialize.struct({ id: string.u32, mode: string.u32, flags: string.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 distribution.BufferCombiner(bufferSize);
  }
  #write(buffer) {
    return consumable.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 structDeserialize.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 reverse.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 reverse.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 encoding.TextDecoderStream()).pipeThrough(new distribution.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 = string.encodeUtf8("OKAY");
const FAIL = string.encodeUtf8("FAIL");
class AdbServerStream {
  #connection;
  #buffered;
  #writer;
  constructor(connection) {
    this.#connection = connection;
    this.#buffered = new structDeserialize.BufferedReadableStream(connection.readable);
    this.#writer = connection.writable.getWriter();
  }
  readExactly(length) {
    return this.#buffered.readExactly(length);
  }
  readString = structDeserialize.bipedal(function* (then) {
    const data = yield* then(this.readExactly(4));
    const length = reverse.hexToNumber(data);
    if (length === 0) {
      return "";
    } else {
      const decoder = new string.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 (reverse.sequenceEqual(response, OKAY)) {
      return;
    }
    if (reverse.sequenceEqual(response, FAIL)) {
      const reason = await this.readString();
      throw new Error(reason);
    }
    throw new Error(`Unexpected response: ${string.decodeUtf8(response)}`);
  }
  async writeString(value) {
    const encoded = string.encodeUtf8(value);
    const buffer = new Uint8Array(4 + encoded.length);
    reverse.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 consumable.tryCancel(this.#buffered);
    void consumable.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 (reverse.sequenceEqual(response, FAIL)) {
        throw new Error(await connection.readString());
      }
      const length = reverse.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 reverse.EventEmitter();
    const onDeviceRemove = new reverse.EventEmitter();
    const onListChange = new reverse.StickyEventEmitter();
    const onError = new reverse.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 consumable.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 = reverse.hexToNumber(await connection.readExactly(4));
      const version = reverse.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 = string.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 consumable.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 consumable.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);
    }
  }
}

exports.decodeUtf8 = string.decodeUtf8;
exports.encodeUtf8 = string.encodeUtf8;
exports.AdbReverseError = reverse.AdbReverseError;
exports.AdbReverseErrorResponse = reverse.AdbReverseErrorResponse;
exports.AdbReverseNotSupportedError = reverse.AdbReverseNotSupportedError;
exports.AdbReverseService = reverse.AdbReverseService;
exports.AdbServiceBase = reverse.AdbServiceBase;
exports.NOOP = reverse.NOOP;
exports.hexToNumber = reverse.hexToNumber;
exports.sequenceEqual = reverse.sequenceEqual;
exports.unreachable = reverse.unreachable;
exports.write4HexDigits = reverse.write4HexDigits;
exports.ADB_SERVER_DEFAULT_FEATURES = ADB_SERVER_DEFAULT_FEATURES;
exports.ADB_SYNC_MAX_PACKET_SIZE = ADB_SYNC_MAX_PACKET_SIZE;
exports.Adb = Adb;
exports.AdbBanner = AdbBanner;
exports.AdbBannerKey = AdbBannerKey;
exports.AdbFeature = AdbFeature;
exports.AdbFrameBufferError = AdbFrameBufferError;
exports.AdbFrameBufferForbiddenError = AdbFrameBufferForbiddenError;
exports.AdbFrameBufferUnsupportedVersionError = AdbFrameBufferUnsupportedVersionError;
exports.AdbFrameBufferV1 = AdbFrameBufferV1;
exports.AdbFrameBufferV2 = AdbFrameBufferV2;
exports.AdbNoneProtocolProcessImpl = AdbNoneProtocolProcessImpl;
exports.AdbNoneProtocolPtyProcess = AdbNoneProtocolPtyProcess;
exports.AdbNoneProtocolSpawner = AdbNoneProtocolSpawner;
exports.AdbNoneProtocolSubprocessService = AdbNoneProtocolSubprocessService;
exports.AdbPower = AdbPower;
exports.AdbServerClient = AdbServerClient;
exports.AdbServerDeviceObserverOwner = AdbServerDeviceObserverOwner;
exports.AdbServerStream = AdbServerStream;
exports.AdbServerTransport = AdbServerTransport;
exports.AdbShellProtocolId = AdbShellProtocolId;
exports.AdbShellProtocolPacket = AdbShellProtocolPacket;
exports.AdbShellProtocolProcessImpl = AdbShellProtocolProcessImpl;
exports.AdbShellProtocolPtyProcess = AdbShellProtocolPtyProcess;
exports.AdbShellProtocolSpawner = AdbShellProtocolSpawner;
exports.AdbShellProtocolSubprocessService = AdbShellProtocolSubprocessService;
exports.AdbSubprocessService = AdbSubprocessService;
exports.AdbSync = AdbSync;
exports.AdbSyncDataResponse = AdbSyncDataResponse;
exports.AdbSyncEntry2Response = AdbSyncEntry2Response;
exports.AdbSyncEntryResponse = AdbSyncEntryResponse;
exports.AdbSyncError = AdbSyncError;
exports.AdbSyncFailResponse = AdbSyncFailResponse;
exports.AdbSyncLstatResponse = AdbSyncLstatResponse;
exports.AdbSyncNumberRequest = AdbSyncNumberRequest;
exports.AdbSyncOkResponse = AdbSyncOkResponse;
exports.AdbSyncRequestId = AdbSyncRequestId;
exports.AdbSyncResponseId = AdbSyncResponseId;
exports.AdbSyncSendV2Flags = AdbSyncSendV2Flags;
exports.AdbSyncSendV2Request = AdbSyncSendV2Request;
exports.AdbSyncSocket = AdbSyncSocket;
exports.AdbSyncSocketLocked = AdbSyncSocketLocked;
exports.AdbSyncStatErrorCode = AdbSyncStatErrorCode;
exports.AdbSyncStatResponse = AdbSyncStatResponse;
exports.AdbTcpIpService = AdbTcpIpService;
exports.AutoResetEvent = AutoResetEvent;
exports.FAIL = FAIL;
exports.LinuxFileType = LinuxFileType;
exports.Ref = Ref;
exports.adbSyncEncodeId = adbSyncEncodeId;
exports.adbSyncLstat = adbSyncLstat;
exports.adbSyncOpenDir = adbSyncOpenDir;
exports.adbSyncOpenDirV1 = adbSyncOpenDirV1;
exports.adbSyncOpenDirV2 = adbSyncOpenDirV2;
exports.adbSyncPull = adbSyncPull;
exports.adbSyncPullGenerator = adbSyncPullGenerator;
exports.adbSyncPush = adbSyncPush;
exports.adbSyncPushV1 = adbSyncPushV1;
exports.adbSyncPushV2 = adbSyncPushV2;
exports.adbSyncReadResponse = adbSyncReadResponse;
exports.adbSyncReadResponses = adbSyncReadResponses;
exports.adbSyncStat = adbSyncStat;
exports.adbSyncWriteRequest = adbSyncWriteRequest;
exports.dirname = dirname;
exports.escapeArg = escapeArg;
exports.framebuffer = framebuffer;
exports.raceSignal = raceSignal;
exports.splitCommand = splitCommand;
exports.unorderedRemove = unorderedRemove;