Service Worker is a programmable network proxy, allowing you to control how network requests from your page are handled. Service Workers require HTTPs on the page (The only exception is when working locally).
Note: If there is information that you need to persist and reuse across restarts, service workers do have access to the IndexDB API.
// Check if the SW is provided by the Browser
if(navigator.serviceWorker){
navigator.serviceWorker.register(scriptUrl, options)
.then((registration) => {
// SW was registered
})
.catch((err) => {
// SW registration failed
});
}
- Parameters:
- scriptURL: The URL of the SW script.
- options: An object containing the SW options. Currently available options are:
- scope: a URL representing the SW’s registration scope; that is, what range of URLs the SW can control. It is relative to the base URL of the application. By default, the scope value for a service worker registration is set to the directory where the service worker script is located.
- Returns:
- Promise that resolves with the
ServiceWorkerRegistration
Object.
- Promise that resolves with the
sw script Location | Default Scope | Pages Controlled |
---|---|---|
/foo/sw.js |
/foo/ |
/foo/ or deeper but not shallower like /foo |
/foo/bar/sw.js |
/foo/bar/ |
/foo/bar/ or deeper |
/sw.js |
/ |
/ or deeper |
Spinning up a new Service Worker thread to download and cache resources in the background can work against your goal of providing the shortest time-to-interactive experience the first time a user visits your site.
The solution is to control start of the service worker by choosing when to call navigator.serviceWorker.register()
. A simple rule of thumb would be to delay registration until after the load
event fires on window, like so:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js');
// ...
});
}
All said and done, the right time to register a Service Worker depends on how the app works. Read more about improving service worker boiler plate.
After the sw is installed, the service worker will begin to receive fetch
events, but only after the user navigates to a different page or refreshes (not always for the latter).
self.addEventListener('fetch', (event) => {
event.respondWith(
// accepts promise that resolves with a response object.
);
});
This will hijack all requests. To hijack particular requests we can use event.request.url
:
self.addEventListener('fetch', (event) => {
if(event.request.url === '/foo'){
event.respondWith(
// return custom response here
);
} else {
event.respondWith(
// return default response here
);
}
});
We can also hijack 404 responses by checking the status and returning another fetch()
request:
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
if(response.status === 404){
return fetch('/path/to/custom/file');
}
return response;
})
.catch((err) => {
// Handle Error
})
);
});
We can also cache data to respond with, caching this data can be done when the service worker is being installed; we can check that with the install
event:
self.addEventListener('install', (event) => {
// Perform install steps
});
Install steps:
- Open cache:
To open a cache we need to call the
open()
on the Cache API;
caches.open(CACHE_NAME).then((openedCache) => {
// add items to cache
}).catch((err) => {
// handle error
});
Returns a Promise
that resolves to the requested cache object.
2. Cache files:
To add cache items we can either use the put()
or addAll()
methods on the openedCache object.
openedCache.put(req, res).then(() => {
// request/response pair has been added to the cache
}).catch(() => {
// cache wasn't successful
});
Takes the request to add to the cache, and the response to match that request. And returns a Promise
that resolves with void.
openedCache.addAll(urlsToCache).then(() => {
// URLs/requests have been added to the cache
}).catch(() => {
// cache wasn't successful
});
- Confirm whether all the required assets are cached:
The confirmation can be done by using the
event.waitUntil()
method.
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((openedCache) => {
return openedCache.addAll(urlsToCache);
})
);
});
The install events in service workers use waitUntil()
to hold the service worker in the installing phase until tasks complete. If the promise passed to waitUntil()
rejects, the install is considered a failure, and the installing service worker is discarded. This is primarily used to ensure that a service worker is not considered installed until all of the core caches it depends on are successfully populated.
When registering a service worker we have access to the serviceWorkerRegistration
object.
navigator.serviceWorker.register(PATH)
.then((registration) => {
// successful registration, track registration
}).catch((err) => {
// registration failed, Do something about it
});
The registration
object has a bunch of methods and properties:
reg.unregister()
unregisters the service worker and returns a promise. The service worker will finish any ongoing operations before it is unregistered.reg.update()
checks the server for an updated version of the service worker, if the new service worker script is not byte-to-byte identical with the current, it updates the service worker.reg.installing
returns a SW whose state is installing, initially set to null.reg.waiting
returns a SW whose state is waiting, initially set to null.reg.active
returns a SW whose state is either active or activating, initially set to null.
If the SW has been changed since the last time it was registered the registration
object fires an updatefound
event:
registration.addEventListener('updatefound', () => {
// when fired it means that there is a new SW
// being installed.
// and can be accessed with reg.installing
});
Every installing service worker has a state that can be accessed with reg.installing.state
:
- installing hasn’t yet completed installation.
- installed installed but hasn’t yet been activated.
- activating active event fired but not yet completed.
- active the worker is ready to receive fetch events.
- redundant the service worker has been updated or failed to install.
The installing service worker fires an event whenever the its state (i.e reg.installing.state
) changes:
registration.installing.addEventListener('statechange', () => {
// Do something
});
navigator.serviceWorker.controller
refers to the service worker currently controlling the page. Returns a service worker if its state is activated or null if there is no active worker.
The skipWaiting()
method allows the service worker to progress from the registration's waiting position to active even while service worker clients are using the registration.
self.skipWaiting();
The postMessage()
method of the Worker interface sends a message to the worker's inner scope (i.e. inside the service worker script sw.js
).
worker.postMessage(message);
The worker’s inner scope can listen for messages with the message
event:
self.addEventListener('message', (event) => {
// the message object can be accessed with event.data
});
When the service worker controlling the page changes a controllerchange
event is fired
navigator.serviceWorker.addEventListener('controllerchange', () => {
// a new service worker in charge
});
Notes:
- Synchronous requests are not allowed from within a service worker — only asynchronous requests, like those initiated via the
fetch()
method, can be used. - In the above code
self
refers to the ServiceWorkerGlobalScope.