From 462d7fce781f4840ba5734f1ba3a6e7857eb855c Mon Sep 17 00:00:00 2001 From: Jaifroid Date: Sun, 3 Nov 2024 10:49:17 +0000 Subject: [PATCH] Separate resetApp functions --- www/js/app.js | 5 +- www/js/lib/resetApp.js | 184 ++++++++++++++++++++++++++++++++++++ www/js/lib/settingsStore.js | 157 +----------------------------- 3 files changed, 188 insertions(+), 158 deletions(-) create mode 100644 www/js/lib/resetApp.js diff --git a/www/js/app.js b/www/js/app.js index 0a6e3ce3..c69ad1ee 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -39,6 +39,7 @@ import transformStyles from './lib/transformStyles.js'; import transformZimit from './lib/transformZimit.js'; import kiwixServe from './lib/kiwixServe.js'; import updater from './lib/updater.js'; +import resetApp from './lib/resetApp.js'; // Import stylesheets programmatically // document.adoptedStyleSheets = [styles, bootstrap]; @@ -1859,7 +1860,7 @@ document.getElementById('manipulateImagesCheck').addEventListener('click', funct }); ['btnReset', 'btnReset2'].forEach(function (id) { document.getElementById(id).addEventListener('click', function () { - settingsStore.reset(); + resetApp.reset(); }); }); document.getElementById('btnRefreshApp').addEventListener('click', function () { @@ -1872,7 +1873,7 @@ document.getElementById('bypassAppCacheCheck').addEventListener('change', functi } else { params.appCache = !this.checked; settingsStore.setItem('appCache', params.appCache, Infinity); - settingsStore.reset('cacheAPI'); + resetApp.reset('cacheAPI'); } // This will also send any new values to Service Worker refreshCacheStatus(); diff --git a/www/js/lib/resetApp.js b/www/js/lib/resetApp.js new file mode 100644 index 00000000..63ae1dcf --- /dev/null +++ b/www/js/lib/resetApp.js @@ -0,0 +1,184 @@ +/** + * reset.js : Provide utilities for resetting the app to a fresh state + * Copyright 2024 Jaifroid 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 + */ + +'use strict'; + +/* global params, assetsCache */ +/* eslint-disable indent */ + +import uiUtil from './uiUtil.js'; + +/** + * Performs a full app reset, deleting all caches and settings + * Or, if a parameter is supplied, deletes or disables the object + * @param {String} object Optional name of the object to disable or delete ('cookie', 'localStorage', 'cacheAPI') + */ +function reset (object) { + var performReset = function () { + // 1. Clear any cookie entries + if (!object || object === 'cookie') { + var regexpCookieKeys = /(?:^|;)\s*([^=]+)=([^;]*)/ig; + var currentCookie = document.cookie; + var foundCrumb = false; + var cookieCrumb = regexpCookieKeys.exec(currentCookie); + while (cookieCrumb !== null) { + // DEV: Note that we don't use the keyPrefix in legacy cookie support + foundCrumb = true; + // This expiry date will cause the browser to delete the cookie crumb on next page refresh + document.cookie = cookieCrumb[1] + '=;expires=Thu, 21 Sep 1979 00:00:01 UTC;'; + cookieCrumb = regexpCookieKeys.exec(currentCookie); + } + if (foundCrumb) console.debug('All cookie keys were expired...'); + } + + // 2. Clear any localStorage settings + if (!object || object === 'localStorage') { + if (/localStorage/.test(assetsCache.capability)) { + localStorage.clear(); + console.debug('All Local Storage settings were deleted...'); + } + } + + // 3. Clear any IndexedDB entries + if (!object || object === 'indexedDB') { + if (/indexedDB/.test(assetsCache.capability)) { + window.indexedDB.deleteDatabase(params.indexedDB); + console.debug('All IndexedDB entries were deleted...'); + } + } + + // 4. Clear any (remaining) Cache API caches + if (!object || object === 'cacheAPI') { + getCacheNames(function (cacheNames) { + if (cacheNames && !cacheNames.error) { + var cnt = 0; + for (var cacheName in cacheNames) { + cnt++; + caches.delete(cacheNames[cacheName]).then(function () { + cnt--; + if (!cnt) { + // All caches deleted + console.debug('All Cache API caches were deleted...'); + // Reload if user performed full reset or if appCache is needed + if (!object || params.appCache) reloadApp(); + } + }); + } + } else { + console.debug('No Cache API caches were in use (or we do not have access to the names).'); + // All operations complete, reload if user performed full reset or if appCache is needed + if (!object || params.appCache) reloadApp(); + } + }); + } + + // 5. Clear any Origin Private File System Archives + // DEV: Method is currently behind a flag, so wait till fully implemented + // if (!object || object === 'OPFS') { + // if (navigator && navigator.storage && 'getDirectory' in navigator.storage) { + // navigator.storage.getDirectory().then(function (handle) { + // handle.remove({ recursive: true }).then(function () { + // console.debug('All OPFS archives were deleted...'); + // }); + // }); + // } + // } + }; + // If no specific object was specified, we are doing a general reset, so ask user for confirmation + if (object) performReset(); + else { + uiUtil.systemAlert('

