import { createServer } from "node:http"; import { createReadStream, statSync } from "node:fs"; import { extname, join, normalize } from "node:path"; import { fileURLToPath } from "node:url"; const rootDir = fileURLToPath(new URL(".", import.meta.url)); const port = Number(process.env.INVITE_TEST_PORT || 65248); const apiTarget = String(process.env.INVITE_API_TARGET || "http://127.0.0.1:2900").replace(/\/$/, ""); const mimeTypes = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".svg": "image/svg+xml", }; function sendText(response, status, text) { response.writeHead(status, { "content-type": "text/plain; charset=utf-8" }); response.end(text); } function serveStatic(request, response) { const requestPath = new URL(request.url, "http://localhost").pathname; const pathname = requestPath === "/" ? "/dev-test.html" : requestPath; const filePath = normalize(join(rootDir, pathname)); if (!filePath.startsWith(rootDir)) { sendText(response, 403, "Forbidden"); return; } try { const stat = statSync(filePath); if (!stat.isFile()) { sendText(response, 404, "Not found"); return; } response.writeHead(200, { "content-type": mimeTypes[extname(filePath)] || "application/octet-stream", "content-length": stat.size, }); createReadStream(filePath).pipe(response); } catch { sendText(response, 404, "Not found"); } } async function proxyAPI(request, response) { const incomingURL = new URL(request.url, "http://localhost"); const targetURL = new URL(incomingURL.pathname.replace(/^\/api/, "") + incomingURL.search, apiTarget); const headers = new Headers(); for (const [key, value] of Object.entries(request.headers)) { if (value !== undefined && key.toLowerCase() !== "host") { headers.set(key, Array.isArray(value) ? value.join(",") : value); } } try { const upstream = await fetch(targetURL, { method: request.method, headers, body: request.method === "GET" || request.method === "HEAD" ? undefined : request, duplex: "half", }); response.writeHead(upstream.status, { "content-type": upstream.headers.get("content-type") || "application/octet-stream", }); if (upstream.body) { for await (const chunk of upstream.body) { response.write(chunk); } } response.end(); } catch (error) { response.writeHead(502, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify({ status: false, code: "proxy_error", message: error instanceof Error ? error.message : "proxy error", })); } } createServer((request, response) => { if (request.url?.startsWith("/api/") || request.url === "/api") { proxyAPI(request, response); return; } serveStatic(request, response); }).listen(port, "127.0.0.1", () => { console.log(`Invite dev test page: http://127.0.0.1:${port}/dev-test.html`); console.log(`Proxy target: ${apiTarget}`); });