You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.2 KiB
JavaScript
109 lines
3.2 KiB
JavaScript
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)`);
|
|
});
|