Service Workers - aakash14goplani/FullStack GitHub Wiki

Topics Covered


Introduction to Service Workers

Service workers do a lot of work behind the scenes, they allow us to make our application offline ready so that it works even if we have no internet connection and they allow us to use a lot of other next generation web application features, like push notifications or background synchronization.

To dive deep into service workers let us first start with normal Javascript, Javascript runs on a single thread. That means that if you visit a web application and that returns you a HTML page which then execute some Javascript, the Javascript code loaded by this page runs on one thread. Even if you have multiple Javascript files loaded by one and the same page, they still share one thread on which they execute their code.

Now do service workers fit into this picture? - Service workers are also Javascript files, Javascript code, however they have access to a different set of features, they also run on a separate single thread i.e. they don't share the same thread as the normal Javascript code loaded by our HTML pages. Having their own thread is what decouples them for main HTML page and hence it is then able to perform tasks like push notifications, background synchronization etc.

Whilst we do register a service worker through code initially, once we have registered it, for example that could be the domain of your page. So the service worker is then not attached to a single page but available to all the pages of your web application. So if you have multiple HTML files served from different URLs and the service worker is attached to your root page, to your domain therefore, then it is applicable to all these HTML pages and not attached to a single page like your normal Javascript is.

Service workers also live on even after you closed all the pages in the browser, it's a background process. You use Javascript in there but they work totally different than the normal Javascript code, they can't attract with the DOM, they are not attached to a page, they are attached to a certain scope.

What can you do in service workers then? - Well since they run in the background, they're really good at one thing, reacting to events. They can listen to incoming events either emitted by your Javascript code or by another server like web push notifications.

Service Workers

TOP


Events that Service Workers responds to

  1. Fetch

    • It is one of the most important event. It is triggered whenever the browser or page-related Javascript initiates a fetch which is a HTTP request. Example: you have an image tag in your HTML code and it point to some image resource on internet and when the browser tries to get that image to display it, it actually sends such a fetch request.
    • You can react to that fetch request in the service worker and you can basically think of the service worker serving as a network proxy. Every HTTP request sent via fetch i.e. every request sent by the browser due to HTML loading some assets goes through the service worker. Hence whenever you load a CSS or JavaScript file because you import it in your HTML file, that triggers fetch.
    • Whenever you use the fetch API in your own Javascript code, you also trigger such a fetch request. Important, you don't trigger a fetch request if you use a normal traditional Ajax request (or any package like axios which builds up on that) in Javascript, so the XML HTTP request o, that won't trigger the fetch event.
  2. Push Notifications

    • These are sent from another server, basically every browser window like Google for Chrome and Mozilla for Firefox has its own push web push server.
    • You can send push notifications to the service from your own server and then these vendor service of the browser vendors will send this push notification to your client application and in the service worker, you can listen for such a push event. Why do you do this in the service worker and not in the normal Javascript page? Because service workers live in the background and they even live once all your pagers were closed, so push notifications obviously are all about getting you as a user back into the application after you closed it. If you are still in the application, yes you maybe still want to push notify the user but it's far more valuable if your phone is in your pocket, make your phone ring, give you a push notification. With service workers, you can get this behavior you know from native applications into your browser web applications and service workers are the key because they can react to that push notification.
  3. Notification Interaction

    • Now once you've got that push notification, you often want to show an alert, a notification to the user in your application or on the phone. For example he taps on that notification, you can also listen to that interaction in the service worker to do something with it like show an example page, load something from the cache, etc, you can react to the user interaction with notifications in the service worker.
    • Why do you do that in the service worker and not in the normal Javascript code? Because again, that might not be loaded, there might not be a tab open with your web application, the service worker always runs though, so it's the best place to handle notification interaction.
  4. Background Sync

    • Another very useful event is background synchronisation. Imagine a case where you don't have that good of an internet connection and you send a post. Now if the internet connection is bad, that will fail. In such situation with the help of background synchronisation, you store a certain action if it can't be executed right now and you execute it once Internet connection was re-established.
    • The background synchronization will issue a certain event or emit a certain event to which you can listen in the service worker. Why in the service worker? For the same reason as before, maybe you already closed the application. Well the good thing is the service worker is still alive and therefore if internet connection is established again, you can react to it in the service worker and execute that action you stored and you couldn't execute earlier, so it allows you basically to do something once the internet connection is re-established, very useful too.
  5. Service Worker Lifecycle

    • There are certain events which are related to the service worker lifecycle, you can hook into these lifecycle phases and execute some code.

TOP


Service Worker Lifecycle

Let's understand how a service worker is registered. This is my project structure

Project
   - public
      - src
         - css
         - images
         - js
            - app.js
         - index.html
         - manifest.json
         - service-worker.js
         - favicon.ico

We have an index.html file which loads an app.js file. In the app.js file, we can execute some code to register a service worker which lives in its own Javascript file. This is some code which tells the browser, hey the content of the service-worker.js file is Javascript but don't execute it right now as you do with all the other Javascript files, instead please take it and register it as a background process, as a service worker.

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js').then(() => {
    console.log('service worker registered');
  });
}

During that registration, two lifecycle phases are reached-

  1. the browser installs the service worker. During that it emits an install event which we can hook into to execute some code inside of the service worker whilst it is getting installed.
  2. There is another event which is executed as soon as installation finishes, once the service worker is activated.
this.addEventListener('install', (e) => {
  console.log('Service Worker: Installed', e);
});

this.addEventListener('activate', (e) => {
  console.log('Service Worker: Activated', e);
});

Now it's not necessarily activated right after the installation finishes, it's activated as soon as it can be activated which depends on the question whether there is an old version of the service worker running or not. Service workers will only get active if there is no old service worker instance running, so you might need to close existing tabs and re-open them to install a new version of the service worker.

This is required because the service worker is not attached to a single page but to an overall domain or scope i.e. it lives on even if you close the tab, so you have to make sure that at least no tab is open to be able to replace it because then browser you use basically knows OK it's now safe to switch it because no page is actively communicating with the service worker at least. So then once the browser decides that it can be activated, the activate event will be fired.

Scope of service worker means that you can decide which parts of your application are controlled by a service worker, put in other words, which pages of your application can work with the service worker. So for example if we want to listen to fetch events, well if a page is outside of the scope of the service worker, the fetch listener in the service worker won't trigger up on fetched requests of the pages outside of the service worker scope.

Now one important note about the install event or about how the browser installs a service worker - you might wonder whether it always installs it whenever our application basically executes, so if we refresh the page, does it then install our service worker again? The answer is it depends. It always executes this registration code but it doesn't necessarily install the service worker. It will only do that if your service worker file change by one byte or more, if it's the same file, if it is unchanged, then it will not install it.

So point to remember - the browser will only install a service worker if it is an updated version or if it's for the first time ever that you have one, not on every page refresh.

Once we have an activated service worker, it enters idle mode which means it's basically just sitting there because it's a background process handling events and if no events are coming in, it's doing nothing.

After a time of idling around, it will terminate, this does not mean that it is uninstalled or unregistered, it just means it's kind of sleeping. You can wake it up though or actually it will wake up automatically as soon as events are coming in.

lifecycle

If we run the contents of service worker file now

this.addEventListener('install', (e) => {
  console.log('Service Worker: Installed', e);
});

this.addEventListener('activate', (e) => {
  console.log('Service Worker: Activated', e);
});

The code will react to the install and activate event and once we did so and reloaded the page, we see 'service worker registered' (from app.js) and 'Service Worker: Installed' (from service-worker.js*) message in the console and we can see installing service worker.

The 'Service Worker: Activated' log is missing - shouldn't this execute once registration is done? Well actually since we have two separate threads here, app.js, your normal Javascript code in the service worker, Chrome can't guarantee the order here but in theory this executes after you installed the service worker, however you can't rely on this.

Now it's important to understand if you have a tab open or if you have a window open with your page, then new service workers will get installed but not activated. The reason for this is that the page is maybe still communicating with the old service worker and activating a new one which might introduce breaking changes might break the running page. Therefore the way to activate a new version is to close the existing tab and reopen it so that you have all tabs closed, if you have multiple ones open, close all the tabs. Re-open it and now you see the new version is activated and running and in the console, you now see installing and then activating messages.

This is crucial to understand, if you change code in your service worker, you need to close all the existing tabs and open a new one to load the newest version. Now when using the developer tools, there are shortcuts, you can check (1) update and reload - and then reload will have hard overwrite existing service workers, so you don't need to close that page. (2) click unregistered and reload to fetch a new version

TOP


Non Lifecycle Events

Another important event to be discussed here is fetch event. Fetch will get triggered whenever our web application fetches something. Example - load assets like the scripts, CSS etc.

this.addEventListener('fetch', (e) => {
  console.log('Service Worker: Fetch', e);
});

