import http from "http"; import { URL } from "url"; /** * Start a local HTTP server to receive OAuth callback * @param {Function} onCallback - Called with query params when callback received * @param {number} fixedPort - Optional fixed port number (default: random) * @returns {Promise<{server: http.Server, port: number, close: Function}>} */ export function startLocalServer(onCallback, fixedPort = null) { return new Promise((resolve, reject) => { const server = http.createServer((req, res) => { const url = new URL(req.url, `http://localhost`); if (url.pathname === "/callback" || url.pathname === "/auth/callback") { const params = Object.fromEntries(url.searchParams); // Send success response to browser with auto-close attempt res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(` Authentication Successful
✓

Authentication Successful

Closing in 3 seconds...

`); // Call callback with params onCallback(params); } else { res.writeHead(404); res.end("Not found"); } }); // Listen on fixed port or find available port const portToUse = fixedPort || 0; server.listen(portToUse, "127.0.0.1", () => { const { port } = server.address(); resolve({ server, port, close: () => server.close(), }); }); server.on("error", (err) => { if (err.code === "EADDRINUSE" && fixedPort) { reject(new Error(`Port ${fixedPort} is already in use. Please close other applications using this port.`)); } else { reject(err); } }); }); } /** * Wait for callback with timeout * @param {number} timeoutMs - Timeout in milliseconds * @returns {Promise} - Callback params */ export function waitForCallback(timeoutMs = 300000) { return new Promise((resolve, reject) => { let resolved = false; const timeout = setTimeout(() => { if (!resolved) { resolved = true; reject(new Error("Authentication timeout")); } }, timeoutMs); const onCallback = (params) => { if (!resolved) { resolved = true; clearTimeout(timeout); resolve(params); } }; // Return the callback function resolve.__onCallback = onCallback; }); } // Singleton proxy server for Codex OAuth callback on fixed port let codexProxyServer = null; let codexProxyTimeout = null; const CODEX_PROXY_TIMEOUT_MS = 300000; // 5 minutes /** * Start a proxy server on Codex fixed port (1455) that redirects callback to the app port. * Returns { success: true } if started, or { success: false } if port is busy. */ export function startCodexProxy(appPort) { return new Promise((resolve) => { // Already running if (codexProxyServer) { resolve({ success: true }); return; } const CODEX_PORT = 1455; const server = http.createServer((req, res) => { const url = new URL(req.url, "http://localhost"); if (url.pathname === "/callback" || url.pathname === "/auth/callback") { // Redirect to app port with all query params preserved const redirectUrl = `http://localhost:${appPort}/callback${url.search}`; res.writeHead(302, { Location: redirectUrl }); res.end(); // Auto-close after redirect stopCodexProxy(); return; } res.writeHead(404); res.end("Not found"); }); server.listen(CODEX_PORT, "127.0.0.1", () => { codexProxyServer = server; // Auto-cleanup after timeout codexProxyTimeout = setTimeout(() => stopCodexProxy(), CODEX_PROXY_TIMEOUT_MS); resolve({ success: true }); }); server.on("error", (err) => { if (err.code === "EADDRINUSE") { resolve({ success: false, reason: "port_busy" }); } else { resolve({ success: false, reason: err.message }); } }); }); } /** * Stop the Codex proxy server and cleanup */ export function stopCodexProxy() { if (codexProxyTimeout) { clearTimeout(codexProxyTimeout); codexProxyTimeout = null; } if (codexProxyServer) { codexProxyServer.close(); codexProxyServer = null; } }