Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions components/webui/server/src/routes/static.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
import path from "node:path";
import {fileURLToPath} from "node:url";

import {fastifyStatic} from "@fastify/static";
import fastifyStatic from "@fastify/static";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type reference for FastifyStaticOptions — default import doesn’t expose types

fastifyStatic.FastifyStaticOptions will not resolve when using a default import. Bring the type in via a separate type-only import to avoid a TS compile error.

Apply this diff to add the type import:

 import fastifyStatic from "@fastify/static";
+import type { FastifyStaticOptions } from "@fastify/static";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import fastifyStatic from "@fastify/static";
import fastifyStatic from "@fastify/static";
import type { FastifyStaticOptions } from "@fastify/static";
🤖 Prompt for AI Agents
In components/webui/server/src/routes/static.ts around line 4, the code uses a
default import (import fastifyStatic from "@fastify/static") and references
fastifyStatic.FastifyStaticOptions which doesn't resolve; add a separate
type-only import like `import type { FastifyStaticOptions } from
"@fastify/static"` and replace usages of fastifyStatic.FastifyStaticOptions with
the imported FastifyStaticOptions type while keeping the existing default import
for runtime use.

import {FastifyPluginAsync} from "fastify";

import settings from "../../settings.json" with {type: "json"};


const CACHE_CONTROL_HEADER_KEY = "Cache-Control";
const CACHE_CONTROL_HEADER_VALUE_NO_CACHE = "public, max-age=0";

// Cache all other static files for 1 year without revalidation.
const CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE = "public, max-age=31536000, immutable";

Comment on lines +10 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Optional: strengthen revalidation for no-cache responses
If you want stricter behaviour for intermediaries, consider appending must-revalidate to the no-cache value. Current value is broadly fine; this is a belt-and-suspenders tweak.

-const CACHE_CONTROL_HEADER_VALUE_NO_CACHE = "public, max-age=0";
+const CACHE_CONTROL_HEADER_VALUE_NO_CACHE = "public, max-age=0, must-revalidate";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const CACHE_CONTROL_HEADER_KEY = "Cache-Control";
const CACHE_CONTROL_HEADER_VALUE_NO_CACHE = "public, max-age=0";
// Cache all other static files for 1 year without revalidation.
const CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE = "public, max-age=31536000, immutable";
const CACHE_CONTROL_HEADER_KEY = "Cache-Control";
const CACHE_CONTROL_HEADER_VALUE_NO_CACHE = "public, max-age=0, must-revalidate";
// Cache all other static files for 1 year without revalidation.
const CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE = "public, max-age=31536000, immutable";
🤖 Prompt for AI Agents
In components/webui/server/src/routes/static.ts around lines 10 to 15, the
no-cache header value should enforce stronger revalidation by intermediaries;
change the CACHE_CONTROL_HEADER_VALUE_NO_CACHE constant to include
must-revalidate (e.g. "public, max-age=0, must-revalidate") so that proxies and
caches are required to revalidate before serving cached responses.


/**
* Configures `Cache-Control` header for static files to reduce network traffic.
*
* @param res
* @param reqPath
* @param extraNoCachePaths
*/
const setCacheHeaders = (
res: Parameters<NonNullable<fastifyStatic.FastifyStaticOptions["setHeaders"]>>[0],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use the proper type name instead of fastifyStatic.FastifyStaticOptions

This reference will fail under default import; switch to the named type.

-    res: Parameters<NonNullable<fastifyStatic.FastifyStaticOptions["setHeaders"]>>[0],
+    res: Parameters<NonNullable<FastifyStaticOptions["setHeaders"]>>[0],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res: Parameters<NonNullable<fastifyStatic.FastifyStaticOptions["setHeaders"]>>[0],
res: Parameters<NonNullable<FastifyStaticOptions["setHeaders"]>>[0],
🤖 Prompt for AI Agents
In components/webui/server/src/routes/static.ts around line 25, the parameter
type references fastifyStatic.FastifyStaticOptions which breaks under a default
import; change to the named type from the package (e.g. import type {
FastifyStaticOptions } from 'fastify-static') and use FastifyStaticOptions in
the signature, ensuring the import is a named type import rather than relying on
a default namespace import.

reqPath: string,
extraNoCachePaths: string[] = []
): void => {
const noCachePaths = [
"/index.html",
...extraNoCachePaths,
];

if (noCachePaths.some((noCachePath) => reqPath.endsWith(noCachePath))) {
res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_NO_CACHE);

return;
}

