0
votes

I am using service worker (via Workbox) to cache the result of an .NET MVC index view which is a simple URL (eg. https://mysite.local/MyWebsite/Product/Index). Note that the cache is made up of the page itself as well as the data (in JSON format) presented in a grid that is unknown at design/compile time (i.e. the JSON data is retrieved from the database at run-time via https://mysite.local/MyWebsite/Product/LoadData). This all works well - caching when offline.

The grid contains a button for each row that (when clicked) goes to another MVC view. I want to also cache these pages - say the top 2 that appear in my initial grid. Hence I don't know the URL of these top two pages because they contain parameters retrieved from the database - an example of what they look like:-

https://mysite.local/MyWebsite/Product/FurtherInformation/33

...the '33' part is retrieved from the database and hence not known ahead of time

The below (in the Service Worker) iterates through the JSON data that is cached from the initial index page, gets the Id field of each row and suffixes that onto the end of the URL - I am then trying to 'fetch' the page and then cache it.

    cache.match('https://mysite.local/MyWebsite/Product/LoadData')
        .then(function(matchedResponse) {
            return matchedResponse.json();
        })
        .then(function (json) {
            for (var i = 0; i < json.data.length; i++) {
                const API_FurtherInformation_URL = 'https://mysite.local/MyWebsite/Product/FurtherInformation/' + json.data[i].Id + '/0';

                const apiLoadDataCallHandler = workbox.strategies.networkFirst({
                    cacheName: 'my-cache'
                });

                workbox.routing.registerRoute(
                    API_FurtherInformation_URL ,
                    apiLoadDataCallHandler 
                );

                self.addEventListener('install', event => {
                    event.waitUntil(
                      caches.open('my-cache')
                        .then(cache => cache.add(API_FurtherInformation_URL))
                    );
                });

                self.addEventListener('fetch', event => {
                    if (event.request.mode === 'navigate') {
                        event.respondWith(
                          fetch(event.request).catch(() => caches.match(API_FurtherInformation_URL))
                        );
                    }
                });
            }
        });
});

However the 'install' and 'fetch' event logic in the code both get warning messages:-

"Event handler of 'install' event must be added on the initial evaluation of worker script"

"Event handler of 'fetch' event must be added on the initial evaluation of worker script"

...and the page is not cached

Any ideas what I can do - is this scenario accommodated ?

2

2 Answers

1
votes

The error message means: you cannot add self.addEventListener('fetch') or self.addEventListener('install') after the initial execution of the SW script - in other words you cannot add them inside a loop. You have to add those event listeners at the top level of the script at the first execution.

You should also note that those event listeners you're adding are event listeners for the events. They are not orders to cache something, as you say they are.

I think you could do something like:

caches.open('my-cache')
  .then(cache => cache.add(API_FurtherInformation_URL))

inside your loop (mind you that the cache is probably already open, at least you have a variable with that name in the outer scope). You don't need to add the event listeners!


All in all I think you should not really implement the functionality like this. It makes your Service Worker code very coupled into the API endpoints etc. I would suggest you to refactor the code for instance to work somewhat like this:

  1. Service Worker listens for messages from the page
  2. The messages include urls that should be cached
  3. When messages are received, the SW caches the urls right away
  4. Now when the JS is executing on the page and it renders the urls for the user, it can send the SW a message and tell it to cache whatever urls you like

The messaging between the page and the SW should be implemented via postMessage (https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).

0
votes

What I finished up doing is to handle the logic in a common page (the shared _Layout.cshtml). This will be hit regardless of how the user initially visits the website, queries the database (via fetch) when needed (cache refresh timer), and caches the dynamic pages so when the user does navigate to the page I want cache, it is fresh