Files

72 lines
2.3 KiB
JavaScript

import http from "node:http";
import { readFile, stat } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(scriptDir, "../..");
const port = Number(process.env.HEROLL_MVP_PORT || 4173);
const parentPidFlag = process.argv.indexOf("--parent-pid");
const parentPid = parentPidFlag >= 0 ? Number(process.argv[parentPidFlag + 1]) : 0;
const mime = new Map([
[".html", "text/html; charset=utf-8"],
[".js", "text/javascript; charset=utf-8"],
[".mjs", "text/javascript; charset=utf-8"],
[".css", "text/css; charset=utf-8"],
[".json", "application/json; charset=utf-8"],
[".csv", "text/csv; charset=utf-8"],
[".png", "image/png"],
[".jpg", "image/jpeg"],
[".jpeg", "image/jpeg"],
[".svg", "image/svg+xml"],
[".wav", "audio/wav"],
[".ogg", "audio/ogg"]
]);
function safePath(urlPath) {
const decoded = decodeURIComponent(urlPath.split("?")[0] || "/");
const relative = decoded === "/" ? "mvp/index.html" : decoded.replace(/^\/+/, "");
const resolved = path.resolve(rootDir, relative);
if (!resolved.startsWith(rootDir + path.sep) && resolved !== rootDir) return null;
return resolved;
}
const server = http.createServer(async (req, res) => {
try {
let target = safePath(req.url || "/");
if (!target) {
res.writeHead(403).end("Forbidden");
return;
}
const info = await stat(target);
if (info.isDirectory()) target = path.join(target, "index.html");
const body = await readFile(target);
res.writeHead(200, {
"Content-Type": mime.get(path.extname(target).toLowerCase()) || "application/octet-stream",
"Cache-Control": "no-store",
"Cross-Origin-Resource-Policy": "same-origin"
});
res.end(body);
} catch (error) {
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
res.end(`Not found\n${String(error)}`);
}
});
server.listen(port, "127.0.0.1", () => {
console.log(`HEROLL MVP running at http://127.0.0.1:${port}/mvp/`);
console.log("Press Ctrl+C to stop.");
});
if (Number.isInteger(parentPid) && parentPid > 0) {
const parentWatch = setInterval(() => {
try {
process.kill(parentPid, 0);
} catch {
clearInterval(parentWatch);
server.close(() => process.exit(0));
}
}, 1000);
parentWatch.unref();
}