/** * desktop/src/port.ts — pick a bindable TCP port for the embedded server. * * The backend derives allowedOrigins from the concrete port (config.ts M1), so * the desktop shell must resolve a DEFINITE port BEFORE loadConfig — never hand * loadConfig a PORT=0, or http://127.0.0.1: would fall off the Origin * whitelist. We probe the preferred port on the loopback interface; if it is * taken (EADDRINUSE) we ask the OS for a free one (listen on 0), read it, and * close — so the returned port was confirmed bindable a moment ago. */ import net from 'node:net' /** Loopback host we probe against — the embedded server always binds here in private mode. */ const LOOPBACK_HOST = '127.0.0.1' /** listen(OS_ASSIGN_PORT) → the kernel picks a free ephemeral port for us. */ const OS_ASSIGN_PORT = 0 /** * Return `preferred` if it can be bound on 127.0.0.1; otherwise an OS-assigned * free port. A bind failure that is EADDRINUSE falls back to an OS-assigned * port; any other error (e.g. EACCES on a privileged port) is rethrown. */ export async function pickFreePort(preferred: number): Promise { try { return await tryBind(preferred) } catch (err: unknown) { if (isAddrInUse(err)) return tryBind(OS_ASSIGN_PORT) throw err } } /** Bind a throwaway server on `port` (0 → OS-assigned), read the actual port, close, resolve it. */ function tryBind(port: number): Promise { return new Promise((resolve, reject) => { const server = net.createServer() server.on('error', (err) => { server.close() reject(err) }) server.listen(port, LOOPBACK_HOST, () => { const address = server.address() const bound = address !== null && typeof address === 'object' ? address.port : null server.close(() => { if (bound === null) { reject(new Error('pickFreePort: server.address() did not return an AddressInfo')) return } resolve(bound) }) }) }) } /** True iff `err` is a Node errno error whose code is EADDRINUSE. */ function isAddrInUse(err: unknown): boolean { return ( typeof err === 'object' && err !== null && 'code' in err && err.code === 'EADDRINUSE' ) }