2
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
3
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
4
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
5
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
6
Code distributed by Google as part of the polymer project is also
7
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9
<link rel="import" href="../polymer/polymer.html">
13
* The `<platinum-sw-register>` element handles
14
* [service worker](http://www.html5rocks.com/en/tutorials/service-worker/introduction/)
15
* registration, reflects the overall service worker state, and coordinates the configuration
16
* provided by other Service Worker Elements.
17
* `<platinum-sw-register>` is used as a parent element for child elements in the
18
* `<platinum-sw-*>` group.
20
* <platinum-sw-register skip-waiting
24
* on-service-worker-error="handleSWError"
25
* on-service-worker-updated="handleSWUpdated"
26
* on-service-worker-installed="handleSWInstalled">
27
* ...one or more <platinum-sw-*> children which share the service worker registration...
28
* </platinum-sw-register>
30
* Please see https://github.com/PolymerElements/platinum-sw#top-level-sw-importjs for a
31
* *crucial* prerequisite file you must create before `<platinum-sw-register>` can be used!
33
* @demo demo/index.html An offline-capable eReader demo.
36
is: 'platinum-sw-register',
38
// Used as an "emergency" switch if we make breaking changes in the way <platinum-sw-register>
39
// talks to service-worker.js. Otherwise, it shouldn't need to change, and isn't meant to be
40
// kept in sync with the element's release number.
44
* Fired when the initial service worker installation completes successfully.
45
* The service worker will normally only be installed once, the first time a page with a
46
* `<platinum-sw-register>` element is visited in a given browser. If the same page is visited
47
* again, the existing service worker will be reused, and there won't be another
48
* `service-worker-installed` fired.
50
* @event service-worker-installed
51
* @param {String} A message indicating that the installation succeeded.
55
* Fired when the service worker update flow completes successfully.
56
* If you make changes to your `<platinum-sw-register>` configuration (i.e. by adding in new
57
* `<platinum-sw-*>` child elements, or changing their attributes), users who had the old
58
* service worker installed will get the update installed when they see the modified elements.
60
* @event service-worker-updated
61
* @param {String} A message indicating that the update succeeded.
65
* Fired when an error prevents the service worker installation from completing.
67
* @event service-worker-error
68
* @param {String} A message indicating what went wrong.
73
* Whether this element should automatically register the corresponding service worker as
74
* soon as its added to a page.
76
* If set to `false`, then the service worker won't be automatically registered, and you
77
* must call this element's `register()` method if you want service worker functionality.
78
* This is useful if, for example, the service worker needs to be configured using
79
* information that isn't immediately available at the time the page loads.
81
* If set to `true`, the service worker will be automatically registered without having to
90
* The URI used as a base when constructing relative paths to service worker helper libraries
91
* that need to be loaded.
93
* This can normally be kept set to the default, which will use the directory containing this
94
* element as the base. However, if you [Vulcanize](https://github.com/polymer/vulcanize) your
95
* elements, then the default base might not be appropriate anymore. This will allow you to
98
* See https://github.com/PolymerElements/platinum-sw#relative-paths--vulcanization for more
103
// Grab the URI of this file to use as a base when resolving relative paths.
104
// See https://github.com/webcomponents/webcomponentsjs/blob/88240ba9ef4cebb1579e07f7888c7b58ec017a39/src/HTMLImports/base.js#L31
105
// for background on document._currentScript. We want to support document.currentScript
106
// as well, on the off chance that the polyfills aren't loaded.
107
// Fallback to './' as a default, though current browsers that don't support
108
// document.currentScript also don't support service workers.
109
value: document._currentScript ? document._currentScript.baseURI :
110
(document.currentScript ? document.currentScript.baseURI : './')
114
* Whether the activated service worker should [take immediate control](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#clients-claim-method)
115
* of any pages under its scope.
117
* If this is `false`, the service worker won't have any effect until the next time the page
118
* is visited/reloaded.
119
* If this is `true`, it will take control and start handling events for the current page
120
* (and any pages under the same scope open in other tabs/windows) as soon it's active.
121
* @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#clients-claim-method}
129
* The service worker script that is [registered](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-register).
130
* The script *should* be located at the top level of your site, to ensure that it is able
131
* to control all the pages on your site.
133
* It's *strongly* recommended that you create a top-level file named `sw-import.js`
136
* `importScripts('bower_components/platinum-sw/service-worker.js');`
138
* (adjust to match the path where your `platinum-sw` element directory can be found).
140
* This will ensure that your service worker script contains everything needed to play
141
* nicely with the Service Worker Elements group.
143
* @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-register}
147
value: 'sw-import.js'
151
* Whether the page should be automatically reloaded (via `window.location.reload()`) when
152
* the service worker is successfully installed.
154
* While it's perfectly valid to continue using a page with a freshly installed service
155
* worker, it's a common pattern to want to reload it immediately following the install.
156
* This ensures that, for example, if you're using a `<platinum-sw-cache>` with an on the
157
* fly caching strategy, it will get a chance to intercept all the requests needed to render
158
* your page and store them in the cache.
160
* If you don't immediately reload your page, then any resources that were loaded before the
161
* service worker was installed (e.g. this `platinum-sw-register.html` file) won't be present
162
* in the cache until the next time the page is loaded.
164
* Note that this reload will only happen when a service worker is installed for the first
165
* time. If the service worker is subsequently updated, it won't trigger another reload.
173
* By default, the service worker will use a scope that applies to all pages at the same
174
* directory level or lower. This is almost certainly what you want, as illustrated by the
175
* following hypothetical serving setup:
187
* So by default, registering `/root/service-worker.js` will cause the service worker's scope
188
* to cover `/root/index.html`, `/root/subdir1/index.html`, and /root/subdir2/index.html`.
190
* If, for some reason, you need to register `/root/service-worker.js` from within
191
* `/root/subdir1/index.html`, *and* you want that registration to only cover
192
* `/root/subdir1/**`, you can override this `scope` property and set it to `'./'`.
194
* There is more context about default scopes and how scope overrides work in
195
* [this Stack Overflow](http://stackoverflow.com/a/33881341/385997) response.
197
* @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-register}
205
* Whether an updated service worker should [bypass the `waiting` state](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-global-scope-skipwaiting)
206
* and immediately become `active`.
208
* Normally, during an update, the new service worker stays in the
209
* `waiting` state until the current page and any other tabs/windows that are using the old
210
* service worker are unloaded.
212
* If this is `false`, an updated service worker won't be activated until all instances of
213
* the old server worker have been unloaded.
215
* If this is `true`, an updated service worker will become `active` immediately.
216
* @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-global-scope-skipwaiting}
224
* The current state of the service worker registered by this element.
240
* Registers the service worker based on the configuration options in this element and any
243
* If you set the `autoRegister` property to `true`, then this method is called automatically
245
* It can be useful to set `autoRegister` to `false` and then explicitly call this method if
246
* there are options that are only configured after the page is loaded.
248
register: function() {
249
if ('serviceWorker' in navigator) {
250
this._constructServiceWorkerUrl().then(function(serviceWorkerUrl) {
251
this._registerServiceWorker(serviceWorkerUrl);
254
this._setState('unsupported');
255
this.fire('service-worker-error', 'Service workers are not available in the current browser.');
259
_constructServiceWorkerUrl: function() {
260
var paramsPromises = [];
261
var children = Polymer.dom(this).children;
262
var baseUri = new URL(this.baseUri, window.location.href);
264
for (var i = 0; i < children.length; i++) {
265
if (typeof children[i]._getParameters === 'function') {
266
paramsPromises.push(children[i]._getParameters(baseUri));
270
return Promise.all(paramsPromises).then(function(paramsResolutions) {
273
version: this._version
276
paramsResolutions.forEach(function(childParams) {
277
Object.keys(childParams).forEach(function(key) {
278
if (Array.isArray(params[key])) {
279
params[key] = params[key].concat(childParams[key]);
281
params[key] = [].concat(childParams[key]);
287
}.bind(this)).then(function(params) {
288
if (params.importscriptLate) {
289
if (params.importscript) {
290
params.importscript = params.importscript.concat(params.importscriptLate);
292
params.importscript = params.importscriptLate;
296
if (params.importscript) {
297
params.importscript = this._unique(params.importscript);
300
// We've already concatenated importscriptLate, so don't include it in the serialized URL.
301
delete params.importscriptLate;
303
params.clientsClaim = this.clientsClaim;
304
params.skipWaiting = this.skipWaiting;
306
var serviceWorkerUrl = new URL(this.href, window.location);
307
// It's very important to ensure that the serialization is stable.
308
// Serializing the same settings should always produce the same URL.
309
// Serializing different settings should always produce a different URL.
310
// This ensures that the service worker upgrade flow is triggered when settings change.
311
serviceWorkerUrl.search = this._serializeUrlParams(params);
313
return serviceWorkerUrl;
317
_unique: function(arr) {
318
return arr.filter(function(item, index) {
319
return arr.indexOf(item) === index;
323
_serializeUrlParams: function(params) {
324
return Object.keys(params).sort().map(function(key) {
325
// encodeURIComponent(['a', 'b']) => 'a%2Cb',
326
// so this will still work when the values are Arrays.
327
// TODO: It won't work if the values in the Arrays have ',' characters in them.
328
return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
332
_registerServiceWorker: function(serviceWorkerUrl) {
333
var options = this.scope ? {scope: this.scope} : null;
334
navigator.serviceWorker.register(serviceWorkerUrl, options).then(function(registration) {
335
if (registration.active) {
336
this._setState('installed');
339
registration.onupdatefound = function() {
340
var installingWorker = registration.installing;
341
installingWorker.onstatechange = function() {
342
switch (installingWorker.state) {
344
if (navigator.serviceWorker.controller) {
345
this._setState('updated');
346
this.fire('service-worker-updated',
347
'A new service worker was installed, replacing the old service worker.');
349
if (this.reloadOnInstall) {
350
window.location.reload();
352
this._setState('installed');
353
this.fire('service-worker-installed', 'A new service worker was installed.');
359
this._setState('error');
360
this.fire('service-worker-error', 'The installing service worker became redundant.');
365
}.bind(this)).catch(function(error) {
366
this._setState('error');
367
this.fire('service-worker-error', error.toString());
368
if (error.name === 'NetworkError') {
369
var location = serviceWorkerUrl.origin + serviceWorkerUrl.pathname;
370
console.error('A valid service worker script was not found at ' + location + '\n' +
371
'To learn how to fix this, please see\n' +
372
'https://github.com/PolymerElements/platinum-sw#top-level-sw-importjs');
377
attached: function() {
378
if (this.autoRegister) {
379
this.async(this.register);