From 034aa69f8da3c500d76bba70af6ed389577ec268 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 4 Jan 2026 11:44:50 +0100 Subject: [PATCH] feat: update service worker to version 2.5 and enhance caching strategies for network requests, including cache bypass for refresh actions in LibraryClientWrapper, SeriesClientWrapper, and HomeClientWrapper components --- public/sw.js | 17 ++++++++-- .../[libraryId]/LibraryClientWrapper.tsx | 32 ++++++++++++++++--- .../series/[seriesId]/SeriesClientWrapper.tsx | 31 ++++++++++++++---- src/components/home/HomeClientWrapper.tsx | 16 ++++++++-- 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/public/sw.js b/public/sw.js index a12f2a7..9a12695 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,7 +1,7 @@ // StripStream Service Worker - Version 2 // Architecture: SWR (Stale-While-Revalidate) for all resources -const VERSION = "v2.4"; +const VERSION = "v2.5"; const STATIC_CACHE = `stripstream-static-${VERSION}`; const PAGES_CACHE = `stripstream-pages-${VERSION}`; // Navigation + RSC (client-side navigation) const API_CACHE = `stripstream-api-${VERSION}`; @@ -129,10 +129,23 @@ async function cacheFirstStrategy(request, cacheName, options = {}) { /** * Stale-While-Revalidate: Serve from cache immediately, update in background * Used for: API calls, images + * Respects Cache-Control: no-cache to force network-first (for refresh buttons) */ async function staleWhileRevalidateStrategy(request, cacheName, options = {}) { const cache = await caches.open(cacheName); - const cached = await cache.match(request); + + // Check if client requested no-cache (refresh button, router.refresh(), etc.) + // 1. Check Cache-Control header + const cacheControl = request.headers.get("Cache-Control"); + const noCacheHeader = + cacheControl && (cacheControl.includes("no-cache") || cacheControl.includes("no-store")); + // 2. Check request.cache mode (used by Next.js router.refresh()) + const noCacheMode = + request.cache === "no-cache" || request.cache === "no-store" || request.cache === "reload"; + const noCache = noCacheHeader || noCacheMode; + + // If no-cache, skip cached response and go network-first + const cached = noCache ? null : await cache.match(request); // Start network request (don't await) const fetchPromise = fetch(request) diff --git a/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx b/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx index 76ec334..efef5c1 100644 --- a/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx +++ b/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx @@ -17,21 +17,45 @@ interface LibraryClientWrapperProps { preferences: UserPreferences; } -export function LibraryClientWrapper({ children }: LibraryClientWrapperProps) { +export function LibraryClientWrapper({ + children, + libraryId, + currentPage, + unreadOnly, + search, + pageSize, +}: LibraryClientWrapperProps) { const router = useRouter(); const [isRefreshing, setIsRefreshing] = useState(false); const handleRefresh = async () => { try { setIsRefreshing(true); - // Revalider la page côté serveur + + // Fetch fresh data from network with cache bypass + const params = new URLSearchParams({ + page: String(currentPage), + size: String(pageSize), + ...(unreadOnly && { unreadOnly: "true" }), + ...(search && { search }), + }); + + const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`, { + cache: "no-store", + headers: { "Cache-Control": "no-cache" }, + }); + + if (!response.ok) { + throw new Error("Failed to refresh library"); + } + + // Trigger Next.js revalidation to update the UI router.refresh(); return { success: true }; } catch { return { success: false, error: "Error refreshing library" }; } finally { - // Petit délai pour laisser le temps au serveur de revalider - setTimeout(() => setIsRefreshing(false), 500); + setIsRefreshing(false); } }; diff --git a/src/app/series/[seriesId]/SeriesClientWrapper.tsx b/src/app/series/[seriesId]/SeriesClientWrapper.tsx index 332e6ff..1b9ec9d 100644 --- a/src/app/series/[seriesId]/SeriesClientWrapper.tsx +++ b/src/app/series/[seriesId]/SeriesClientWrapper.tsx @@ -18,6 +18,10 @@ interface SeriesClientWrapperProps { export function SeriesClientWrapper({ children, + seriesId, + currentPage, + unreadOnly, + pageSize, }: SeriesClientWrapperProps) { const router = useRouter(); const [isRefreshing, setIsRefreshing] = useState(false); @@ -25,14 +29,30 @@ export function SeriesClientWrapper({ const handleRefresh = async () => { try { setIsRefreshing(true); - // Revalider la page côté serveur + + // Fetch fresh data from network with cache bypass + const params = new URLSearchParams({ + page: String(currentPage), + size: String(pageSize), + ...(unreadOnly && { unreadOnly: "true" }), + }); + + const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`, { + cache: "no-store", + headers: { "Cache-Control": "no-cache" }, + }); + + if (!response.ok) { + throw new Error("Failed to refresh series"); + } + + // Trigger Next.js revalidation to update the UI router.refresh(); return { success: true }; } catch { return { success: false, error: "Error refreshing series" }; } finally { - // Petit délai pour laisser le temps au serveur de revalider - setTimeout(() => setIsRefreshing(false), 500); + setIsRefreshing(false); } }; @@ -52,10 +72,7 @@ export function SeriesClientWrapper({ canRefresh={pullToRefresh.canRefresh} isHiding={pullToRefresh.isHiding} /> - - {children} - + {children} ); } - diff --git a/src/components/home/HomeClientWrapper.tsx b/src/components/home/HomeClientWrapper.tsx index 9a5ff9e..63c94f2 100644 --- a/src/components/home/HomeClientWrapper.tsx +++ b/src/components/home/HomeClientWrapper.tsx @@ -20,15 +20,25 @@ export function HomeClientWrapper({ children }: HomeClientWrapperProps) { const handleRefresh = async () => { try { setIsRefreshing(true); - // Revalider la page côté serveur + + // Fetch fresh data from network with cache bypass + const response = await fetch("/api/komga/home", { + cache: "no-store", + headers: { "Cache-Control": "no-cache" }, + }); + + if (!response.ok) { + throw new Error("Failed to refresh home"); + } + + // Trigger Next.js revalidation to update the UI router.refresh(); return { success: true }; } catch (error) { logger.error({ err: error }, "Erreur lors du rafraîchissement:"); return { success: false, error: "Erreur lors du rafraîchissement de la page d'accueil" }; } finally { - // Petit délai pour laisser le temps au serveur de revalider - setTimeout(() => setIsRefreshing(false), 500); + setIsRefreshing(false); } };