From bbeafab1efca446c984eae037d78732c8e6fcc69 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 23 Aug 2024 01:45:36 -0400 Subject: [PATCH] Verify response is not html when pre-caching in install event --- src/sw.js | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/sw.js b/src/sw.js index 2ce1a0648..f9ae8dc17 100644 --- a/src/sw.js +++ b/src/sw.js @@ -105,11 +105,35 @@ const isNotMedia = req => { return !url.pathname.startsWith('/media/') } +const isSuccessful = (resp) => { + if (!resp.ok) { + return false + } + if ((new URL(resp.url)).pathname === '/index.html') { + // For index.html itself, there is no fallback possible. + return true + } + const type = resp.headers.get('Content-Type') + // Backend will revert to index.html if the file does not exist, so text/html for emojis and assets is a failure + return type && !type.includes('text/html') +} + self.addEventListener('install', async (event) => { if (shouldCache) { event.waitUntil((async () => { const cache = await caches.open(cacheKey) - await cache.addAll(cacheFiles) + await Promise.allSettled(cacheFiles.map(async (route) => { + // https://developer.mozilla.org/en-US/docs/Web/API/Cache/add + // originally we used addAll() but it will raise a problem in one edge case: + // when the file for the route is not found, backend will return index.html with code 200 + // but it's wrong, and it's cached, so we end up with a bad cache. + // this can happen when you refresh when you are in the process of upgrading + // the frontend. + const resp = await fetch(route) + if (isSuccessful(resp)) { + await cache.put(route, resp) + } + })) })()) } }) @@ -191,22 +215,17 @@ self.addEventListener('notificationclick', (event) => { self.addEventListener('fetch', (event) => { // Do not mess up with remote things const isSameOrigin = (new URL(event.request.url)).origin === self.location.origin - const isEmojiSuccessful = (resp) => { - const type = resp.headers.get('Content-Type') - // Backend will revert to index.html if the file does not exist, so text/html for emojis is a failure - return type && !type.includes('text/html') - } if (shouldCache && event.request.method === 'GET' && isSameOrigin && isNotMedia(event.request)) { event.respondWith((async () => { const r = await caches.match(event.request) - if (r && (isEmojiSuccessful(r) || !isEmoji(event.request))) { + if (r && (isSuccessful(r) || !isEmoji(event.request))) { return r } try { const response = await fetch(event.request) - if (response.ok && isEmojiSuccessful(response) && isEmoji(event.request)) { + if (response.ok && isSuccessful(response) && isEmoji(event.request)) { const cache = await caches.open(emojiCacheKey) await cache.put(event.request.clone(), response.clone()) }