consortium
Version:
Remote control and session sharing CLI for AI coding agents
280 lines (269 loc) • 7.47 kB
JavaScript
;
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;