Reading About the Service Worker API
I want to implement notifications for this single page application, so I want to know how the Service Worker API works.
References
Definitions
- Worker
- The
Worker
interface of the Web Workers API represents a background task that can be created via script, which can send messages back to its creator.
- The
- Man in the Middle Attacks
- "A manipulator in the middle (MitM) intercepts communication between two systems."
- MTM attacks are tough to defend against. A few tips:
- Don't just ignore certificate warnings. You could be connection to a phishing server or an imposter server.
- Sensitive sites without HTTPS encryption on public Wi-Fi networks aren't trustworthy.
- Check for HTTPS in your address bar and ensure encryption is in-place before logging in.
- Associated Document
- The
Window
object has an associatedDocument
, which is a Document object. It is set when the Window object is created, and only ever changed during navigation from the initialabout:blank
Document
- The
- Capability URL
- Capability URLs grant access to a resource to anyone who has the URL. There are times when this is useful, for example one-shot password reset URLs, but overuse can be problematic as URLs cannot generally be kept secret.
Notes
Service workers essentially act as proxy servers that sit between web applications, the browser, and the network (when available). They are intended, among other things, to enable the creation of of effective offline experiences, intercept network requests, and take appropriate action based on whether the network is available, and update assets residing on the server. They will also allow access to push notifications and background sync APIs.
Concepts and Usage
- A service worker is an event driven worker registered again an origin and a path. It takes the form of a JavaScript file that can control the web-page/site that it is associated with, intercepting and modifying navigation and resource requests, and caching resources in a very granular fashion to give you complete control over how your app behaves in certain situations (the most obvious being when the network is not available).
- A service worker is run in a worker context: it therefore has no DOM access, and runs on a different thread to the main JavaScript thread that powers your app, so it is non blocking.
- It is designed to be fully async; as a consequence, APIs such as synchronous XHR and Web Storage can't be used inside a service worker.
- Service workers can't import JavaScript module dynamically, and
import()
will throw if it is called in a service worker global scope. Status import using theimport
statement is allowed. - Service workers only run over HTTPS, for security reasons. Most significantly, HTTP connections are susceptible to malicious code injection by man in the middle attacks, and such attacks could be worse if allowed access to these powerful APIs.
Registration
- A service worker is first registered using the
ServiceWorkerContainer.register()
method. If successful, your service worker will be downloaded to the client and attempt installation/activation (see below) for URLs accessed by the user inside the whole origin, or inside a subset specified by you.
Download, Install, and Activate
- The Service Worker Lifecycle:
- Download
- The service worker is immediately downloaded when a suer first accesses a service worker-controlled site / page.
- It is then updated when:
- A navigation to an in-scope page occurs.
- An event is fired on the service worker and it hasn't been downloaded in the last 24 hours.
- Install
- Installation is attempted when the downloaded file is found to be new - either different to an existing service worker (byte-wise compared), or the first service worker encountered for this page / site.
- If this is the first time the service worker has been made available, installation is attempted and after successful installation, it is activated.
- If there is an existing service worker available, the new version is installed in the background, but not yet activated - at this point it is called the worker in waiting. It is only activated when there are no longer any pages loaded that are still using the old service worker. As soon as there are no more pages to be loaded, the new service worker activates (becomes the active worker). Activation can happen sooner using
ServiceWorkerGlobalScope.skipWaiting()
and existing pages can be claimed by the active worker usingClients.claim()
. - You can listen for the
install
event.
- Activate
- You can listen for the
activate
event. The point where this event fires is generally a good time to clean up old caches and other things associated with the previous version of your service worker.
- You can listen for the
- Service workers can respond to requests using the
FetchEvent
event. You can modify the response to these requests in any way you want, using theFetchEvnet.respondWith()
method.
Other Use Case Ideas
- Background data synchronization
- Responding to resource requests from other origins
- Receiving centralized updates to expensive-to-calculate data such as geolocation or gyroscope, so multiple pages can make use of one set of data
- Client-side compiling and dependency management of CoffeeScript, less, CJS/AMD modules for development purposes
- Custom templating based on certain URL patterns
- Performance enhancements
- API mocking
Service Workers bring Progress Web Apps Closed to Native App Viability
Interfaces
Cache
- Represents the storage for
Request
/Response
object pairs that are cached as part of theServiceWorker
life cycle
- Represents the storage for
CacheStorage
- Represents the storage for
Cache
objects. It provides a master directory of all the named caches that aServiceWorker
can access, and maintains a mapping of string names to correspondingCache
objects
- Represents the storage for
Client
- Represents the scope of a service worker client. A service worker client is either a document in a browser context or a
SharedWroker
, which is controlled by an active worker.
- Represents the scope of a service worker client. A service worker client is either a document in a browser context or a
Clients
- Represents a container for a list of
Client
objects; the main way to access the active service worker clients at the current origin
- Represents a container for a list of
ExtendableEvent
- Extends the lifetime of the
install
andactivate
events dispatched on theServiceWorkerGlobalScope
, as part of the service worker lifecycle. This ensures that any functional events, likeFetchEvent
, are not dispatched to theServiceWorker
, until it upgrades database schemas, and deleted outdated cache entries, etc.
- Extends the lifetime of the
ExtendableMessageEvent
- The event object of a message event fired on a service worker (when a channel message is received on the
ServiceWorkerGlobalScope
from another context) - extends the lifetime of such events
- The event object of a message event fired on a service worker (when a channel message is received on the
FetchEvent
- The parameter passed into the
onfetch
handler,FetchEvent
represents a fetch action that is dispatched on theServiceWorkerGlobalScope
of aServiceWorker
. It contains information about the request and resulting response, and provides theFetchEvent.respondWith()
metho, which allows us to provide an arbitrary response back to the controlled page.
- The parameter passed into the
InstallEvent
- The parameter passed into the
oninstall
handler, theInstallEvent
interface represents an install action that is dispatched on theServiceWrkerGlobalScope
of aServiceWorker
. As a child ofExtendableEvent
, it ensures that functional events such asFetchEvent
are not dispatched during installation.
- The parameter passed into the
NavigationPreloadManager
- Provides methods for managing the preloading of resources with a service worker.
ServiceWorker
- Represents a service worker. Multiple browsing contexts (e.g. pages, workers, etc.) can be associated with the same
ServiceWorker
object.
- Represents a service worker. Multiple browsing contexts (e.g. pages, workers, etc.) can be associated with the same
ServiceWorkerContainer
- Provides an object representing the service worker as an overall unit in the network ecosystem, including facilities to register, unregister, and update service workers, and access the state of service workers and their registrations.
ServiceWorkerGlobalScope
- Represents the global execution context of a service worker
ServiceWorkerRegistration
- Represents a service worker registration
WindowClient
- Represents the scope of a service worker client that is a document in a browser context, controlled by an active worker. This is a special type of
Client
object, with some additional methods and properties available.
- Represents the scope of a service worker client that is a document in a browser context, controlled by an active worker. This is a special type of
Extensions to Other Interfaces
Window.caches
- Returns the
CacheStorage
object associated with the current context
- Returns the
WorkerGlobalScope.caches
- Returns the
CacheStorage
object associated with the current context
- Returns the
Navigator.serviceWorker
- Returns a
ServiceWorkerContainer
object, which provides access to registration, removal, upgrade, and communication with theServiceWorker
objects for the associated document
- Returns a
WorkerNavigator.serviceWorker
- Returns a
ServiceWorkerContainer
object, which provides access to registration, removal, upgrade, and communication with theServiceWorker
objects for the associated document
- Returns a
The Service Worker Lifecycle
The lifecycle of the service worker is the most complicated part. If you don't know what it's trying to do and what the benefits are, it can feel like it's fighting you. But once you know how it works, you can deliver seamless, unobtrusive updates to users, mixing the best of web and native patterns.
- The intent of the lifecycle is to:
- Make offline-first possible
- Allow a new service worker to get itself ready without disrupting the current one
- Ensure an in-scope page is controlled by the same service worker (or no service worker) throughput
- Ensure there's only one version of your site running at once
The First Service Worker
- The
install
event is the first event a service worker gets, and it only happens once - A promise passed to
installEvent.waitUnit()
signals the duration and success or failure of your install - A service worker won't receive events like
fetch
andpush
until it successfully finished installing and becomesactive
- A page's fetches won't go through a service worker unless the page request itself went through a service worker. So you'll need to refresh the page to see the effects of the service worker
clients.claim()
can override this default, and take control of non-controlled pages
Scope and Control
- The default scope of a service worker registration is
./
relative to the script URL. - We call pages, workers, and shared worker
clients
. Your service worker can only control clients that are in-scope. Once a client iscontrolled
, its fetches go through the in-scope service worker. You can detect if a client is controlled vianavigator.serviceWorker.controller
which will be null or a service worker instance
Download, parse and Execute
- Your very first service worker downloads when you call
.register()
. If your script fails to download, parse, or throws an error in its initial execution, the register promise rejects, and the service worker is discarded.
Install
- The first event a service worker gets is
install
. It's triggered as soon as the worker executes, and it's only called once per service worker. If you alter your service worker script the browser considers it a different service worker, and it'll get its owninstall
event. - The
install
event is your chance to cache everything you need before being able to control clients. The promise you pass toevent.waitUntil()
lets the browser know when your install completes, and f it was successful. If it rejects, the browser throws the service worker away.
Activate
- Once your service worker is ready to control clients and handle functional events like
push
andsync
, you'll get anactivate
event. But that doesn't mean the page that called.register()
will be controlled.
clients.claim
- You can take control of uncontrolled clients by calling
clients.claim()
within your service worker once it's activated.
Updating the Service Worker
- An update is triggered if any of the following happens:
- A navigation to an in-scope page
- A functional event such as
push
andsync
occurs, unless there's been an update check within the previous 24 hours - Calling
.register()
only if the service worker URL has changed. You should avoid changing the URL of the service worker script.
Install
- Changing the cache name sets up a new cache without overwriting things in the current one. This pattern creates version-specific caches.
Waiting
- After being installed, the updated service worker delays activating until the existing service worker is no longer controlling clients. This state is called
waiting
, and it's how the browser ensures that only one version of your service is running at a time.
Activate
- This fires once the old service worker is gone, and your new service worker is able to control clients. This is the ideal time to do stuff that you couldn't do while the old worker was still in use, such as migrating databases and clearing caches.
Skip the Waiting Process
- The waiting phase means that you're only running one version of your site at once, but if you don't need that feature, you can make your new service worker activate sooner by calling
self.skipWaiting()
. This causes your service worker to kick out the current active worker and activate itself as soon as it enters the waiting phase.
Manual Updates
- You can trigger manual updates with the
.update()
method. If you expect the user to be using the site for a long time without reloading, you may want to callupdate()
on an interval (such as hourly).
Avoid Changing the URL of your Service Worker Script
- It is best to update the script at its current location.
Making Development Easy
Update on Reload
- In the Service Worker tab in Chrome DevTools, check
Update on Reload
so that you get updates on each navigation.
Skip Waiting
- You can click
skip waiting
in Chrome DevTools to promote the service worker to active immediately.
Handling Updates
- The whole update cycle is observable - there are events that you can listen to and properties on the Service Worker that you can check to see the state of the Service Worker.
Using Service Workers
- Service workers fix issues related to asset caching and custom network requests, and they seek to provide a better user experience when the user loses connection. Using a service worker you can set an app up to use cached assets first, thus providing a default experience even when offline, before getting more data from the network (commonly known as
offline first
). This is already available in native apps, which is one of the main reasons why native apps are often chosen over web apps. - A service worker functions like a proxy server, allowing you to modify requests and responses replacing them with items from its own cache.
Basic Architecture
- The service worker code is fetched and then registered using
serviceWorkerContainer.register()
. If successful, the service worker is executed in aServiceWorkerGlobalScope
; this is basically a special kind of worker context, running off the main script execution thread; with no DOM access. The service worker is now ready to process events. - Installation takes place. An
install
event is always the first one sent to a service worker (this can be used to start the process of populating an IndexedDB, and caching site assets). During this step, the application is preparing to make everything available for use offline. - When the
install
handler completes, the service worker is considered installed. At this point, a previous version of the service worker may be active and controlling open pages. Because we don't want two different versions of the same service worker running at the same time, the new version is not active. - Once all pages controlled by the old version of the service worker have closed, it's safe to retire the old version, and the newly installed service worker receives an
activate
event. The primary use ofactivate
is to clean up resources used in previous versions of the service worker. The new service worker can callskipWaiting()
to ask to be activated immediately without waiting for open pages to be closed. - After activation, the service worker will now control pages, but only those that were opened after the
register
is successful. In other words, documents will have to be reloaded to actually be controlled, because a document starts life with or without service worker and maintains that for its lifetime. To override this default behavior and adopt open pages, a service worker can callclients.claim()
- Whenever a new version of a service worker is fetched, this cycle happens again and remains of the previous version are cleaned during the new version's activation.
- Summary of service worker events:
install
activate
message
- The
message
event of theServiceWorkerGlobalScope
interface occurs when incoming messages are received. Controlled pages can use theServiceWorker.postMessage()
method to send messages back via the service workers. The service worker can optionally send a response back via theClient.postMessage()
, corresponding to the controlled page.
- The
- Functional Events:
fetch
sync
push
The article goes over an example of using the service worker which would be a good idea to reference.
APIs Related to Service Workers
Background Fetch API
- Background Fetch API Mozilla Docs
- Not available in Safari, so I'm not going to go into depth on it
- When an application requires the user to download large files, this often presents a problem when the user needs to stay connected to the page for the download to complete. If they lose connectivity, close the tab or navigate away from the page the download stops.
- The Background Synchronization API provides a way for service workers to defer process until user is connected; however it can't be used for long tasks such as downloading a large file. The Background Sync requires that the service worker stays alive until the fetch is completed, and to conserve battery life and to prevent unwanted tasks happening in the background the browser will at some point terminate the task.
- The Background Fetch API solves this problem. It creates a way for developers to tell the browser to perform some fetches in the background, for example when the user clicks a button to download a video file. The browser then performs the fetches in a user-visible way, displaying progress to the user and giving them a method to cancel the download. Once the download is complete the browser opens the service workers, at which point your application can do something with the response if required.
- The Background Fetch API will enable the fetch to happen if the user starts the process while offline. Once they are connected it will begin. If the user goes offline, the process pauses until the user is on again.
Background Synchronization API
- Background Synchronization API Mozilla Docs
- Not available in Safari, so I'm not going to go into depth on it.
- The Background Synchronization API enables a web app to defer tasks so that they can be run in a service worker once the user has a stable network connection.
- The API allows web applications to defer server synchronization work to their service to handle at a later time, if the device is offline. Uses may include sending requests in the background if they couldn't be sent while the application was being used.
Content Index API
- Content Index API Mozilla Docs
- Not available in most browsers, so I'm not going to go into depth on it.
- The API allows developers to register their offline enabled content with the browser.
- Content indexing allows developers to tell the browser about their specific offline content.
Cookie Store API
- Cookie Store API Mozilla Docs
- Not available in Safari, so I'm not going to go into depth on it.
- The Cookie Store API is an asynchronous API for managing cookies. The Cookie Store API provides an updated method for managing cookies. It is asynchronous and promise-based, therefore does not block the event loop.
Notifications API
The Notifications API allows web pages to control the display of system notifications to the end user. These are outside the top-level browsing context viewport, so therefore can be displayed even when the user has switched tabs or moved to a different app. The API is designed to be compatible with existing notification systems, across different platforms.
- On supported platforms, showing a system notification generally involves two things. First, the user needs to grant the current origin permission to display system notifications, which is generally done when the app or site initializes, using the
Notification.reequestPermission()
method. This should be done in response to a user gesture. - A new notification is created using the
Notification()
constructor. This must be passed a title argument, and can optionally be passed an options object to specify options, such as text direction, body text, icon to display, notification sound to play, and more.
Payment Handler API
- Payment Handler API Mozilla Docs
- Not available in Safari, so I'm not going to go into depth on it.
- This API provides a standardized set of functionality for web applications to directly handle payments, rather than having to be redirected to a separate site for payment handling. The Payment Handler API handles the discovery of applicable payment apps, presenting them as choice to the user, opening a payment handler window once a choice has been made to allow the user to enter their payment details, and handling the payment transaction with the payment app.
- Communication via payment apps (authorization, passing of payment credentials) is handled via Service Workers.
Push API
The Push API gives web applications the ability to receive messages pushed to them from a server, whether or not the web app is in the foreground, or event loaded, on a user agent. This lets developers deliver asynchronous notifications and updates to users that opt in, resulting in better engagement with timely new content.
- A service worker can subscribe to push notifications with
PushManager.subscribe()
- The resulting
PushSubscription
includes all the information that the application needs to send a push message: an endpoint and the encryption key needed for sending data. - The service worker will be started as necessary to handle incoming messages, which are delivered to the
onpush
event handler. This allows apps top react to push messages being received, for example, by displaying a notification (usingServiceWorkerRegistration.showNotification()
). - Each subscription is unique to a service worker. The endpoint for the subscription is a unique capability URL: knowledge of the endpoint is all that is needed to send a message to your application. The endpoint URL needs to be kept secret, or other applications might be able to send push messages to your application.
- Delivering push messages can increase battery resource usage. Some browsers implement limits on push notifications.
Web Periodic Background Synchronization API
- Not available in Safari, so I'm not going to go into depth on it.
- This API provides a way to register tasks to be run in a service worker at periodic intervals with network connectivity. These tasks are referred to as background sync requests.
- The API allows web applications to alert their service worker to make any updates, at a periodic time interval. Uses may include fetching latest content while a device is connected to Wi-Fi, or allowing background updates to an application.
Comments
There are currently no comments to show for this article.