diff --git a/pwabuilder-sw.js b/pwabuilder-sw.js index da8931bf..934d5541 100644 --- a/pwabuilder-sw.js +++ b/pwabuilder-sw.js @@ -93,117 +93,120 @@ const regexpZIMUrlWithNamespace = /(?:^|\/)([^\/]+\/)([-ABIJMUVWX])\/(.+)/; self.addEventListener("fetch", function (event) { console.log('[SW] Service Worker ' + (event.request.method === "GET" ? 'intercepted ' : 'noted ') + event.request.url, event.request.method); if (event.request.method !== "GET") return; - if (/\.zim\w{0,2}\//i.test(event.request.url) && regexpZIMUrlWithNamespace.test(event.request.url)) { - // If the user has disabled the display of images, and the browser wants an image, respond with empty SVG - // A URL with "?kiwix-display" query string acts as a passthrough so that the regex will not match and - // the image will be fetched by app.js - // DEV: If you need to hide more image types, add them to regex below and also edit equivalent regex in app.js - if (imageDisplay !== 'all' && /(^|\/)[IJ]\/.*\.(jpe?g|png|svg|gif)($|[?#])(?!kiwix-display)/i.test(event.request.url)) { - var svgResponse; - if (imageDisplay === 'manual') - svgResponse = ""; - else - svgResponse = ""; - event.respondWith(new Response(svgResponse, { - headers: { - 'Content-Type': 'image/svg+xml' - } - })); - return; - } - // Let's ask app.js for that content - event.respondWith(new Promise(function (resolve, reject) { - var nameSpace; - var title; - var titleWithNameSpace; - var regexpResult = regexpZIMUrlWithNamespace.exec(event.request.url); - var prefix = regexpResult[1]; - nameSpace = regexpResult[2]; - title = regexpResult[3]; + event.respondWith( + fromCache(event.request).then( + function (response) { + // The response was found in the cache so we respond with it and update the entry - // We need to remove the potential parameters in the URL - title = removeUrlParameters(decodeURIComponent(title)); + // This is where we call the server to get the newest version of the + // file to use the next time we show view + // event.waitUntil( + // fetch(event.request).then(function (response) { + // console.log('[SW] Refreshing CACHE from server...'); + // return updateCache(event.request, response); + // }) + // ); - titleWithNameSpace = nameSpace + '/' + title; - - // Let's instantiate a new messageChannel, to allow app.js to give us the content - var messageChannel = new MessageChannel(); - messageChannel.port1.onmessage = function (event) { - if (event.data.action === 'giveContent') { - // Content received from app.js - var contentLength = event.data.content ? event.data.content.byteLength : null; - var contentType = event.data.mimetype; - // Set the imageDisplay variable if it has been sent in the event data - imageDisplay = typeof event.data.imageDisplay !== 'undefined' ? - event.data.imageDisplay : imageDisplay; - var headers = new Headers(); - if (contentLength) headers.set('Content-Length', contentLength); - if (contentType) headers.set('Content-Type', contentType); - // Test if the content is a video or audio file - // See kiwix-js #519 and openzim/zimwriterfs #113 for why we test for invalid types like "mp4" or "webm" (without "video/") - // The full list of types produced by zimwriterfs is in https://github.com/openzim/zimwriterfs/blob/master/src/tools.cpp - if (contentLength >= 1 && /^(video|audio)|(^|\/)(mp4|webm|og[gmv]|mpeg)$/i.test(contentType)) { - // In case of a video (at least), Chrome and Edge need these HTTP headers else seeking doesn't work - // (even if we always send all the video content, not the requested range, until the backend supports it) - headers.set('Accept-Ranges', 'bytes'); - headers.set('Content-Range', 'bytes 0-' + (contentLength - 1) + '/' + contentLength); - } - var responseInit = { - status: 200, - statusText: 'OK', - headers: headers - }; - - var httpResponse = new Response(event.data.content, responseInit); - - // Let's send the content back from the ServiceWorker - resolve(httpResponse); - } else if (event.data.action === 'sendRedirect') { - resolve(Response.redirect(prefix + event.data.redirectUrl)); - } else { - console.error('Invalid message received from app.js for ' + titleWithNameSpace, event.data); - reject(event.data); - } - }; - outgoingMessagePort.postMessage({ - 'action': 'askForContent', - 'title': titleWithNameSpace - }, [messageChannel.port2]); - })); - } else { - event.respondWith( - fromCache(event.request).then( - function (response) { - // The response was found in the cache so we responde with it and update the entry - - // This is where we call the server to get the newest version of the - // file to use the next time we show view - event.waitUntil( - fetch(event.request).then(function (response) { - console.log('[SW] Refreshing CACHE from server...'); - return updateCache(event.request, response); - }) - ); - console.log('[SW] Supplying ' + event.request.url + ' from CACHE...'); - return response; - }, - function () { - // The response was not found in the cache so we look for it on the server - return fetch(event.request) - .then(function (response) { - // If request was success, add or update it in the cache - event.waitUntil(updateCache(event.request, response.clone())); - - return response; - }) - .catch(function (error) { - console.log("[SW] Network request failed and no cache.", error); + console.log('[SW] Supplying ' + event.request.url + ' from CACHE...'); + return response; + }, + function () { + // The response was not found in the cache so we look for it on the server + if (/\.zim\w{0,2}\//i.test(event.request.url) && regexpZIMUrlWithNamespace.test(event.request.url)) { + // If the user has disabled the display of images, and the browser wants an image, respond with empty SVG + // A URL with "?kiwix-display" query string acts as a passthrough so that the regex will not match and + // the image will be fetched by app.js + // DEV: If you need to hide more image types, add them to regex below and also edit equivalent regex in app.js + if (imageDisplay !== 'all' && /(^|\/)[IJ]\/.*\.(jpe?g|png|svg|gif)($|[?#])(?!kiwix-display)/i.test(event.request.url)) { + var svgResponse; + if (imageDisplay === 'manual') + svgResponse = ""; + else + svgResponse = ""; + return new Response(svgResponse, { + headers: { + 'Content-Type': 'image/svg+xml' + } }); + } + + // Let's ask app.js for that content + return new Promise(function (resolve, reject) { + var nameSpace; + var title; + var titleWithNameSpace; + var regexpResult = regexpZIMUrlWithNamespace.exec(event.request.url); + var prefix = regexpResult[1]; + nameSpace = regexpResult[2]; + title = regexpResult[3]; + + // We need to remove the potential parameters in the URL + title = removeUrlParameters(decodeURIComponent(title)); + + titleWithNameSpace = nameSpace + '/' + title; + + // Let's instantiate a new messageChannel, to allow app.js to give us the content + var messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = function (msgEvent) { + if (msgEvent.data.action === 'giveContent') { + // Content received from app.js + var contentLength = msgEvent.data.content ? msgEvent.data.content.byteLength : null; + var contentType = msgEvent.data.mimetype; + // Set the imageDisplay variable if it has been sent in the event data + imageDisplay = typeof msgEvent.data.imageDisplay !== 'undefined' ? + msgEvent.data.imageDisplay : imageDisplay; + var headers = new Headers(); + if (contentLength) headers.set('Content-Length', contentLength); + if (contentType) headers.set('Content-Type', contentType); + // Test if the content is a video or audio file + // See kiwix-js #519 and openzim/zimwriterfs #113 for why we test for invalid types like "mp4" or "webm" (without "video/") + // The full list of types produced by zimwriterfs is in https://github.com/openzim/zimwriterfs/blob/master/src/tools.cpp + if (contentLength >= 1 && /^(video|audio)|(^|\/)(mp4|webm|og[gmv]|mpeg)$/i.test(contentType)) { + // In case of a video (at least), Chrome and Edge need these HTTP headers else seeking doesn't work + // (even if we always send all the video content, not the requested range, until the backend supports it) + headers.set('Accept-Ranges', 'bytes'); + headers.set('Content-Range', 'bytes 0-' + (contentLength - 1) + '/' + contentLength); + } + var responseInit = { + status: 200, + statusText: 'OK', + headers: headers + }; + + var httpResponse = new Response(msgEvent.data.content, responseInit); + + // Add or update css or javascript assets to the cache + if (/(text|application)\/(css|javascript)/i.test(contentType)) { + updateCache(event.request, httpResponse.clone()); + } + + // Let's send the content back from the ServiceWorker + resolve(httpResponse); + } else if (msgEvent.data.action === 'sendRedirect') { + resolve(Response.redirect(prefix + msgEvent.data.redirectUrl)); + } else { + console.error('Invalid message received from app.js for ' + titleWithNameSpace, msgEvent.data); + reject(msgEvent.data); + } + }; + outgoingMessagePort.postMessage({ + 'action': 'askForContent', + 'title': titleWithNameSpace + }, [messageChannel.port2]); + }); + } else { + return fetch(event.request).then(function (response) { + // If request was success, add or update it in the cache + event.waitUntil(updateCache(event.request, response.clone())); + return response; + }).catch(function (error) { + console.log("[SW] Network request failed and no cache.", error); + }); } - ) - ); - } + } + ) + ); }); function fromCache(request) { diff --git a/www/js/app.js b/www/js/app.js index d39ef0ca..841a3ea1 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -1221,6 +1221,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'util', 'utf8', 'images', 'cooki } var keepAliveServiceWorkerHandle; + var serviceWorkerRegistration = null; /** * Send an 'init' message to the ServiceWorker with a new MessageChannel @@ -1351,8 +1352,6 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'util', 'utf8', 'images', 'cooki setContentInjectionMode('jquery'); } - var serviceWorkerRegistration = null; - /** * Tells if the ServiceWorker API is available * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker