UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

280 lines (269 loc) • 7.47 kB
'use strict'; var string = require('./string-CdxeepiN.cjs'); var structDeserialize = require('./struct-deserialize-BJQW83nw.cjs'); class AutoDisposable { #disposables = []; constructor() { this.dispose = this.dispose.bind(this); } addDisposable(disposable) { this.#disposables.push(disposable); return disposable; } dispose() { for (const disposable of this.#disposables) { disposable.dispose(); } this.#disposables = []; } } class EventEmitter { listeners = []; constructor() { this.event = this.event.bind(this); } addEventListener(info) { this.listeners.push(info); const remove = () => { const index = this.listeners.indexOf(info); if (index !== -1) { this.listeners.splice(index, 1); } }; remove.dispose = remove; return remove; } event = (listener, thisArg, ...args) => { const info = { listener, thisArg, args }; return this.addEventListener(info); }; fire(e) { for (const info of this.listeners.slice()) { info.listener.call(info.thisArg, e, ...info.args); } } dispose() { this.listeners.length = 0; } } const Undefined = Symbol("undefined"); class StickyEventEmitter extends EventEmitter { #value = Undefined; addEventListener(info) { if (this.#value !== Undefined) { info.listener.call(info.thisArg, this.#value, ...info.args); } return super.addEventListener(info); } fire(e) { this.#value = e; super.fire(e); } } class AdbServiceBase extends AutoDisposable { #adb; get adb() { return this.#adb; } constructor(adb) { super(); this.#adb = adb; } } function hexCharToNumber(char) { if (char < 48) { throw new TypeError(`Invalid hex char ${char}`); } if (char < 58) { return char - 48; } if (char < 65) { throw new TypeError(`Invalid hex char ${char}`); } if (char < 71) { return char - 55; } if (char < 97) { throw new TypeError(`Invalid hex char ${char}`); } if (char < 103) { return char - 87; } throw new TypeError(`Invalid hex char ${char}`); } function hexToNumber(data) { let result = 0; for (let i = 0; i < data.length; i += 1) { result = result << 4 | hexCharToNumber(data[i]); } return result; } function write4HexDigits(buffer, index, value) { const start = index; index += 3; while (index >= start && value > 0) { const digit = value & 15; value >>= 4; if (digit < 10) { buffer[index] = digit + 48; } else { buffer[index] = digit + 87; } index -= 1; } while (index >= start) { buffer[index] = 48; index -= 1; } } const NOOP = /* @__NO_SIDE_EFFECTS__ */ () => { }; function unreachable(...args) { throw new Error("Unreachable. Arguments:\n" + args.join("\n")); } function sequenceEqual(a, b) { if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i += 1) { if (a[i] !== b[i]) { return false; } } return true; } const AdbReverseStringResponse = structDeserialize.struct({ length: string.string(4), content: string.string({ field: "length", convert(value) { return Number.parseInt(value, 16); }, back(value) { return value.toString(16).padStart(4, "0"); } }) }, { littleEndian: true }); class AdbReverseError extends Error { constructor(message) { super(message); } } class AdbReverseNotSupportedError extends AdbReverseError { constructor() { super("ADB reverse tunnel is not supported on this device when connected wirelessly."); } } const AdbReverseErrorResponse = string.extend(AdbReverseStringResponse, {}, { postDeserialize(value) { if (value.content === "more than one device/emulator") { throw new AdbReverseNotSupportedError(); } else { throw new AdbReverseError(value.content); } } }); function decimalToNumber(buffer) { let value = 0; for (const byte of buffer) { if (byte < 48 || byte > 57) { return value; } value = value * 10 + byte - 48; } return value; } const OKAY = string.encodeUtf8("OKAY"); class AdbReverseService extends AdbServiceBase { #deviceAddressToLocalAddress = /* @__PURE__ */ new Map(); async createBufferedStream(service) { const socket = await this.adb.createSocket(service); return new structDeserialize.BufferedReadableStream(socket.readable); } async sendRequest(service) { const stream = await this.createBufferedStream(service); const response = await stream.readExactly(4); if (!sequenceEqual(response, OKAY)) { await AdbReverseErrorResponse.deserialize(stream); } return stream; } /** * Get a list of all reverse port forwarding on the device. */ async list() { const stream = await this.createBufferedStream("reverse:list-forward"); const response = await AdbReverseStringResponse.deserialize(stream); return response.content.split("\n").filter((line) => !!line).map((line) => { const [deviceSerial, localName, remoteName] = line.split(" "); return { deviceSerial, localName, remoteName }; }); } /** * Add a reverse port forwarding for a program that already listens on a port. */ async addExternal(deviceAddress, localAddress) { const stream = await this.sendRequest(`reverse:forward:${deviceAddress};${localAddress}`); if (deviceAddress.startsWith("tcp:")) { const position = stream.position; try { const length = hexToNumber(await stream.readExactly(4)); const port = decimalToNumber(await stream.readExactly(length)); deviceAddress = `tcp:${port}`; } catch (e) { if (e instanceof structDeserialize.ExactReadableEndedError && stream.position === position) ; else { throw e; } } } return deviceAddress; } /** * Add a reverse port forwarding. */ async add(deviceAddress, handler, localAddress) { localAddress = await this.adb.transport.addReverseTunnel(handler, localAddress); try { deviceAddress = await this.addExternal(deviceAddress, localAddress); this.#deviceAddressToLocalAddress.set(deviceAddress, localAddress); return deviceAddress; } catch (e) { await this.adb.transport.removeReverseTunnel(localAddress); throw e; } } /** * Remove a reverse port forwarding. */ async remove(deviceAddress) { const localAddress = this.#deviceAddressToLocalAddress.get(deviceAddress); if (localAddress) { await this.adb.transport.removeReverseTunnel(localAddress); } await this.sendRequest(`reverse:killforward:${deviceAddress}`); } /** * Remove all reverse port forwarding, including the ones added by other programs. */ async removeAll() { await this.adb.transport.clearReverseTunnels(); this.#deviceAddressToLocalAddress.clear(); await this.sendRequest(`reverse:killforward-all`); } } exports.AdbReverseError = AdbReverseError; exports.AdbReverseErrorResponse = AdbReverseErrorResponse; exports.AdbReverseNotSupportedError = AdbReverseNotSupportedError; exports.AdbReverseService = AdbReverseService; exports.AdbServiceBase = AdbServiceBase; exports.EventEmitter = EventEmitter; exports.NOOP = NOOP; exports.StickyEventEmitter = StickyEventEmitter; exports.hexToNumber = hexToNumber; exports.sequenceEqual = sequenceEqual; exports.unreachable = unreachable; exports.write4HexDigits = write4HexDigits;