diff --git a/www/js/app.js b/www/js/app.js index 010f1ae7..52052d10 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -35,6 +35,15 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles */ var MAX_SEARCH_RESULT_SIZE = 50; + /** + * The delay (in milliseconds) between two "keepalive" messages + * sent to the ServiceWorker (so that it is not stopped by + * the browser, and keeps the MessageChannel to communicate + * with the application) + * @type Integer + */ + var DELAY_BETWEEN_KEEPALIVE_SERVICEWORKER = 30000; + /** * @type ZIMArchive */ @@ -191,7 +200,6 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles else { setContentInjectionMode('jquery'); } - }); /** @@ -225,6 +233,29 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles } var contentInjectionMode; + var keepAliveServiceWorkerHandle; + + /** + * Send an 'init' message to the ServiceWorker with a new MessageChannel + * to initialize it, or to keep it alive. + * This MessageChannel allows a 2-way communication between the ServiceWorker + * and the application + */ + function initOrKeepAliveServiceWorker() { + if (contentInjectionMode === 'serviceworker') { + // Create a new messageChannel + var tmpMessageChannel = new MessageChannel(); + tmpMessageChannel.port1.onmessage = handleMessageChannelMessage; + // Send the init message to the ServiceWorker, with this MessageChannel as a parameter + navigator.serviceWorker.controller.postMessage({'action': 'init'}, [tmpMessageChannel.port2]); + messageChannel = tmpMessageChannel; + console.log("init message sent to ServiceWorker"); + // Schedule to do it again regularly to keep the 2-way communication alive. + // See https://github.com/kiwix/kiwix-js/issues/145 to understand why + clearTimeout(keepAliveServiceWorkerHandle); + keepAliveServiceWorkerHandle = setTimeout(initOrKeepAliveServiceWorker, DELAY_BETWEEN_KEEPALIVE_SERVICEWORKER, false); + } + } /** * Sets the given injection mode. @@ -256,13 +287,6 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles return; } - if (!messageChannel) { - // Let's create the messageChannel for the 2-way communication - // with the Service Worker - messageChannel = new MessageChannel(); - messageChannel.port1.onmessage = handleMessageChannelMessage; - } - if (!isServiceWorkerReady()) { $('#serviceWorkerStatus').html("ServiceWorker API available : trying to register it..."); navigator.serviceWorker.register('../service-worker.js').then(function (reg) { @@ -275,19 +299,24 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles var serviceWorker = reg.installing || reg.waiting || reg.active; serviceWorker.addEventListener('statechange', function(statechangeevent) { if (statechangeevent.target.state === 'activated') { - console.log("try to post an init message to ServiceWorker"); - navigator.serviceWorker.controller.postMessage({'action': 'init'}, [messageChannel.port2]); - console.log("init message sent to ServiceWorker"); + // Create the MessageChannel + // and send the 'init' message to the ServiceWorker + initOrKeepAliveServiceWorker(); } }); + if (serviceWorker.state === 'activated') { + // Even if the ServiceWorker is already activated, + // We need to re-create the MessageChannel + // and send the 'init' message to the ServiceWorker + // in case it has been stopped and lost its context + initOrKeepAliveServiceWorker(); + } }, function (err) { console.error('error while registering serviceWorker', err); refreshAPIStatus(); }); } else { - console.log("try to re-post an init message to ServiceWorker, to re-enable it in case it was disabled"); - navigator.serviceWorker.controller.postMessage({'action': 'init'}, [messageChannel.port2]); - console.log("init message sent to ServiceWorker"); + initOrKeepAliveServiceWorker(); } } $('input:radio[name=contentInjectionMode]').prop('checked', false); @@ -324,7 +353,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles } return true; } - + // At launch, we try to set the last content injection mode (stored in a cookie) var lastContentInjectionMode = cookies.getItem('lastContentInjectionMode'); if (lastContentInjectionMode) {