const { spawn } = require('child_process');
const relays = new Map();
function startRelay({ binPath, port, peers = [] }) {
const args = ['start', '--port', String(port), '--memory-storage', 'true', '--redb-storage', 'false'];
if (peers.length > 0) {
args.push('--peers', peers.join(','));
}
const proc = spawn(binPath, args, { stdio: ['ignore', 'pipe', 'pipe'] });
proc.stdout.on('data', (d) => process.stdout.write(`[beam:${port}] ${d}`));
proc.stderr.on('data', (d) => process.stderr.write(`[beam:${port}!] ${d}`));
relays.set(String(port), proc);
return { process: proc, port };
}
function stopRelay(port) {
const proc = relays.get(String(port));
if (proc) {
proc.kill('SIGTERM');
relays.delete(String(port));
}
}
function waitForPortFree(port, timeoutMs = 10000) {
const net = require('net');
const deadline = Date.now() + timeoutMs;
function portOpen(p) {
return new Promise((resolve) => {
const sock = net.connect({ port: p, host: '127.0.0.1' });
const done = (open) => {
sock.destroy();
resolve(open);
};
sock.once('connect', () => done(true));
sock.once('error', () => done(false));
});
}
return new Promise((resolve, reject) => {
const attempt = async () => {
const wsOpen = await portOpen(port);
const uiOpen = await portOpen(port + 1);
if (!wsOpen && !uiOpen) return resolve();
if (Date.now() > deadline) {
return reject(new Error(`waitForPortFree: ports ${port}/${port + 1} still in use after ${timeoutMs}ms`));
}
setTimeout(attempt, 250);
};
attempt();
});
}
function stopAll() {
for (const [port, proc] of relays) {
proc.kill('SIGTERM');
}
relays.clear();
}
module.exports = { startRelay, stopRelay, stopAll, waitForPortFree };