diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index af802a6..3746f75 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -25,8 +25,8 @@ crossorigin="anonymous" referrerpolicy="no-referrer" > - - + + - - + +
diff --git a/Overlord-Server/public/hvnc.html b/Overlord-Server/public/hvnc.html index 0f34a34..be76cd8 100644 --- a/Overlord-Server/public/hvnc.html +++ b/Overlord-Server/public/hvnc.html @@ -21,7 +21,7 @@ - +
@@ -273,7 +273,7 @@ - + diff --git a/Overlord-Server/public/index.html b/Overlord-Server/public/index.html index dabf752..96622be 100644 --- a/Overlord-Server/public/index.html +++ b/Overlord-Server/public/index.html @@ -174,8 +174,8 @@ - - + + diff --git a/Overlord-Server/public/keylogger.html b/Overlord-Server/public/keylogger.html index 6a0069c..0924b79 100644 --- a/Overlord-Server/public/keylogger.html +++ b/Overlord-Server/public/keylogger.html @@ -14,7 +14,7 @@ /> - +
diff --git a/Overlord-Server/public/logs.html b/Overlord-Server/public/logs.html index 653206c..46be72a 100644 --- a/Overlord-Server/public/logs.html +++ b/Overlord-Server/public/logs.html @@ -20,8 +20,8 @@ - - + +
@@ -132,6 +132,6 @@ - + diff --git a/Overlord-Server/public/metrics.html b/Overlord-Server/public/metrics.html index bfa496e..9694c22 100644 --- a/Overlord-Server/public/metrics.html +++ b/Overlord-Server/public/metrics.html @@ -47,9 +47,9 @@ z-index: 2; } - - - + + +
@@ -302,7 +302,7 @@ - + diff --git a/Overlord-Server/public/notifications.html b/Overlord-Server/public/notifications.html index 2227b21..04368e8 100644 --- a/Overlord-Server/public/notifications.html +++ b/Overlord-Server/public/notifications.html @@ -18,12 +18,12 @@ crossorigin="anonymous" referrerpolicy="no-referrer" /> - - + + - - + +
diff --git a/Overlord-Server/public/processes.html b/Overlord-Server/public/processes.html index 6522ea3..3c38def 100644 --- a/Overlord-Server/public/processes.html +++ b/Overlord-Server/public/processes.html @@ -19,7 +19,7 @@ /> - + - - + +
@@ -264,10 +264,10 @@ - - - - + + + + diff --git a/Overlord-Server/public/socks5-manager.html b/Overlord-Server/public/socks5-manager.html index e64f507..dfd22ef 100644 --- a/Overlord-Server/public/socks5-manager.html +++ b/Overlord-Server/public/socks5-manager.html @@ -14,7 +14,7 @@ /> - +
diff --git a/Overlord-Server/public/sol-publish.html b/Overlord-Server/public/sol-publish.html index 09cbc98..03557ea 100644 --- a/Overlord-Server/public/sol-publish.html +++ b/Overlord-Server/public/sol-publish.html @@ -20,8 +20,8 @@ - - + +
@@ -118,6 +118,6 @@ - + diff --git a/Overlord-Server/public/users.html b/Overlord-Server/public/users.html index 892584c..ed98273 100644 --- a/Overlord-Server/public/users.html +++ b/Overlord-Server/public/users.html @@ -14,8 +14,8 @@ /> - - + +
diff --git a/Overlord-Server/public/webcam.html b/Overlord-Server/public/webcam.html index e8a2b9e..f694421 100644 --- a/Overlord-Server/public/webcam.html +++ b/Overlord-Server/public/webcam.html @@ -21,7 +21,7 @@ - +
@@ -125,7 +125,7 @@ - + diff --git a/Overlord-Server/public/winre.html b/Overlord-Server/public/winre.html index 7c202ed..37338a8 100644 --- a/Overlord-Server/public/winre.html +++ b/Overlord-Server/public/winre.html @@ -14,8 +14,8 @@ /> - - + +
diff --git a/Overlord-Server/src/server/routes/assets-routes.ts b/Overlord-Server/src/server/routes/assets-routes.ts index fc76967..3346578 100644 --- a/Overlord-Server/src/server/routes/assets-routes.ts +++ b/Overlord-Server/src/server/routes/assets-routes.ts @@ -7,6 +7,49 @@ type AssetsRouteDeps = { mimeType: (path: string) => string; }; +const COMPRESSIBLE_TYPES = new Set(["text/html", "text/css", "text/javascript", "application/json", "image/svg+xml"]); + +function isCompressible(contentType: string): boolean { + for (const t of COMPRESSIBLE_TYPES) { + if (contentType.startsWith(t)) return true; + } + return false; +} + +function acceptsGzip(req: Request): boolean { + return (req.headers.get("accept-encoding") ?? "").includes("gzip"); +} + +const STATIC_ASSET_CACHE = "public, max-age=3600, stale-while-revalidate=86400"; +const MUTABLE_ASSET_CACHE = "public, max-age=60, stale-while-revalidate=300"; +const NO_CACHE = "no-cache"; + +function assetCacheControl(relativePath: string): string { + if (relativePath === "custom.css") return NO_CACHE; + if (relativePath === "notification-sw.js") return NO_CACHE; + if (relativePath.endsWith(".min.js") || relativePath.endsWith(".min.css")) { + return STATIC_ASSET_CACHE; + } + if (/\.(ico|png|jpg|jpeg|webp|woff2?|ttf|eot|svg)$/.test(relativePath)) { + return STATIC_ASSET_CACHE; + } + return MUTABLE_ASSET_CACHE; +} + +async function compressedResponse( + req: Request, + body: Uint8Array | ArrayBuffer, + headers: Record, +): Promise { + if (!isCompressible(headers["Content-Type"] ?? "") || !acceptsGzip(req) || body.byteLength < 1024) { + return new Response(body, { headers }); + } + const compressed = Bun.gzipSync(new Uint8Array(body instanceof ArrayBuffer ? body : body)); + return new Response(compressed, { + headers: { ...headers, "Content-Encoding": "gzip", "Vary": "Accept-Encoding" }, + }); +} + export async function handleAssetsRoutes( req: Request, url: URL, @@ -17,7 +60,7 @@ export async function handleAssetsRoutes( return new Response(css, { headers: { "Content-Type": "text/css; charset=utf-8", - "Cache-Control": "no-cache", + "Cache-Control": NO_CACHE, }, }); } @@ -25,7 +68,8 @@ export async function handleAssetsRoutes( if (req.method === "GET" && url.pathname === "/favicon.ico") { const file = Bun.file(path.join(deps.PUBLIC_ROOT, "assets", "favicon.ico")); if (await file.exists()) { - return new Response(file, { headers: deps.secureHeaders("image/x-icon") }); + const headers = { ...deps.secureHeaders("image/x-icon"), "Cache-Control": STATIC_ASSET_CACHE }; + return new Response(file, { headers }); } return new Response("Not found", { status: 404 }); } @@ -56,10 +100,26 @@ export async function handleAssetsRoutes( const file = Bun.file(resolvedPath); if (await file.exists()) { - const headers: Record = { ...deps.secureHeaders(deps.mimeType(url.pathname)) }; + const contentType = deps.mimeType(url.pathname); + const headers: Record = { + ...deps.secureHeaders(contentType), + "Cache-Control": assetCacheControl(relativePath), + }; if (relativePath === "notification-sw.js") { headers["Service-Worker-Allowed"] = "/"; } + + const stat = file; + const etag = `"${stat.size.toString(36)}-${stat.lastModified.toString(36)}"`; + headers["ETag"] = etag; + if (req.headers.get("if-none-match") === etag) { + return new Response(null, { status: 304, headers }); + } + + if (isCompressible(contentType) && acceptsGzip(req)) { + const bytes = await file.arrayBuffer(); + return compressedResponse(req, bytes, headers); + } return new Response(file, { headers }); } return new Response("Not found", { status: 404 });