now displaying a user-facing HTML error page when a navigation request fails (ref. #36)

master^2
Michał 'rysiek' Woźniak 2024-03-02 19:15:23 +00:00
rodzic 58cb74cb2b
commit e443e26aae
1 zmienionych plików z 120 dodań i 73 usunięć

Wyświetl plik

@ -1317,15 +1317,15 @@ self.addEventListener('activate', async event => {
*/
let still_loading_messages = {
title: "Still loading",
body: "The content is still being loaded, thank you for your patience.<br/><br/>This page will auto-reload in a few seconds. If it does not, please <a href='./'>click here</a>."
body: "The resource is still being loaded, thank you for your patience.<br/><br/>This page will auto-reload in a few seconds. If it does not, please <a href='./'>click here</a>."
}
let success_messages = {
title: "Loaded, redirecting!",
body: "The content has loaded, you are being redirected."
body: "The resource has loaded, you are being redirected."
}
let failure_messages = {
title: "Loading failed.",
body: "We're sorry, we were unable to load this page."
body: "We're sorry, we were unable to load this resource."
}
/**
@ -1395,7 +1395,13 @@ let getUserFacingHTML = (init_msgs, success_msgs=false, failure_msgs=false) => {
font-family: monospace;
}
</style>
<h1 id="header">${init_msgs.title}<span id="throbber1">&#x2022;</span><span id="throbber2">&#x2022;</span><span id="throbber3">&#x2022;</span></h1>
<h1 id="header">${init_msgs.title}`
if (success_msgs !== false) {
html += `<span id="throbber1">&#x2022;</span><span id="throbber2">&#x2022;</span><span id="throbber3">&#x2022;</span>`
}
html += `</h1>
<p id="working">attempts:&nbsp;<span id="status">1</span></p>
<p id="text">${init_msgs.body}</p>
<p id="errors"></p>
@ -1466,6 +1472,7 @@ let getUserFacingHTML = (init_msgs, success_msgs=false, failure_msgs=false) => {
self.addEventListener('fetch', async event => {
return void event.respondWith(async function () {
// initialize the SW; this is necessary as SW can be stopped at any time
// and restarted when an event gets triggered -- `fetch` is just such an event.
//
@ -1475,6 +1482,7 @@ self.addEventListener('fetch', async event => {
//
// the good news is that the config.json should have been cached already
await initServiceWorker()
// if event.resultingClientId is available, we need to use this
// otherwise event.clientId is what we want
// ref. https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/resultingClientId
@ -1541,82 +1549,121 @@ self.addEventListener('fetch', async event => {
// get handled by plugins in case of an error
let lrPromise = getResourceThroughLibResilient(url, init, clientId)
// is the stillLoadingScreen enabled, and are we navigating, or just fetching some resource?
if ( ( self.LibResilientConfig.stillLoadingTimeout > 0 ) && ( event.request.mode === 'navigate' ) ) {
// are we navigating, or just fetching some resource?
if ( event.request.mode === 'navigate' ) {
self.log('service-worker', `handling a navigate request; still-loading timeout: ${self.LibResilientConfig.stillLoadingTimeout}.`)
// this is the promise we will want to return in the end
let finalPromise = null
let slPromise, slTimeoutId
[slPromise, slTimeoutId] = promiseTimeout(self.LibResilientConfig.stillLoadingTimeout, true)
// navigating! is the still-loading screen enabled?
if ( self.LibResilientConfig.stillLoadingTimeout > 0 ) {
// make sure to clear the timeout related to slPromise
// in case we manage to get the content through the plugins
lrPromise
.then(()=>{
self.log('service-worker', `content retrieved; still-loading timeout cleared.`)
clearTimeout(slTimeoutId)
})
// it is enabled!
self.log('service-worker', `handling a navigate request; still-loading timeout: ${self.LibResilientConfig.stillLoadingTimeout}.`)
let slPromise, slTimeoutId
[slPromise, slTimeoutId] = promiseTimeout(self.LibResilientConfig.stillLoadingTimeout, true)
// make sure to clear the timeout related to slPromise
// in case we manage to get the content through the plugins
lrPromise
.then(()=>{
self.log('service-worker', `content retrieved; still-loading timeout cleared.`)
clearTimeout(slTimeoutId)
})
// prepare a Promise that races the "still loading" screen promise against the LibResilient plugins
finalPromise = Promise.race([
// regular fetch-through-plugins
lrPromise,
// the "still loading screen"
//
// this will delay a specified time, and ten return a Response
// with very basic HTML informing the user that the page is still loading,
// a Refresh header set, and a link for the user to reload the screen manually
slPromise
.then(()=>{
// inform
self.log('service-worker', 'handling a navigate request is taking too long, showing the still-loading screen')
// we need to create a new Response object
// with all the headers added explicitly,
// since response.headers is immutable
var responseInit = {
status: 202,
statusText: "Accepted",
headers: {},
url: url
};
responseInit.headers['Content-Type'] = "text/html"
responseInit.headers['X-LibResilient-Method'] = "still-loading"
// get the still-loading page contents
let stillLoadingHTML = getUserFacingHTML(
still_loading_messages,
success_messages,
failure_messages
)
let blob = new Blob(
[stillLoadingHTML],
{type: "text/html"}
)
return new Response(
blob,
responseInit
)
})
])
// okay, no still-loading screen, but this is still a navigate request, so we want to display something to the user
} else {
self.log('service-worker', `handling a navigate request, but still-loading screen is disabled.`)
finalPromise = lrPromise
}
// return a Promise that races the "still loading" screen promise against the LibResilient plugins
return Promise.race([
// regular fetch-through-plugins
lrPromise,
// the "still loading screen"
//
// this will delay a specified time, and ten return a Response
// with very basic HTML informing the user that the page is still loading,
// a Refresh header set, and a link for the user to reload the screen manually
slPromise
.then(()=>{
// inform
self.log('service-worker', 'handling a navigate request is taking too long, showing the still-loading screen')
// we need to create a new Response object
// with all the headers added explicitly,
// since response.headers is immutable
var responseInit = {
status: 202,
statusText: "Accepted",
headers: {},
url: url
};
responseInit.headers['Content-Type'] = "text/html"
// refresh: we want a minimum of 1s; stillLoadingTimeout is in ms!
//responseInit.headers['Refresh'] = Math.ceil( self.LibResilientConfig.stillLoadingTimeout / 1000 )
//responseInit.headers['ETag'] = ???
//responseInit.headers['X-LibResilient-ETag'] = ???
responseInit.headers['X-LibResilient-Method'] = "still-loading"
// get the still-loading page contents
let stillLoadingHTML = getUserFacingHTML(
still_loading_messages,
success_messages,
failure_messages
)
let blob = new Blob(
[stillLoadingHTML],
{type: "text/html"}
)
return new Response(
blob,
responseInit
)
})
])
// return finalPromise, with a catch!
return finalPromise.catch((e)=>{
// inform
self.log('service-worker', 'handling a failed navigate request, showing the user-facing error screen')
// we need to create a new Response object
// with all the headers added explicitly,
// since response.headers is immutable
var responseInit = {
status: 404,
statusText: "Not Found",
headers: {},
url: url
};
responseInit.headers['Content-Type'] = "text/html"
responseInit.headers['X-LibResilient-Method'] = "failed"
// get the still-loading page contents
// it only needs to display the failure messages
let stillLoadingHTML = getUserFacingHTML(
failure_messages
)
let blob = new Blob(
[stillLoadingHTML],
{type: "text/html"}
)
return new Response(
blob,
responseInit
)
})
// nope, just fetching a resource
} else {
if ( event.request.mode === 'navigate' ) {
self.log('service-worker', `handling a navigate request, but still-loading screen is disabled.`)
} else {
self.log('service-worker', 'handling a regular request; still-loading screen will not be used.')
}
// no need for the whole "still loading screen" flow
return lrPromise;
// no need for a still-loading screen, no need for an user-facing error screen if the request fails
self.log('service-worker', 'handling a regular request; still-loading screen will not be used.')
return lrPromise
}
}())
});