Richard Connon
Richard Connon

Reputation: 360

Best practice for instant claim of a page by Service Worker

I currently have a service worker set up to claim the page immediately, due to the nature of the application and user experience.

As this application is converting over from AppCache, which had a dynamic Manifest file created for each user, I found that the best method was to Parse this Manifest file to a JSON array and send it to the Service Worker in order to cache it. The problem being I need to wait until the Service Worker is active before it can receive this array.

I currently have set a timeout on the function to 10000 (see below), but the success rate is not 100%. Edit: I often find that the Service Worker is not activated before the end of this 10000 timeout resulting in an error: "TypeError: navigator.serviceWorker.controller is null".

//Get Request - Service Worker Preperation 
setTimeout(function getRequest() {
  console.log("APP: Enetered getRequest() Method");
  $.ajax({
    type : "GET",
    url : "https://ExampleURL/App/" + 
    localStorage.getItem("user") + ".manifest",
    contentType: "text/plain",
    async : false,
    success : function(response) {
        var myArray = listToArray(response, '\n'); 
        send_message_to_sw(myArray);
    },
    error : function(msg) {
        console.log("ERROR: " + msg);
     }
    });
}, 10000);

My question is what is the best practice for checking if the Service worker is active, or should I just increase the amount of time in the timeout?

I am attaching the relevant Service Worker code below incase there is a problem with the way I have set up the immediate claim.

// INSTALL
self.addEventListener('install', function(event) {
  console.log('[ServiceWorker] Installed version', version);
  event.waitUntil(
    caches.open(version).then(function(cache) {
        console.log('[ServiceWorker] Cached cachedFiles for', version);

        return cache.addAll(cacheFiles);

    }).then(function() {

      console.log('[ServiceWorker] Skip waiting on install');
      return self.skipWaiting();
    })
  );
});


 //ACTIVATE
 self.addEventListener('activate', function(event) {

  self.clients.matchAll({
    includeUncontrolled: true
  }).then(function(clientList) {
    var urls = clientList.map(function(client) {
      return client.url;
    });
    console.log('[ServiceWorker] Matching clients:', urls.join(', '));
  });

  event.waitUntil(

    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheName !== version) {
            console.log('[ServiceWorker] Deleting old cache:', cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    }).then(function() {


      console.log('[ServiceWorker] Claiming clients for version', version);
      return self.clients.claim();
    })
  );
});


//RECIEVE DATA FROM JAVASCRIPT FILE
self.addEventListener('message', function(event){
  console.log("SW Received Message: " + event.data);
  var fullArray = [];

  var che = event.data;
  fullArray = che.splice(',');
  console.log("SWChe2: " + fullArray);
  var result = fullArray.slice(1,-1);


  caches.open(version + 'Manifest')
  .then(function(cache) {
    return cache.addAll(result);
 });
});

Upvotes: 4

Views: 1313

Answers (2)

Richard Connon
Richard Connon

Reputation: 360

I ended up wrapping the contents of my getRequest() function in the promise suggested by Jeff.

navigator.serviceWorker.ready

This allowed my application to complete with the shortest delay!

Upvotes: 0

Jeff Posnick
Jeff Posnick

Reputation: 56044

navigator.serviceWorker.ready is a promise that client pages can wait on, and will resolve when there's an active service worker whose scope encompasses the current page.

navigator.serviceWorker.ready.then(registration => {
  // Do something, confident that registration corresponds to
  // an active SW's registration.
});

But... two things.

  • You can add items using the Cache Storage API from the context of your page, without having to send a message to a service worker. window.caches.open('my-cache-name') will give you access to the same cache that a service worker can use.

  • If you've got a set of resources that always need to be cached, you're probably better off caching them inside of your install handler. That ensures that the service worker won't finish installation unless the required files are cached. You can use fetch() inside of your install handler to retrieve a JSON manifest of resources that need to be precached, and IndexedDB (instead of localStorage, which isn't exposed to service workers) to store the name of the current user. Just make sure that if you go this route, you make use of event.waitUntil() to delay the install handler's completion until the full set of operations is successful.

Upvotes: 2

Related Questions