feat: add PWA support

This commit is contained in:
2026-07-31 22:52:18 +02:00
parent 6998e8bdb7
commit 530162cbf7
11 changed files with 156 additions and 0 deletions
+36
View File
@@ -723,6 +723,42 @@ export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
reply.type("text/html; charset=utf-8");
return reply.send(createReadStream(resolve(webRoot, "index.html")));
});
const publicFiles: Record<string, { file: string; type: string; cacheControl: string }> = {
"/manifest.webmanifest": {
file: "manifest.webmanifest",
type: "application/manifest+json; charset=utf-8",
cacheControl: "no-cache"
},
"/service-worker.js": {
file: "service-worker.js",
type: "text/javascript; charset=utf-8",
cacheControl: "no-cache"
},
"/icon.svg": { file: "icon.svg", type: "image/svg+xml", cacheControl: "public, max-age=86400" },
"/pwa-192x192.png": { file: "pwa-192x192.png", type: "image/png", cacheControl: "public, max-age=86400" },
"/pwa-512x512.png": { file: "pwa-512x512.png", type: "image/png", cacheControl: "public, max-age=86400" },
"/pwa-maskable-512x512.png": {
file: "pwa-maskable-512x512.png",
type: "image/png",
cacheControl: "public, max-age=86400"
},
"/apple-touch-icon.png": {
file: "apple-touch-icon.png",
type: "image/png",
cacheControl: "public, max-age=86400"
}
};
for (const [route, asset] of Object.entries(publicFiles)) {
app.get(route, async (_request, reply) => {
const assetPath = resolve(webRoot, asset.file);
if (!existsSync(assetPath) || !statSync(assetPath).isFile()) {
return reply.code(404).send({ error: { code: "NOT_FOUND", message: "Asset not found" } });
}
reply.type(asset.type);
reply.header("Cache-Control", asset.cacheControl);
return reply.send(createReadStream(assetPath));
});
}
app.get<{ Params: { file: string } }>("/assets/:file", async (request, reply) => {
if (!/^[a-zA-Z0-9._-]+$/.test(request.params.file)) {
return reply.code(404).send({ error: { code: "NOT_FOUND", message: "Asset not found" } });