diff --git a/config.json b/config.json index 4ff6c4dd4cd..a87dadd8ec7 100644 --- a/config.json +++ b/config.json @@ -150,7 +150,8 @@ "version", "library_version.inc.php", "manifest.json", - "../node_modules/monaco-editor" + "../node_modules/monaco-editor", + "ServiceWorker.js" ] } }, @@ -307,6 +308,13 @@ } }, + "update-version-cache": { + "shell" : + { + "command" : "utils/update_version.py worker" + } + }, + "build" : { "extend" : [ "parts-config" ], @@ -318,7 +326,7 @@ "run" : [ "build-resources", - "update-version", + "update-version-cache", "build-script", "build-files", "build-libs", diff --git a/doc/manual/de/config/url-params.rst b/doc/manual/de/config/url-params.rst index ac86f82ff4d..62f9a25fd1f 100644 --- a/doc/manual/de/config/url-params.rst +++ b/doc/manual/de/config/url-params.rst @@ -217,3 +217,17 @@ In der Entwicklerversion sind diese standardmäßig eingeschaltet in einem Relea Default: false im Release, true in Entwicklerversion Options: true (log=true), false (log=false) + + +.. _worker: + +*worker* - ServiceWorker Cache in der Entwicklerversion einschalten + +In der Entwicklerversion ist der ServiceWorker zum Caching der Dateien abgeschaltet, damit man Änderungen +während des Entwickelns beim neu Laden direkt testen kann. Mit diesem URL-Parameter kann der ServiceWorker +trotzdem eingeschaltet werden + +.. code:: + + Default: false (worker=false) + Options: true (worker=true) [nur in Entwicklungsversion, in einem Release hat dieser Parameter eine Funktion] diff --git a/source/ServiceWorker.js b/source/ServiceWorker.js new file mode 100644 index 00000000000..81006f5006d --- /dev/null +++ b/source/ServiceWorker.js @@ -0,0 +1,147 @@ +/** + * ServiceWorker for the CometVisu + * + * @author Tobias Bräutigam + * @since (0.11.0) 2017 + */ + +var CACHE = "cv-cache-v2"; +var NO_CACHE_TEST = /.+\.php$/i; +var CONFIG_TEST = /.+visu_config.*\.xml.*/i; +var config = {}; +var updateQueue = []; +var queueTid = null; + +self.addEventListener('message', function(event) { + var data = event.data; + + console.log(data); + if (data.command === "configure") { + config = data.message; + } +}); + +self.addEventListener('install', function(event) { + // take over right now + console.log("install"); +}); + +// delete old caches after activation +self.addEventListener('activate', function(event) { + console.log("activate"); + var cacheWhitelist = [CACHE]; + event.waitUntil( + caches.keys().then(function(cacheNames) { + return Promise.all( + cacheNames.map(function(cacheName) { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); + +self.addEventListener('fetch', function(ev) { + if (config.disableCache === true || + !ev.request.url.startsWith(this.registration.scope) || + CONFIG_TEST.test(ev.request.url) + ) { + // fallback to "normal" behaviour without serviceWorker -> sends HTTP request + return; + } + + if (!NO_CACHE_TEST.test(ev.request.url) && + (!ev.request.headers.pragma || ev.request.headers.pragma !== "no-cache") && + (!ev.request.headers['Cache-Control'] || ev.request.headers['Cache-Control'] !== "no-cache") + ) { + + ev.respondWith(fromCache(ev.request).then(function(response){ + // console.log(ev.request.url+" from cache"); + if (config.forceReload === true) { + update(ev.request); + } else { + updateQueue.push(ev.request); + if (queueTid) { + clearTimeout(queueTid); + } + queueTid = setTimeout(processQueue, 1000); + } + + return response; + }).catch(function () { + // not cached -> do now + // console.log("caching " + ev.request.url); + return fetchAndUpdate(ev.request); + })); + } else { + ev.respondWith(fromNetwork(ev.request)); + } +}); + +function fromNetwork(request, timeout) { + return new Promise(function (resolve, reject) { + if (timeout) { + var timeoutId = setTimeout(reject, timeout); + } + + fetch(request).then(function (response) { + if (timeoutId) { + clearTimeout(timeoutId); + } + resolve(response); + }, reject); + }); +} + +function processQueue() { + while (updateQueue.length) { + var request = updateQueue.shift(); + update(request); + } +} + +/** + * Get response from cache + * @param request {Request} + * @return {Response} + */ +function fromCache(request) { + return caches.open(CACHE).then(function (cache) { + return cache.match(request).then(function (matching) { + return matching || Promise.reject('no-match'); + }); + }); +} + +/** + * Fetch request from network and update the cache + * @param request {Request} + * @return {Promise} + */ +function update(request) { + caches.open(CACHE).then(function (cache) { + fetch(request).then(function (response) { + if (response.status < 400) { + cache.put(request, response); + } + }); + }); +} + +/** + * Fetch request from network, update the cache and return the response + * @param request {Request} + * @return {Response} + */ +function fetchAndUpdate(request) { + return caches.open(CACHE).then(function (cache) { + return fetch(request).then(function (response) { + if (response.status < 400) { + cache.put(request, response.clone()); + } + return response; + }); + }); +} \ No newline at end of file diff --git a/source/class/cv/Application.js b/source/class/cv/Application.js index 6a7b5087904..6b204e76170 100644 --- a/source/class/cv/Application.js +++ b/source/class/cv/Application.js @@ -148,6 +148,8 @@ qx.Class.define("cv.Application", console.log(info); + this.registerServiceWorker(); + if (qx.core.Environment.get("qx.aspects")) { qx.dev.Profile.stop(); qx.dev.Profile.start(); @@ -322,7 +324,7 @@ qx.Class.define("cv.Application", var ajaxRequest = new qx.io.request.Xhr(uri); ajaxRequest.set({ accept: "application/xml", - cache: !cv.Config.forceReload + cache: false }); ajaxRequest.setUserData("noDemo", true); ajaxRequest.addListenerOnce("success", function (e) { @@ -564,6 +566,35 @@ qx.Class.define("cv.Application", } }, + /** + * Install the service-worker if possible + */ + registerServiceWorker: function() { + if (cv.Config.useServiceWorker === true) { + navigator.serviceWorker.register('ServiceWorker.js').then(function(reg) { + this.debug("ServiceWorker successfully registered for scope "+reg.scope); + + // configure service worker + var configMessage = { + "command": "configure", + "message": { + forceReload: cv.Config.forceReload + } + }; + + if (reg.active) { + reg.active.postMessage(configMessage); + } else { + navigator.serviceWorker.ready.then(function(ev) { + ev.active.postMessage(configMessage); + }); + } + }.bind(this)).catch(function(err) { + this.error("Error registering service-worker: ", err); + }.bind(this)); + } + }, + /** * Handle errors that occur during loading ot the config file * @param textStatus {String} error status diff --git a/source/class/cv/Config.js b/source/class/cv/Config.js index bc7100b2033..683db6e105f 100644 --- a/source/class/cv/Config.js +++ b/source/class/cv/Config.js @@ -174,6 +174,13 @@ qx.Class.define('cv.Config', { */ configServer: null, + /** + * If the CometVisu can use service workers + */ + useServiceWorker: false, + + enableServiceWorkerCache : true, + /** * Get the structure that is related to this design * @param design {String?} name of the design @@ -331,5 +338,14 @@ qx.Class.define('cv.Config', { if (isNaN(cv.Config.use_maturity)) { cv.Config.use_maturity = statics.Maturity.release; // default to release } + + cv.Config.useServiceWorker = 'serviceWorker' in navigator && (req.protocol === "https" || req.host === "localhost"); + + if (cv.Config.useServiceWorker) { + if (qx.core.Environment.get("qx.debug")) { + // disable service worker in dev environment unless the user wants it + cv.Config.useServiceWorker = req.queryKey.worker === "true"; + } + } } -}); \ No newline at end of file +}); diff --git a/utils/update_version.py b/utils/update_version.py index 0e5b84967ce..143693341cf 100755 --- a/utils/update_version.py +++ b/utils/update_version.py @@ -5,11 +5,13 @@ import datetime import json import os +import sys +import re root_dir = os.path.abspath(os.path.join(os.path.realpath(os.path.dirname(__file__)), '..')) -def update_version(): +def update_version(service_worker_cache=False): # gather information from git data = { "revision": subprocess.check_output(["git", "rev-parse", "HEAD"]).strip("\n"), @@ -34,5 +36,13 @@ def update_version(): }); ''' % data) + if service_worker_cache is True: + regex = re.compile("var CACHE = \"(.+)\";", re.IGNORECASE) + with open(os.path.join(root_dir, "source", "ServiceWorker.js"), 'r+') as f: + content = f.read() + content = regex.sub("var CACHE = \"%s\";" % data['revision'], content) + f.seek(0) + f.write(content) + if __name__ == '__main__': - update_version() \ No newline at end of file + update_version(len(sys.argv) == 2 and sys.argv[1] == "worker")