import 'dotenv/config'; import http from 'http'; import https from 'https'; const TARGET = process.env.PROXY_API_TARGET; const PORT = process.env.PROXY_API_PORT; const LOCAL_ORIGIN = process.env.PROXY_LOCAL_ORIGIN; const PROD_ORIGIN = process.env.PROXY_PROD_ORIGIN; if (!TARGET) throw new Error('PROXY_API_TARGET environment variable is required'); if (!PORT) throw new Error('PROXY_API_PORT environment variable is required'); if (!LOCAL_ORIGIN) throw new Error('PROXY_LOCAL_ORIGIN environment variable is required'); if (!PROD_ORIGIN) throw new Error('PROXY_PROD_ORIGIN environment variable is required'); const parsedPort = parseInt(PORT, 10); const server = http.createServer((req, res) => { // Handle preflight OPTIONS requests if (req.method === 'OPTIONS') { res.setHeader('Access-Control-Allow-Origin', LOCAL_ORIGIN); res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Timezone, X-Locale'); res.writeHead(204); res.end(); return; } // Rewrite origin to match production so backend CORS accepts it const headers = { ...req.headers, host: TARGET }; if (headers.origin === LOCAL_ORIGIN) { headers.origin = PROD_ORIGIN; } const options = { hostname: TARGET, port: 443, path: req.url, method: req.method, headers, }; const proxyReq = https.request(options, (proxyRes) => { // Merge CORS headers with response headers const headers = { ...proxyRes.headers }; headers['access-control-allow-origin'] = LOCAL_ORIGIN; headers['access-control-allow-credentials'] = 'true'; res.writeHead(proxyRes.statusCode, headers); proxyRes.pipe(res); }); proxyReq.on('error', (err) => { console.error('Proxy error:', err); res.writeHead(500); res.end('Proxy error'); }); req.pipe(proxyReq); }); // Handle WebSocket upgrades for Socket.IO server.on('upgrade', (req, socket, head) => { // Rewrite origin to match production so backend CORS accepts it const headers = { ...req.headers, host: TARGET }; if (headers.origin === LOCAL_ORIGIN) { headers.origin = PROD_ORIGIN; } const options = { hostname: TARGET, port: 443, path: req.url, method: 'GET', headers, }; const proxyReq = https.request(options); proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => { let response = 'HTTP/1.1 101 Switching Protocols\r\n' + 'Upgrade: websocket\r\n' + 'Connection: Upgrade\r\n' + `Sec-WebSocket-Accept: ${proxyRes.headers['sec-websocket-accept']}\r\n`; // Forward compression extension if backend negotiated it if (proxyRes.headers['sec-websocket-extensions']) { response += `Sec-WebSocket-Extensions: ${proxyRes.headers['sec-websocket-extensions']}\r\n`; } response += '\r\n'; socket.write(response); proxySocket.pipe(socket); socket.pipe(proxySocket); }); proxyReq.on('error', (err) => { console.error('WebSocket proxy error:', err); socket.end(); }); proxyReq.end(); }); server.listen(parsedPort, () => { console.log(`API Proxy running: localhost:${parsedPort} -> ${TARGET} (HTTP + WebSocket)`); });