UNPKG

navflow-proxy-server

Version:

Dynamic WebSocket proxy server for NavFlow

145 lines (127 loc) 4.41 kB
/** * HTTP request proxy handler - forwards requests through WebSocket tunnels */ /** * Handle HTTP request proxying through WebSocket tunnel * @param {TunnelManager} tunnelManager * @returns {Function} Express route handler */ function createProxyHandler(tunnelManager) { return async (req, res) => { const tunnelId = req.tunnel.id; try { // Prepare request data to send through tunnel const requestData = { type: 'http_request', method: req.method, url: req.url, path: req.path, query: req.query, headers: filterHeaders(req.headers), body: req.body }; console.log(`[ProxyHandler] Forwarding ${req.method} ${req.url} through tunnel ${tunnelId}`); // Send request through WebSocket tunnel const response = await tunnelManager.sendToTunnel(tunnelId, requestData); if (response.error) { console.error(`[ProxyHandler] Tunnel error for ${tunnelId}:`, response.error); return res.status(500).json({ error: 'Tunnel request failed', details: response.error }); } // Set response headers if (response.headers) { Object.entries(response.headers).forEach(([key, value]) => { // Skip headers that shouldn't be forwarded if (!shouldSkipHeader(key)) { res.set(key, value); } }); } // Set CORS headers for browser requests res.set('Access-Control-Allow-Origin', '*'); res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH'); res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Tunnel-Auth, X-Tunnel-ID'); // Send response const statusCode = response.statusCode || 200; if (response.body !== undefined) { // Handle different body types if (typeof response.body === 'string') { res.status(statusCode).send(response.body); } else if (Buffer.isBuffer(response.body)) { res.status(statusCode).send(response.body); } else { res.status(statusCode).json(response.body); } } else { res.status(statusCode).end(); } } catch (error) { console.error(`[ProxyHandler] Error proxying request for tunnel ${tunnelId}:`, error); if (error.message === 'Tunnel not available') { res.status(503).json({ error: 'Local server not connected', message: 'The local browser server is not connected to this tunnel' }); } else if (error.message === 'Tunnel request timeout') { res.status(504).json({ error: 'Request timeout', message: 'The local server did not respond within the timeout period' }); } else { res.status(500).json({ error: 'Proxy error', message: error.message }); } } }; } /** * Filter headers to remove problematic ones * @param {Object} headers * @returns {Object} Filtered headers */ function filterHeaders(headers) { const filtered = { ...headers }; // Remove headers that shouldn't be forwarded delete filtered.host; delete filtered.connection; delete filtered['x-forwarded-for']; delete filtered['x-forwarded-proto']; delete filtered['x-forwarded-host']; delete filtered['x-tunnel-auth']; // Remove our auth header delete filtered['x-tunnel-id']; // Remove our tunnel ID header return filtered; } /** * Check if header should be skipped in response * @param {string} headerName * @returns {boolean} */ function shouldSkipHeader(headerName) { const skipHeaders = [ 'connection', 'transfer-encoding', 'content-encoding', // Let browser handle encoding 'content-length' // Will be set automatically ]; return skipHeaders.includes(headerName.toLowerCase()); } /** * Handle CORS preflight requests * @param {Object} req * @param {Object} res */ function handleCorsOptions(req, res) { res.set('Access-Control-Allow-Origin', '*'); res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH'); res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Tunnel-Auth, X-Tunnel-ID'); res.set('Access-Control-Max-Age', '86400'); // 24 hours res.status(200).end(); } module.exports = { createProxyHandler, handleCorsOptions };