|  |  |  | @@ -15,22 +15,34 @@ export class CacheManager { | 
		
	
		
			
				|  |  |  |  |     this._setupCache(); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |   /** | 
		
	
		
			
				|  |  |  |  |    * Sets up the service worker's fetch event to intercept and cache responses. | 
		
	
		
			
				|  |  |  |  |    */ | 
		
	
		
			
				|  |  |  |  |   private _setupCache = () => { | 
		
	
		
			
				|  |  |  |  |     // Create a matching request. For internal requests, reuse the original; for external requests, create one with CORS settings. | 
		
	
		
			
				|  |  |  |  |     const createMatchRequest = (requestArg: Request): Request => { | 
		
	
		
			
				|  |  |  |  |       // Create a matchRequest based on whether the request is internal or external. | 
		
	
		
			
				|  |  |  |  |       let matchRequest: Request; | 
		
	
		
			
				|  |  |  |  |       if (requestArg.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin)) { | 
		
	
		
			
				|  |  |  |  |         // Internal request; use the original. | 
		
	
		
			
				|  |  |  |  |         matchRequest = requestArg; | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         // External request; create a new Request with appropriate CORS settings. | 
		
	
		
			
				|  |  |  |  |         matchRequest = new Request(requestArg.url, { | 
		
	
		
			
				|  |  |  |  |           method: requestArg.method, | 
		
	
		
			
				|  |  |  |  |           headers: requestArg.headers, | 
		
	
		
			
				|  |  |  |  |           mode: 'cors', | 
		
	
		
			
				|  |  |  |  |           credentials: 'same-origin', | 
		
	
		
			
				|  |  |  |  |           redirect: 'follow' | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |       try { | 
		
	
		
			
				|  |  |  |  |         if ( | 
		
	
		
			
				|  |  |  |  |           requestArg.url.startsWith( | 
		
	
		
			
				|  |  |  |  |             this.losslessServiceWorkerRef.serviceWindowRef.location.origin | 
		
	
		
			
				|  |  |  |  |           ) | 
		
	
		
			
				|  |  |  |  |         ) { | 
		
	
		
			
				|  |  |  |  |           // Internal request | 
		
	
		
			
				|  |  |  |  |           matchRequest = requestArg; | 
		
	
		
			
				|  |  |  |  |         } else { | 
		
	
		
			
				|  |  |  |  |           // External request: create a new Request with appropriate CORS settings. | 
		
	
		
			
				|  |  |  |  |           matchRequest = new Request(requestArg.url, { | 
		
	
		
			
				|  |  |  |  |             method: requestArg.method, | 
		
	
		
			
				|  |  |  |  |             headers: requestArg.headers, | 
		
	
		
			
				|  |  |  |  |             mode: 'cors', | 
		
	
		
			
				|  |  |  |  |             credentials: 'same-origin', | 
		
	
		
			
				|  |  |  |  |             redirect: 'follow' | 
		
	
		
			
				|  |  |  |  |           }); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } catch (err) { | 
		
	
		
			
				|  |  |  |  |         logger.log('error', `Error creating match request for ${requestArg.url}: ${err}`); | 
		
	
		
			
				|  |  |  |  |         throw err; | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       return matchRequest; | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
	
		
			
				
					
					|  |  |  | @@ -39,192 +51,208 @@ export class CacheManager { | 
		
	
		
			
				|  |  |  |  |      * Creates a 500 error response. | 
		
	
		
			
				|  |  |  |  |      */ | 
		
	
		
			
				|  |  |  |  |     const create500Response = async (requestArg: Request, responseArg: Response): Promise<Response> => { | 
		
	
		
			
				|  |  |  |  |       return new Response( | 
		
	
		
			
				|  |  |  |  |         ` | 
		
	
		
			
				|  |  |  |  |         <html> | 
		
	
		
			
				|  |  |  |  |           <head> | 
		
	
		
			
				|  |  |  |  |             <style> | 
		
	
		
			
				|  |  |  |  |               .note { | 
		
	
		
			
				|  |  |  |  |                 padding: 10px; | 
		
	
		
			
				|  |  |  |  |                 color: #fff; | 
		
	
		
			
				|  |  |  |  |                 background: #000; | 
		
	
		
			
				|  |  |  |  |                 border-bottom: 1px solid #e4002b; | 
		
	
		
			
				|  |  |  |  |                 margin-bottom: 20px; | 
		
	
		
			
				|  |  |  |  |               } | 
		
	
		
			
				|  |  |  |  |             </style> | 
		
	
		
			
				|  |  |  |  |           </head> | 
		
	
		
			
				|  |  |  |  |           <body> | 
		
	
		
			
				|  |  |  |  |             <div class="note"> | 
		
	
		
			
				|  |  |  |  |               <strong>serviceworker running, but status 500</strong><br> | 
		
	
		
			
				|  |  |  |  |             </div> | 
		
	
		
			
				|  |  |  |  |             serviceworker is unable to fetch this request<br> | 
		
	
		
			
				|  |  |  |  |             Here is some info about the request/response pair:<br> | 
		
	
		
			
				|  |  |  |  |             <br> | 
		
	
		
			
				|  |  |  |  |             requestUrl: ${requestArg.url}<br> | 
		
	
		
			
				|  |  |  |  |             responseType: ${responseArg.type}<br> | 
		
	
		
			
				|  |  |  |  |             responseBody: ${await responseArg.clone().text()}<br> | 
		
	
		
			
				|  |  |  |  |           </body> | 
		
	
		
			
				|  |  |  |  |         </html> | 
		
	
		
			
				|  |  |  |  |       `, | 
		
	
		
			
				|  |  |  |  |         { | 
		
	
		
			
				|  |  |  |  |           headers: { | 
		
	
		
			
				|  |  |  |  |             "Content-Type": "text/html" | 
		
	
		
			
				|  |  |  |  |           }, | 
		
	
		
			
				|  |  |  |  |           status: 500 | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       ); | 
		
	
		
			
				|  |  |  |  |       try { | 
		
	
		
			
				|  |  |  |  |         const responseText = await responseArg.clone().text(); | 
		
	
		
			
				|  |  |  |  |         return new Response( | 
		
	
		
			
				|  |  |  |  |           ` | 
		
	
		
			
				|  |  |  |  |           <html> | 
		
	
		
			
				|  |  |  |  |             <head> | 
		
	
		
			
				|  |  |  |  |               <style> | 
		
	
		
			
				|  |  |  |  |                 .note { | 
		
	
		
			
				|  |  |  |  |                   padding: 10px; | 
		
	
		
			
				|  |  |  |  |                   color: #fff; | 
		
	
		
			
				|  |  |  |  |                   background: #000; | 
		
	
		
			
				|  |  |  |  |                   border-bottom: 1px solid #e4002b; | 
		
	
		
			
				|  |  |  |  |                   margin-bottom: 20px; | 
		
	
		
			
				|  |  |  |  |                 } | 
		
	
		
			
				|  |  |  |  |               </style> | 
		
	
		
			
				|  |  |  |  |             </head> | 
		
	
		
			
				|  |  |  |  |             <body> | 
		
	
		
			
				|  |  |  |  |               <div class="note"> | 
		
	
		
			
				|  |  |  |  |                 <strong>ServiceWorker error 500</strong><br> | 
		
	
		
			
				|  |  |  |  |               </div> | 
		
	
		
			
				|  |  |  |  |               ServiceWorker is unable to fetch this request.<br> | 
		
	
		
			
				|  |  |  |  |               <br> | 
		
	
		
			
				|  |  |  |  |               <strong>Request URL:</strong> ${requestArg.url}<br> | 
		
	
		
			
				|  |  |  |  |               <strong>Response Type:</strong> ${responseArg.type}<br> | 
		
	
		
			
				|  |  |  |  |               <strong>Response Body:</strong> ${responseText}<br> | 
		
	
		
			
				|  |  |  |  |             </body> | 
		
	
		
			
				|  |  |  |  |           </html> | 
		
	
		
			
				|  |  |  |  |         `, | 
		
	
		
			
				|  |  |  |  |           { | 
		
	
		
			
				|  |  |  |  |             headers: { 'Content-Type': 'text/html' }, | 
		
	
		
			
				|  |  |  |  |             status: 500 | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         ); | 
		
	
		
			
				|  |  |  |  |       } catch (err) { | 
		
	
		
			
				|  |  |  |  |         logger.log('error', `Error creating 500 response for ${requestArg.url}: ${err}`); | 
		
	
		
			
				|  |  |  |  |         return new Response('Internal error', { status: 500 }); | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     // Listen for fetch events. | 
		
	
		
			
				|  |  |  |  |     // Listen for fetch events on the service worker's controlled window. | 
		
	
		
			
				|  |  |  |  |     this.losslessServiceWorkerRef.serviceWindowRef.addEventListener('fetch', async (fetchEventArg: any) => { | 
		
	
		
			
				|  |  |  |  |       // Block specific scopes. | 
		
	
		
			
				|  |  |  |  |       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.') | 
		
	
		
			
				|  |  |  |  |       ) { | 
		
	
		
			
				|  |  |  |  |         logger.log('note', `serviceworker not active for ${parsedUrl.toString()}`); | 
		
	
		
			
				|  |  |  |  |         return; | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       try { | 
		
	
		
			
				|  |  |  |  |         const originalRequest: Request = fetchEventArg.request; | 
		
	
		
			
				|  |  |  |  |         const parsedUrl = new URL(originalRequest.url); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |       // Create a deferred response. | 
		
	
		
			
				|  |  |  |  |       const done = plugins.smartpromise.defer<Response>(); | 
		
	
		
			
				|  |  |  |  |       fetchEventArg.respondWith(done.promise); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |       if ( | 
		
	
		
			
				|  |  |  |  |         (originalRequest.method === 'GET' && | 
		
	
		
			
				|  |  |  |  |          (originalRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin) && | 
		
	
		
			
				|  |  |  |  |           !originalRequest.url.includes('/api/') && | 
		
	
		
			
				|  |  |  |  |           !originalRequest.url.includes('smartserve/reloadcheck'))) || | 
		
	
		
			
				|  |  |  |  |         originalRequest.url.includes('https://assetbroker.') || | 
		
	
		
			
				|  |  |  |  |         originalRequest.url.includes('https://unpkg.com') || | 
		
	
		
			
				|  |  |  |  |         originalRequest.url.includes('https://fonts.googleapis.com') || | 
		
	
		
			
				|  |  |  |  |         originalRequest.url.includes('https://fonts.gstatic.com') | 
		
	
		
			
				|  |  |  |  |       ) { | 
		
	
		
			
				|  |  |  |  |         // Check for updates asynchronously. | 
		
	
		
			
				|  |  |  |  |         this.losslessServiceWorkerRef.updateManager.checkUpdate(this); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Try to serve from cache. | 
		
	
		
			
				|  |  |  |  |         const matchRequest = createMatchRequest(originalRequest); | 
		
	
		
			
				|  |  |  |  |         const cachedResponse = await caches.match(matchRequest); | 
		
	
		
			
				|  |  |  |  |         if (cachedResponse) { | 
		
	
		
			
				|  |  |  |  |           logger.log('ok', `CACHED: found cached response for ${matchRequest.url}`); | 
		
	
		
			
				|  |  |  |  |           done.resolve(cachedResponse); | 
		
	
		
			
				|  |  |  |  |         // Block requests that we don't want the service worker to handle. | 
		
	
		
			
				|  |  |  |  |         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.') | 
		
	
		
			
				|  |  |  |  |         ) { | 
		
	
		
			
				|  |  |  |  |           logger.log('note', `ServiceWorker not active for ${parsedUrl.toString()}`); | 
		
	
		
			
				|  |  |  |  |           return; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // In case there is no cached response, fetch from the network. | 
		
	
		
			
				|  |  |  |  |         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)); | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |         // Create a deferred promise for the fetch event's response. | 
		
	
		
			
				|  |  |  |  |         const done = plugins.smartpromise.defer<Response>(); | 
		
	
		
			
				|  |  |  |  |         fetchEventArg.respondWith(done.promise); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // If the response status is an error or the response is opaque, do not cache it. | 
		
	
		
			
				|  |  |  |  |         if (newResponse.status > 299 || newResponse.type === 'opaque' || (newResponse.headers.get('access-control-allow-origin') === null && !matchRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin))) { | 
		
	
		
			
				|  |  |  |  |           logger.log( | 
		
	
		
			
				|  |  |  |  |             'error', | 
		
	
		
			
				|  |  |  |  |             `NOTCACHED: not caching response for ${matchRequest.url} due to status ${newResponse.status} and type ${newResponse.type}` | 
		
	
		
			
				|  |  |  |  |           ); | 
		
	
		
			
				|  |  |  |  |           // Simply return the network response without caching. | 
		
	
		
			
				|  |  |  |  |           done.resolve(newResponse); | 
		
	
		
			
				|  |  |  |  |           return; | 
		
	
		
			
				|  |  |  |  |         } else { | 
		
	
		
			
				|  |  |  |  |           // Cache the response. | 
		
	
		
			
				|  |  |  |  |           const cache = await caches.open(this.usedCacheNames.runtimeCacheName); | 
		
	
		
			
				|  |  |  |  |           const responseToPutToCache = newResponse.clone(); | 
		
	
		
			
				|  |  |  |  |           const headers = new Headers(); | 
		
	
		
			
				|  |  |  |  |           responseToPutToCache.headers.forEach((value, key) => { | 
		
	
		
			
				|  |  |  |  |             // Preserve all headers except caching-related ones. | 
		
	
		
			
				|  |  |  |  |             if (![ | 
		
	
		
			
				|  |  |  |  |               'Cache-Control', | 
		
	
		
			
				|  |  |  |  |               'cache-control', | 
		
	
		
			
				|  |  |  |  |               'Expires', | 
		
	
		
			
				|  |  |  |  |               'expires', | 
		
	
		
			
				|  |  |  |  |               'Pragma', | 
		
	
		
			
				|  |  |  |  |               'pragma' | 
		
	
		
			
				|  |  |  |  |             ].includes(key)) { | 
		
	
		
			
				|  |  |  |  |               headers.set(key, value); | 
		
	
		
			
				|  |  |  |  |         // Determine whether this request should be cached. | 
		
	
		
			
				|  |  |  |  |         if ( | 
		
	
		
			
				|  |  |  |  |           (originalRequest.method === 'GET' && | 
		
	
		
			
				|  |  |  |  |             originalRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin) && | 
		
	
		
			
				|  |  |  |  |             !originalRequest.url.includes('/api/') && | 
		
	
		
			
				|  |  |  |  |             !originalRequest.url.includes('smartserve/reloadcheck')) || | 
		
	
		
			
				|  |  |  |  |           originalRequest.url.includes('https://assetbroker.') || | 
		
	
		
			
				|  |  |  |  |           originalRequest.url.includes('https://unpkg.com') || | 
		
	
		
			
				|  |  |  |  |           originalRequest.url.includes('https://fonts.googleapis.com') || | 
		
	
		
			
				|  |  |  |  |           originalRequest.url.includes('https://fonts.gstatic.com') | 
		
	
		
			
				|  |  |  |  |         ) { | 
		
	
		
			
				|  |  |  |  |           // Kick off an asynchronous update check. | 
		
	
		
			
				|  |  |  |  |           this.losslessServiceWorkerRef.updateManager.checkUpdate(this); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |           const matchRequest = createMatchRequest(originalRequest); | 
		
	
		
			
				|  |  |  |  |           const cachedResponse = await caches.match(matchRequest); | 
		
	
		
			
				|  |  |  |  |           if (cachedResponse) { | 
		
	
		
			
				|  |  |  |  |             logger.log('ok', `CACHED: Found cached response for ${matchRequest.url}`); | 
		
	
		
			
				|  |  |  |  |             done.resolve(cachedResponse); | 
		
	
		
			
				|  |  |  |  |             return; | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |           logger.log('info', `NOTYETCACHED: Trying to cache ${matchRequest.url}`); | 
		
	
		
			
				|  |  |  |  |           let newResponse: Response; | 
		
	
		
			
				|  |  |  |  |           try { | 
		
	
		
			
				|  |  |  |  |             newResponse = await fetch(matchRequest); | 
		
	
		
			
				|  |  |  |  |           } catch (err: any) { | 
		
	
		
			
				|  |  |  |  |             logger.log('error', `Fetch error for ${matchRequest.url}: ${err}`); | 
		
	
		
			
				|  |  |  |  |             newResponse = await create500Response(matchRequest, new Response(err.message)); | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |           // Check if the response should be cached. In this version, if the response status is >299 or the response is opaque, we do not cache. | 
		
	
		
			
				|  |  |  |  |           if (newResponse.status > 299 || newResponse.type === 'opaque') { | 
		
	
		
			
				|  |  |  |  |             logger.log( | 
		
	
		
			
				|  |  |  |  |               'error', | 
		
	
		
			
				|  |  |  |  |               `NOTCACHED: Can't cache response for ${matchRequest.url} (status: ${newResponse.status}, type: ${newResponse.type})` | 
		
	
		
			
				|  |  |  |  |             ); | 
		
	
		
			
				|  |  |  |  |             // Optionally, you can force a 500 response so errors are clearly visible. | 
		
	
		
			
				|  |  |  |  |             done.resolve(await create500Response(matchRequest, newResponse)); | 
		
	
		
			
				|  |  |  |  |           } else { | 
		
	
		
			
				|  |  |  |  |             try { | 
		
	
		
			
				|  |  |  |  |               const cache = await caches.open(this.usedCacheNames.runtimeCacheName); | 
		
	
		
			
				|  |  |  |  |               const responseToPutToCache = newResponse.clone(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |               // Create new headers preserving all except caching-related ones. | 
		
	
		
			
				|  |  |  |  |               const headers = new Headers(); | 
		
	
		
			
				|  |  |  |  |               responseToPutToCache.headers.forEach((value, key) => { | 
		
	
		
			
				|  |  |  |  |                 if (!['Cache-Control', 'cache-control', 'Expires', 'expires', 'Pragma', 'pragma'].includes(key)) { | 
		
	
		
			
				|  |  |  |  |                   headers.set(key, value); | 
		
	
		
			
				|  |  |  |  |                 } | 
		
	
		
			
				|  |  |  |  |               }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |               // Ensure that CORS-related headers are present. | 
		
	
		
			
				|  |  |  |  |               if (!headers.has('Access-Control-Allow-Origin')) { | 
		
	
		
			
				|  |  |  |  |                 headers.set('Access-Control-Allow-Origin', '*'); | 
		
	
		
			
				|  |  |  |  |               } | 
		
	
		
			
				|  |  |  |  |               if (!headers.has('Access-Control-Allow-Methods')) { | 
		
	
		
			
				|  |  |  |  |                 headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); | 
		
	
		
			
				|  |  |  |  |               } | 
		
	
		
			
				|  |  |  |  |               if (!headers.has('Access-Control-Allow-Headers')) { | 
		
	
		
			
				|  |  |  |  |                 headers.set('Access-Control-Allow-Headers', 'Content-Type'); | 
		
	
		
			
				|  |  |  |  |               } | 
		
	
		
			
				|  |  |  |  |               // Prevent browser caching while allowing ServiceWorker 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'); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |               // IMPORTANT: Read the full response body as a blob to avoid issues (e.g., Safari locked streams). | 
		
	
		
			
				|  |  |  |  |               const bodyBlob = await responseToPutToCache.blob(); | 
		
	
		
			
				|  |  |  |  |               const newCachedResponse = new Response(bodyBlob, { | 
		
	
		
			
				|  |  |  |  |                 status: responseToPutToCache.status, | 
		
	
		
			
				|  |  |  |  |                 statusText: responseToPutToCache.statusText, | 
		
	
		
			
				|  |  |  |  |                 headers | 
		
	
		
			
				|  |  |  |  |               }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |               await cache.put(matchRequest, newCachedResponse); | 
		
	
		
			
				|  |  |  |  |               logger.log('ok', `NOWCACHED: Cached response for ${matchRequest.url} for subsequent requests!`); | 
		
	
		
			
				|  |  |  |  |               done.resolve(newResponse); | 
		
	
		
			
				|  |  |  |  |             } catch (err) { | 
		
	
		
			
				|  |  |  |  |               logger.log('error', `Error caching response for ${matchRequest.url}: ${err}`); | 
		
	
		
			
				|  |  |  |  |               done.resolve(await create500Response(matchRequest, newResponse)); | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |           }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |           // Ensure CORS headers are present. | 
		
	
		
			
				|  |  |  |  |           if (!headers.has('Access-Control-Allow-Origin')) { | 
		
	
		
			
				|  |  |  |  |             headers.set('Access-Control-Allow-Origin', '*'); | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |           headers.set('Vary', 'Origin'); | 
		
	
		
			
				|  |  |  |  |           if (!headers.has('Access-Control-Expose-Headers')) { | 
		
	
		
			
				|  |  |  |  |             headers.set('Access-Control-Expose-Headers', '*') | 
		
	
		
			
				|  |  |  |  |         } else { | 
		
	
		
			
				|  |  |  |  |           // For requests not intended for caching, simply fetch from the origin. | 
		
	
		
			
				|  |  |  |  |           logger.log('ok', `NOTCACHED: Not caching ${originalRequest.url}. Fetching from origin...`); | 
		
	
		
			
				|  |  |  |  |           try { | 
		
	
		
			
				|  |  |  |  |             const originResponse = await fetch(originalRequest); | 
		
	
		
			
				|  |  |  |  |             done.resolve(originResponse); | 
		
	
		
			
				|  |  |  |  |           } catch (err: any) { | 
		
	
		
			
				|  |  |  |  |             logger.log('error', `Fetch error for ${originalRequest.url}: ${err}`); | 
		
	
		
			
				|  |  |  |  |             done.resolve(await create500Response(originalRequest, new Response(err.message))); | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |           if (!headers.has('Access-Control-Allow-Methods')) { | 
		
	
		
			
				|  |  |  |  |             headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |           if (!headers.has('Access-Control-Allow-Headers')) { | 
		
	
		
			
				|  |  |  |  |             headers.set('Access-Control-Allow-Headers', 'Content-Type'); | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |           // 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, | 
		
	
		
			
				|  |  |  |  |             headers | 
		
	
		
			
				|  |  |  |  |           })); | 
		
	
		
			
				|  |  |  |  |           logger.log('ok', `NOWCACHED: cached response for ${matchRequest.url} for subsequent requests!`); | 
		
	
		
			
				|  |  |  |  |           done.resolve(newResponse); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         // For remote requests not intended for caching, fetch directly from the origin. | 
		
	
		
			
				|  |  |  |  |         logger.log( | 
		
	
		
			
				|  |  |  |  |           'ok', | 
		
	
		
			
				|  |  |  |  |           `NOTCACHED: not caching any responses for ${originalRequest.url}. Fetching from origin now...` | 
		
	
		
			
				|  |  |  |  |         ); | 
		
	
		
			
				|  |  |  |  |         done.resolve( | 
		
	
		
			
				|  |  |  |  |           await fetch(originalRequest).catch(async err => { | 
		
	
		
			
				|  |  |  |  |             return await create500Response(originalRequest, new Response(err.message)); | 
		
	
		
			
				|  |  |  |  |           }) | 
		
	
		
			
				|  |  |  |  |         ); | 
		
	
		
			
				|  |  |  |  |       } catch (err) { | 
		
	
		
			
				|  |  |  |  |         logger.log('error', `Unhandled fetch event error: ${err}`); | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |   /** | 
		
	
		
			
				|  |  |  |  |    * Cleans all caches. | 
		
	
		
			
				|  |  |  |  |    * Should only be run when a new service worker is activated. | 
		
	
		
			
				|  |  |  |  |    * Should only be run when a new ServiceWorker is activated. | 
		
	
		
			
				|  |  |  |  |    */ | 
		
	
		
			
				|  |  |  |  |   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; | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |     await Promise.all(deletePromises); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   public cleanCaches = async (reasonArg = 'no reason given'): Promise<void> => { | 
		
	
		
			
				|  |  |  |  |     try { | 
		
	
		
			
				|  |  |  |  |       logger.log('info', `MAJOR CACHEEVENT: Cleaning caches now! Reason: ${reasonArg}`); | 
		
	
		
			
				|  |  |  |  |       const cacheNames = await caches.keys(); | 
		
	
		
			
				|  |  |  |  |       const deletePromises = cacheNames.map((cacheToDelete) => | 
		
	
		
			
				|  |  |  |  |         caches.delete(cacheToDelete).then(() => { | 
		
	
		
			
				|  |  |  |  |           logger.log('ok', `Deleted cache ${cacheToDelete}`); | 
		
	
		
			
				|  |  |  |  |         }) | 
		
	
		
			
				|  |  |  |  |       ); | 
		
	
		
			
				|  |  |  |  |       await Promise.all(deletePromises); | 
		
	
		
			
				|  |  |  |  |     } catch (err) { | 
		
	
		
			
				|  |  |  |  |       logger.log('error', `Error cleaning caches: ${err}`); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |   /** | 
		
	
		
			
				|  |  |  |  |    * Revalidates the runtime cache. | 
		
	
		
			
				|  |  |  |  |    * Revalidates the runtime cache by fetching fresh responses and updating the cache. | 
		
	
		
			
				|  |  |  |  |    */ | 
		
	
		
			
				|  |  |  |  |   public async revalidateCache() { | 
		
	
		
			
				|  |  |  |  |     const runtimeCache = await caches.open(this.usedCacheNames.runtimeCacheName); | 
		
	
		
			
				|  |  |  |  |     const cacheKeys = await runtimeCache.keys(); | 
		
	
		
			
				|  |  |  |  |     for (const requestArg of cacheKeys) { | 
		
	
		
			
				|  |  |  |  |       // 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) { | 
		
	
		
			
				|  |  |  |  |         await runtimeCache.delete(requestArg); | 
		
	
		
			
				|  |  |  |  |         await runtimeCache.put(requestArg, response); | 
		
	
		
			
				|  |  |  |  |   public async revalidateCache(): Promise<void> { | 
		
	
		
			
				|  |  |  |  |     try { | 
		
	
		
			
				|  |  |  |  |       const runtimeCache = await caches.open(this.usedCacheNames.runtimeCacheName); | 
		
	
		
			
				|  |  |  |  |       const cacheKeys = await runtimeCache.keys(); | 
		
	
		
			
				|  |  |  |  |       for (const requestArg of cacheKeys) { | 
		
	
		
			
				|  |  |  |  |         try { | 
		
	
		
			
				|  |  |  |  |           const clonedRequest = requestArg.clone(); | 
		
	
		
			
				|  |  |  |  |           const response = await plugins.smartpromise.timeoutWrap(fetch(clonedRequest), 5000); | 
		
	
		
			
				|  |  |  |  |           if (response && response.status >= 200 && response.status < 300) { | 
		
	
		
			
				|  |  |  |  |             await runtimeCache.delete(requestArg); | 
		
	
		
			
				|  |  |  |  |             await runtimeCache.put(requestArg, response); | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         } catch (err) { | 
		
	
		
			
				|  |  |  |  |           logger.log('error', `Error revalidating cache for ${requestArg.url}: ${err}`); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } catch (err) { | 
		
	
		
			
				|  |  |  |  |       logger.log('error', `Error revalidating runtime cache: ${err}`); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } |