This works
This commit is contained in:
@@ -673,6 +673,11 @@ func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request, _ *visito
|
|||||||
// handleStatic returns all static resources (excluding the docs), including the web app
|
// handleStatic returns all static resources (excluding the docs), including the web app
|
||||||
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
||||||
r.URL.Path = webSiteDir + r.URL.Path
|
r.URL.Path = webSiteDir + r.URL.Path
|
||||||
|
// Prevent caching of HTML files to ensure auth proxies can intercept unauthenticated requests.
|
||||||
|
// Static hashed assets (JS, CSS, images) can still be cached normally.
|
||||||
|
if strings.HasSuffix(r.URL.Path, ".html") {
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
}
|
||||||
util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r)
|
util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from "workbox-precaching";
|
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching";
|
||||||
import { NavigationRoute, registerRoute } from "workbox-routing";
|
import { registerRoute } from "workbox-routing";
|
||||||
import { NetworkFirst } from "workbox-strategies";
|
import { NetworkFirst, StaleWhileRevalidate } from "workbox-strategies";
|
||||||
|
import { CacheableResponsePlugin } from "workbox-cacheable-response";
|
||||||
|
import { ExpirationPlugin } from "workbox-expiration";
|
||||||
import { clientsClaim } from "workbox-core";
|
import { clientsClaim } from "workbox-core";
|
||||||
import { dbAsync } from "../src/app/db";
|
import { dbAsync } from "../src/app/db";
|
||||||
import { badge, icon, messageWithSequenceId, notificationTag, toNotificationParams } from "../src/app/notificationUtils";
|
import { badge, icon, messageWithSequenceId, notificationTag, toNotificationParams } from "../src/app/notificationUtils";
|
||||||
@@ -337,63 +339,42 @@ clientsClaim();
|
|||||||
cleanupOutdatedCaches();
|
cleanupOutdatedCaches();
|
||||||
|
|
||||||
if (!import.meta.env.DEV) {
|
if (!import.meta.env.DEV) {
|
||||||
// we need the app_root setting, so we import the config.js file from the go server
|
// Use NetworkFirst for navigation requests. This ensures that auth proxies (like Authelia)
|
||||||
// this does NOT include the same base_url as the web app running in a window,
|
// can intercept unauthenticated requests, while still providing offline fallback.
|
||||||
// since we don't have access to `window` like in `src/app/config.js`
|
// The 3-second timeout means if the network is slow/unavailable, cached HTML is served.
|
||||||
self.importScripts("/config.js");
|
|
||||||
|
|
||||||
// This is the fallback single-page-app route, matching vite.config.js PWA config,
|
|
||||||
// and is served by the go web server. It is needed for the single-page-app to work.
|
|
||||||
// https://developer.chrome.com/docs/workbox/modules/workbox-routing/#how-to-register-a-navigation-route
|
|
||||||
registerRoute(
|
registerRoute(
|
||||||
new NavigationRoute(createHandlerBoundToURL("/app.html"), {
|
({ request }) => request.mode === "navigate",
|
||||||
allowlist: [
|
new NetworkFirst({
|
||||||
// the app root itself, could be /, or not
|
cacheName: "html-cache",
|
||||||
new RegExp(`^${config.app_root}$`),
|
networkTimeoutSeconds: 3,
|
||||||
|
plugins: [new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 60 })],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cache static assets (JS, CSS, images, fonts) with StaleWhileRevalidate for better performance.
|
||||||
|
// Serves cached version immediately while fetching fresh version in the background.
|
||||||
|
registerRoute(
|
||||||
|
({ request }) =>
|
||||||
|
request.destination === "script" ||
|
||||||
|
request.destination === "style" ||
|
||||||
|
request.destination === "image" ||
|
||||||
|
request.destination === "font",
|
||||||
|
new StaleWhileRevalidate({
|
||||||
|
cacheName: "assets-cache",
|
||||||
|
plugins: [
|
||||||
|
new CacheableResponsePlugin({ statuses: [200] }),
|
||||||
|
new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 }),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// The manifest excludes config.js (see vite.config.js) since the dist-file differs from the
|
// Handle config.js with NetworkFirst. The manifest excludes it (see vite.config.js) since
|
||||||
// actual config served by the go server. This adds it back with a custom handler that validates
|
// the dist-file differs from the actual config served by the go server.
|
||||||
// the response. If the response is HTML (e.g., from an auth proxy like Authelia), the service
|
|
||||||
// worker unregisters itself and clears caches to allow the auth proxy to handle the request.
|
|
||||||
registerRoute(
|
registerRoute(
|
||||||
({ url }) => url.pathname === "/config.js",
|
({ url }) => url.pathname === "/config.js",
|
||||||
async ({ request }) => {
|
new NetworkFirst({
|
||||||
const cache = await caches.open("config-cache");
|
cacheName: "config-cache",
|
||||||
try {
|
plugins: [new CacheableResponsePlugin({ statuses: [200] })],
|
||||||
const response = await fetch(request);
|
})
|
||||||
const contentType = response.headers.get("content-type") || "";
|
|
||||||
|
|
||||||
// If we got HTML instead of JavaScript, we're likely logged out at the proxy level
|
|
||||||
// (e.g., Authelia is serving its login page). Clear caches, unregister the service
|
|
||||||
// worker, and reload all clients so the browser can handle the auth flow properly.
|
|
||||||
if (contentType.includes("text/html")) {
|
|
||||||
console.log("[ServiceWorker] Config returned HTML - proxy session likely expired, unregistering");
|
|
||||||
const cacheNames = await caches.keys();
|
|
||||||
await Promise.all(cacheNames.map((name) => caches.delete(name)));
|
|
||||||
await self.registration.unregister();
|
|
||||||
|
|
||||||
// Reload all open clients so they go through the auth proxy
|
|
||||||
const allClients = await self.clients.matchAll({ type: "window" });
|
|
||||||
allClients.forEach((client) => client.navigate(client.url));
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid config response - cache it and return
|
|
||||||
await cache.put(request, response.clone());
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
// Network failed, try to serve from cache
|
|
||||||
console.log("[ServiceWorker] Network failed for config.js, trying cache", e);
|
|
||||||
const cached = await cache.match(request);
|
|
||||||
if (cached) {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user