diff --git a/dist/offliner-client.js b/dist/offliner-client.js
index d6dbd1b..7aebb58 100644
--- a/dist/offliner-client.js
+++ b/dist/offliner-client.js
@@ -79,21 +79,78 @@
* @return {Promise} A promise resolving if the installation success.
*/
install: function () {
- this._installMessageHandlers();
+ if (!('serviceWorker' in navigator)) {
+ return Promise.reject(new Error('serviceworkers-not-supported'));
+ }
+
return navigator.serviceWorker.register(workerURL, {
scope: root
- });
+ }).then(function (registration) {
+ return this.connect().then(function () {
+ return registration;
+ });
+ }.bind(this));
},
/**
- * If you are using offliner as a serviceworkerware middleware, instead
- * of calling {{#crossLink OfflinerClient/install:method}}{{/crossLink}},
- * call `connect()` to avoid registering the worker.
+ * Keeps the promise of connect.
+ *
+ * @property _connecting
+ * @type Promise
+ * @private
+ */
+ _connecting: null,
+
+ /**
+ * Connects the client with offliner allowing the client to control offliner
+ * and receive events.
*
* @method connect
+ * @return {Promise} A promise resolving once connection has been stablished
+ * with the worker and communication is possible.
*/
connect: function () {
- this._installMessageHandlers();
+ if (!this._connecting) { this._connecting = this._connect(); }
+ return this._connecting;
+ },
+
+ /**
+ * The actual implementation for {{#crossLink "connect:method"}}{{/crossLink}}
+ *
+ * @method _connect
+ * @return {Promise} A promise resolving once connection has been stablished
+ * with the worker and communication is possible.
+ * @private
+ */
+ _connect: function () {
+ if (!('serviceWorker' in navigator)) {
+ return Promise.reject(new Error('serviceworkers-not-supported'));
+ }
+
+ var installMessageHandlers = this._installMessageHandlers.bind(this);
+ var checkForActivationPending = this._checkForActivationPending.bind(this);
+ return new Promise(function (fulfill, reject) {
+ navigator.serviceWorker.getRegistration(root).then(function (registration) {
+ if (registration.active) {
+ installMessageHandlers();
+ checkForActivationPending();
+ return fulfill();
+ }
+
+ var installingWorker = registration.installing;
+ if (!installingWorker) {
+ return reject(new Error('impossible-to-connect'));
+ }
+
+ installingWorker.onstatechange = function () {
+ if (installingWorker.state === 'installed') {
+ installMessageHandlers();
+ checkForActivationPending();
+ fulfill();
+ }
+ };
+ });
+ });
},
/**
@@ -117,7 +174,7 @@
* @method update
* @return {Promise} If the update process is successful, the promise will
* resolve to a new version and an
- * {{#crossLink OfflinerClient/activationPending:event}}{{/crossLink}}
+ * {{#crossLink "OfflinerClient/activationPending:event"}}{{/crossLink}}
* will be triggered. If the update is not needed, the promise will be
* rejected with `no-update-needed` reason.
*/
@@ -145,6 +202,7 @@
* @param type {String} The type of the events selecting the listeners to
* be run.
* @param evt {Object} The event contents.
+ * @private
*/
_runListeners: function (type, evt) {
var listeners = this._eventListeners[type] || [];
@@ -160,6 +218,7 @@
* and the client code.
*
* @method _installMessageHandlers
+ * @private
*/
_installMessageHandlers: function installMessageHandlers() {
var that = this;
@@ -185,7 +244,20 @@
},
/**
- * Discriminates between {{#crossLink OfflinerClient/xpromise:event}}{{/crossLink}}
+ * Make offliner to check for pending activations and dispatch
+ * {{#crossLink "OfflinerClient/activationPending:event"}}{{/crossLink}}
+ * if so.
+ *
+ * @method _checkForActivationPending
+ * @private
+ */
+ _checkForActivationPending: function () {
+ // TODO: should we add a prefix for offliner messages?
+ this._send({ type: 'checkForActivationPending' });
+ },
+
+ /**
+ * Discriminates between {{#crossLink "OfflinerClient/xpromise:event"}}{{/crossLink}}
* events which are treated in a special way and the rest of the events that
* simply trigger the default dispatching algorithm.
*
@@ -193,6 +265,7 @@
* @param offlinerType {String} The type of the message without the
* `offliner:` prefix.
* @param msg {Any} The event.
+ * @private
*/
_handleMessage: function (offlinerType, msg) {
var sw = navigator.serviceWorker;
@@ -211,6 +284,7 @@
* @param willBeThis {Object} The context object `this` which the function
* will be called with.
* @return `true` if the listener registration already exists.
+ * @private
*/
_has: function (type, handler, willBeThis) {
var listeners = this._eventListeners[type] || [];
@@ -233,6 +307,7 @@
* the implementation to run.
* @return {Promise} A promise delegating its implementation in some code
* running in a worker.
+ * @private
*/
_xpromise: function (order) {
return new Promise(function (accept, reject) {
@@ -253,16 +328,18 @@
/**
* Sends a message to the worker.
+ *
* @method _send
* @param msg {Any} The message to be sent.
+ * @private
*/
_send: function (msg) {
- navigator.serviceWorker.getRegistration()
+ navigator.serviceWorker.getRegistration(root)
.then(function (registration) {
if (!registration || !registration.active) {
// TODO: Wait for the service worker to be active and try to
// resend.
- warn('Not service worker active right now.');
+ console.warn('Not service worker active right now.');
}
else {
return registration.active.postMessage(msg);
@@ -276,6 +353,7 @@
*
* @method _resolveCrossPromise
* @param msg {Object} An object with the proper data to resolve a xpromise.
+ * @private
*/
_resolveCrossPromise: function (msg) {
var implementation = this._xpromises[msg.id];
diff --git a/dist/offliner-client.min.js b/dist/offliner-client.min.js
index 15fe5b8..9d78d1a 100644
--- a/dist/offliner-client.min.js
+++ b/dist/offliner-client.min.js
@@ -1 +1 @@
-!function(e){"use strict";var t=1,n=e.off,r=function(){var e=new URL(document.currentScript.dataset.root||"",window.location.origin).href;return e.endsWith("/")?e:e+"/"}(),s=r+(document.currentScript.dataset.worker||"offliner-worker.js");e.off={_eventListeners:{},_xpromises:{},restore:function(){return e.off=n,this},install:function(){return this._installMessageHandlers(),navigator.serviceWorker.register(s,{scope:r})},connect:function(){this._installMessageHandlers()},on:function(e,t,n){this._has(e,t,n)||(this._eventListeners[e]=this._eventListeners[e]||[],this._eventListeners[e].push([t,n]))},update:function(){return this._xpromise("update")},activate:function(){return this._xpromise("activate")},_runListeners:function(e,t){var n=this._eventListeners[e]||[];n.forEach(function(e){var n=e[0],r=e[1];n.call(r,t)})},_installMessageHandlers:function i(){function e(e){var n=e.data,r=n?n.type:"",s=r.split(":");"offliner"===s[0]&&t._handleMessage(s[1],n)}var t=this;if(!i.done){if("function"==typeof BroadcastChannel){var n=new BroadcastChannel("offliner-channel");n.onmessage=e}else navigator.serviceWorker.addEventListener("message",e);i.done=!0}},_handleMessage:function(e,t){navigator.serviceWorker;"xpromise"===e?this._resolveCrossPromise(t):this._runListeners(e,t)},_has:function(e,t,n){for(var r,s=this._eventListeners[e]||[],i=0;r=s[i];i++)if(r[0]===t&&r[1]===n)return!0;return!1},_xpromise:function(e){return new Promise(function(n,r){function s(e){r(new Error(e))}var i=t++,o={type:"xpromise",id:i,order:e};this._xpromises[i]=[n,s],this._send(o)}.bind(this))},_send:function(e){navigator.serviceWorker.getRegistration().then(function(t){return t&&t.active?t.active.postMessage(e):void warn("Not service worker active right now.")})},_resolveCrossPromise:function(e){var t=this._xpromises[e.id];t?t["rejected"===e.status?1:0](e.value):console.warn("Trying to resolve unexistent promise:",e.id)}}}(this.exports||this);
\ No newline at end of file
+!function(e){"use strict";var n=1,t=e.off,r=function(){var e=new URL(document.currentScript.dataset.root||"",window.location.origin).href;return e.endsWith("/")?e:e+"/"}(),i=r+(document.currentScript.dataset.worker||"offliner-worker.js");e.off={_eventListeners:{},_xpromises:{},restore:function(){return e.off=t,this},install:function(){return"serviceWorker"in navigator?navigator.serviceWorker.register(i,{scope:r}).then(function(e){return this.connect().then(function(){return e})}.bind(this)):Promise.reject(new Error("serviceworkers-not-supported"))},_connecting:null,connect:function(){return this._connecting||(this._connecting=this._connect()),this._connecting},_connect:function(){if(!("serviceWorker"in navigator))return Promise.reject(new Error("serviceworkers-not-supported"));var e=this._installMessageHandlers.bind(this),n=this._checkForActivationPending.bind(this);return new Promise(function(t,i){navigator.serviceWorker.getRegistration(r).then(function(r){if(r.active)return e(),n(),t();var s=r.installing;return s?void(s.onstatechange=function(){"installed"===s.state&&(e(),n(),t())}):i(new Error("impossible-to-connect"))})})},on:function(e,n,t){this._has(e,n,t)||(this._eventListeners[e]=this._eventListeners[e]||[],this._eventListeners[e].push([n,t]))},update:function(){return this._xpromise("update")},activate:function(){return this._xpromise("activate")},_runListeners:function(e,n){var t=this._eventListeners[e]||[];t.forEach(function(e){var t=e[0],r=e[1];t.call(r,n)})},_installMessageHandlers:function s(){function e(e){var t=e.data,r=t?t.type:"",i=r.split(":");"offliner"===i[0]&&n._handleMessage(i[1],t)}var n=this;if(!s.done){if("function"==typeof BroadcastChannel){var t=new BroadcastChannel("offliner-channel");t.onmessage=e}else navigator.serviceWorker.addEventListener("message",e);s.done=!0}},_checkForActivationPending:function(){this._send({type:"checkForActivationPending"})},_handleMessage:function(e,n){navigator.serviceWorker;"xpromise"===e?this._resolveCrossPromise(n):this._runListeners(e,n)},_has:function(e,n,t){for(var r,i=this._eventListeners[e]||[],s=0;r=i[s];s++)if(r[0]===n&&r[1]===t)return!0;return!1},_xpromise:function(e){return new Promise(function(t,r){function i(e){r(new Error(e))}var s=n++,o={type:"xpromise",id:s,order:e};this._xpromises[s]=[t,i],this._send(o)}.bind(this))},_send:function(e){navigator.serviceWorker.getRegistration(r).then(function(n){return n&&n.active?n.active.postMessage(e):void console.warn("Not service worker active right now.")})},_resolveCrossPromise:function(e){var n=this._xpromises[e.id];n?n["rejected"===e.status?1:0](e.value):console.warn("Trying to resolve unexistent promise:",e.id)}}}(this.exports||this);
\ No newline at end of file
diff --git a/dist/offliner.js b/dist/offliner.js
index 3e2fe40..939a4eb 100644
--- a/dist/offliner.js
+++ b/dist/offliner.js
@@ -130,7 +130,7 @@
* @method standalone
* @throws {Error} offliner throws when trying to install it in standalone
* mode if it was already used as middleware by calling
- * {{#crossLink Offliner/asMiddleware:method}}{{/crossLink}}.
+ * {{#crossLink "Offliner/asMiddleware:method"}}{{/crossLink}}.
*/
Offliner.prototype.standalone = function () {
if (this._isMiddleware) {
@@ -185,7 +185,7 @@
* @method asMiddleware
* @return {Object} A serviceworkerware middleware.
* @throws {Error} offliner will throw if you try to use it as middleware
- * after calling {{#crossLink Offliner/standalone:method}}{{/crossLink}}.
+ * after calling {{#crossLink "Offliner/standalone:method"}}{{/crossLink}}.
*/
Offliner.prototype.asMiddleware = function () {
if (this._isStarted) {
@@ -228,6 +228,9 @@
case 'xpromise':
this._receiveCrossPromise(msg.id, msg.order);
break;
+ case 'checkForActivationPending':
+ this._checkForActivationPending();
+ break;
default:
warn('Message not recognized:', msg);
break;
@@ -263,6 +266,21 @@
}
};
+ /**
+ * Check if there is an activation pending. If so, offliner dispatches an
+ * activation pending request.
+ *
+ * @method _checkForActivationPending
+ * @private
+ */
+ Offliner.prototype._checkForActivationPending = function () {
+ this.get('activation-pending').then(function (isActivationPending) {
+ if (isActivationPending) {
+ this._sendActivationPending();
+ }
+ }.bind(this));
+ };
+
/**
* Resolves a cross promise.
*
@@ -461,6 +479,7 @@
*
* @method _sendActivationDone
* @private
+ * @for Offliner
*/
Offliner.prototype._sendActivationDone = function () {
this._broadcastMessage({ type: 'activationDone' });
@@ -480,7 +499,7 @@
/**
* Broadcast a message in the clients. The method will add the `offliner:`
* prefix to the type of the events but this is stripped out automatically by
- * the {{#crossLink OfflinerClient/_installMessageHandlers:method}}{{/crossLink}}
+ * the {{#crossLink "OfflinerClient/_installMessageHandlers:method"}}{{/crossLink}}
* client side.
*
* @method _broadcastMessage
@@ -768,7 +787,7 @@
*/
/**
- * Normalizes a resource not following the {{#crossLink Resource}}
+ * Normalizes a resource not following the {{#crossLink "Resource"}}
* {{/crossLink}} convention.
*
* @method normalize
@@ -798,7 +817,7 @@
}
/**
- * Register a {{#crossLink Fetcher}}{{/crossLink}}. The fetcher will be used
+ * Register a {{#crossLink "Fetcher"}}{{/crossLink}}. The fetcher will be used
* to retrieve the resources of the fetcher's type.
*
* @method use
diff --git a/dist/offliner.min.js b/dist/offliner.min.js
index 74b803b..4a56221 100644
--- a/dist/offliner.min.js
+++ b/dist/offliner.min.js
@@ -1 +1 @@
-!function(e){"use strict";function t(e){Object.defineProperty(this,"_uniquename",{get:function(){return e?e+":":""}}),this._isStarted=!1,this._isMiddleware=!1,this._middleware=null,Object.defineProperty(this,"_updateControl",{value:{scheduled:!1,alreadyRunOnce:!1,intervalId:null,inProgressProcess:null}}),Object.defineProperty(this,"fetch",{value:new r}),Object.defineProperty(this,"prefetch",{value:new n}),Object.defineProperty(this,"update",{value:new i})}function n(){this._resourceFetchers={},this._resources=[]}function i(){this._options={}}function r(){this._pipeline=[]}["log","warn","error"].forEach(function(t){e[t]=console[t].bind(console)});var o="-offliner:v0",s="__offliner-config";t.prototype.standalone=function(){if(this._isMiddleware)throw new Error("offliner has been already started as a middleware.");this._isStarted||(e.addEventListener("install",function(t){t.waitUntil(this._install().then(function(){return log("Offliner installed"),"function"==typeof e.skipWaiting?e.skipWaiting():Promise.resolve()}))}.bind(this)),e.addEventListener("activate",function(t){var n=function(){return log("Offliner activated!"),"function"==typeof e.clients.claim?e.clients.claim():Promise.resolve()};t.waitUntil(this._activate().then(n,n))}.bind(this)),e.addEventListener("fetch",function(e){e.respondWith("GET"!==e.request.method?fetch(e.request):this._fetch(e.request))}.bind(this)),e.addEventListener("message",function(e){this._processMessage(e.data)}.bind(this)),this._isStarted=!0)},t.prototype.asMiddleware=function(){if(this._isStarted)throw new Error("offliner has been already installed in standalone mode");return this._middleware||(this._middleware={onInstall:this._install.bind(this),onActivate:this._activate.bind(this),onFetch:function(e,t){return t||"GET"!==e.method?Promise.resolve(t):void this._fetch(e)}.bind(this),onMessage:function(e){this._processMessage(e.data)}.bind(this)}),this._isMiddleware=!0,this._middleware},t.prototype._activate=function(){return this.get("activation-pending").then(function(e){e&&this._sendActivationPending()}.bind(this))},t.prototype._processMessage=function(e){switch(e.type){case"xpromise":this._receiveCrossPromise(e.id,e.order);break;default:warn("Message not recognized:",e)}},t.prototype._receiveCrossPromise=function(e,t){switch(t){case"update":;this._update().then(this._resolve.bind(this,e),this._reject.bind(this,e));break;case"activate":this._activateNextCache().then(this._resolve.bind(this,e),this._reject.bind(this,e));break;default:warn("Cross Promise implementation not recognized:",t)}},t.prototype._resolve=function(e,t){this._resolvePromise(e,"resolved",t)},t.prototype._reject=function(e,t){this._resolvePromise(e,"rejected",t)},t.prototype._resolvePromise=function(e,t,n){this._broadcastMessage({type:"xpromise",id:e,status:t,value:n})},t.prototype.get=function(e){var t=this._getConfigURL(e);return caches.open(s).then(function(e){return e.match(t).then(function(e){return e?e.json():Promise.resolve(null)})})},t.prototype.set=function(e,t){var n=this._getConfigURL(e),i=new Response(JSON.stringify(t));return caches.open(s).then(function(e){return e.put(n,i)})},t.prototype._getConfigURL=function(e){return"http://config/"+this._uniquename+e},t.prototype._install=function(){var e=!0;return this.get("current-version").then(function(t){var n=this.update.option("enabled");return t?n?this._update(e):Promise.resolve():this._initialize().then(this._prefetch.bind(this))}.bind(this),error)},t.prototype._initialize=function(){return this._getCacheNameForVersion(o).then(this.set.bind(this,"active-cache")).then(this.set.bind(this,"current-version",o)).then(this.set.bind(this,"activation-pending",!1))},t.prototype._update=function(e){function t(e){return e?i._getCacheNameForVersion(e).then(caches.open.bind(caches)).then(i._evolveCache.bind(i)).then(i.set.bind(i,"activation-pending",!0)).then(i._sendActivationPending.bind(i)).then(function(){return n(),Promise.resolve(e)}):(n(),Promise.reject("no-update-needed"))}function n(){i._updateControl.alreadyRunOnce=!0,i._updateControl.inProgressProcess=null}var i=this;return this._updateControl.inProgressProcess||(this._updateControl.inProgressProcess=this.get("current-version").then(function(t){this.update.flags={isCalledFromInstall:e,isFirstUpdate:t===o}}.bind(this)).then(this._getLatestVersion.bind(this)).then(this._checkIfNewVersion.bind(this)).then(t)),this._updateControl.inProgressProcess},t.prototype._sendActivationPending=function(){this._broadcastMessage({type:"activationPending"})},t.prototype._sendActivationDone=function(){this._broadcastMessage({type:"activationDone"})},t.prototype._sendActivationFailed=function(){this._broadcastMessage({type:"activationFailed"})},t.prototype._broadcastMessage=function(e){if(e.type="offliner:"+e.type,this._isMiddleware)this.asMiddleware().broadcastMessage(e,"offliner-channel");else if("function"==typeof BroadcastChannel){var t=new BroadcastChannel("offliner-channel");t.postMessage(e),t.close()}else clients.matchAll().then(function(t){t.forEach(function(t){t.postMessage(e)})})},t.prototype._getCacheNameForVersion=function(e){return Promise.resolve(this._uniquename+"cache-"+e)},t.prototype._prefetch=function(){return this._openActiveCache().then(this._doPrefetch.bind(this))},t.prototype._doPrefetch=function(e){function t(e,t){var n=e.reduce(function(e,t){return e[t.type]=[],e},{});return t.forEach(function(e){var t=n[e.type];t&&t.push(e)}),n}var n=this.prefetch.resources(),i=this.prefetch.fetchers(),r=t(i,n);return i.reduce(function(t,n){return t.then(function(){var t=r[n.type];return n.prefetch(t,e)})},Promise.resolve())},t.prototype._getLatestVersion=function(){return this.update.check()},t.prototype._checkIfNewVersion=function(e){return this.get("current-version").then(function(t){var n=this.update.isNewVersion(t,e);return n?(log("New version "+e+" found!"),log(t?"Updating from version "+t:"First update"),this.set("next-version",e).then(function(){return e})):(log("No update needed"),null)}.bind(this))},t.prototype._evolveCache=function(e){return this._openActiveCache().then(function(t){var n=this._doPrefetch.bind(this,e);return this.update.evolve(t,e,n)}.bind(this))},t.prototype._openActiveCache=function(){return this.get("active-cache").then(caches.open.bind(caches))},t.prototype._activateNextCache=function(){return this.get("activation-pending").then(function(e){return e?this._swapCaches().then(this._updateCurrentVersion.bind(this)):Promise.reject("no-activation-pending")}.bind(this))},t.prototype._swapCaches=function(){function e(){return r.get("active-cache")}function t(){return r.get("next-version").then(r._getCacheNameForVersion.bind(r))}function n(e){var t=(e[0],e[1]);return r.set("active-cache",t).then(i([t,s]))}function i(e){return function(){return caches.keys().then(function(t){return Promise.all(t.filter(function(t){return e.indexOf(t)<0}).map(function(e){return caches["delete"](e)}))})}}var r=this;return Promise.all([e(),t()]).then(n)},t.prototype._updateCurrentVersion=function(){var e=this.get("next-version");return e.then(this.set.bind(this,"current-version")).then(this.set.bind(this,"activation-pending",!1)).then(function(){return e})},t.prototype._fetch=function(e){return new Promise(function(t,n){this._openActiveCache().then(function(i){function r(o,s){s=s||0;o.length;s===o.length?n():o[s](e,i).then(t,function(){r(o,s+1)})}var o=this.fetch.pipeline();r(o)}.bind(this))}.bind(this))},n.prototype.use=function(e){return this._resourceFetchers[e.type]=e,this._activeFetcher=e,this},n.prototype.resources=function(e){if(0===arguments.length)return this._resources;Array.isArray(e)||(e=[e]);for(var t,n=0;t=e[n];n++){var i;if("object"!=typeof t||!t||!t.type)try{i=this._activeFetcher.normalize(t)}catch(r){}i?this._resources.push(i):warn(t,"can not be normalized by",this._activeFetcher.type)}return this},n.prototype.fetchers=function(){return Object.keys(this._resourceFetchers).map(function(e){return this._resourceFetchers[e]}.bind(this))},i.prototype.option=function(e,t){return 2===arguments.length?(this._options[e]=t,this):1===arguments.length?this._options[e]:void 0},i.prototype.use=function(e){return this.option("enabled",!0),this._impl=e,this},Object.defineProperty(i.prototype,"flags",{set:function(e){this._impl.flags=e},get:function(){return this._impl.flags}}),i.prototype.check=function(){return this._impl&&this._impl.check()},i.prototype.isNewVersion=function(e,t){return this._impl.isNewVersion(e,t)},i.prototype.evolve=function(e,t,n){return this._impl.evolve(e,t,n)},r.prototype.use=function(e){return this._pipeline.push(e),this},r.prototype.pipeline=function(){return this._pipeline},r.prototype.orFail=function(){this.use(function(){return Promise.reject(new Error("End of fetch pipeline!"))})},e.off={},e.off.Offliner=t,e.off.sources={},e.off.fetchers={},e.off.updaters={}}("undefined"==typeof self?this:self);
\ No newline at end of file
+!function(e){"use strict";function t(e){Object.defineProperty(this,"_uniquename",{get:function(){return e?e+":":""}}),this._isStarted=!1,this._isMiddleware=!1,this._middleware=null,Object.defineProperty(this,"_updateControl",{value:{scheduled:!1,alreadyRunOnce:!1,intervalId:null,inProgressProcess:null}}),Object.defineProperty(this,"fetch",{value:new r}),Object.defineProperty(this,"prefetch",{value:new n}),Object.defineProperty(this,"update",{value:new i})}function n(){this._resourceFetchers={},this._resources=[]}function i(){this._options={}}function r(){this._pipeline=[]}["log","warn","error"].forEach(function(t){e[t]=console[t].bind(console)});var o="-offliner:v0",s="__offliner-config";t.prototype.standalone=function(){if(this._isMiddleware)throw new Error("offliner has been already started as a middleware.");this._isStarted||(e.addEventListener("install",function(t){t.waitUntil(this._install().then(function(){return log("Offliner installed"),"function"==typeof e.skipWaiting?e.skipWaiting():Promise.resolve()}))}.bind(this)),e.addEventListener("activate",function(t){var n=function(){return log("Offliner activated!"),"function"==typeof e.clients.claim?e.clients.claim():Promise.resolve()};t.waitUntil(this._activate().then(n,n))}.bind(this)),e.addEventListener("fetch",function(e){"GET"!==e.request.method?e.respondWith(fetch(e.request)):e.respondWith(this._fetch(e.request))}.bind(this)),e.addEventListener("message",function(e){this._processMessage(e.data)}.bind(this)),this._isStarted=!0)},t.prototype.asMiddleware=function(){if(this._isStarted)throw new Error("offliner has been already installed in standalone mode");return this._middleware||(this._middleware={onInstall:this._install.bind(this),onActivate:this._activate.bind(this),onFetch:function(e,t){return t||"GET"!==e.method?Promise.resolve(t):void this._fetch(e)}.bind(this),onMessage:function(e){this._processMessage(e.data)}.bind(this)}),this._isMiddleware=!0,this._middleware},t.prototype._activate=function(){return this.get("activation-pending").then(function(e){e&&this._sendActivationPending()}.bind(this))},t.prototype._processMessage=function(e){switch(e.type){case"xpromise":this._receiveCrossPromise(e.id,e.order);break;case"checkForActivationPending":this._checkForActivationPending();break;default:warn("Message not recognized:",e)}},t.prototype._receiveCrossPromise=function(e,t){switch(t){case"update":;this._update().then(this._resolve.bind(this,e),this._reject.bind(this,e));break;case"activate":this._activateNextCache().then(this._resolve.bind(this,e),this._reject.bind(this,e));break;default:warn("Cross Promise implementation not recognized:",t)}},t.prototype._checkForActivationPending=function(){this.get("activation-pending").then(function(e){e&&this._sendActivationPending()}.bind(this))},t.prototype._resolve=function(e,t){this._resolvePromise(e,"resolved",t)},t.prototype._reject=function(e,t){this._resolvePromise(e,"rejected",t)},t.prototype._resolvePromise=function(e,t,n){this._broadcastMessage({type:"xpromise",id:e,status:t,value:n})},t.prototype.get=function(e){var t=this._getConfigURL(e);return caches.open(s).then(function(e){return e.match(t).then(function(e){return e?e.json():Promise.resolve(null)})})},t.prototype.set=function(e,t){var n=this._getConfigURL(e),i=new Response(JSON.stringify(t));return caches.open(s).then(function(e){return e.put(n,i)})},t.prototype._getConfigURL=function(e){return"http://config/"+this._uniquename+e},t.prototype._install=function(){var e=!0;return this.get("current-version").then(function(t){var n=this.update.option("enabled");return t?n?this._update(e):Promise.resolve():this._initialize().then(this._prefetch.bind(this))}.bind(this),error)},t.prototype._initialize=function(){return this._getCacheNameForVersion(o).then(this.set.bind(this,"active-cache")).then(this.set.bind(this,"current-version",o)).then(this.set.bind(this,"activation-pending",!1))},t.prototype._update=function(e){function t(e){return e?i._getCacheNameForVersion(e).then(caches.open.bind(caches)).then(i._evolveCache.bind(i)).then(i.set.bind(i,"activation-pending",!0)).then(i._sendActivationPending.bind(i)).then(function(){return n(),Promise.resolve(e)}):(n(),Promise.reject("no-update-needed"))}function n(){i._updateControl.alreadyRunOnce=!0,i._updateControl.inProgressProcess=null}var i=this;return this._updateControl.inProgressProcess||(this._updateControl.inProgressProcess=this.get("current-version").then(function(t){this.update.flags={isCalledFromInstall:e,isFirstUpdate:t===o}}.bind(this)).then(this._getLatestVersion.bind(this)).then(this._checkIfNewVersion.bind(this)).then(t)),this._updateControl.inProgressProcess},t.prototype._sendActivationPending=function(){this._broadcastMessage({type:"activationPending"})},t.prototype._sendActivationDone=function(){this._broadcastMessage({type:"activationDone"})},t.prototype._sendActivationFailed=function(){this._broadcastMessage({type:"activationFailed"})},t.prototype._broadcastMessage=function(e){if(e.type="offliner:"+e.type,this._isMiddleware)this.asMiddleware().broadcastMessage(e,"offliner-channel");else if("function"==typeof BroadcastChannel){var t=new BroadcastChannel("offliner-channel");t.postMessage(e),t.close()}else clients.matchAll().then(function(t){t.forEach(function(t){t.postMessage(e)})})},t.prototype._getCacheNameForVersion=function(e){return Promise.resolve(this._uniquename+"cache-"+e)},t.prototype._prefetch=function(){return this._openActiveCache().then(this._doPrefetch.bind(this))},t.prototype._doPrefetch=function(e){function t(e,t){var n=e.reduce(function(e,t){return e[t.type]=[],e},{});return t.forEach(function(e){var t=n[e.type];t&&t.push(e)}),n}var n=this.prefetch.resources(),i=this.prefetch.fetchers(),r=t(i,n);return i.reduce(function(t,n){return t.then(function(){var t=r[n.type];return n.prefetch(t,e)})},Promise.resolve())},t.prototype._getLatestVersion=function(){return this.update.check()},t.prototype._checkIfNewVersion=function(e){return this.get("current-version").then(function(t){var n=this.update.isNewVersion(t,e);return n?(log("New version "+e+" found!"),t?log("Updating from version "+t):log("First update"),this.set("next-version",e).then(function(){return e})):(log("No update needed"),null)}.bind(this))},t.prototype._evolveCache=function(e){return this._openActiveCache().then(function(t){var n=this._doPrefetch.bind(this,e);return this.update.evolve(t,e,n)}.bind(this))},t.prototype._openActiveCache=function(){return this.get("active-cache").then(caches.open.bind(caches))},t.prototype._activateNextCache=function(){return this.get("activation-pending").then(function(e){return e?this._swapCaches().then(this._updateCurrentVersion.bind(this)):Promise.reject("no-activation-pending")}.bind(this))},t.prototype._swapCaches=function(){function e(){return r.get("active-cache")}function t(){return r.get("next-version").then(r._getCacheNameForVersion.bind(r))}function n(e){var t=(e[0],e[1]);return r.set("active-cache",t).then(i([t,s]))}function i(e){return function(){return caches.keys().then(function(t){return Promise.all(t.filter(function(t){return e.indexOf(t)<0}).map(function(e){return caches["delete"](e)}))})}}var r=this;return Promise.all([e(),t()]).then(n)},t.prototype._updateCurrentVersion=function(){var e=this.get("next-version");return e.then(this.set.bind(this,"current-version")).then(this.set.bind(this,"activation-pending",!1)).then(function(){return e})},t.prototype._fetch=function(e){return new Promise(function(t,n){this._openActiveCache().then(function(i){function r(o,s){s=s||0;o.length;s===o.length?n():o[s](e,i).then(t,function(){r(o,s+1)})}var o=this.fetch.pipeline();r(o)}.bind(this))}.bind(this))},n.prototype.use=function(e){return this._resourceFetchers[e.type]=e,this._activeFetcher=e,this},n.prototype.resources=function(e){if(0===arguments.length)return this._resources;Array.isArray(e)||(e=[e]);for(var t,n=0;t=e[n];n++){var i;if("object"!=typeof t||!t||!t.type)try{i=this._activeFetcher.normalize(t)}catch(r){}i?this._resources.push(i):warn(t,"can not be normalized by",this._activeFetcher.type)}return this},n.prototype.fetchers=function(){return Object.keys(this._resourceFetchers).map(function(e){return this._resourceFetchers[e]}.bind(this))},i.prototype.option=function(e,t){return 2===arguments.length?(this._options[e]=t,this):1===arguments.length?this._options[e]:void 0},i.prototype.use=function(e){return this.option("enabled",!0),this._impl=e,this},Object.defineProperty(i.prototype,"flags",{set:function(e){this._impl.flags=e},get:function(){return this._impl.flags}}),i.prototype.check=function(){return this._impl&&this._impl.check()},i.prototype.isNewVersion=function(e,t){return this._impl.isNewVersion(e,t)},i.prototype.evolve=function(e,t,n){return this._impl.evolve(e,t,n)},r.prototype.use=function(e){return this._pipeline.push(e),this},r.prototype.pipeline=function(){return this._pipeline},r.prototype.orFail=function(){this.use(function(){return Promise.reject(new Error("End of fetch pipeline!"))})},e.off={},e.off.Offliner=t,e.off.sources={},e.off.fetchers={},e.off.updaters={}}("undefined"==typeof self?this:self);
\ No newline at end of file
diff --git a/docs/classes/FetchConfig.html b/docs/classes/FetchConfig.html
index f828501..930eb78 100644
--- a/docs/classes/FetchConfig.html
+++ b/docs/classes/FetchConfig.html
@@ -116,7 +116,7 @@
Broadcast a message in the clients. The method will add the offliner:
+prefix to the type of the events but this is stripped out automatically by
+the _installMessageHandlers
+client side.
Broadcast a message in the clients. The method will add the offliner:
-prefix to the type of the events but this is stripped out automatically by
-the
-client side.
+
Make offliner to check for pending activations and dispatch
+activationPending
+if so.
Processes prefetch declared resources using the registered middlewares.
+
Discriminates between OfflinerClient/xpromise:event
+events which are treated in a special way and the rest of the events that
+simply trigger the default dispatching algorithm.
@@ -722,14 +540,31 @@
Parameters:
- cache
- Cache
+ offlinerType
+ String
+
+
+
+
+
+
The type of the message without the
+offliner: prefix.
If the update process is successful, the promise will
resolve to a new version and an
-
+activationPending
will be triggered. If the update is not needed, the promise will be
rejected with no-update-needed reason.
+(function (exports) {
+ 'use strict';
+
+ var nextPromiseId = 1;
+
+ var originalOff = exports.off;
+
+ var root = (function () {
+ var root = new URL(
+ document.currentScript.dataset.root || '',
+ window.location.origin
+ ).href;
+ return root.endsWith('/') ? root : root + '/';
+ }());
+
+ var workerURL =
+ root + (document.currentScript.dataset.worker || 'offliner-worker.js');
+
+ /**
+ * The exported global `off` object contains methods for communicating with
+ * the offliner worker in charge.
+ *
+ * @class OfflinerClient
+ */
+ exports.off = {
+
+ /**
+ * Callbacks for the events.
+ *
+ * @property _eventListeners
+ * @type Object
+ * @private
+ */
+ _eventListeners: {},
+
+ /**
+ * Implementation callbacks for cross promises by its unique id.
+ *
+ * @property _xpromises
+ * @type Object
+ * @private
+ */
+ _xpromises: {},
+
+ /**
+ * Call `restore()` when you want the `off` name in the global scope for
+ * other purposes. The method will restore the previous contents to the
+ * global variable and return the `OfflinerClient`.
+ *
+ * @method restore
+ * @return {OfflinerClient} The current offliner client.
+ */
+ restore: function () {
+ exports.off = originalOff;
+ return this;
+ },
+
+ /**
+ * Register the offliner worker. The worker will be installed with
+ * root `/` [scope](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#Syntax)
+ * unless you add the `data-root` attribute to the script tag.
+ *
+ * In the same way, the client will look for a script in the specified root
+ * called `offliner-worker.js`. If you want to change this behaviour, use
+ * the `data-worker` attribute.
+ *
+ * For instance, suppose your web application is running under:
+ * https://delapuente.github.com/offliner
+ *
+ * And you have your worker at:
+ * https://delapuente.github.com/offliner/worker.js
+ *
+ * Then the script tag should looks like:
+ * ```html
+ * <script src="js/offliner-client.js" data-root="offliner" data-worker="worker.js"></script>
+ * ```
+ *
+ * @method install
+ * @return {Promise} A promise resolving if the installation success.
+ */
+ install: function () {
+ if (!('serviceWorker' in navigator)) {
+ return Promise.reject(new Error('serviceworkers-not-supported'));
+ }
+
+ return navigator.serviceWorker.register(workerURL, {
+ scope: root
+ }).then(function (registration) {
+ return this.connect().then(function () {
+ return registration;
+ });
+ }.bind(this));
+ },
+
+ /**
+ * Keeps the promise of connect.
+ *
+ * @property _connecting
+ * @type Promise
+ * @private
+ */
+ _connecting: null,
+
+ /**
+ * Connects the client with offliner allowing the client to control offliner
+ * and receive events.
+ *
+ * @method connect
+ * @return {Promise} A promise resolving once connection has been stablished
+ * with the worker and communication is possible.
+ */
+ connect: function () {
+ if (!this._connecting) { this._connecting = this._connect(); }
+ return this._connecting;
+ },
+
+ /**
+ * The actual implementation for {{#crossLink "connect:method"}}{{/crossLink}}
+ *
+ * @method _connect
+ * @return {Promise} A promise resolving once connection has been stablished
+ * with the worker and communication is possible.
+ * @private
+ */
+ _connect: function () {
+ if (!('serviceWorker' in navigator)) {
+ return Promise.reject(new Error('serviceworkers-not-supported'));
+ }
+
+ var installMessageHandlers = this._installMessageHandlers.bind(this);
+ var checkForActivationPending = this._checkForActivationPending.bind(this);
+ return new Promise(function (fulfill, reject) {
+ navigator.serviceWorker.getRegistration(root).then(function (registration) {
+ if (registration.active) {
+ installMessageHandlers();
+ checkForActivationPending();
+ return fulfill();
+ }
+
+ var installingWorker = registration.installing;
+ if (!installingWorker) {
+ return reject(new Error('impossible-to-connect'));
+ }
+
+ installingWorker.onstatechange = function () {
+ if (installingWorker.state === 'installed') {
+ installMessageHandlers();
+ checkForActivationPending();
+ fulfill();
+ }
+ };
+ });
+ });
+ },
+
+ /**
+ * Attaches a listener for a type of event.
+ *
+ * @method on
+ * @param type {String} The type of the event.
+ * @param handler {Callback} The callback receiving the event.
+ * @param willBeThis {Object} The context object `this` for the `handler`.
+ */
+ on: function (type, handler, willBeThis) {
+ if (!this._has(type, handler, willBeThis)) {
+ this._eventListeners[type] = this._eventListeners[type] || [];
+ this._eventListeners[type].push([handler, willBeThis]);
+ }
+ },
+
+ /**
+ * Request an update to offliner.
+ *
+ * @method update
+ * @return {Promise} If the update process is successful, the promise will
+ * resolve to a new version and an
+ * {{#crossLink "OfflinerClient/activationPending:event"}}{{/crossLink}}
+ * will be triggered. If the update is not needed, the promise will be
+ * rejected with `no-update-needed` reason.
+ */
+ update: function () {
+ return this._xpromise('update');
+ },
+
+ /**
+ * Performs the activation of the pending update. I.e. replaces the current
+ * cache with that updated in the update process. Normally, you want to
+ * reload the application when the activation ends successfuly.
+ *
+ * @method activate
+ * @return {Promise} A promise resolving into the activated version or
+ * rejected with `no-activation-pending` if there was not an activation.
+ */
+ activate: function () {
+ return this._xpromise('activate');
+ },
+
+ /**
+ * Run the listeners for some type of event.
+ *
+ * @method _runListeners
+ * @param type {String} The type of the events selecting the listeners to
+ * be run.
+ * @param evt {Object} The event contents.
+ * @private
+ */
+ _runListeners: function (type, evt) {
+ var listeners = this._eventListeners[type] || [];
+ listeners.forEach(function (listenerAndThis) {
+ var listener = listenerAndThis[0];
+ var willBeThis = listenerAndThis[1];
+ listener.call(willBeThis, evt);
+ });
+ },
+
+ /**
+ * Registers the listeners for enabling communication between the worker
+ * and the client code.
+ *
+ * @method _installMessageHandlers
+ * @private
+ */
+ _installMessageHandlers: function installMessageHandlers() {
+ var that = this;
+ if (!installMessageHandlers.done) {
+ if (typeof BroadcastChannel === 'function') {
+ var bc = new BroadcastChannel('offliner-channel');
+ bc.onmessage = onmessage;
+ }
+ else {
+ navigator.serviceWorker.addEventListener('message', onmessage);
+ }
+ installMessageHandlers.done = true;
+ }
+
+ function onmessage(e) {
+ var msg = e.data;
+ var type = msg ? msg.type : '';
+ var typeAndSubType = type.split(':');
+ if (typeAndSubType[0] === 'offliner') {
+ that._handleMessage(typeAndSubType[1], msg);
+ }
+ }
+ },
+
+ /**
+ * Make offliner to check for pending activations and dispatch
+ * {{#crossLink "OfflinerClient/activationPending:event"}}{{/crossLink}}
+ * if so.
+ *
+ * @method _checkForActivationPending
+ * @private
+ */
+ _checkForActivationPending: function () {
+ // TODO: should we add a prefix for offliner messages?
+ this._send({ type: 'checkForActivationPending' });
+ },
+
+ /**
+ * Discriminates between {{#crossLink "OfflinerClient/xpromise:event"}}{{/crossLink}}
+ * events which are treated in a special way and the rest of the events that
+ * simply trigger the default dispatching algorithm.
+ *
+ * @method _handleMessage
+ * @param offlinerType {String} The type of the message without the
+ * `offliner:` prefix.
+ * @param msg {Any} The event.
+ * @private
+ */
+ _handleMessage: function (offlinerType, msg) {
+ var sw = navigator.serviceWorker;
+ if (offlinerType === 'xpromise') {
+ this._resolveCrossPromise(msg);
+ }
+ else {
+ this._runListeners(offlinerType, msg);
+ }
+ },
+
+ /**
+ * @method _has
+ * @param type {String} The type for the listener registration.
+ * @param handler {Function} The listener.
+ * @param willBeThis {Object} The context object `this` which the function
+ * will be called with.
+ * @return `true` if the listener registration already exists.
+ * @private
+ */
+ _has: function (type, handler, willBeThis) {
+ var listeners = this._eventListeners[type] || [];
+ for (var i = 0, listenerAndThis; (listenerAndThis = listeners[i]); i++) {
+ if (listenerAndThis[0] === handler &&
+ listenerAndThis[1] === willBeThis) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Creates a cross promise registration. A _cross promise_ or xpromise
+ * is a special kind of promise that is generated in the client but whose
+ * implementation is in a worker.
+ *
+ * @method _xpromise
+ * @param order {String} The string for the implementation part to select
+ * the implementation to run.
+ * @return {Promise} A promise delegating its implementation in some code
+ * running in a worker.
+ * @private
+ */
+ _xpromise: function (order) {
+ return new Promise(function (accept, reject) {
+ var uniqueId = nextPromiseId++;
+ var msg = {
+ type: 'xpromise',
+ id: uniqueId,
+ order: order
+ };
+ this._xpromises[uniqueId] = [accept, rejectWithError];
+ this._send(msg);
+
+ function rejectWithError(errorKey) {
+ reject(new Error(errorKey)); // TODO: Add a OfflinerError type
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Sends a message to the worker.
+ *
+ * @method _send
+ * @param msg {Any} The message to be sent.
+ * @private
+ */
+ _send: function (msg) {
+ navigator.serviceWorker.getRegistration(root)
+ .then(function (registration) {
+ if (!registration || !registration.active) {
+ // TODO: Wait for the service worker to be active and try to
+ // resend.
+ console.warn('Not service worker active right now.');
+ }
+ else {
+ return registration.active.postMessage(msg);
+ }
+ });
+ },
+
+ /**
+ * Resolves a cross promise based on information received by the
+ * implementation in the worker.
+ *
+ * @method _resolveCrossPromise
+ * @param msg {Object} An object with the proper data to resolve a xpromise.
+ * @private
+ */
+ _resolveCrossPromise: function (msg) {
+ var implementation = this._xpromises[msg.id];
+ if (implementation) {
+ implementation[msg.status === 'rejected' ? 1 : 0](msg.value);
+ }
+ else {
+ console.warn('Trying to resolve unexistent promise:', msg.id);
+ }
+ }
+ };
+
+}(this.exports || this));
+
+
diff --git a/src/offliner-client.js b/src/offliner-client.js
index d6dbd1b..7aebb58 100644
--- a/src/offliner-client.js
+++ b/src/offliner-client.js
@@ -79,21 +79,78 @@
* @return {Promise} A promise resolving if the installation success.
*/
install: function () {
- this._installMessageHandlers();
+ if (!('serviceWorker' in navigator)) {
+ return Promise.reject(new Error('serviceworkers-not-supported'));
+ }
+
return navigator.serviceWorker.register(workerURL, {
scope: root
- });
+ }).then(function (registration) {
+ return this.connect().then(function () {
+ return registration;
+ });
+ }.bind(this));
},
/**
- * If you are using offliner as a serviceworkerware middleware, instead
- * of calling {{#crossLink OfflinerClient/install:method}}{{/crossLink}},
- * call `connect()` to avoid registering the worker.
+ * Keeps the promise of connect.
+ *
+ * @property _connecting
+ * @type Promise
+ * @private
+ */
+ _connecting: null,
+
+ /**
+ * Connects the client with offliner allowing the client to control offliner
+ * and receive events.
*
* @method connect
+ * @return {Promise} A promise resolving once connection has been stablished
+ * with the worker and communication is possible.
*/
connect: function () {
- this._installMessageHandlers();
+ if (!this._connecting) { this._connecting = this._connect(); }
+ return this._connecting;
+ },
+
+ /**
+ * The actual implementation for {{#crossLink "connect:method"}}{{/crossLink}}
+ *
+ * @method _connect
+ * @return {Promise} A promise resolving once connection has been stablished
+ * with the worker and communication is possible.
+ * @private
+ */
+ _connect: function () {
+ if (!('serviceWorker' in navigator)) {
+ return Promise.reject(new Error('serviceworkers-not-supported'));
+ }
+
+ var installMessageHandlers = this._installMessageHandlers.bind(this);
+ var checkForActivationPending = this._checkForActivationPending.bind(this);
+ return new Promise(function (fulfill, reject) {
+ navigator.serviceWorker.getRegistration(root).then(function (registration) {
+ if (registration.active) {
+ installMessageHandlers();
+ checkForActivationPending();
+ return fulfill();
+ }
+
+ var installingWorker = registration.installing;
+ if (!installingWorker) {
+ return reject(new Error('impossible-to-connect'));
+ }
+
+ installingWorker.onstatechange = function () {
+ if (installingWorker.state === 'installed') {
+ installMessageHandlers();
+ checkForActivationPending();
+ fulfill();
+ }
+ };
+ });
+ });
},
/**
@@ -117,7 +174,7 @@
* @method update
* @return {Promise} If the update process is successful, the promise will
* resolve to a new version and an
- * {{#crossLink OfflinerClient/activationPending:event}}{{/crossLink}}
+ * {{#crossLink "OfflinerClient/activationPending:event"}}{{/crossLink}}
* will be triggered. If the update is not needed, the promise will be
* rejected with `no-update-needed` reason.
*/
@@ -145,6 +202,7 @@
* @param type {String} The type of the events selecting the listeners to
* be run.
* @param evt {Object} The event contents.
+ * @private
*/
_runListeners: function (type, evt) {
var listeners = this._eventListeners[type] || [];
@@ -160,6 +218,7 @@
* and the client code.
*
* @method _installMessageHandlers
+ * @private
*/
_installMessageHandlers: function installMessageHandlers() {
var that = this;
@@ -185,7 +244,20 @@
},
/**
- * Discriminates between {{#crossLink OfflinerClient/xpromise:event}}{{/crossLink}}
+ * Make offliner to check for pending activations and dispatch
+ * {{#crossLink "OfflinerClient/activationPending:event"}}{{/crossLink}}
+ * if so.
+ *
+ * @method _checkForActivationPending
+ * @private
+ */
+ _checkForActivationPending: function () {
+ // TODO: should we add a prefix for offliner messages?
+ this._send({ type: 'checkForActivationPending' });
+ },
+
+ /**
+ * Discriminates between {{#crossLink "OfflinerClient/xpromise:event"}}{{/crossLink}}
* events which are treated in a special way and the rest of the events that
* simply trigger the default dispatching algorithm.
*
@@ -193,6 +265,7 @@
* @param offlinerType {String} The type of the message without the
* `offliner:` prefix.
* @param msg {Any} The event.
+ * @private
*/
_handleMessage: function (offlinerType, msg) {
var sw = navigator.serviceWorker;
@@ -211,6 +284,7 @@
* @param willBeThis {Object} The context object `this` which the function
* will be called with.
* @return `true` if the listener registration already exists.
+ * @private
*/
_has: function (type, handler, willBeThis) {
var listeners = this._eventListeners[type] || [];
@@ -233,6 +307,7 @@
* the implementation to run.
* @return {Promise} A promise delegating its implementation in some code
* running in a worker.
+ * @private
*/
_xpromise: function (order) {
return new Promise(function (accept, reject) {
@@ -253,16 +328,18 @@
/**
* Sends a message to the worker.
+ *
* @method _send
* @param msg {Any} The message to be sent.
+ * @private
*/
_send: function (msg) {
- navigator.serviceWorker.getRegistration()
+ navigator.serviceWorker.getRegistration(root)
.then(function (registration) {
if (!registration || !registration.active) {
// TODO: Wait for the service worker to be active and try to
// resend.
- warn('Not service worker active right now.');
+ console.warn('Not service worker active right now.');
}
else {
return registration.active.postMessage(msg);
@@ -276,6 +353,7 @@
*
* @method _resolveCrossPromise
* @param msg {Object} An object with the proper data to resolve a xpromise.
+ * @private
*/
_resolveCrossPromise: function (msg) {
var implementation = this._xpromises[msg.id];
diff --git a/src/offliner.js b/src/offliner.js
index 3e2fe40..939a4eb 100644
--- a/src/offliner.js
+++ b/src/offliner.js
@@ -130,7 +130,7 @@
* @method standalone
* @throws {Error} offliner throws when trying to install it in standalone
* mode if it was already used as middleware by calling
- * {{#crossLink Offliner/asMiddleware:method}}{{/crossLink}}.
+ * {{#crossLink "Offliner/asMiddleware:method"}}{{/crossLink}}.
*/
Offliner.prototype.standalone = function () {
if (this._isMiddleware) {
@@ -185,7 +185,7 @@
* @method asMiddleware
* @return {Object} A serviceworkerware middleware.
* @throws {Error} offliner will throw if you try to use it as middleware
- * after calling {{#crossLink Offliner/standalone:method}}{{/crossLink}}.
+ * after calling {{#crossLink "Offliner/standalone:method"}}{{/crossLink}}.
*/
Offliner.prototype.asMiddleware = function () {
if (this._isStarted) {
@@ -228,6 +228,9 @@
case 'xpromise':
this._receiveCrossPromise(msg.id, msg.order);
break;
+ case 'checkForActivationPending':
+ this._checkForActivationPending();
+ break;
default:
warn('Message not recognized:', msg);
break;
@@ -263,6 +266,21 @@
}
};
+ /**
+ * Check if there is an activation pending. If so, offliner dispatches an
+ * activation pending request.
+ *
+ * @method _checkForActivationPending
+ * @private
+ */
+ Offliner.prototype._checkForActivationPending = function () {
+ this.get('activation-pending').then(function (isActivationPending) {
+ if (isActivationPending) {
+ this._sendActivationPending();
+ }
+ }.bind(this));
+ };
+
/**
* Resolves a cross promise.
*
@@ -461,6 +479,7 @@
*
* @method _sendActivationDone
* @private
+ * @for Offliner
*/
Offliner.prototype._sendActivationDone = function () {
this._broadcastMessage({ type: 'activationDone' });
@@ -480,7 +499,7 @@
/**
* Broadcast a message in the clients. The method will add the `offliner:`
* prefix to the type of the events but this is stripped out automatically by
- * the {{#crossLink OfflinerClient/_installMessageHandlers:method}}{{/crossLink}}
+ * the {{#crossLink "OfflinerClient/_installMessageHandlers:method"}}{{/crossLink}}
* client side.
*
* @method _broadcastMessage
@@ -768,7 +787,7 @@
*/
/**
- * Normalizes a resource not following the {{#crossLink Resource}}
+ * Normalizes a resource not following the {{#crossLink "Resource"}}
* {{/crossLink}} convention.
*
* @method normalize
@@ -798,7 +817,7 @@
}
/**
- * Register a {{#crossLink Fetcher}}{{/crossLink}}. The fetcher will be used
+ * Register a {{#crossLink "Fetcher"}}{{/crossLink}}. The fetcher will be used
* to retrieve the resources of the fetcher's type.
*
* @method use