skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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));
                }
            })
        );
    });
    
    

  2. Creating a timeout won’t stop the fetch event handler. You need a promise that you can wait for before making the fetch() request. You can initialise that promise when asking for the refreshed header:

    // runs when sw (re-)init
    self.secureHeaderPromise = new Promise(resolve => {
        console.log('Refreshing header');
    
        postMessage({ type: 'refreshHeader', msg: '' }, '*');
    
        self.setSecureHeader = (value) => {
            console.log('First header received: '+value);
            resolve(value);
     
            // allow later updates
            self.setSecureHeader = (value) => {
                console.log('Header received :'+value);
                self.secureHeaderPromise = Promise.resolve(value);
            };
        };
    });
    self.addEventListener('message', function (event) {
        if (event.data.type == 'setHeader') {
            self.setSecureHeader(event.data.msg);
        } 
    }, false);
    
    self.addEventListener('fetch', function(e) {
        console.log(`Got request: ${e}`);
        e.respondWith(
            self.secureHeaderPromise.then(secureHeader => {
                console.log(`Forwarding request: ${secureHeader}`);
                return fetch(request, {
                    headers: getSecureHeaders(request)
                })
            }).catch(() => {
                return caches.match(request);
            }).then(response => {
                console.log('Responding');
                return response;
            })
        );
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search