mirror of
https://github.com/kiwix/kiwix-js-pwa.git
synced 2025-08-06 04:48:44 -04:00

Former-commit-id: 2b90792c29e2c5f7080d3b5f86a95a4d6b972e22 [formerly 424568deb8d362edd388d5b41e83f6a0a2305afb] Former-commit-id: ebf9a6c741bd51a917c713e0a54d4a5d51b90291
170 lines
8.0 KiB
JavaScript
170 lines
8.0 KiB
JavaScript
/**
|
|
* service-worker.js : Service Worker implementation,
|
|
* in order to capture the HTTP requests made by an article, and respond with the
|
|
* corresponding content, coming from the archive
|
|
*
|
|
* Copyright 2015 Mossroy and contributors
|
|
* License GPL v3:
|
|
*
|
|
* This file is part of Kiwix.
|
|
*
|
|
* Kiwix is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Kiwix is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
'use strict';
|
|
|
|
/**
|
|
* A global Boolean that governs whether images are displayed
|
|
* app.js can alter this variable via messaging
|
|
*/
|
|
var imageDisplay;
|
|
|
|
self.addEventListener('install', function(event) {
|
|
event.waitUntil(self.skipWaiting());
|
|
});
|
|
|
|
self.addEventListener('activate', function(event) {
|
|
// "Claiming" the ServiceWorker is necessary to make it work right away,
|
|
// without the need to reload the page.
|
|
// See https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim
|
|
event.waitUntil(self.clients.claim());
|
|
});
|
|
|
|
var regexpRemoveUrlParameters = new RegExp(/([^?#]+)[?#].*$/);
|
|
|
|
// This function is duplicated from uiUtil.js
|
|
// because using requirejs would force to add the 'fetch' event listener
|
|
// after the initial evaluation of this script, which is not supported any more
|
|
// in recent versions of the browsers.
|
|
// Cf https://bugzilla.mozilla.org/show_bug.cgi?id=1181127
|
|
// TODO : find a way to avoid this duplication
|
|
|
|
/**
|
|
* Removes parameters and anchors from a URL
|
|
* @param {type} url
|
|
* @returns {String} same URL without its parameters and anchors
|
|
*/
|
|
function removeUrlParameters(url) {
|
|
return url.replace(regexpRemoveUrlParameters, "$1");
|
|
}
|
|
|
|
var outgoingMessagePort = null;
|
|
var fetchCaptureEnabled = false;
|
|
self.addEventListener('fetch', fetchEventListener);
|
|
|
|
self.addEventListener('message', function (event) {
|
|
if (event.data.action === 'init') {
|
|
// On 'init' message, we initialize the outgoingMessagePort and enable the fetchEventListener
|
|
outgoingMessagePort = event.ports[0];
|
|
fetchCaptureEnabled = true;
|
|
}
|
|
if (event.data.action === 'disable') {
|
|
// On 'disable' message, we delete the outgoingMessagePort and disable the fetchEventListener
|
|
outgoingMessagePort = null;
|
|
fetchCaptureEnabled = false;
|
|
}
|
|
});
|
|
|
|
// Pattern for ZIM file namespace - see https://wiki.openzim.org/wiki/ZIM_file_format#Namespaces
|
|
// In our case, there is also the ZIM file name, used as a prefix in the URL
|
|
var regexpZIMUrlWithNamespace = /(?:^|\/)([^\/]+\/)([-ABIJMUVWX])\/(.+)/;
|
|
|
|
function fetchEventListener(event) {
|
|
if (fetchCaptureEnabled) {
|
|
|
|
if (!/\.zim\//i.test(event.request.url)) {
|
|
console.log('SW is getting file from cache: ' + event.request.url);
|
|
return;
|
|
}
|
|
|
|
if (regexpZIMUrlWithNamespace.test(event.request.url)) {
|
|
// The ServiceWorker will handle this request
|
|
|
|
// 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 = "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><rect width='1' height='1' style='fill:lightblue'/></svg>";
|
|
else
|
|
svgResponse = "<svg xmlns='http://www.w3.org/2000/svg'/>";
|
|
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];
|
|
|
|
// 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(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]);
|
|
}));
|
|
}
|
|
// If event.respondWith() isn't called because this wasn't a request that we want to handle,
|
|
// then the default request/response behavior will automatically be used.
|
|
}
|
|
}
|