import { registerPlugin, loadConfig } from '../index.js'; import * as logs from '../utils/logs.js'; import { createProxyMiddleware } from 'http-proxy-middleware'; import express from 'express'; const proxyConfig = {}; await loadConfig('proxy', proxyConfig); // Map configuration to internal structure const enabled = proxyConfig.Core.Enabled; const wsTimeout = proxyConfig.Timeouts.WebSocketTimeoutMs; const upstreamTimeout = proxyConfig.Timeouts.UpstreamTimeoutMs; // Build proxy mappings from array format const proxyMappings = {}; proxyConfig.Mapping.forEach(mapping => { proxyMappings[mapping.Host] = mapping.Target; }); logs.plugin('proxy', `Proxy mappings loaded: ${JSON.stringify(proxyMappings)}`); function createProxyForHost(target) { return createProxyMiddleware({ target, changeOrigin: true, ws: true, // Enable WebSocket support timeout: upstreamTimeout, proxyTimeout: upstreamTimeout, onProxyReq: (proxyReq, req, res) => { // Remove undefined headers const headersToRemove = ['x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-for']; headersToRemove.forEach(header => { proxyReq.removeHeader(header); }); // Set proper forwarded headers const forwarded = { for: req.ip || req.connection.remoteAddress, host: req.get('host'), proto: req.protocol }; proxyReq.setHeader('X-Forwarded-For', forwarded.for); proxyReq.setHeader('X-Forwarded-Host', forwarded.host); proxyReq.setHeader('X-Forwarded-Proto', forwarded.proto); // Log the proxied request const startTime = Date.now(); res.on('finish', () => { const latency = Date.now() - startTime; logs.plugin('proxy', `Proxied request to: ${target}${req.url} (${res.statusCode}) (${latency}ms)`); }); }, onProxyReqWs: (proxyReq, req, socket, options, head) => { // Set WebSocket timeout socket.setTimeout(wsTimeout); logs.plugin('proxy', `WebSocket proxied to: ${target}${req.url}`); }, onError: (err, req, res) => { logs.error('proxy', `Proxy error: ${err.message}`); if (!res.headersSent) { res.status(502).send('Bad Gateway'); } }, // Handle SSE and streaming responses properly onProxyRes: (proxyRes, req, res) => { // For SSE responses, ensure proper headers const contentType = proxyRes.headers['content-type']; if (contentType && contentType.includes('text/event-stream')) { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('X-Accel-Buffering', 'no'); // Remove compression for SSE delete proxyRes.headers['content-encoding']; // Force connection keep-alive res.setHeader('Connection', 'keep-alive'); } }, // Advanced options for better compatibility followRedirects: false, preserveHeaderKeyCase: true, autoRewrite: true, protocolRewrite: 'http', cookieDomainRewrite: { "*": "" // Remove domain restrictions from cookies } }); } function proxyMiddleware() { const router = express.Router(); // Skip checkpoint endpoints router.use('/api/challenge', (req, res, next) => next('route')); router.use('/api/verify', (req, res, next) => next('route')); // Skip static assets (already handled by static middleware) router.use('/webfont/', (req, res, next) => next('route')); router.use('/js/', (req, res, next) => next('route')); // Create a proxy instance for each host const proxyInstances = {}; Object.entries(proxyMappings).forEach(([host, target]) => { proxyInstances[host] = createProxyForHost(target); }); // Main proxy handler router.use((req, res, next) => { const hostname = req.hostname || req.headers.host?.split(':')[0]; const proxyInstance = proxyInstances[hostname]; if (proxyInstance) { proxyInstance(req, res, next); } else { next(); } }); return { middleware: router }; } if (enabled) { registerPlugin('proxy', proxyMiddleware()); } else { logs.plugin('proxy', 'Proxy plugin disabled via config'); }