/** * Reverse-proxy OpenFinclaw loopback dashboard API through the gateway under * `/plugins/openfinclaw/*` so the Control UI can use same-origin fetch (CSP connect-src 'self'). */ import type { IncomingMessage, ServerResponse } from "node:http"; import type { DashboardLogger } from "./server.js"; export const OPENFINCLAW_GATEWAY_PROXY_PREFIX = "/plugins/openfinclaw"; const HOP_BY_HOP = new Set([ "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", ]); export function createOpenFinclawGatewayProxy(params: { port: number; logger: DashboardLogger; }): (req: IncomingMessage, res: ServerResponse) => Promise { const upstream = `http://127.0.0.1:${params.port}`; return async (req: IncomingMessage, res: ServerResponse): Promise => { const raw = req.url ?? "/"; const u = new URL(raw, "http://localhost"); if (!u.pathname.startsWith(OPENFINCLAW_GATEWAY_PROXY_PREFIX)) { return false; } const subPath = u.pathname === OPENFINCLAW_GATEWAY_PROXY_PREFIX ? "/" : u.pathname.slice(OPENFINCLAW_GATEWAY_PROXY_PREFIX.length) || "/"; const targetUrl = `${upstream}${subPath.startsWith("/") ? subPath : `/${subPath}`}${u.search}`; const method = req.method ?? "GET"; if (method !== "GET" && method !== "HEAD") { res.statusCode = 405; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.end("Method Not Allowed"); return true; } try { const r = await fetch(targetUrl, { method, headers: { Accept: (req.headers.accept as string) || "application/json" }, signal: AbortSignal.timeout(30_000), }); r.headers.forEach((value, key) => { if (!HOP_BY_HOP.has(key.toLowerCase())) { res.setHeader(key, value); } }); res.statusCode = r.status; res.end(Buffer.from(await r.arrayBuffer())); return true; } catch (e) { params.logger.info(`[OpenFinClaw] Gateway proxy → ${targetUrl} failed: ${String(e)}`); if (!res.headersSent) { res.statusCode = 502; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.end("Bad Gateway (OpenFinClaw loopback server unreachable)"); } return true; } }; }