diff --git a/src/features/settings/screens/ImagesSettings.tsx b/src/features/settings/screens/ImagesSettings.tsx index 14d81307..cc3255b6 100644 --- a/src/features/settings/screens/ImagesSettings.tsx +++ b/src/features/settings/screens/ImagesSettings.tsx @@ -28,7 +28,7 @@ export const ImagesSettings = () => { const clearCache = async () => { try { - await Promise.all([triggerClearServerCache(), ImageCache.clear()]); + await Promise.all([triggerClearServerCache(), ImageCache.clearAll()]); makeToast(t`Cleared the cache`, 'success'); } catch (e) { makeToast(t`Could not clear the cache`, 'error', getErrorMessage(e)); diff --git a/src/lib/service-worker/ImageCache.ts b/src/lib/service-worker/ImageCache.ts index 5aa03ccb..65213cc8 100644 --- a/src/lib/service-worker/ImageCache.ts +++ b/src/lib/service-worker/ImageCache.ts @@ -6,12 +6,41 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -const CACHE_NAME = 'image-cache'; +import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; + +// !!! IMPORTANT !!! - Update along with vite.config.ts workbox config +export enum ImageCacheKey { + OTHER = 'image-cache-other', + EXTENSION_ICONS = 'image-cache-extension-icons', + MANGA_THUMBNAILS = 'image-cache-manga-thumbnails', + CHAPTER_PAGES = 'image-cache-chapter-pages', +} + +const IMAGE_CACHE_KEYS = Object.values(ImageCacheKey); + +caches.delete('image-cache').catch(defaultPromiseErrorHandler('ImageCache: delete removed "image-cache"')); export class ImageCache { - static async has(url: string): Promise { + // !!! IMPORTANT !!! - Update along with vite.config.ts workbox config + static getKeyFor(url: string): ImageCacheKey { + if (url.match(/\/chapter\/[0-9]+\/page\/[0-9]+/g)) { + return ImageCacheKey.CHAPTER_PAGES; + } + + if (url.match(/\/manga\/[0-9]+\/thumbnail/g)) { + return ImageCacheKey.MANGA_THUMBNAILS; + } + + if (url.includes('/extension/icon/')) { + return ImageCacheKey.EXTENSION_ICONS; + } + + return ImageCacheKey.OTHER; + } + + static async has(url: string, key: ImageCacheKey = this.getKeyFor(url)): Promise { try { - const cache = await caches.open(CACHE_NAME); + const cache = await caches.open(key); const response = await cache.match(url, { ignoreVary: true }); return response !== undefined; @@ -20,9 +49,9 @@ export class ImageCache { } } - static async get(url: string): Promise { + static async get(url: string, key: ImageCacheKey = this.getKeyFor(url)): Promise { try { - const cache = await caches.open(CACHE_NAME); + const cache = await caches.open(key); const response = await cache.match(url); return response || null; @@ -31,10 +60,14 @@ export class ImageCache { } } - static async clear(): Promise { - const cache = await caches.open(CACHE_NAME); - const keys = await cache.keys(); + static async clear(key: ImageCacheKey): Promise { + const cache = await caches.open(key); + const imageKeys = await cache.keys(); - await Promise.all(keys.map((key) => cache.delete(key))); + await Promise.all(imageKeys.map((imageKey) => cache.delete(imageKey))); + } + + static async clearAll(): Promise { + IMAGE_CACHE_KEYS.forEach(ImageCache.clear); } } diff --git a/vite.config.ts b/vite.config.ts index 122ec57c..a88b2164 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -61,23 +61,67 @@ export default defineConfig(({ command }) => ({ globPatterns: [], runtimeCaching: [ { - urlPattern: ({ request, url }) => { - if (request.destination === 'image') { - return true; - } - + urlPattern: ({ url }) => { const { pathname } = url; - return ( - pathname.match(/\/chapter\/[0-9]+\/page\/[0-9]+/g) || - pathname.match(/\/manga\/[0-9]+\/thumbnail/g) || - pathname.includes('/extension/icon/') - ); + return pathname.match(/\/chapter\/[0-9]+\/page\/[0-9]+/g); }, handler: 'CacheFirst', options: { - cacheName: 'image-cache', + // !!! IMPORTANT !!! - Update along with ImageCache.ts + cacheName: 'image-cache-chapter-pages', expiration: { - maxEntries: 10000, + maxEntries: 2500, + purgeOnQuotaError: true, + }, + cacheableResponse: { + statuses: [0, 200], + }, + }, + }, + { + urlPattern: ({ url }) => { + const { pathname } = url; + return pathname.match(/\/manga\/[0-9]+\/thumbnail/g); + }, + handler: 'CacheFirst', + options: { + // !!! IMPORTANT !!! - Update along with ImageCache.ts + cacheName: 'image-cache-manga-thumbnails', + expiration: { + maxEntries: 5000, + purgeOnQuotaError: true, + }, + cacheableResponse: { + statuses: [0, 200], + }, + }, + }, + { + urlPattern: ({ url }) => { + const { pathname } = url; + return pathname.includes('/extension/icon/'); + }, + handler: 'CacheFirst', + options: { + // !!! IMPORTANT !!! - Update along with ImageCache.ts + cacheName: 'image-cache-extension-icons', + expiration: { + maxEntries: 300, + purgeOnQuotaError: true, + }, + cacheableResponse: { + statuses: [0, 200], + }, + }, + }, + { + urlPattern: ({ request }) => request.destination === 'image', + handler: 'CacheFirst', + options: { + // !!! IMPORTANT !!! - Update along with ImageCache.ts + cacheName: 'image-cache-other', + expiration: { + maxEntries: 500, purgeOnQuotaError: true, }, cacheableResponse: {