WARNING: This will reset the app to a freshly installed state, deleting all app caches,' + + // ' Archives stored in the Private File System,' + + ' and settings! (Archives stored in the OPFS will be preserved.)

Make sure you have an Internet connection' + + ' if this is an offline PWA, because it will be erased and reloaded.

', 'Warning!', true).then(function (confirm) { + if (confirm) performReset(); + else console.debug('User cancelled'); + }); + } +} + +// Gets cache names from Service Worker, as we cannot rely on having them in params.cacheNames +function getCacheNames (callback) { + if (navigator.serviceWorker && navigator.serviceWorker.controller) { + var channel = new MessageChannel(); + channel.port1.onmessage = function (event) { + var names = event.data; + callback(names); + }; + navigator.serviceWorker.controller.postMessage({ + action: 'getCacheNames' + }, [channel.port2]); + } else { + callback(null); + } +} + +// Deregisters all Service Workers and reboots the app +function reloadApp () { + var reboot = function () { + // Temporarily disable the beforeunload event listener + params.interceptBeforeUnload = false; + console.debug('Performing app reload...'); + setTimeout(function () { + window.location.href = location.origin + location.pathname + uriParams + }, 600); + }; + // Blank the querystring, so that parameters are not set on reload + var uriParams = ''; + if (~window.location.href.indexOf(params.PWAServer) && params.referrerExtensionURL) { + // However, if we're in a PWA that was called from local code, then by definition we must remain in SW mode and we need to + // ensure the user still has access to the referrerExtensionURL (so they can get back to local code from the UI) + uriParams = '?allowInternetAccess=truee&contentInjectionMode=serviceworker'; + uriParams += '&referrerExtensionURL=' + encodeURIComponent(params.referrerExtensionURL); + } + if (navigator && navigator.serviceWorker) { + console.debug('Deregistering Service Workers...'); + var cnt = 0; + navigator.serviceWorker.getRegistrations().then(function (registrations) { + if (!registrations.length) { + reboot(); + return; + } + cnt++; + registrations.forEach(function (registration) { + registration.unregister().then(function () { + cnt--; + if (!cnt) { + console.debug('All Service Workers unregistered...'); + reboot(); + } + }); + }); + }).catch(function (err) { + console.error(err); + reboot(); + }); + } else { + console.debug('Performing app reload...'); + reboot(); + } +} + +export default { + reset: reset, + reloadApp: reloadApp, + getCacheNames: getCacheNames +} diff --git a/www/js/lib/settingsStore.js b/www/js/lib/settingsStore.js index 4c88d101..fbfea1aa 100644 --- a/www/js/lib/settingsStore.js +++ b/www/js/lib/settingsStore.js @@ -1,10 +1,8 @@ 'use strict'; -/* global params, assetsCache */ +/* global params */ /* eslint-disable indent */ -import uiUtil from './uiUtil.js'; - var regexpCookieKeysToMigrate = new RegExp([ 'contentInjectionMode', 'lastSelectedArchive', 'lastSelectedArchivePath', 'imageDisplay', 'useMathJax', 'appVersion', @@ -68,157 +66,6 @@ function getBestAvailableStorageAPI () { return type; } -/** - * Performs a full app reset, deleting all caches and settings - * Or, if a parameter is supplied, deletes or disables the object - * @param {String} object Optional name of the object to disable or delete ('cookie', 'localStorage', 'cacheAPI') - */ -function reset (object) { - var performReset = function () { - // 1. Clear any cookie entries - if (!object || object === 'cookie') { - var regexpCookieKeys = /(?:^|;)\s*([^=]+)=([^;]*)/ig; - var currentCookie = document.cookie; - var foundCrumb = false; - var cookieCrumb = regexpCookieKeys.exec(currentCookie); - while (cookieCrumb !== null) { - // DEV: Note that we don't use the keyPrefix in legacy cookie support - foundCrumb = true; - // This expiry date will cause the browser to delete the cookie crumb on next page refresh - document.cookie = cookieCrumb[1] + '=;expires=Thu, 21 Sep 1979 00:00:01 UTC;'; - cookieCrumb = regexpCookieKeys.exec(currentCookie); - } - if (foundCrumb) console.debug('All cookie keys were expired...'); - } - - // 2. Clear any localStorage settings - if (!object || object === 'localStorage') { - if (/localStorage/.test(assetsCache.capability)) { - localStorage.clear(); - console.debug('All Local Storage settings were deleted...'); - } - } - - // 3. Clear any IndexedDB entries - if (!object || object === 'indexedDB') { - if (/indexedDB/.test(assetsCache.capability)) { - window.indexedDB.deleteDatabase(params.indexedDB); - console.debug('All IndexedDB entries were deleted...'); - } - } - - // 4. Clear any (remaining) Cache API caches - if (!object || object === 'cacheAPI') { - getCacheNames(function (cacheNames) { - if (cacheNames && !cacheNames.error) { - var cnt = 0; - for (var cacheName in cacheNames) { - cnt++; - caches.delete(cacheNames[cacheName]).then(function () { - cnt--; - if (!cnt) { - // All caches deleted - console.debug('All Cache API caches were deleted...'); - // Reload if user performed full reset or if appCache is needed - if (!object || params.appCache) _reloadApp(); - } - }); - } - } else { - console.debug('No Cache API caches were in use (or we do not have access to the names).'); - // All operations complete, reload if user performed full reset or if appCache is needed - if (!object || params.appCache) _reloadApp(); - } - }); - } - - // 5. Clear any Origin Private File System Archives - // DEV: Method is currently behind a flag, so wait till fully implemented - // if (!object || object === 'OPFS') { - // if (navigator && navigator.storage && 'getDirectory' in navigator.storage) { - // navigator.storage.getDirectory().then(function (handle) { - // handle.remove({ recursive: true }).then(function () { - // console.debug('All OPFS archives were deleted...'); - // }); - // }); - // } - // } - }; - // If no specific object was specified, we are doing a general reset, so ask user for confirmation - if (object) performReset(); - else { - uiUtil.systemAlert('

