161 lines
4.2 KiB
JavaScript
161 lines
4.2 KiB
JavaScript
import { readFile, stat } from "node:fs/promises";
|
|
import { createServer } from "node:http";
|
|
import { extname, join, normalize, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const _rootDir = fileURLToPath(new URL("..", import.meta.url));
|
|
const distDir = resolve(
|
|
process.env.ADMINFRONT_BUILD_OUT_DIR ?? "/tmp/baron-sso-adminfront-dist",
|
|
);
|
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
const port = Number(process.env.PORT ?? process.env.ADMINFRONT_PORT ?? 5173);
|
|
const backendTarget = new URL(
|
|
process.env.API_PROXY_TARGET || "http://localhost:3000",
|
|
);
|
|
|
|
const contentTypes = {
|
|
".css": "text/css; charset=utf-8",
|
|
".html": "text/html; charset=utf-8",
|
|
".js": "application/javascript; charset=utf-8",
|
|
".json": "application/json; charset=utf-8",
|
|
".map": "application/json; charset=utf-8",
|
|
".mjs": "application/javascript; charset=utf-8",
|
|
".svg": "image/svg+xml",
|
|
};
|
|
|
|
function getContentType(filePath) {
|
|
return (
|
|
contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
|
);
|
|
}
|
|
|
|
function sendJson(res, statusCode, body) {
|
|
res.writeHead(statusCode, {
|
|
"Content-Type": "application/json; charset=utf-8",
|
|
"Cache-Control": "no-store",
|
|
});
|
|
res.end(JSON.stringify(body));
|
|
}
|
|
|
|
function toSafePath(pathname) {
|
|
const decoded = decodeURIComponent(pathname);
|
|
const relative = decoded.replace(/^\/+/, "");
|
|
const safe = normalize(relative).replace(/^(\.\.(?:[\\/]|$))+/, "");
|
|
return join(distDir, safe);
|
|
}
|
|
|
|
async function tryReadFile(filePath) {
|
|
try {
|
|
return await readFile(filePath);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function proxyToBackend(req, res, pathname, search) {
|
|
const target = new URL(pathname + search, backendTarget);
|
|
const headers = new Headers();
|
|
|
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
if (!value) continue;
|
|
if (key === "host" || key === "content-length" || key === "connection") {
|
|
continue;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
headers.set(key, value.join(", "));
|
|
continue;
|
|
}
|
|
headers.set(key, value);
|
|
}
|
|
|
|
const hasBody = !["GET", "HEAD"].includes(req.method ?? "GET");
|
|
const response = await fetch(target, {
|
|
method: req.method,
|
|
headers,
|
|
body: hasBody ? req : undefined,
|
|
duplex: hasBody ? "half" : undefined,
|
|
});
|
|
|
|
const responseHeaders = new Headers(response.headers);
|
|
responseHeaders.delete("content-length");
|
|
responseHeaders.delete("transfer-encoding");
|
|
responseHeaders.delete("connection");
|
|
|
|
res.writeHead(response.status, Object.fromEntries(responseHeaders.entries()));
|
|
|
|
if (req.method === "HEAD") {
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
res.end(Buffer.from(arrayBuffer));
|
|
}
|
|
|
|
async function serveStatic(req, res, pathname) {
|
|
const indexPath = join(distDir, "index.html");
|
|
const filePath = toSafePath(pathname);
|
|
|
|
let resolvedPath = filePath;
|
|
try {
|
|
const fileStat = await stat(resolvedPath);
|
|
if (fileStat.isDirectory()) {
|
|
resolvedPath = join(resolvedPath, "index.html");
|
|
}
|
|
} catch {
|
|
resolvedPath = indexPath;
|
|
}
|
|
|
|
let body = await tryReadFile(resolvedPath);
|
|
if (!body) {
|
|
body = await tryReadFile(indexPath);
|
|
resolvedPath = indexPath;
|
|
}
|
|
|
|
if (!body) {
|
|
sendJson(res, 500, { error: "dist_not_found" });
|
|
return;
|
|
}
|
|
|
|
res.writeHead(200, {
|
|
"Content-Type": getContentType(resolvedPath),
|
|
"Cache-Control": resolvedPath.endsWith("index.html")
|
|
? "no-cache"
|
|
: "public, max-age=31536000, immutable",
|
|
});
|
|
|
|
if (req.method === "HEAD") {
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
res.end(body);
|
|
}
|
|
|
|
createServer(async (req, res) => {
|
|
try {
|
|
const url = new URL(
|
|
req.url ?? "/",
|
|
`http://${req.headers.host ?? "localhost"}`,
|
|
);
|
|
const { pathname, search } = url;
|
|
|
|
if (pathname === "/api" || pathname.startsWith("/api/")) {
|
|
await proxyToBackend(req, res, pathname, search);
|
|
return;
|
|
}
|
|
|
|
const normalizedPath = pathname === "/" ? "/index.html" : pathname;
|
|
await serveStatic(req, res, normalizedPath);
|
|
} catch (error) {
|
|
sendJson(res, 500, {
|
|
error: "internal_server_error",
|
|
message: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
}).listen(port, host, () => {
|
|
console.log(
|
|
`Adminfront production server listening on http://${host}:${port}`,
|
|
);
|
|
});
|