mirror of
https://github.com/kiwix/kiwix-js-pwa.git
synced 2025-09-11 05:08:43 -04:00
Port SW initialization solutions from Kiwix JS (#495)
This commit is contained in:
parent
f43f0b5b6e
commit
82c3c27d9d
@ -386,6 +386,12 @@ self.addEventListener('message', function (event) {
|
|||||||
if (event.data.action === 'init') {
|
if (event.data.action === 'init') {
|
||||||
// On 'init' message, we enable the fetchEventListener
|
// On 'init' message, we enable the fetchEventListener
|
||||||
fetchCaptureEnabled = true;
|
fetchCaptureEnabled = true;
|
||||||
|
// Acdknowledge the init message to all clients
|
||||||
|
self.clients.matchAll().then(function (clientList) {
|
||||||
|
clientList.forEach(function (client) {
|
||||||
|
client.postMessage({ action: 'acknowledge' });
|
||||||
|
});
|
||||||
|
});
|
||||||
} else if (event.data.action === 'disable') {
|
} else if (event.data.action === 'disable') {
|
||||||
// On 'disable' message, we disable the fetchEventListener
|
// On 'disable' message, we disable the fetchEventListener
|
||||||
// Note that this code doesn't currently run because the app currently never sends a 'disable' message
|
// Note that this code doesn't currently run because the app currently never sends a 'disable' message
|
||||||
|
147
www/js/app.js
147
www/js/app.js
@ -3,21 +3,21 @@
|
|||||||
* This file handles the interaction between the Kiwix JS back end and the user
|
* This file handles the interaction between the Kiwix JS back end and the user
|
||||||
*
|
*
|
||||||
* Copyright 2013-2023 Jaifroid, Mossroy and contributors
|
* Copyright 2013-2023 Jaifroid, Mossroy and contributors
|
||||||
* License GPL v3:
|
* Licence GPL v3:
|
||||||
*
|
*
|
||||||
* This file is part of Kiwix.
|
* This file is part of Kiwix.
|
||||||
*
|
*
|
||||||
* Kiwix is free software: you can redistribute it and/or modify
|
* Kiwix is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public Licence as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the Licence, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* Kiwix is distributed in the hope that it will be useful,
|
* Kiwix is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public Licence for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public Licence
|
||||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -57,11 +57,13 @@ const DELAY_BETWEEN_KEEPALIVE_SERVICEWORKER = 30000;
|
|||||||
// The global parameter and app state objects are defined in init.js
|
// The global parameter and app state objects are defined in init.js
|
||||||
/* global params, appstate, nw, electronAPI, Windows, webpMachine, dialog, LaunchParams, launchQueue, abstractFilesystemAccess, MSApp */
|
/* global params, appstate, nw, electronAPI, Windows, webpMachine, dialog, LaunchParams, launchQueue, abstractFilesystemAccess, MSApp */
|
||||||
|
|
||||||
// Placeholders for the article container, the article window and the article DOM
|
// Placeholders for the article container, the article window, the article DOM and some UI elements
|
||||||
var articleContainer = document.getElementById('articleContent');
|
var articleContainer = document.getElementById('articleContent');
|
||||||
articleContainer.kiwixType = 'iframe';
|
articleContainer.kiwixType = 'iframe';
|
||||||
var articleWindow = articleContainer.contentWindow;
|
var articleWindow = articleContainer.contentWindow;
|
||||||
var articleDocument;
|
var articleDocument;
|
||||||
|
var scrollbox = document.getElementById('scrollbox');
|
||||||
|
var prefix = document.getElementById('prefix');
|
||||||
|
|
||||||
// The following variables are used to store the current article and its state
|
// The following variables are used to store the current article and its state
|
||||||
|
|
||||||
@ -104,8 +106,12 @@ if (typeof Windows !== 'undefined' && Windows.UI && Windows.UI.WebUI && Windows.
|
|||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At launch, we set the correct content injection mode
|
||||||
|
setContentInjectionMode(params.contentInjectionMode);
|
||||||
|
|
||||||
// Test caching capability
|
// Test caching capability
|
||||||
cache.test(function () {});
|
cache.test(function () {});
|
||||||
|
|
||||||
// Unique identifier of the article expected to be displayed
|
// Unique identifier of the article expected to be displayed
|
||||||
appstate.expectedArticleURLToBeDisplayed = '';
|
appstate.expectedArticleURLToBeDisplayed = '';
|
||||||
// Check if we have managed to switch to PWA mode (if running UWP app)
|
// Check if we have managed to switch to PWA mode (if running UWP app)
|
||||||
@ -216,8 +222,6 @@ function onPointerUp (e) {
|
|||||||
|
|
||||||
if (/UWP/.test(params.appType)) document.body.addEventListener('pointerup', onPointerUp);
|
if (/UWP/.test(params.appType)) document.body.addEventListener('pointerup', onPointerUp);
|
||||||
|
|
||||||
var prefix = document.getElementById('prefix');
|
|
||||||
var scrollbox = document.getElementById('scrollbox');
|
|
||||||
var searchArticlesFocused = false;
|
var searchArticlesFocused = false;
|
||||||
|
|
||||||
document.getElementById('searchArticles').addEventListener('click', function () {
|
document.getElementById('searchArticles').addEventListener('click', function () {
|
||||||
@ -2663,61 +2667,71 @@ function refreshCacheStatus () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var initServiceWorkerHandle = null;
|
|
||||||
var serviceWorkerRegistration = null;
|
var serviceWorkerRegistration = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an 'init' message to the ServiceWorker and inititalizes the onmessage event
|
* Sends an 'init' message to the ServiceWorker and inititalizes the onmessage event
|
||||||
* When the event is received, it will provide a MessageChannel port to respond to the ServiceWorker
|
* It is called when the Service Worker is first activated, and also when a new archive is loaded
|
||||||
|
* When a message is received, it will provide a MessageChannel port to respond to the ServiceWorker
|
||||||
*/
|
*/
|
||||||
function initServiceWorkerMessaging () {
|
function initServiceWorkerMessaging () {
|
||||||
// If no ZIM archive is loaded, return (it will be called when one is loaded)
|
if (!(isServiceWorkerAvailable() && isMessageChannelAvailable())) {
|
||||||
if (!appstate.selectedArchive) return;
|
console.warn('Cannot initiate ServiceWorker messaging, because one or more API is unavailable!');
|
||||||
if (params.contentInjectionMode === 'serviceworker') {
|
return;
|
||||||
// Create a message listener
|
};
|
||||||
navigator.serviceWorker.onmessage = function (event) {
|
// Create a message listener
|
||||||
|
navigator.serviceWorker.onmessage = function (event) {
|
||||||
|
if (event.data.error) {
|
||||||
|
console.error('Error in MessageChannel', event.data.error);
|
||||||
|
throw event.data.error;
|
||||||
|
} else if (event.data.action === 'acknowledge') {
|
||||||
|
// The Service Worker is acknowledging receipt of init message
|
||||||
|
console.log('SW acknowledged init message');
|
||||||
|
serviceWorkerRegistration = true;
|
||||||
|
refreshAPIStatus();
|
||||||
|
} else if (event.data.action === 'askForContent') {
|
||||||
|
// The Service Worker is asking for content. Check we have a loaded ZIM in this instance.
|
||||||
|
// DEV: This can happen if there are various instances of the app open in different tabs or windows, and no archive has been selected in this instance.
|
||||||
if (!appstate.selectedArchive) {
|
if (!appstate.selectedArchive) {
|
||||||
console.warn('Message from SW received, but no archive is selected!');
|
console.warn('Message from SW received, but no archive is selected!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.data.error) {
|
// See below for explanation of this exception
|
||||||
console.error('Error in MessageChannel', event.data.error);
|
const videoException = appstate.selectedArchive.zimType === 'zimit' && /\/\/youtubei.*player/.test(event.data.title);
|
||||||
throw event.data.error;
|
// Check that the zimFileId in the messageChannel event data is the same as the one in the currently open archive
|
||||||
}
|
// Because the SW broadcasts its request to all open tabs or windows, we need to check that the request is for this instance
|
||||||
if (event.data.action === 'askForContent') {
|
if (event.data.zimFileName !== appstate.selectedArchive.file.name && !videoException) {
|
||||||
// Check that the zimFileId in the messageChannel event data is the same as the one in the currently open archive
|
// Do nothing if the request is not for this instance
|
||||||
// Because the SW broadcasts its request to all open tabs or windows, we need to check that the request is for this instance
|
// console.debug('SW request does not match this instance', '[zimFileName:' + event.data.zimFileName + ' !== ' + appstate.selectedArchive.file.name + ']');
|
||||||
if (event.data.zimFileName !== appstate.selectedArchive.file.name) {
|
|
||||||
console.warn('SW request does not match this insstance', '[zimFileName:' + event.data.zimFileName + ' !== ' + appstate.selectedArchive.file.name + ']');
|
|
||||||
if (appstate.selectedArchive.zimType === 'zimit' && /\/\/youtubei.*player/.test(event.data.title)) {
|
|
||||||
// DEV: This is a hack to allow YouTube videos to play in Zimit archives:
|
|
||||||
// Because links are embedded in a nested iframe, the SW cannot identify the top-level window from which to request the ZIM content
|
|
||||||
// Until we find a way to tell where it is coming from, we allow the request through and try to load the content
|
|
||||||
console.warn('>>> Allowing passthrough to process YouTube video <<<');
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleMessageChannelMessage(event)
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Invalid message received', event.data);
|
if (videoException) {
|
||||||
|
// DEV: This is a hack to allow YouTube videos to play in Zimit archives:
|
||||||
|
// Because links are embedded in a nested iframe, the SW cannot identify the top-level window from which to request the ZIM content
|
||||||
|
// Until we find a way to tell where it is coming from, we allow the request through on all controlled clients and try to load the content
|
||||||
|
console.warn('>>> Allowing passthrough of SW request to process Zimit video <<<');
|
||||||
|
}
|
||||||
|
handleMessageChannelMessage(event);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
// Send the init message to the ServiceWorker
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
navigator.serviceWorker.controller.postMessage({
|
|
||||||
action: 'init'
|
|
||||||
});
|
|
||||||
} else if (initServiceWorkerHandle) {
|
|
||||||
console.error('The Service Worker is active but is not controlling the current page! We have to reload.');
|
|
||||||
// Turn off failsafe, as this is a controlled reboot
|
|
||||||
settingsStore.setItem('lastPageLoad', 'rebooting', Infinity);
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
} else {
|
||||||
// If this is the first time we are initiating the SW, allow Promises to complete by delaying potential reload till next tick
|
console.error('Invalid message received', event.data);
|
||||||
console.debug('The Service Worker needs more time to load...');
|
|
||||||
initServiceWorkerHandle = setTimeout(initServiceWorkerMessaging, 0);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
// Send the init message to the ServiceWorker
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
console.log('Initializing SW messaging...');
|
||||||
|
navigator.serviceWorker.controller.postMessage({
|
||||||
|
action: 'init'
|
||||||
|
});
|
||||||
|
} else if (serviceWorkerRegistration) {
|
||||||
|
// If this is the first time we are initiating the SW, allow Promises to complete by delaying potential reload till next tick
|
||||||
|
console.warn('The Service Worker needs more time to load, or else the app was force-refrshed...');
|
||||||
|
serviceWorkerRegistration = null;
|
||||||
|
setTimeout(initServiceWorkerMessaging, 1200);
|
||||||
|
} else {
|
||||||
|
console.error('The Service Worker is not controlling the current page! We have to reload.');
|
||||||
|
// Turn off failsafe, as this is a controlled reboot
|
||||||
|
settingsStore.setItem('lastPageLoad', 'rebooting', Infinity);
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2736,17 +2750,16 @@ function setContentInjectionMode (value) {
|
|||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
serviceWorkerRegistration = null;
|
serviceWorkerRegistration = null;
|
||||||
}
|
}
|
||||||
// User has switched to jQuery mode, so no longer needs ASSETS_CACHE
|
// User has switched to jQuery mode, so no longer needs ASSETS_CACHE on SW side (it will still be used app-side)
|
||||||
// We should empty it and turn it off to prevent unnecessary space usage
|
// if ('caches' in window && isMessageChannelAvailable()) {
|
||||||
if ('caches' in window && isMessageChannelAvailable()) {
|
// if (isServiceWorkerAvailable() && navigator.serviceWorker.controller) {
|
||||||
if (isServiceWorkerAvailable() && navigator.serviceWorker.controller) {
|
// var channel = new MessageChannel();
|
||||||
var channel = new MessageChannel();
|
// navigator.serviceWorker.controller.postMessage({
|
||||||
navigator.serviceWorker.controller.postMessage({
|
// action: { assetsCache: 'disable' }
|
||||||
action: { assetsCache: 'disable' }
|
// }, [channel.port2]);
|
||||||
}, [channel.port2]);
|
// }
|
||||||
}
|
// caches.delete(cache.ASSETS_CACHE);
|
||||||
caches.delete(cache.ASSETS_CACHE);
|
// }
|
||||||
}
|
|
||||||
refreshAPIStatus();
|
refreshAPIStatus();
|
||||||
} else if (value === 'serviceworker') {
|
} else if (value === 'serviceworker') {
|
||||||
if (!isServiceWorkerAvailable()) {
|
if (!isServiceWorkerAvailable()) {
|
||||||
@ -2839,21 +2852,13 @@ function setContentInjectionMode (value) {
|
|||||||
}
|
}
|
||||||
$('input:radio[name=contentInjectionMode]').prop('checked', false);
|
$('input:radio[name=contentInjectionMode]').prop('checked', false);
|
||||||
$('input:radio[name=contentInjectionMode]').filter('[value="' + value + '"]').prop('checked', true);
|
$('input:radio[name=contentInjectionMode]').filter('[value="' + value + '"]').prop('checked', true);
|
||||||
params.contentInjectionMode = value;
|
// Save the value in the Settings Store, so that to be able to keep it after a reload/restart
|
||||||
// Save the value in a cookie, so that to be able to keep it after a reload/restart
|
|
||||||
settingsStore.setItem('contentInjectionMode', value, Infinity);
|
settingsStore.setItem('contentInjectionMode', value, Infinity);
|
||||||
setWindowOpenerUI();
|
setWindowOpenerUI();
|
||||||
|
// Even in JQuery mode, the PWA needs to be able to serve the app in offline mode
|
||||||
|
setTimeout(initServiceWorkerMessaging, 600);
|
||||||
}
|
}
|
||||||
|
|
||||||
// At launch, we try to set the last content injection mode (stored in Settings Store)
|
|
||||||
setContentInjectionMode(params.contentInjectionMode);
|
|
||||||
// var contentInjectionMode = settingsStore.getItem('contentInjectionMode');
|
|
||||||
// if (contentInjectionMode) {
|
|
||||||
// setContentInjectionMode(contentInjectionMode);
|
|
||||||
// } else {
|
|
||||||
// setContentInjectionMode('jquery');
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detects whether the ServiceWorker API is available
|
* Detects whether the ServiceWorker API is available
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker
|
* https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker
|
||||||
|
@ -139,7 +139,7 @@ function count (callback) {
|
|||||||
var channel = new MessageChannel();
|
var channel = new MessageChannel();
|
||||||
navigator.serviceWorker.controller.postMessage({
|
navigator.serviceWorker.controller.postMessage({
|
||||||
action: {
|
action: {
|
||||||
assetsCache: params.assetsCache ? 'enable' : 'disable',
|
assetsCache: params.assetsCache && params.contentInjectionMode === 'serviceworker' ? 'enable' : 'disable',
|
||||||
appCache: params.appCache ? 'enable' : 'disable',
|
appCache: params.appCache ? 'enable' : 'disable',
|
||||||
checkCache: window.location.href
|
checkCache: window.location.href
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user