fix(cachemanager): Improve cache management and error handling
This commit is contained in:
		| @@ -1,5 +1,14 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-02-04 - 3.0.60 - fix(cachemanager) | ||||
| Improve cache management and error handling | ||||
|  | ||||
| - Updated comments for clarity and consistency. | ||||
| - Enhanced error handling in `fetch` event listener. | ||||
| - Optimized cache key management and cleanup process. | ||||
| - Ensured CORS headers are set for cached responses. | ||||
| - Improved logging for caching operations. | ||||
|  | ||||
| ## 2025-02-03 - 3.0.59 - fix(serviceworker) | ||||
| Fixed CORS and Cache Control handling for Service Worker | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@api.global/typedserver', | ||||
|   version: '3.0.59', | ||||
|   version: '3.0.60', | ||||
|   description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.' | ||||
| } | ||||
|   | ||||
| @@ -17,13 +17,17 @@ export class CacheManager { | ||||
|  | ||||
|   private _setupCache = () => { | ||||
|     const createMatchRequest = (requestArg: Request) => { | ||||
|       // lets create a matchRequest | ||||
|       // Create a matchRequest. | ||||
|       let matchRequest: Request; | ||||
|       if (requestArg.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin)) { | ||||
|       if ( | ||||
|         requestArg.url.startsWith( | ||||
|           this.losslessServiceWorkerRef.serviceWindowRef.location.origin | ||||
|         ) | ||||
|       ) { | ||||
|         // internal request | ||||
|         matchRequest = requestArg; | ||||
|       } else { | ||||
|         // For external requests, create a new request with appropriate CORS settings | ||||
|         // For external requests, create a new Request with CORS settings. | ||||
|         matchRequest = new Request(requestArg.url, { | ||||
|           method: requestArg.method, | ||||
|           headers: requestArg.headers, | ||||
| @@ -34,9 +38,9 @@ export class CacheManager { | ||||
|       } | ||||
|       return matchRequest; | ||||
|     }; | ||||
|    | ||||
|  | ||||
|     /** | ||||
|      * creates a 500 response | ||||
|      * Creates a 500 response | ||||
|      */ | ||||
|     const create500Response = async (requestArg: Request, responseArg: Response) => { | ||||
|       return new Response( | ||||
| @@ -75,27 +79,27 @@ export class CacheManager { | ||||
|         } | ||||
|       ); | ||||
|     }; | ||||
|    | ||||
|  | ||||
|     // A list of local resources we always want to be cached. | ||||
|     this.losslessServiceWorkerRef.serviceWindowRef.addEventListener('fetch', async (fetchEventArg: any) => { | ||||
|       // Lets block scopes we don't want to be passing through the serviceworker | ||||
|       // Block scopes we don't want the serviceworker to handle. | ||||
|       const originalRequest: Request = fetchEventArg.request; | ||||
|       const parsedUrl = new URL(originalRequest.url); | ||||
|       if ( | ||||
|         parsedUrl.hostname.includes('paddle.com') | ||||
|         || parsedUrl.hostname.includes('paypal.com') | ||||
|         || parsedUrl.hostname.includes('reception.lossless.one') | ||||
|         || parsedUrl.pathname.startsWith('/socket.io') | ||||
|         || originalRequest.url.startsWith('https://umami.') | ||||
|         parsedUrl.hostname.includes('paddle.com') || | ||||
|         parsedUrl.hostname.includes('paypal.com') || | ||||
|         parsedUrl.hostname.includes('reception.lossless.one') || | ||||
|         parsedUrl.pathname.startsWith('/socket.io') || | ||||
|         originalRequest.url.startsWith('https://umami.') | ||||
|       ) { | ||||
|         logger.log('note',`serviceworker not active for ${parsedUrl.toString()}`); | ||||
|         logger.log('note', `serviceworker not active for ${parsedUrl.toString()}`); | ||||
|         return; | ||||
|       } | ||||
|        | ||||
|       // lets continue for the rest | ||||
|  | ||||
|       // Continue for the rest. | ||||
|       const done = plugins.smartpromise.defer<Response>(); | ||||
|       fetchEventArg.respondWith(done.promise); | ||||
|    | ||||
|  | ||||
|       if ( | ||||
|         (originalRequest.method === 'GET' && | ||||
|           (originalRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin) && | ||||
| @@ -106,12 +110,10 @@ export class CacheManager { | ||||
|         originalRequest.url.includes('https://fonts.googleapis.com') || | ||||
|         originalRequest.url.includes('https://fonts.gstatic.com') | ||||
|       ) { | ||||
|    | ||||
|         // lets see if things need to be updated | ||||
|         // not waiting here | ||||
|         // Check for updates asynchronously. | ||||
|         this.losslessServiceWorkerRef.updateManager.checkUpdate(this); | ||||
|    | ||||
|         // this code block is executed for local requests | ||||
|  | ||||
|         // Handle local or approved remote requests. | ||||
|         const matchRequest = createMatchRequest(originalRequest); | ||||
|         const cachedResponse = await caches.match(matchRequest); | ||||
|         if (cachedResponse) { | ||||
| @@ -119,15 +121,14 @@ export class CacheManager { | ||||
|           done.resolve(cachedResponse); | ||||
|           return; | ||||
|         } | ||||
|    | ||||
|         // in case there is no cached response | ||||
|  | ||||
|         // No cached response found; try to fetch and cache. | ||||
|         logger.log('info', `NOTYETCACHED: trying to cache ${matchRequest.url}`); | ||||
|         const newResponse: Response = await fetch(matchRequest).catch(async err => { | ||||
|           return await create500Response(matchRequest, new Response(err.message)); | ||||
|         }); | ||||
|    | ||||
|         // fill cache | ||||
|         // Put a copy of the response in the runtime cache. | ||||
|  | ||||
|         // If status > 299 or opaque response, don't cache. | ||||
|         if (newResponse.status > 299 || newResponse.type === 'opaque') { | ||||
|           logger.log( | ||||
|             'error', | ||||
| @@ -139,9 +140,10 @@ export class CacheManager { | ||||
|         } else { | ||||
|           const cache = await caches.open(this.usedCacheNames.runtimeCacheName); | ||||
|           const responseToPutToCache = newResponse.clone(); | ||||
|  | ||||
|           // Create new headers preserving all except caching-related headers. | ||||
|           const headers = new Headers(); | ||||
|           responseToPutToCache.headers.forEach((value, key) => { | ||||
|             // Preserve all headers except caching headers | ||||
|             if (![ | ||||
|               'Cache-Control', | ||||
|               'cache-control', | ||||
| @@ -153,8 +155,8 @@ export class CacheManager { | ||||
|               headers.set(key, value); | ||||
|             } | ||||
|           }); | ||||
|            | ||||
|           // Ensure CORS headers are present in cached response | ||||
|  | ||||
|           // Ensure CORS headers are present in cached response. | ||||
|           if (!headers.has('Access-Control-Allow-Origin')) { | ||||
|             headers.set('Access-Control-Allow-Origin', '*'); | ||||
|           } | ||||
| @@ -164,28 +166,29 @@ export class CacheManager { | ||||
|           if (!headers.has('Access-Control-Allow-Headers')) { | ||||
|             headers.set('Access-Control-Allow-Headers', 'Content-Type'); | ||||
|           } | ||||
|           // Prevent browser caching while allowing service worker caching | ||||
|           // Prevent browser caching while allowing service worker caching. | ||||
|           headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); | ||||
|           headers.set('Pragma', 'no-cache'); | ||||
|           headers.set('Expires', '0'); | ||||
|           headers.set('Surrogate-Control', 'no-store'); | ||||
|           await cache.put(matchRequest, new Response(responseToPutToCache.body, { | ||||
|             ...responseToPutToCache, | ||||
|  | ||||
|           // IMPORTANT: Read the entire response body as a blob so that | ||||
|           // Safari does not have issues with a locked stream when caching. | ||||
|           const bodyBlob = await responseToPutToCache.blob(); | ||||
|           const newCachedResponse = new Response(bodyBlob, { | ||||
|             status: responseToPutToCache.status, | ||||
|             statusText: responseToPutToCache.statusText, | ||||
|             headers | ||||
|           })); | ||||
|           logger.log( | ||||
|             'ok', | ||||
|             `NOWCACHED: cached response for ${matchRequest.url} for subsequent requests!` | ||||
|           ); | ||||
|           }); | ||||
|           await cache.put(matchRequest, newCachedResponse); | ||||
|           logger.log('ok', `NOWCACHED: cached response for ${matchRequest.url} for subsequent requests!`); | ||||
|           done.resolve(newResponse); | ||||
|         } | ||||
|       } else { | ||||
|         // this code block is executed for remote requests | ||||
|         // For remote requests that don't qualify for caching. | ||||
|         logger.log( | ||||
|           'ok', | ||||
|           `NOTCACHED: not caching any responses for ${ | ||||
|             originalRequest.url | ||||
|           }. Fetching from origin now...` | ||||
|           `NOTCACHED: not caching any responses for ${originalRequest.url}. Fetching from origin now...` | ||||
|         ); | ||||
|         done.resolve( | ||||
|           await fetch(originalRequest).catch(async err => { | ||||
| @@ -197,39 +200,35 @@ export class CacheManager { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * update caches | ||||
|    * @param reasonArg | ||||
|    */ | ||||
|  | ||||
|   /** | ||||
|    * cleans all caches | ||||
|    * should only be run when running a new service worker | ||||
|    * Cleans all caches. | ||||
|    * Should only be run when running a new service worker. | ||||
|    * @param reasonArg | ||||
|    */ | ||||
|   public cleanCaches = async (reasonArg = 'no reason given') => { | ||||
|     logger.log('info', `MAJOR CACHEEVENT: cleaning caches now! Reason: ${reasonArg}`); | ||||
|     const cacheNames = await caches.keys(); | ||||
|      | ||||
|  | ||||
|     const deletePromises = cacheNames.map(cacheToDelete => { | ||||
|         const deletePromise = caches.delete(cacheToDelete); | ||||
|         deletePromise.then(() => { | ||||
|           logger.log('ok', `Deleted cache ${cacheToDelete}`); | ||||
|         }); | ||||
|         return deletePromise; | ||||
|       const deletePromise = caches.delete(cacheToDelete); | ||||
|       deletePromise.then(() => { | ||||
|         logger.log('ok', `Deleted cache ${cacheToDelete}`); | ||||
|       }); | ||||
|       return deletePromise; | ||||
|     }); | ||||
|     await Promise.all(deletePromises); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * revalidate cache | ||||
|    * Revalidates the runtime cache. | ||||
|    */ | ||||
|   public async revalidateCache() { | ||||
|     const runtimeCache = await caches.open(this.usedCacheNames.runtimeCacheName); | ||||
|     const cacheKeys = await runtimeCache.keys(); | ||||
|     for (const requestArg of cacheKeys) { | ||||
|       // Get the cached response. | ||||
|       const cachedResponse = runtimeCache.match(requestArg); | ||||
|  | ||||
|       // lets get a new response for comparison | ||||
|       // Fetch a new response for comparison. | ||||
|       const clonedRequest = requestArg.clone(); | ||||
|       const response = await plugins.smartpromise.timeoutWrap(fetch(clonedRequest), 5000); // Increased timeout for better mobile compatibility | ||||
|       if (response && response.status >= 200 && response.status < 300) { | ||||
| @@ -238,4 +237,4 @@ export class CacheManager { | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user