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 || 8800); 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", }; const corsHeaders = { "access-control-allow-origin": "*", "access-control-allow-methods": "GET,POST,OPTIONS", "access-control-allow-headers": "authorization,content-type,accept", }; function withCORS(headers = {}) { return { ...corsHeaders, ...headers }; } function sendText(response, status, text) { response.writeHead(status, withCORS({ "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 = normalizeStaticPath(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, { ...corsHeaders, "content-type": mimeTypes[extname(filePath)] || "application/octet-stream", "content-length": stat.size, }); createReadStream(filePath).pipe(response); } catch { sendText(response, 404, "Not found"); } } function normalizeStaticPath(requestPath) { if (requestPath === "/") { return "/dev-test.html"; } if (requestPath === "/public/h5/invite-campaign/landing") { return "/landing.html"; } if (requestPath === "/h5/app-invite" || requestPath === "/h5/app-invite/") { return "/index.html"; } if (requestPath.startsWith("/h5/app-invite/")) { return requestPath.replace(/^\/h5\/app-invite/, "") || "/index.html"; } return requestPath; } 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, { ...corsHeaders, "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, withCORS({ "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.method === "OPTIONS") { response.writeHead(204, corsHeaders); response.end(); return; } if (request.url?.startsWith("/api/") || request.url === "/api") { proxyAPI(request, response); return; } serveStatic(request, response); }).listen(port, "127.0.0.1", () => { console.log(`Invite H5 page: http://127.0.0.1:${port}/index.html`); console.log(`Invite dev test page: http://127.0.0.1:${port}/dev-test.html`); console.log(`Proxy target: ${apiTarget}`); });