My service worker has a variable to store a secured hash, and injects it into headers for relevant HTTP requests, server app will then compare with its own to make it a secured login.
The hash is generated dynamically behind (in browser class using c# dotnet in my case).
Previously I always called postmessage on page loaded from browser to update the hash into sw. But encountered an issue that with 5-6 min inactive (without opening devtools), once click the button on page, sw is re-initialised or wake up with empty hash value, thus responding with empty value and failed the validation.
So I added a trigger from sw, but then encountered the race condition between message listener and fetch listener. I need the message listener to update the hash in sw, but fetch listener reposonded at same time. Seems timeout delay is not working.
console.log('top root log');
if (!self.secureHeader) {
self.secureHeader = 'Empty';
console.log('Refreshing header');
// this is my trigger when sw re-init
postMessage({ type: 'refreshHeader', msg: '' }, '*');
//// this is a bad attempt which didn't block the listener
// setTimeout(function() {
// console.log('timeout ended');
// }, 50);
}
self.addEventListener('message', function (event) {
if (event.data.type == 'setHeader') {
self.secureHeader = event.data.msg;
console.log('Header received:'+ self.secureHeader);
}
}, false);
self.addEventListener('fetch', function(e) {
console.log(Responding: ${self.secureHeader});
////I tried timeout here, error: The event handler is already finished.
//if (self.secureHeader === 'Empty')
//timeout to do respond
e.respondWith(
fetch(request, {
headers: getSecureHeaders(request)
})
.catch(() => {
return caches.match(request);
})
);
});
From my testing, after a few minutes waiting and sw expiry,
The logs appear to be:
(sw wake up) top root logs appear.
"Refreshing header" triggered.
fetch event started.
Then "Header received" and "Responding..." comes at the same time,
how can I properly chain them? Any advice, thanks.
Workout with a lazy way and another elegant way using Promise.
1. Add a delay inside respondWith
self.addEventListener('fetch', function (e) {
function delayPromise(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
e.respondWith(
new Promise(resolve => {
if (self.secureHeader === 'Empty') {
delayPromise(50)
.then(() => {
resolve(handleFetch(e));
});
}
})
);
});
2. chain promises to respond
self.addEventListener('message', function (event) {
if (event.data.type == 'setHeader') {
self.secureHeader = event.data.msg;
if (self.resolveHeader) {
self.resolveHeader();
self.resolveHeader = null;
}
}
}, false);
self.addEventListener('fetch', function(e) {
e.respondWith(
new Promise(resolve => {
if (self.secureHeader === 'Empty') {
self.headerPromise = new Promise((headerResolve, headerReject) => {
const timeoutId = setTimeout(() => {
headerReject(new Error('timed out'));
}, 200);
self.resolveHeader = () => {
clearTimeout(timeoutId);
headerResolve();
};
});
self.headerPromise
.then(() => resolve(handleFetch(e)))
.catch((error) => {
});
} else {
resolve(handleFetch(e));
}
})
);
});