import { createReadStream, existsSync } from "node:fs"; import { stat } from "node:fs/promises"; import { createServer } from "node:http"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { attachDiceSyncServer } from "./dice-sync.mjs"; const serverDir = path.dirname(fileURLToPath(import.meta.url)); const distDir = path.resolve(serverDir, "../dist"); const port = Number(process.env.PORT || 3000); const contentTypes = { ".css": "text/css; charset=utf-8", ".html": "text/html; charset=utf-8", ".ico": "image/x-icon", ".jpeg": "image/jpeg", ".jpg": "image/jpeg", ".js": "text/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp", ".woff": "font/woff", ".woff2": "font/woff2", }; const sendFile = async (request, response, filePath) => { const fileStat = await stat(filePath); response.writeHead(200, { "Content-Type": contentTypes[path.extname(filePath)] || "application/octet-stream", "Content-Length": fileStat.size, "Cache-Control": filePath.endsWith("index.html") ? "no-cache" : "public, max-age=31536000, immutable", }); if (request.method === "HEAD") response.end(); else createReadStream(filePath).pipe(response); }; const httpServer = createServer(async (request, response) => { try { const requestUrl = new URL(request.url || "/", "http://localhost"); if (requestUrl.pathname === "/api/health") { response.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store", }); response.end(JSON.stringify({ ok: true })); return; } if (!["GET", "HEAD"].includes(request.method || "")) { response.writeHead(405, { Allow: "GET, HEAD" }); response.end("Method not allowed"); return; } const decodedPath = decodeURIComponent(requestUrl.pathname); const requestedFile = path.resolve(distDir, `.${decodedPath}`); const safePath = requestedFile.startsWith(`${distDir}${path.sep}`) ? requestedFile : path.join(distDir, "index.html"); let filePath = path.join(distDir, "404.html"); if (existsSync(safePath)) { const safeStat = await stat(safePath); if (safeStat.isFile()) filePath = safePath; else if ( safeStat.isDirectory() && existsSync(path.join(safePath, "index.html")) ) filePath = path.join(safePath, "index.html"); } await sendFile(request, response, filePath); } catch { response.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" }); response.end("Internal server error"); } }); attachDiceSyncServer(httpServer); httpServer.listen(port, "0.0.0.0", () => { console.log(`Interactives is listening on port ${port}`); if ( typeof process.getuid === "function" && process.getuid() === 0 && typeof process.setuid === "function" ) { try { process.setuid("node"); } catch { // The container can still run safely with its filesystem mounted read-only by the platform. } } }); const shutdown = () => httpServer.close(() => process.exit(0)); process.on("SIGTERM", shutdown); process.on("SIGINT", shutdown);