feat: add Service Worker for robust offline image caching

- Implement Service Worker to intercept and cache image requests
- Service Worker persists across hard reloads unlike Cache API alone
- Simplify useImageCache hook to work with Service Worker
- Images now work offline even after hard reload
- Service Worker handles transparent cache-first serving for images
This commit is contained in:
Gigi
2025-10-09 18:17:27 +01:00
parent b20a67d4d0
commit 1e8182d984
3 changed files with 102 additions and 68 deletions

View File

@@ -1,94 +1,46 @@
import { useState, useEffect } from 'react'
import { cacheImage, getCachedImage, loadCachedImage } from '../services/imageCacheService'
import { cacheImage, getCachedImage } from '../services/imageCacheService'
import { UserSettings } from '../services/settingsService'
/**
* Hook to cache and retrieve images using Cache API
* Hook to pre-cache images and return the URL for display
* With Service Worker active, images are automatically cached and served offline
* This hook ensures proactive caching for better offline experience
*
* @param imageUrl - The URL of the image to cache
* @param settings - User settings to determine if caching is enabled
* @returns The cached blob URL or the original URL
* @returns The image URL (Service Worker handles caching transparently)
*/
export function useImageCache(
imageUrl: string | undefined,
settings: UserSettings | undefined
): string | undefined {
const [cachedUrl, setCachedUrl] = useState<string | undefined>(imageUrl)
const [isLoading, setIsLoading] = useState(false)
const [displayUrl, setDisplayUrl] = useState<string | undefined>(imageUrl)
useEffect(() => {
if (!imageUrl) {
setCachedUrl(undefined)
setDisplayUrl(undefined)
return
}
// If caching is disabled, just use the original URL
const enableCache = settings?.enableImageCache ?? true // Default to enabled
if (!enableCache) {
setCachedUrl(imageUrl)
// Always show the original URL - Service Worker will serve from cache if available
setDisplayUrl(imageUrl)
// If caching is disabled, don't pre-cache
const enableCache = settings?.enableImageCache ?? true
if (!enableCache || !navigator.onLine) {
return
}
// Store imageUrl in local variable for closure
const urlToCache = imageUrl
const isOffline = !navigator.onLine
// When online: show original URL first for immediate display
// When offline: don't show anything until we load from cache
if (!isOffline) {
setCachedUrl(urlToCache)
}
// Try to load from cache asynchronously
loadCachedImage(urlToCache)
.then(blobUrl => {
if (blobUrl) {
console.log('📦 Using cached image:', urlToCache.substring(0, 50))
setCachedUrl(blobUrl)
} else if (!isOffline) {
// Not cached and online - cache it now
if (!isLoading) {
setIsLoading(true)
const maxSize = settings?.imageCacheSizeMB ?? 210
cacheImage(urlToCache, maxSize)
.then(newBlobUrl => {
// Only update if we got a blob URL back
if (newBlobUrl && newBlobUrl.startsWith('blob:')) {
setCachedUrl(newBlobUrl)
}
})
.catch(err => {
console.error('Failed to cache image:', err)
// Keep using original URL on error
})
.finally(() => {
setIsLoading(false)
})
}
} else {
// Offline and not cached - no image available
console.warn('⚠️ Image not available offline:', urlToCache.substring(0, 50))
setCachedUrl(undefined)
}
})
.catch(err => {
console.error('Failed to load cached image:', err)
// If online, fall back to original URL
if (!isOffline) {
setCachedUrl(urlToCache)
}
})
// Cleanup: revoke blob URLs when component unmounts or URL changes
return () => {
if (cachedUrl && cachedUrl.startsWith('blob:')) {
URL.revokeObjectURL(cachedUrl)
}
}
// Pre-cache the image for offline availability
// Service Worker will handle serving it, but we ensure it's cached
const maxSize = settings?.imageCacheSizeMB ?? 210
cacheImage(imageUrl, maxSize).catch(err => {
console.warn('Failed to pre-cache image:', err)
})
}, [imageUrl, settings?.enableImageCache, settings?.imageCacheSizeMB])
return cachedUrl
return displayUrl
}
/**

View File

@@ -3,6 +3,32 @@ import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
// Register Service Worker for offline image caching
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/sw.js')
.then(registration => {
console.log('✅ Service Worker registered:', registration.scope)
// Update service worker when a new version is available
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing
if (newWorker) {
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'activated') {
console.log('🔄 Service Worker updated, page may need reload')
}
})
}
})
})
.catch(error => {
console.error('❌ Service Worker registration failed:', error)
})
})
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />