135 lines
4.3 KiB
JavaScript
135 lines
4.3 KiB
JavaScript
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 };
|
|
}
|
|
|
|
// Export WebSocket handler for compatibility
|
|
export const proxyWebSocketHandler = {
|
|
// http-proxy-middleware handles WebSocket internally
|
|
// These are kept for compatibility but won't be used
|
|
open: () => {},
|
|
message: () => {},
|
|
close: () => {},
|
|
error: () => {}
|
|
};
|
|
|
|
if (enabled) {
|
|
registerPlugin('proxy', proxyMiddleware());
|
|
} else {
|
|
logs.plugin('proxy', 'Proxy plugin disabled via config');
|
|
}
|