134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
import type { Connect, Plugin } from "vite";
|
|
import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
|
|
import { extname, resolve } from "node:path";
|
|
|
|
const MIME_TYPES: Record<string, string> = {
|
|
".css": "text/css",
|
|
".js": "application/javascript",
|
|
".html": "text/html",
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".svg": "image/svg+xml",
|
|
".woff": "font/woff",
|
|
".woff2": "font/woff2",
|
|
".ttf": "font/ttf",
|
|
".json": "application/json",
|
|
};
|
|
|
|
/** WESP hub page routes → static HTML (same as wesp/app/routes/pages.py). */
|
|
const WESP_PAGE_ROUTES: Record<string, string> = {
|
|
"/login": "login.html",
|
|
"/recipes": "recipes.html",
|
|
"/recipes_content": "recipes.html",
|
|
"/components": "components.html",
|
|
"/feed_dispensers": "feed_dispensers.html",
|
|
"/reports": "reports.html",
|
|
"/feed_consumption": "consumption.html",
|
|
"/consumption": "consumption.html",
|
|
"/lab": "lab.html",
|
|
"/lab-profiles": "lab-profiles.html",
|
|
"/admin": "admin.html",
|
|
};
|
|
|
|
function injectOrchestratorScripts(html: string, urlPath: string): string {
|
|
const favicon =
|
|
'<link rel="icon" href="/favicon.svg" type="image/svg+xml">\n' +
|
|
'<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32">';
|
|
const scripts =
|
|
urlPath === "/login"
|
|
? [
|
|
'<script src="/static/js/wesp-orchestrator-auth-bridge.js"></script>',
|
|
'<script src="/static/js/wesp-orchestrator-login.js"></script>',
|
|
]
|
|
: [
|
|
'<script src="/static/js/wesp-orchestrator-auth-bridge.js"></script>',
|
|
'<script src="/static/js/wesp-orchestrator-boot.js"></script>',
|
|
];
|
|
const block = [favicon, ...scripts].join("\n");
|
|
if (html.includes("wesp-orchestrator-auth-bridge.js")) {
|
|
if (!html.includes('rel="icon"') && html.includes("</head>")) {
|
|
return html.replace("</head>", `${favicon}\n</head>`);
|
|
}
|
|
return html;
|
|
}
|
|
if (html.includes("</head>")) {
|
|
return html.replace("</head>", `${block}\n</head>`);
|
|
}
|
|
return `${block}\n${html}`;
|
|
}
|
|
|
|
function sendFile(res: Connect.ServerResponse, filePath: string, transform?: (body: string) => string): void {
|
|
const ext = extname(filePath).toLowerCase();
|
|
if (transform && ext === ".html") {
|
|
const body = transform(readFileSync(filePath, "utf-8"));
|
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
res.end(body);
|
|
return;
|
|
}
|
|
res.setHeader("Content-Type", MIME_TYPES[ext] ?? "application/octet-stream");
|
|
createReadStream(filePath).pipe(res);
|
|
}
|
|
|
|
export function wespStaticPlugin(staticRoot: string): Plugin {
|
|
const root = resolve(staticRoot);
|
|
|
|
return {
|
|
name: "kompton-wesp-static",
|
|
configureServer(server) {
|
|
server.middlewares.use(createWespMiddleware(root));
|
|
},
|
|
configurePreviewServer(server) {
|
|
server.middlewares.use(createWespMiddleware(root));
|
|
},
|
|
};
|
|
}
|
|
|
|
function createWespMiddleware(root: string): Connect.NextHandleFunction {
|
|
return (req, res, next) => {
|
|
const urlPath = decodeURIComponent((req.url ?? "").split("?")[0]);
|
|
if (!urlPath || urlPath === "/") {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
if (urlPath === "/favicon.ico") {
|
|
const pngPath = resolve(root, "favicon-32.png");
|
|
if (existsSync(pngPath) && statSync(pngPath).isFile()) {
|
|
res.setHeader("Content-Type", "image/png");
|
|
sendFile(res, pngPath);
|
|
return;
|
|
}
|
|
}
|
|
if (urlPath === "/favicon.svg") {
|
|
const svgPath = resolve(root, "favicon.svg");
|
|
if (existsSync(svgPath) && statSync(svgPath).isFile()) {
|
|
res.setHeader("Content-Type", "image/svg+xml");
|
|
sendFile(res, svgPath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const pageFile = WESP_PAGE_ROUTES[urlPath];
|
|
if (pageFile) {
|
|
const htmlPath = resolve(root, pageFile);
|
|
if (existsSync(htmlPath) && statSync(htmlPath).isFile()) {
|
|
sendFile(res, htmlPath, (body) => injectOrchestratorScripts(body, urlPath));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (urlPath.startsWith("/static/")) {
|
|
const relative = urlPath.replace(/^\/static\//, "");
|
|
const filePath = resolve(root, relative);
|
|
if (filePath.startsWith(root) && existsSync(filePath) && statSync(filePath).isFile()) {
|
|
sendFile(res, filePath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
export { WESP_PAGE_ROUTES };
|