diff --git a/advanced_topics.md b/advanced_topics.md deleted file mode 100644 index dcaf9661..00000000 --- a/advanced_topics.md +++ /dev/null @@ -1,93 +0,0 @@ -

ServiceWorkers Explained -- Advanced Topics

- -So you've read the [Explainer](explainer.md) for ServiceWorkers but you've still got questions -- great! We've got (more) answers. - -## Caching of ServiceWorker scripts - -The script that you register, as well as any additional scripts that -are imported during initial load, are persistently cached with a separate policy from normal web content, or any other web storage mechanisms. - -This allows the browser to start up the ServiceWorker at any point, generally in response to document loading. - -## Offline - -How does this handle offline, or more specifically, how does this replace AppCache? - -The fetch event is simply the gateway through which all network access for a given is managed. By intercepting all fetch events and optionally routing them through a cache, you can control access to the network, possibly avoiding it altogether. - -To do this you're going to need an actual Cache. ServiceWorkers (and eventually other contexts) have access to a separate Cache API which allows storage of arbitrary data that can be used to respond to fetch events. - -## Understanding ServiceWorker script Caching - -It's important to keep in mind that ServiceWorkers are a type of [Shared Worker](http://www.w3.org/TR/workers/#shared-workers-and-the-sharedworker-interface) -- uniquely imbued with additional APIs for access to cache objects and such -- but in general, what you can do in a Shared Worker, you can do in a ServiceWorker. That includes calling [`importScripts()`](https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers#Importing_scripts_and_libraries) to include libraries. - -`importScripts()` is a dynamic call, much like the addition of a ` - - - - - - Example App Logo - - +```js +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/my-app/sw.js', { + scope: '/my-app/' + }).then(function(reg) { + console.log('◕‿◕', reg); + }).catch(function() { + console.log('ಠ_ಠ', err); + }); +} ``` -The ServiceWorker itself is a bit of JavaScript that runs in a context that's very much like a [shared worker](http://www.whatwg.org/specs/web-apps/current-work/multipage/workers.html#shared-workers "HTML5 Shared Workers"). +In this example, `/my-app/sw.js` is the location of the ServiceWorker script, and it controls pages whose URL begins `/my-app/`. The scope is optional, and defaults to `/`. -The browser now attempts to download and "install" `worker.js`; a process covered later in this document. Once it is successfully installed, our `success!` message will be sent to the console and, crucially, the next time the user visits `index.html` or any other page located at `https://videos.example.com/`, `worker.js` will be consulted about what to do and what content to load -- even if the device has no internet connection. On pages that are "controlled" in this way, other resources (like the image in the body) are also requested first from `worker.js` before the normal browser cache is consulted for them. +`.register` returns a promise. If you're new to promises, check out the [HTML5Rocks article](http://www.html5rocks.com/en/tutorials/es6/promises/). -### Controlled & Uncontrolled Documents +Some restrictions: -The first time `https://videos.example.com/index.html` is loaded, all the resources it requests will come from the network. That means that even if the browser runs the install snippet for `worker.js`, fetches it, and finishes installing it before it begins fetching `logo.png`, the new ServiceWorker script won't be consulted about loading `logo.png`. This is the first rule of ServiceWorkers: +* The registering page must have been served securely (HTTPS without cert errors) +* The ServiceWorker script must be on the same origin as the page, although you can import scripts from other origins using `[importScripts](https://html.spec.whatwg.org/multipage/workers.html#apis-available-to-workers:dom-workerglobalscope-importscripts)` +* …as must the scope -> Documents live out their whole lives using the ServiceWorker they start with. +### HTTPS only you say? -This means that if a document starts life _without_ a ServiceWorker it won't suddenly get a ServiceWorker later in life, even if one is installed for a matching bit of URL space during the document's lifetime. This means documents that are loaded with a ServiceWorker which might later call `navigator.serviceWorker.unregister("/*")` do not become controlled by that worker. Put another way, `serviceWorker.register()` and `serviceWorker.unregister()` only affect *subsequent* navigations. +Using ServiceWorker you can hijack connections, respond differently, & filter responses. Powerful stuff. While you would use these powers for good, a man-in-the-middle might not. To avoid this, you can only register for ServiceWorkers on pages served over HTTPS, so we know the ServiceWorker the browser receives hasn't been tampered with during its journey through the network. -This is good for a couple of important reasons: +Github Pages are served over HTTPS, so they're a great place to host demos. - - Graceful fallback. Browsers that don't yet understand ServiceWorkers will still understand these pages. - - [Good URLs are forever](http://www.w3.org/Provider/Style/URI). Apps that respect some URLs with a ServiceWorker should do sane things without the SW in play. This is good for users and good for the web. - - Reasoning about a page that gets a ServiceWorker halfway through its lifetime -- or worse, has its ServiceWorker replaced -- is complicated. Allowing apps to opt into this (complicated) power is good, but making it the default is make-work for most apps. +## Initial lifecycle -## A Quick Game of `onfetch` +Your worker script goes through three stages when you call `.register`: -Once installed, ServiceWorkers can choose to handle resource requests. A navigation that matches the ServiceWorker's origin and scope will be handled by the ServiceWorker. +1. Download +2. Install +3. Activate -Here's an example of a ServiceWorker that only handles a single resource (`/services/inventory/data.json`) but which logs out requests for all resources it is consulted for: +You can use events to interact with `install` and `activate`: ```js -// hosted at /assets/v1/worker.js -this.version = 1; - -var base = "https://videos.example.com"; -var inventory = new URL("/services/inventory/data.json", base) + ""; - -this.addEventListener("install", function(e) { +self.addEventListener('install', function(event) { + event.waitUntil( + fetchStuffAndInitDatabases() + ); }); -this.addEventListener("fetch", function(e) { - var url = e.request.url; - console.log(url); - if (url == inventory) { - e.respondWith(new Response(JSON.stringify({ videos: { /* ... */ } }), { - headers: { 'Content-Type': 'application/json' }, - status: 200 - })); - } +self.addEventListener('activate', function(event) { + // You're good to go! }); ``` -This simple example will always produce the following output at the console when we load a tab with `https://videos.example.com/index.html`: - -``` -> https://videos.example.com/index.html -> https://videos.example.com/assets/v1/base.css -> https://videos.example.com/assets/v1/app.js -> https://videos.example.com/services/inventory/data.json -> https://videos.example.com/assets/v1/logo.png -``` - -The contents of all but the inventory will be handled by the normal browser resource fetching system because the `onfetch` event handler didn't call `respondWith` when invoked with their requests. The first time the app is loaded (before the ServiceWorker is installed), `data.json` will also be fetched from the network. Thereafter it'll be computed by the ServiceWorker instead. The important thing to remember here is that _normal resource loading is the fallback behavior for fetch events_. - -When combined with access to [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB) and a new form of Cache (covered below), the ability to respond with arbitrary content is incredibly powerful. Since installed ServiceWorkers are invoked even when offline, ServiceWorkers enable apps that are "offline by default" once installed. - -## Mental Notes - -A few things to keep in mind. The second rule of ServiceWorkers is: - -> ServiceWorkers may be killed *at any time*. - -That's right, the browser might unceremoniously kill your ServiceWorker if it's idle, or even stop it mid-work and re-issue the request to a different instance of the worker. There are no guarantees about how long a ServiceWorker will run. ServiceWorkers should be written to avoid holding global state. This can't be stressed enough: _write your workers as though they will die after every request_. +You can pass a promise to `event.waitUntil` to extend the installation process. Once `activate` fires, your ServiceWorker can control pages! -_Service Workers are shared resources_. A single worker might be servicing requests from multiple tabs or documents. Never assume that only one document is talking to a given ServiceWorker. If you care about where a request is coming from or going to, use the `.window` property of the `onfetch` event; but don't create state that you care about without serializing it somewhere like [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB). +## So I'm controlling pages now? -This should be familiar if you've developed servers using Django, Rails, Java, Node etc. A single instance handles connections from many clients (documents in our case) but data persistence is handled by something else, typically a database. +Well, not quite. A document will pick a ServiceWorker to be its controller when it navigates, so the document you called `.register` from isn't being controlled, because there wasn't a ServiceWorker there when it first loaded. -Lastly, exceptions or syntax errors that prevent running a ServiceWorker will ensure that the worker won't be considered successfully installed and won't be used on subsequent navigations. It pays to test. +If you refresh the document, it'll be under the ServiceWorker's control. You can check `navigator.serviceWorker.controller` to see which ServiceWorker is in control, or `null` if there isn't one. -### Resources & Navigations +If you shift+reload a document it'll always load without a controller, which is handy for testing quick CSS & JS changes. -Since loading documents and apps on the web boils down to an [HTTP request](http://shop.oreilly.com/product/9781565925090.do) the same way that any other sort of resource loading does, an interesting question arises: how do we distinguish loading a document from loading, say, an image or a CSS file that's a sub-resource for a document? And how can we distinguish between a top-level document and an ` - +```js +self.addEventListener('fetch', function(event) { + event.respondWith(new Response("Hello world!")); +}); ``` -Assuming a user visits them in order and both ServiceWorker install successfully, what happens the next time that user visits `/index.html`? What ServiceWorker is used for `/services/data?json=1`? - -The answer hinges on how requests map to ServiceWorkers. The third rule of ServiceWorkers is: - -> All _resource requests_ from a controlled document are sent to _that -> document's_ ServiceWorker. - -Looking back at our `index.html`, we see two different request types: a navigation for an `