import { createReadStream, statSync } from "node:fs"; import { createServer } from "node:http"; import { dirname, extname, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const defaultEditorDirectory = resolve(packageRoot, "editor"); const contentTypes = new Map([ [".css", "text/css; charset=utf-8"], [".html", "text/html; charset=utf-8"], [".js", "text/javascript; charset=utf-8"], [".json", "application/json; charset=utf-8"], [".svg", "image/svg+xml"], [".wasm", "application/wasm"], ]); function respond(response, statusCode, message) { response.writeHead(statusCode, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", }); response.end(message); } export function createEditorServer({ editorDirectory = defaultEditorDirectory } = {}) { const root = resolve(editorDirectory); const rootPrefix = `${root}${sep}`; return createServer((request, response) => { if (request.method !== "GET" && request.method !== "HEAD") { respond(response, 405, "Method Not Allowed"); return; } let pathname; try { pathname = decodeURIComponent(new URL(request.url ?? "/", "http://localhost").pathname); } catch { respond(response, 400, "Bad Request"); return; } if (pathname.endsWith("/")) pathname += "index.html"; const filePath = resolve(root, `.${pathname}`); if (filePath !== root && !filePath.startsWith(rootPrefix)) { respond(response, 403, "Forbidden"); return; } let stats; try { stats = statSync(filePath); } catch { respond(response, 404, "Not Found"); return; } if (!stats.isFile()) { respond(response, 404, "Not Found"); return; } response.writeHead(200, { "Content-Type": contentTypes.get(extname(filePath).toLowerCase()) ?? "application/octet-stream", "Content-Length": stats.size, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", }); if (request.method === "HEAD") { response.end(); return; } const stream = createReadStream(filePath); stream.on("error", () => response.destroy()); stream.pipe(response); }); } export function startEditorServer({ host = "127.0.0.1", port = 55173 } = {}) { const server = createEditorServer(); return new Promise((resolvePromise, reject) => { const onError = (error) => reject(error); server.once("error", onError); server.listen(port, host, () => { server.off("error", onError); const address = server.address(); const actualPort = typeof address === "object" && address ? address.port : port; resolvePromise({ server, url: `http://${host}:${actualPort}/` }); }); }); }