104 lines
3 KiB
TypeScript
104 lines
3 KiB
TypeScript
import { extname, normalize } from "node:path";
|
|
import { handleAgentDashboardRoute } from "./routes/agent-dashboard";
|
|
import {
|
|
handleDashboardEventsRoute,
|
|
handleDashboardRoute,
|
|
handleDashboardTileRoute,
|
|
handleDashboardTilesRoute,
|
|
} from "./routes/dashboard";
|
|
|
|
const host = process.env.HOST || "0.0.0.0";
|
|
const port = Number(process.env.PORT || 3000);
|
|
const distRoot = `${process.cwd()}/dist`;
|
|
|
|
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"],
|
|
]);
|
|
|
|
export async function handleRequest(request: Request): Promise<Response> {
|
|
const url = new URL(request.url);
|
|
|
|
if (url.pathname === "/api/dashboard") {
|
|
if (request.method !== "GET") return methodNotAllowed(["GET"]);
|
|
return handleDashboardRoute();
|
|
}
|
|
|
|
if (url.pathname === "/api/dashboard/tiles") {
|
|
if (request.method !== "POST") return methodNotAllowed(["POST"]);
|
|
return handleDashboardTilesRoute(request);
|
|
}
|
|
|
|
if (url.pathname === "/api/dashboard/events") {
|
|
if (request.method !== "GET") return methodNotAllowed(["GET"]);
|
|
return handleDashboardEventsRoute({ signal: request.signal });
|
|
}
|
|
|
|
if (url.pathname.startsWith("/api/dashboard/tile/")) {
|
|
if (request.method !== "GET") return methodNotAllowed(["GET"]);
|
|
return handleDashboardTileRoute(url.pathname);
|
|
}
|
|
|
|
if (url.pathname === "/api/agent/dashboard") {
|
|
if (request.method !== "POST") return methodNotAllowed(["POST"]);
|
|
return handleAgentDashboardRoute(request);
|
|
}
|
|
|
|
if (url.pathname.startsWith("/api/")) {
|
|
return Response.json({ ok: false, message: "Not found" }, { status: 404 });
|
|
}
|
|
|
|
return serveStaticAsset(url.pathname);
|
|
}
|
|
|
|
async function serveStaticAsset(pathname: string): Promise<Response> {
|
|
const safePath = normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
const assetPath = safePath === "/" || safePath === "." ? "/index.html" : safePath;
|
|
const file = Bun.file(`${distRoot}${assetPath}`);
|
|
|
|
if (await file.exists()) {
|
|
return new Response(file, {
|
|
headers: contentTypeHeaders(assetPath),
|
|
});
|
|
}
|
|
|
|
const fallback = Bun.file(`${distRoot}/index.html`);
|
|
if (await fallback.exists()) {
|
|
return new Response(fallback, {
|
|
headers: contentTypeHeaders(".html"),
|
|
});
|
|
}
|
|
|
|
return new Response("Build output not found", { status: 404 });
|
|
}
|
|
|
|
function methodNotAllowed(allowedMethods: string[]): Response {
|
|
return Response.json(
|
|
{ ok: false, message: "Method not allowed" },
|
|
{
|
|
status: 405,
|
|
headers: {
|
|
Allow: allowedMethods.join(", "),
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
function contentTypeHeaders(pathname: string): HeadersInit {
|
|
const contentType = contentTypes.get(extname(pathname));
|
|
return contentType ? { "Content-Type": contentType } : {};
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
Bun.serve({
|
|
hostname: host,
|
|
port,
|
|
fetch: handleRequest,
|
|
});
|
|
|
|
console.info(`Dimension Lab website listening on http://${host}:${port}`);
|
|
}
|