If we reload, this won't activate the new service worker, it only installs it. Click on Update and reload option under Application tab and reload the page, now service worker is activated. Another important thing is, once you activated it, all pages are under control of the service worker except for the page you are currently on, you need to reload this once for the service worker to control this too. So if you reload one more time, you'll see that now we're fetching stuff here and we're emitting the fetch event therefore.

We can use the event which gets passed into the event listener and there we can execute respondWith. This allows us to overwrite the data which gets sent back. Since service worker kind of acts as a network proxy, so every outgoing fetch request goes through the service worker and so does every response.

Now if we don't do anything the response is simply passed on. However, if you add respondWith, you can basically overwrite with what you want to respond. If you respondWith(null) here and reload, we see it's showing us this site can't be reached window here for a brief second before it recovers simply because we return null initially here.

Now if we return fetch event request here, which basically means return the fetch request and this is a promise, so this will automatically be parsed thereafter and this will also then use the response of this promise automatically, if we reload now, we don't get this in-between site not-reachable window because now it never has this point of time where it's not getting its data and where it then has to fallback to some browser mechanism.

this.addEventListener('fetch', (e) => {
  e.respondWith(null); // 
  e.respondWith(event.request); //
});

This has multiple benefits like we can intercept request and return different things depending on whether we have online access, if we have internet access or not, we can then use respond with to simply check the internet connection basically and return stuff from our cache or from the network.

It's important to understand that fetch is triggered by the actual page whilst install and activate are triggered by the browser when the service worker is getting installed or was activated.

TOP


Enable USB debugging using Android device

  1. Enable developer options and USB debugging
  2. Connect mobile device to PC via USB
  3. Click "Ok" when prompted to "Use this device for USB debugging" on your smartphone
  4. Open chrome://inspect/#devices in chrome browser of your PC
  5. Make sure Discover USB devices is already checked
  6. Click on Port forwarding button -> enter URL of localhost (e.g. http://localhost:8080) -> mostly it is already entered. Click on checkbox "Enable Port Forwarding"
  7. You can now see localhost:8080 on your smartphone & when you click on inspect option in chrome browser under your mobile phone name, you will see new developer tools window pops up for your phone.

Reference

TOP


Adding Banner

Defer the installation banner unless user interacts with your site

// app.js file

var deferredPrompt;

this.addEventListener('beforeinstallprompt', (e) => {
  console.log('beforeinstallprompt', e);
  e.preventDefault();
  deferredPrompt = e;
  return false;
});

// feed.js file -> file on which you want to trigger installation banner
function openCreatePostModal() {
  createPostArea.style.display = 'block';
  if (deferredPrompt) {
    deferredPrompt.prompt();
    deferredPrompt.userChoice.then((choiceResult) => {
      console.log(choiceResult.outcome);

      if (choiceResult.outcome === 'dismissed') {
        console.log('User cancelled installation');
      } else {
        console.log('User added to home screen');
      }
    });

    deferredPrompt = null;
  }
}

TOP


FAQs

  1. Is the Service Worker installed everytime I refresh the page?

    • No, whilst the browser does of course (naturally) execute the register() code everytime you refresh the page, it won't install the service worker if the service worker file hasn't changed. If it only changed by 1 byte though, it'll install it as a new service worker (but wait with the activation as explained).
  2. Can I unregister a Service Worker?

    • Yes, this is possible, the following code does the trick:
    navigator.serviceWorker.getRegistrations().then(function(registrations) {
      for(let registration of registrations) {
        registration.unregister()
      }
    })
    
  3. My app behaves strangely/ A new Service Worker isn't getting installed.

    • It probably gets installed but you still have some tab/ window with your app open (in one and the same browser). New service workers don't activate before all tabs/ windows with your app running in it are closed. Make sure to do that and then try again.
  4. Can I have multiple 'fetch' listeners in a service worker?

    • Yes, this is possible.
  5. Can I have multiple service workers on a page?

    • Yes, but only with different scopes. You can use a service worker for the /help "subdirectory" and one for the rest of your app. The more specific service worker (=> /help) overwrites the other one for its scope.
  6. Can Service Workers communicate with my Page/ the "normal" JavaScript code there?

    • Yes, that's possible using messages. Have a look at the following thread for more infos. This is actually not Service Worker specific, it applies to all Web Workers.
  7. What's the difference between Web Workers and Service Workers?

    • Service Workers are a special type of Web Workers. Web Workers also run on a background thread, decoupled from the DOM. They don't keep on living after the page is closed though. The Service Worker on the other hand, keeps on running (depending on the operating system) and also is decoupled from an individual page.

TOP