Files
pebrel/mobile/relay/loopback-proxy.mjs
Kuddev bf2ed9af9b feat(mobile): integrate remote terminal, SSH and reader workflows (#343)
Integrate the Android client with native desktop pairing, remote terminal and SSH/SFTP sessions, keyboard controls, file reading and Agent conversations. Preserve authenticated connection ownership and per-device input permissions.

Validation: all ten required GitHub checks passed, including five native test platforms and both macOS release workspace checks.
2026-09-29 20:09:00 +08:00

54 lines
2.0 KiB
JavaScript

import net from 'node:net';
const MAX_PROXY_CONNECTIONS = 2;
const MAX_PROXY_CHUNK = 2 * 1024 * 1024 + 1024;
/**
* Forward encrypted bytes from a loopback-only socket to the LAN relay. The
* desktop connector therefore has a private wss://127.0.0.1 entry while the
* phone uses the separately bound HTTPS/WSS listener.
*/
export async function startLoopbackProxy(targetHost, targetPort) {
const sockets = new Set();
const connections = new Set();
const server = net.createServer(socket => {
if (connections.size >= MAX_PROXY_CONNECTIONS) { socket.destroy(); return; }
connections.add(socket);
sockets.add(socket);
socket.setNoDelay(true);
const upstream = net.createConnection({ host: targetHost, port: targetPort });
sockets.add(upstream);
upstream.setNoDelay(true);
const close = () => {
socket.destroy(); upstream.destroy(); connections.delete(socket);
sockets.delete(socket); sockets.delete(upstream);
};
socket.on('data', bytes => {
if (bytes.length > MAX_PROXY_CHUNK) { close(); return; }
if (!upstream.write(bytes)) socket.pause();
});
upstream.on('drain', () => socket.resume());
upstream.on('data', bytes => {
if (bytes.length > MAX_PROXY_CHUNK) { close(); return; }
if (!socket.write(bytes)) upstream.pause();
});
socket.on('drain', () => upstream.resume());
socket.on('error', close); upstream.on('error', close);
socket.on('close', close); upstream.on('close', close);
});
server.maxConnections = MAX_PROXY_CONNECTIONS;
await new Promise((resolve, reject) => {
const fail = error => { server.off('listening', resolve); reject(error); };
server.once('error', fail);
server.listen(0, '127.0.0.1', () => { server.off('error', fail); resolve(); });
});
return {
server,
port: server.address().port,
close: async () => {
for (const socket of sockets) socket.destroy();
await new Promise(resolve => server.close(() => resolve()));
},
};
}