res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE);
};
Comment on lines +24 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make no-cache matching OS-agnostic (Windows path separators)
reqPath passed to setHeaders is a filesystem path. On Windows it uses backslashes, so endsWith("/index.html") won’t match. Normalise path separators before checking suffixes.

 const setCacheHeaders = (
     res: Parameters<NonNullable<FastifyStaticOptions["setHeaders"]>>[0],
     reqPath: string,
     extraNoCachePaths: string[] = []
 ): void => {
+    // Normalise to POSIX-style separators so suffix checks work on all OSes
+    const normalizedReqPath = reqPath.split(path.win32.sep).join("/");
     const noCachePaths = [
         "/index.html",
         ...extraNoCachePaths,
     ];
 
-    if (noCachePaths.some((noCachePath) => reqPath.endsWith(noCachePath))) {
+    if (noCachePaths.some((noCachePath) => normalizedReqPath.endsWith(noCachePath))) {
         res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_NO_CACHE);
 
         return;
     }
 
     res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const setCacheHeaders = (
res: Parameters<NonNullable<fastifyStatic.FastifyStaticOptions["setHeaders"]>>[0],
reqPath: string,
extraNoCachePaths: string[] = []
): void => {
const noCachePaths = [
"/index.html",
...extraNoCachePaths,
];
if (noCachePaths.some((noCachePath) => reqPath.endsWith(noCachePath))) {
res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_NO_CACHE);
return;
}
res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE);
};
const setCacheHeaders = (
res: Parameters<NonNullable<fastifyStatic.FastifyStaticOptions["setHeaders"]>>[0],
reqPath: string,
extraNoCachePaths: string[] = []
): void => {
// Normalise to POSIX-style separators so suffix checks work on all OSes
const normalizedReqPath = reqPath.split(path.win32.sep).join("/");
const noCachePaths = [
"/index.html",
...extraNoCachePaths,
];
if (noCachePaths.some((noCachePath) => normalizedReqPath.endsWith(noCachePath))) {
res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_NO_CACHE);
return;
}
res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE);
};
🤖 Prompt for AI Agents
In components/webui/server/src/routes/static.ts around lines 24 to 41, the
no-cache check uses reqPath.endsWith("/index.html") which fails on Windows
because reqPath can contain backslashes; normalize path separators (e.g. replace
backslashes with forward slashes or use a path-normalization utility) before
performing the endsWith checks so the noCachePaths comparisons are OS-agnostic.


/**
* Creates static files serving routes.
*
Expand Down Expand Up @@ -40,21 +73,32 @@ const routes: FastifyPluginAsync = async (fastify) => {
logViewerDir = path.resolve(rootDirname, logViewerDir);
}
await fastify.register(fastifyStatic, {
decorateReply: false,
prefix: "/log-viewer",
root: logViewerDir,
decorateReply: false,

// Prevent fastify-static from adding its own cache headers and provide our own.
cacheControl: false,
setHeaders: (res, reqPath) => {
setCacheHeaders(res, reqPath);
},
});
Comment on lines +80 to 85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Minor TS ergonomics: include the third ‘stat’ parameter for signature parity
Not required at runtime, but including the third arg avoids potential typing friction if lib definitions tighten.

-        setHeaders: (res, reqPath) => {
-            setCacheHeaders(res, reqPath);
-        },
+        setHeaders: (res, reqPath, _stat) => {
+            setCacheHeaders(res, reqPath);
+        },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Prevent fastify-static from adding its own cache headers and provide our own.
cacheControl: false,
setHeaders: (res, reqPath) => {
setCacheHeaders(res, reqPath);
},
});
// Prevent fastify-static from adding its own cache headers and provide our own.
cacheControl: false,
setHeaders: (res, reqPath, _stat) => {
setCacheHeaders(res, reqPath);
},
});
🤖 Prompt for AI Agents
In components/webui/server/src/routes/static.ts around lines 80 to 85, the
setHeaders callback currently declares only (res, reqPath) which mismatches the
expected signature; add the third parameter (stat) to the function signature for
TypeScript parity (e.g. (res, reqPath, _stat) => { setCacheHeaders(res,
reqPath); }) and name it with a leading underscore to avoid unused-variable lint
errors; no runtime change is needed beyond the signature update.


let clientDir = settings.ClientDir;
if (false === path.isAbsolute(clientDir)) {
clientDir = path.resolve(rootDirname, settings.ClientDir);
}

await fastify.register(fastifyStatic, {
decorateReply: true,
prefix: "/",
root: clientDir,
decorateReply: true,
wildcard: false,

// Prevent fastify-static from adding its own cache headers and provide our own.
cacheControl: false,
setHeaders: (res, reqPath) => {
setCacheHeaders(res, reqPath, ["/settings.json"]);
},
});
Comment on lines +97 to 102
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Same as above: include third ‘stat’ param and simplify wrapper
Keeps callback shape consistent with @fastify/static definitions.

-        setHeaders: (res, reqPath) => {
-            setCacheHeaders(res, reqPath, ["/settings.json"]);
-        },
+        setHeaders: (res, reqPath, _stat) => {
+            setCacheHeaders(res, reqPath, ["/settings.json"]);
+        },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Prevent fastify-static from adding its own cache headers and provide our own.
cacheControl: false,
setHeaders: (res, reqPath) => {
setCacheHeaders(res, reqPath, ["/settings.json"]);
},
});
// Prevent fastify-static from adding its own cache headers and provide our own.
cacheControl: false,
setHeaders: (res, reqPath, _stat) => {
setCacheHeaders(res, reqPath, ["/settings.json"]);
},
});
🤖 Prompt for AI Agents
In components/webui/server/src/routes/static.ts around lines 97 to 102, the
setHeaders wrapper currently has only (res, reqPath) but must match
@fastify/static's signature; change it to accept the third stat parameter and
pass it through to setCacheHeaders (i.e. setHeaders: (res, reqPath, stat) =>
setCacheHeaders(res, reqPath, stat, ["/settings.json"])), simplifying the
wrapper by forwarding all three args instead of omitting stat.


// Serve index.html for all unmatched routes in the React Single Page Application (SPA).
Expand Down