Files
stack/packages/cli/live/browser-network.mjs

37 lines
1.8 KiB
JavaScript

// Runs only inside Bubblewrap's network/PID namespaces. No host TCP/UDP handle
// is inherited: the single TCP listener relays bytes to the fixed Unix proxy.
import { spawn } from 'node:child_process';
import { createServer, createConnection } from 'node:net';
import { closeSync, readlinkSync } from 'node:fs';
import { networkInterfaces } from 'node:os';
const [hostNamespace, socket, port, executable, ...args] = process.argv.slice(2);
if (readlinkSync('/proc/self/ns/net') === hostNamespace ||
Object.keys(networkInterfaces()).some(name => name !== 'lo')) {
throw new Error('isolated browser network required');
}
const sockets = new Set();
const track = s => { sockets.add(s); s.once('close', () => sockets.delete(s)); return s; };
const server = createServer(peer => {
track(peer);
const upstream = track(createConnection(socket));
peer.on('error', () => upstream.destroy()); upstream.on('error', () => peer.destroy());
peer.once('close', () => upstream.destroy()); upstream.once('close', () => peer.destroy());
peer.pipe(upstream); upstream.pipe(peer);
});
await new Promise((resolve, reject) => {
server.once('error', reject); server.listen(Number(port), '127.0.0.1', resolve);
});
const child = spawn(executable, args, { stdio:['ignore','ignore','ignore',3,4] });
// Chromium alone owns the CDP pipes. Do not hold its EOF open on startup failure.
closeSync(3); closeSync(4);
let closing;
const close = () => closing ??= (async () => {
const closed = new Promise(resolve => server.close(resolve));
for (const s of sockets) s.destroy();
await closed;
})();
child.once('error', () => close().then(() => { process.exitCode = 1; }));
child.once('exit', (code, signal) => close().then(() => { process.exitCode = signal ? 1 : code; }));
for (const signal of ['SIGTERM','SIGINT']) process.once(signal, () => child.kill(signal));