WARNING: This will reset the app to a freshly installed state, deleting all app caches,' + - // ' Archives stored in the Private File System,' + - ' and settings! (Archives stored in the OPFS will be preserved.)

Make sure you have an Internet connection' + - ' if this is an offline PWA, because it will be erased and reloaded.

', 'Warning!', true).then(function (confirm) { - if (confirm) performReset(); - else console.debug('User cancelled'); - }); - } -} - -// Gets cache names from Service Worker, as we cannot rely on having them in params.cacheNames -function getCacheNames (callback) { - if (navigator.serviceWorker && navigator.serviceWorker.controller) { - var channel = new MessageChannel(); - channel.port1.onmessage = function (event) { - var names = event.data; - callback(names); - }; - navigator.serviceWorker.controller.postMessage({ - action: 'getCacheNames' - }, [channel.port2]); - } else { - callback(null); - } -} - -// Deregisters all Service Workers and reboots the app -function _reloadApp () { - var reboot = function () { - // Temporarily disable the beforeunload event listener - params.interceptBeforeUnload = false; - console.debug('Performing app reload...'); - setTimeout(function () { - window.location.href = location.origin + location.pathname + uriParams - }, 600); - }; - // Blank the querystring, so that parameters are not set on reload - var uriParams = ''; - if (~window.location.href.indexOf(params.PWAServer) && params.referrerExtensionURL) { - // However, if we're in a PWA that was called from local code, then by definition we must remain in SW mode and we need to - // ensure the user still has access to the referrerExtensionURL (so they can get back to local code from the UI) - uriParams = '?allowInternetAccess=truee&contentInjectionMode=serviceworker'; - uriParams += '&referrerExtensionURL=' + encodeURIComponent(params.referrerExtensionURL); - } - if (navigator && navigator.serviceWorker) { - console.debug('Deregistering Service Workers...'); - var cnt = 0; - navigator.serviceWorker.getRegistrations().then(function (registrations) { - if (!registrations.length) { - reboot(); - return; - } - cnt++; - registrations.forEach(function (registration) { - registration.unregister().then(function () { - cnt--; - if (!cnt) { - console.debug('All Service Workers unregistered...'); - reboot(); - } - }); - }); - }).catch(function (err) { - console.error(err); - reboot(); - }); - } else { - console.debug('Performing app reload...'); - reboot(); - } -} - var settingsStore = { getItem: function (sKey) { if (!sKey) { @@ -309,7 +156,5 @@ export default { setItem: settingsStore.setItem, removeItem: settingsStore.removeItem, hasItem: settingsStore.hasItem, - getCacheNames: getCacheNames, - reset: reset, getBestAvailableStorageAPI: getBestAvailableStorageAPI };