I have a basic service worker that returns an offline page when there is no internet connection. Anywhere where the website returns a 302 redirect, the site will break on account of the service worker, regardless of being online of offline.
The service worker:
'use strict';
var cache_key = 'offline_cache_v18';
this.addEventListener('install', event => {
event.waitUntil(
caches.open(cache_key).then(function(cache) {
return cache.addAll([
'/offline.html'
]);
})
);
});
this.addEventListener('fetch', event => {
if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html')) ) {
event.respondWith(
fetch(event.request.url).catch(error => {
return caches.match('/offline.html');
})
);
} else {
event.respondWith(caches.match(event.request).then(function (response) {
return response || fetch(event.request);
}));
}
});
A page that breaks the service worker:
<?php header('Location: /'); ?>
Expected result: pages to load as usual, except when offline, where offline.html should be presented.
Actual result: The site fails to load entirely on pages that respond with a 302 redirect, citing 'This site can’t be reached'. The console also reports: 'The FetchEvent resulted in a network error response: a redirected response was used for a request whose redirect mode is not "follow".'
Tried: checking the response code with response.status, and also tried switching the code to use the 'workbox' library. Also tried {redirect: 'follow'} parameters but it seems to be getting quite advanced now for something so simple. All the 'offline page' tutorials seem to have this same bug.
The underlying security restriction is explained in detail at https://bugs.chromium.org/p/chromium/issues/detail?id=658249
This issue crops up for navigation requests, since those requests use a redirect
mode of 'manual'
. (Most other types of requests will have a redirect
mode of 'follow'
, which means that a redirected HTTP 30x response can be used to fulfill them.)
You can detect whether or not you have a Response
object that was generated by following an HTTP 30x redirect by checking its redirected
property.
Putting that all together: how do you use a Response
object that was generated as part of a 30x redirect to satisfy a fetch
event for a navigation request? The answer is to "clean" the redirected response first, by creating a new (mostly identical) Response
object, but which will have the redirected
property set to false
.
async function cleanRedirect(response) {
const clonedResponse = response.clone();
return new Response(await clonedResponse.blob(), {
headers: clonedResponse.headers,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
});
}
You can use this by checking whether event.request.redirect === 'manual'
inside of a fetch
handler. If so, before calling event.respondWith(response)
, check to see if response.redirected
is true
. If it is, then pass that redirected response to await cleanRedirect()
first.
(This code is adapted from Workbox's implementation. Workbox will automatically "clean" any redirected responses for URLs that are precached, but it does not perform automatic cleaning on URLs that are cached at runtime.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With