41 lines
1.5 KiB
TypeScript
41 lines
1.5 KiB
TypeScript
import { file } from "bun";
|
|
import { join } from "path";
|
|
import { PUBLIC_DIR } from "../config";
|
|
|
|
// Serve static files
|
|
// Defense-in-depth CSP: no inline scripts, same-origin + ws(s) connections,
|
|
// blob: media for cached audio, data:/blob: images, inline styles allowed
|
|
// (style attributes are used throughout the client).
|
|
const CSP = "default-src 'self'; script-src 'self'; connect-src 'self' ws: wss:; media-src 'self' blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'";
|
|
|
|
export async function handleStatic(path: string): Promise<Response | null> {
|
|
if (path === "/" || path === "/index.html" || path.startsWith("/listen/")) {
|
|
return new Response(file(join(PUBLIC_DIR, "index.html")), {
|
|
headers: { "Content-Type": "text/html", "Content-Security-Policy": CSP },
|
|
});
|
|
}
|
|
|
|
if (path === "/styles.css") {
|
|
return new Response(file(join(PUBLIC_DIR, "styles.css")), {
|
|
headers: { "Content-Type": "text/css", "Content-Security-Policy": CSP },
|
|
});
|
|
}
|
|
|
|
if (path === "/favicon.ico") {
|
|
return new Response(file(join(PUBLIC_DIR, "favicon.ico")), {
|
|
headers: { "Content-Type": "image/x-icon", "Content-Security-Policy": CSP },
|
|
});
|
|
}
|
|
|
|
if (path.endsWith(".js")) {
|
|
const jsFile = file(join(PUBLIC_DIR, path.slice(1)));
|
|
if (await jsFile.exists()) {
|
|
return new Response(jsFile, {
|
|
headers: { "Content-Type": "application/javascript", "Content-Security-Policy": CSP },
|
|
});
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|