import { createReadStream, existsSync, promises as fs } from "node:fs"; import type { IncomingMessage, ServerResponse } from "node:http"; import { extname, join, normalize } from "node:path"; const distDir = join(process.cwd(), "dist"); const mimeTypes: Record = { ".html": "text/html; charset=utf-8", ".js": "application/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", ".ico": "image/x-icon", ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".map": "application/json; charset=utf-8", }; const getSafePath = (urlPath: string): string => { const decodedPath = decodeURIComponent(urlPath.split("?")[0]); const normalized = normalize(decodedPath).replace(/^(\.\.(\/|\\|$))+/, ""); return normalized.startsWith("/") ? normalized : `/${normalized}`; }; const resolveFilePath = async (safePath: string): Promise => { let candidate = join(distDir, safePath); try { const stats = await fs.stat(candidate); if (stats.isDirectory()) { candidate = join(candidate, "index.html"); } return candidate; } catch { const fallback = join(distDir, "index.html"); if (existsSync(fallback)) { return fallback; } throw new Error("Dist folder missing. Run `npm run build` before deploying."); } }; const sendFile = (res: ServerResponse, filePath: string) => { const ext = extname(filePath).toLowerCase(); const contentType = mimeTypes[ext] || "application/octet-stream"; res.setHeader("Content-Type", contentType); res.setHeader( "Cache-Control", ext === ".html" ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000, immutable" ); const stream = createReadStream(filePath); stream.on("error", (error) => { console.error("Failed to read file"); if (process.env.NODE_ENV !== "production") { const message = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? error.stack : undefined; console.debug("[companion/api] Failed to read file", { message, stack }); } res.statusCode = 500; res.end("Internal Server Error"); }); stream.pipe(res); }; export default async function handler(req: IncomingMessage, res: ServerResponse) { if (req.method && !["GET", "HEAD"].includes(req.method)) { res.statusCode = 405; res.setHeader("Allow", "GET, HEAD"); res.end("Method Not Allowed"); return; } try { const urlPath = req.url ?? "/"; const safePath = getSafePath(urlPath); const filePath = await resolveFilePath(safePath); sendFile(res, filePath); } catch (error) { console.error("Server render error"); if (process.env.NODE_ENV !== "production") { const message = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? error.stack : undefined; console.debug("[companion/api] Server render error", { message, stack }); } res.statusCode = 500; res.end("Internal Server Error"); } }