Compare commits
4 Commits
0d33462349
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
034aa69f8d | ||
|
|
060dfb3099 | ||
|
|
ad11bce308 | ||
|
|
1ffe99285d |
17
public/sw.js
17
public/sw.js
@@ -1,7 +1,7 @@
|
|||||||
// StripStream Service Worker - Version 2
|
// StripStream Service Worker - Version 2
|
||||||
// Architecture: SWR (Stale-While-Revalidate) for all resources
|
// Architecture: SWR (Stale-While-Revalidate) for all resources
|
||||||
|
|
||||||
const VERSION = "v2.4";
|
const VERSION = "v2.5";
|
||||||
const STATIC_CACHE = `stripstream-static-${VERSION}`;
|
const STATIC_CACHE = `stripstream-static-${VERSION}`;
|
||||||
const PAGES_CACHE = `stripstream-pages-${VERSION}`; // Navigation + RSC (client-side navigation)
|
const PAGES_CACHE = `stripstream-pages-${VERSION}`; // Navigation + RSC (client-side navigation)
|
||||||
const API_CACHE = `stripstream-api-${VERSION}`;
|
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
|
* Stale-While-Revalidate: Serve from cache immediately, update in background
|
||||||
* Used for: API calls, images
|
* Used for: API calls, images
|
||||||
|
* Respects Cache-Control: no-cache to force network-first (for refresh buttons)
|
||||||
*/
|
*/
|
||||||
async function staleWhileRevalidateStrategy(request, cacheName, options = {}) {
|
async function staleWhileRevalidateStrategy(request, cacheName, options = {}) {
|
||||||
const cache = await caches.open(cacheName);
|
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)
|
// Start network request (don't await)
|
||||||
const fetchPromise = fetch(request)
|
const fetchPromise = fetch(request)
|
||||||
|
|||||||
@@ -17,21 +17,45 @@ interface LibraryClientWrapperProps {
|
|||||||
preferences: UserPreferences;
|
preferences: UserPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LibraryClientWrapper({ children }: LibraryClientWrapperProps) {
|
export function LibraryClientWrapper({
|
||||||
|
children,
|
||||||
|
libraryId,
|
||||||
|
currentPage,
|
||||||
|
unreadOnly,
|
||||||
|
search,
|
||||||
|
pageSize,
|
||||||
|
}: LibraryClientWrapperProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
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();
|
router.refresh();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch {
|
} catch {
|
||||||
return { success: false, error: "Error refreshing library" };
|
return { success: false, error: "Error refreshing library" };
|
||||||
} finally {
|
} finally {
|
||||||
// Petit délai pour laisser le temps au serveur de revalider
|
setIsRefreshing(false);
|
||||||
setTimeout(() => setIsRefreshing(false), 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ interface SeriesClientWrapperProps {
|
|||||||
|
|
||||||
export function SeriesClientWrapper({
|
export function SeriesClientWrapper({
|
||||||
children,
|
children,
|
||||||
|
seriesId,
|
||||||
|
currentPage,
|
||||||
|
unreadOnly,
|
||||||
|
pageSize,
|
||||||
}: SeriesClientWrapperProps) {
|
}: SeriesClientWrapperProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
@@ -25,14 +29,30 @@ export function SeriesClientWrapper({
|
|||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
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();
|
router.refresh();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch {
|
} catch {
|
||||||
return { success: false, error: "Error refreshing series" };
|
return { success: false, error: "Error refreshing series" };
|
||||||
} finally {
|
} finally {
|
||||||
// Petit délai pour laisser le temps au serveur de revalider
|
setIsRefreshing(false);
|
||||||
setTimeout(() => setIsRefreshing(false), 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,10 +72,7 @@ export function SeriesClientWrapper({
|
|||||||
canRefresh={pullToRefresh.canRefresh}
|
canRefresh={pullToRefresh.canRefresh}
|
||||||
isHiding={pullToRefresh.isHiding}
|
isHiding={pullToRefresh.isHiding}
|
||||||
/>
|
/>
|
||||||
<RefreshProvider refreshSeries={handleRefresh}>
|
<RefreshProvider refreshSeries={handleRefresh}>{children}</RefreshProvider>
|
||||||
{children}
|
|
||||||
</RefreshProvider>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -311,14 +311,15 @@ function BookDownloadCard({ book, status, onDelete, onRetry }: BookDownloadCardP
|
|||||||
return (
|
return (
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="relative w-12 aspect-[2/3] bg-muted/80 backdrop-blur-md rounded overflow-hidden">
|
<div className="relative w-16 aspect-[2/3] bg-muted rounded overflow-hidden flex-shrink-0">
|
||||||
<Image
|
<Image
|
||||||
src={`/api/komga/images/books/${book.id}/thumbnail`}
|
src={`/api/komga/images/books/${book.id}/thumbnail`}
|
||||||
alt={t("books.coverAlt", { title: book.metadata?.title })}
|
alt={t("books.coverAlt", { title: book.metadata?.title })}
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
fill
|
fill
|
||||||
sizes="48px"
|
sizes="64px"
|
||||||
priority={false}
|
priority={false}
|
||||||
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
|||||||
@@ -20,15 +20,25 @@ export function HomeClientWrapper({ children }: HomeClientWrapperProps) {
|
|||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
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();
|
router.refresh();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error }, "Erreur lors du rafraîchissement:");
|
logger.error({ err: error }, "Erreur lors du rafraîchissement:");
|
||||||
return { success: false, error: "Erreur lors du rafraîchissement de la page d'accueil" };
|
return { success: false, error: "Erreur lors du rafraîchissement de la page d'accueil" };
|
||||||
} finally {
|
} finally {
|
||||||
// Petit délai pour laisser le temps au serveur de revalider
|
setIsRefreshing(false);
|
||||||
setTimeout(() => setIsRefreshing(false), 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user