Skip to content

Commit

Permalink
Provide methods for resetting the app and bypassing appCache #794 (#797)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaifroid authored Jan 16, 2022
1 parent 18fe5a2 commit 06e528a
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 74 deletions.
56 changes: 39 additions & 17 deletions service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* 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
* Copyright 2022 Mossroy, Jaifroid and contributors
* License GPL v3:
*
* This file is part of Kiwix.
Expand Down Expand Up @@ -53,7 +53,15 @@ const APP_CACHE = 'kiwixjs-appCache-' + appVersion;
* Caching is on by default but can be turned off by the user in Configuration
* @type {Boolean}
*/
var useCache = true;
var useAssetsCache = true;

/**
* A global Boolean that governs whether the APP_CACHE will be used
* This is an expert setting in Configuration
* @type {Boolean}
*/
var useAppCache = true;


/**
* A regular expression that matches the Content-Types of assets that may be stored in ASSETS_CACHE
Expand Down Expand Up @@ -176,7 +184,10 @@ self.addEventListener('activate', function (event) {
let outgoingMessagePort = null;
let fetchCaptureEnabled = false;

self.addEventListener('fetch', function (event) {
/**
* Intercept selected Fetch requests from the browser window
*/
self.addEventListener('fetch', function (event) {
// Only cache GET requests
if (event.request.method !== "GET") return;
// Remove any querystring before requesting from the cache
Expand Down Expand Up @@ -207,20 +218,23 @@ self.addEventListener('fetch', function (event) {
} else {
// It's not an asset, or it doesn't match a ZIM URL pattern, so we should fetch it with Fetch API
return fetch(event.request).then(function (response) {
// If request was successful, add or update it in the cache, but be careful not to cache the ZIM archive itself!
if (!regexpExcludedURLSchema.test(rqUrl) && !/\.zim\w{0,2}$/i.test(rqUrl)) {
event.waitUntil(updateCache(APP_CACHE, event.request, response.clone()));
}
return response;
// If request was successful, add or update it in the cache, but be careful not to cache the ZIM archive itself!
if (!regexpExcludedURLSchema.test(rqUrl) && !/\.zim\w{0,2}$/i.test(rqUrl)) {
event.waitUntil(updateCache(APP_CACHE, event.request, response.clone()));
}
return response;
}).catch(function (error) {
console.debug("[SW] Network request failed and no cache.", error);
console.debug("[SW] Network request failed and no cache.", error);
});
}
})
);
});

self.addEventListener('message', function (event) {
/**
* Handle custom commands sent from app.js
*/
self.addEventListener('message', function (event) {
if (event.data.action) {
if (event.data.action === 'init') {
// On 'init' message, we initialize the outgoingMessagePort and enable the fetchEventListener
Expand All @@ -231,10 +245,18 @@ self.addEventListener('message', function (event) {
outgoingMessagePort = null;
fetchCaptureEnabled = false;
}
if (event.data.action.useCache) {
// Turns caching on or off (a string value of 'on' turns it on, any other string turns it off)
useCache = event.data.action.useCache === 'on';
console.debug('[SW] Caching was turned ' + event.data.action.useCache);
var oldValue;
if (event.data.action.assetsCache) {
// Turns caching on or off (a string value of 'enable' turns it on, any other string turns it off)
oldValue = useAssetsCache;
useAssetsCache = event.data.action.assetsCache === 'enable';
if (useAssetsCache !== oldValue) console.debug('[SW] Use of assetsCache was switched to: ' + useAssetsCache);
}
if (event.data.action.appCache) {
// Enables or disables use of appCache
oldValue = useAppCache;
useAppCache = event.data.action.appCache === 'enable';
if (useAppCache !== oldValue) console.debug('[SW] Use of appCache was switched to: ' + useAppCache);
}
if (event.data.action === 'getCacheNames') {
event.ports[0].postMessage({ 'app': APP_CACHE, 'assets': ASSETS_CACHE });
Expand Down Expand Up @@ -328,7 +350,7 @@ function removeUrlParameters(url) {
*/
function fromCache(cache, requestUrl) {
// Prevents use of Cache API if user has disabled it
if (!useCache && cache === ASSETS_CACHE) return Promise.reject('disabled');
if (!useAppCache && cache === APP_CACHE || !useAssetsCache && cache === ASSETS_CACHE) return Promise.reject('disabled');
return caches.open(cache).then(function (cacheObj) {
return cacheObj.match(requestUrl).then(function (matching) {
if (!matching || matching.status === 404) {
Expand All @@ -349,7 +371,7 @@ function fromCache(cache, requestUrl) {
*/
function updateCache(cache, request, response) {
// Prevents use of Cache API if user has disabled it
if (!useCache && cache === ASSETS_CACHE) return Promise.resolve();
if (!useAppCache && cache === APP_CACHE || !useAssetsCache && cache === ASSETS_CACHE) return Promise.resolve();
return caches.open(cache).then(function (cacheObj) {
console.debug('[SW] Adding ' + request.url + ' to ' + cache + '...');
return cacheObj.put(request, response);
Expand All @@ -364,7 +386,7 @@ function updateCache(cache, request, response) {
*/
function testCacheAndCountAssets(url) {
if (regexpExcludedURLSchema.test(url)) return Promise.resolve(['custom', 'custom', 'Custom', '-']);
if (!useCache) return Promise.resolve(['none', 'none', 'None', 0]);
if (!useAssetsCache) return Promise.resolve(['none', 'none', 'None', 0]);
return caches.open(ASSETS_CACHE).then(function (cache) {
return cache.keys().then(function (keys) {
return ['cacheAPI', ASSETS_CACHE, 'Cache API', keys.length];
Expand Down
9 changes: 9 additions & 0 deletions www/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
background: #171717;
}

.dark .card-warning .card-header {
background-color: #FFFF00;
}

/* End of app theme: dark */

/* Content themes: _invert, _mwInvert */
Expand Down Expand Up @@ -222,6 +226,11 @@ button {
margin: 2px;
}

.btn-danger {
background-color: lightyellow;
color: darkred;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
Expand Down
70 changes: 46 additions & 24 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ <h2 id="contents">
<li><a href="#searchSyntax">Title Search Usage</a></li>
<li><a href="#imageDownload">Image Download</a></li>
<li><a href="#privacy">Privacy Policy</a></li>
<li><a href="#expert">Expert Settings and Technical Information</a>
<li><a href="#technical">Technical Information</a>
<ul>
<li><a href="#format">ZIM Archive Format</a></li>
<li><a href="#FAT">Downloading and Storing Large Archives</a></li>
Expand Down Expand Up @@ -246,13 +246,13 @@ <h4>Longer version:</h4>
</p>
<p style="text-align: right"><a href="#contents">↑ Back to Contents</a></p>

<h3 id="expert">Expert settings and technical information</h3>
<h3 id="technical">Technical information</h3>

<h4 id="format">ZIM archive format</h4>
<p>
Offline archives use the <a href="https://www.openzim.org//wiki/OpenZIM" target="_blank">OpenZIM format&nbsp;<img src="img/Icon_External_Link.png" /></a>,
but note that this app has only been tested extensively on Wikipedia, WikiMedia and Stackexchage archives. Video content is in principle supported
if your device, browser or OS understands the format. Some other ZIMs use a proprietary dynamic UI which is only supported in ServiceWorker Mode
but note that this app has only been tested extensively on Wikipedia, WikiMedia, Gutenberg, Stackexchage and TED Talks archives. Video content is
supported if your device, browser or OS understands the format. Some ZIMs use a proprietary dynamic UI which is only supported in ServiceWorker Mode
(<a href="#modes">see below</a>).
</p>

Expand All @@ -266,7 +266,7 @@ <h4 id="FAT">Downloading and storing large archives</h4>
</p>
<p>
If you need to store a large ZIM archive on an older filesystem formatted as FAT16 or FAT32, you need to be aware of the file size limits of those systems
(FAT16 < 2GiB; FAT32 < 4GiB). Most modern microSD cards, thumb drives or hard drives are formatted as exFAT or another modern FS such as NTFS, which do not
(FAT16 &lt; 2GiB; FAT32 &lt; 4GiB). Most modern microSD cards, thumb drives or hard drives are formatted as exFAT or another modern FS such as NTFS, which do not
have this issue. If your ZIM archive is larger than the FS limit, it is possible to split the archive into several 2GiB-1 or 4GiB-1 files (or smaller).
You will need to give a file extension to each chunk in the right order following this pattern: <code>*.zimaa</code>, <code>*.zimab</code>, <code>*.zimac</code>,
<code>...</code>, etc.). When you pick this archive in the app, be sure to pick <b>all</b> the chunks, or drag-and-drop them all into the app.
Expand All @@ -280,7 +280,7 @@ <h4 id="FAT">Downloading and storing large archives</h4>
<h4 id="modes">JQuery and ServiceWorker Modes</h4>
<p>
Depending on your browser or framework, this app may be capable of running in two different modes, which we call
"JQuery Mode" and "ServiceWorker Mode" for short. There is a toggle under Expert Settings in Configuration.
"JQuery Mode" and "ServiceWorker Mode" for short. There is a toggle under Compatibility Settings in Configuration.
Here is a technical explanation of what these modes do:
<ul>
<li>
Expand All @@ -292,16 +292,17 @@ <h4 id="modes">JQuery and ServiceWorker Modes</h4>
but it is compensated for because we do not extract or run JavaScript assets (which would be technically
extremely complicated). As a result, for WikiMedia archives this mode is usually quite fast. On the downside,
ZIMs that have a proprietary dynamic UI (such as Gutenberg or TED talks) are only partially supported in this
mode: the UI does not work, but articles can be searched for and loaded from the search bar.
mode: the UI does not work, but articles can be searched for and loaded from the search bar. This mode is
compatible with older browsers and frameworks.
</li>
<li>
<b>ServiceWorker Mode</b>: This mode, as its name implies, requires that the browser or framework be capable of
installing a Service Worker. It works by intercepting the browser or framework's Fetch calls (network requests)
and supplying the requested content from the ZIM. This mode requires no DOM traversal, and the content is
read and supplied as-is from the archive. Dynamic content (e.g. JavaScript) and proprietary UIs are fully
supported in this mode. It can feel initially a little slower while commonly used assets are being cached,
but it soon equals JQuery mode in speed, at least in modern browsers. However, older browsers such as IE11 do
not support this mode, and the app must be running in a secure context (<code>https:</code>, <code>localhost</code>,
but it soon equals JQuery mode in speed, at least in modern browsers. However, older browsers such as IE11 are
incompatible with this mode, and the app must be running in a secure context (<code>https:</code>, <code>localhost</code>,
or certain browser extensions). While this mode is not natively supported in Mozilla (Firefox) browser
extensions, we provide a functional workaround by re-launching the extension as a Progressive Web App (PWA).
Note that this mode cannot run with the <code>file:</code> protocol (but only IE11 and old Edge allow the app to run
Expand Down Expand Up @@ -439,14 +440,6 @@ <h3>Display settings</h3>
<div class="card card-info" id="displaySettingsDiv">
<div class="card-header">Display options:</div>
<div class="card-body">
<div class="checkbox">
<label title="A warning is shown if you load a ZIM that has active or dynamic content while you are in JQuery mode. It is not recommended to disable this warning." >
<input type="checkbox" name="hideActiveContentWarning"
id="hideActiveContentWarningCheck">
<strong>Permanently hide active content warning</strong> (for
experienced users)
</label>
</div>
<div class="checkbox">
<label title="The animation is on by default and can be disabled by unchecking this option." >
<input type="checkbox" name="showUIAnimations" id="showUIAnimationsCheck"
Expand Down Expand Up @@ -537,24 +530,53 @@ <h3>Performance settings</h3>
</div>
<br />
<div class="container">
<h3>Expert settings</h3>
<div class="card card-danger" id="contentInjectionModeDiv">
<h3>Compatibility settings</h3>
<div class="card card-warning" id="contentInjectionModeDiv">
<div class="card-header">Content injection mode</div>
<div class="card-body">
<p>See <a href="#modes" id="modesLink">About (Expert Settings)</a> for an explanation of the difference between these modes:</p>
<p>See <a href="#modes" id="modesLink">About (Technical Information)</a> for an explanation of the difference between these modes:</p>
<div class="radio">
<label title="This mode is fine for everyday use with Wikipedia / WikiMedia archives, and is stable and safe.">
<input type="radio" name="contentInjectionMode" value="jquery"
id="jQueryModeRadio" checked>
<strong>JQuery</strong> (stable and safe)
<strong>JQuery</strong> (compatible with older browsers and frameworks)
</label>
</div>
<div class="radio">
<label title="This mode requires that the browser or framework be capable of installing a Service Worker. It works by intercepting the browser or framework's Fetch calls and supplying the requested content from the ZIM.">
<input type="radio" name="contentInjectionMode" value="serviceworker"
id="serviceworkerModeRadio">
<strong>ServiceWorker</strong> (not available on all platforms, but supports
more ZIM files)
<strong>ServiceWorker</strong> (supports archives with dynamic content)
</label>
</div>
</div>
</div>
</div>
<br />
<div class="container">
<h3>Expert settings</h3>
<div class="card card-danger" id="expertSettingsDiv">
<div class="card-header">Troubleshooting and development</div>
<div class="card-body">
<div class="checkbox">
<label title="A warning is shown if you load a ZIM that has active or dynamic content while you are in JQuery mode. It is not recommended to disable this warning." >
<input type="checkbox" name="hideActiveContentWarning"
id="hideActiveContentWarningCheck">
<strong>Permanently hide active content warning</strong> (for
experienced users)
</label>
</div>
<div class="checkbox" id="bypassAppCacheDiv">
<label
title="WARNING: Leaving this checked will prevent offline usage of the PWA. Setting will clear all existing Cache API caches, but assetsCache will be used unless also disabled above. For testing new code with the PWA.">
<input type="checkbox" name="bypassAppCache" id="bypassAppCacheCheck">
<b>Bypass AppCache</b> (<i>disables offline use of PWA!</i>)
</label>
</div>
<p class="pt-2">Reset the app to default settings and erase all caches:</p>
<div class="button">
<label title="This will return the app to its original settings on launch, and will empty all caches and settings and deregister Service Workers. The app will then reload.">
<button type="button" class="btn btn-danger" id="btnReset">Reset App</button>
</label>
</div>
</div>
Expand Down Expand Up @@ -592,7 +614,7 @@ <h3>Expert settings</h3>
<strong>Unable to display active content:</strong> This ZIM is not fully supported in jQuery mode.<br />
Content may be available by searching above (type a space or a letter of the alphabet), or else
<a id="swModeLink" href="#contentInjectionModeDiv" class="alert-link">switch to Service Worker mode</a>
if your platform supports it. &nbsp;[<a id="stop" href="#displaySettingsDiv" class="alert-link">Permanently hide</a>]
if your platform supports it. &nbsp;[<a id="stop" href="#expertSettingsDiv" class="alert-link">Permanently hide</a>]
</div>
</div>
<iframe id="articleContent" class="articleIFrame" src="article.html"></iframe>
Expand Down
Loading

0 comments on commit 06e528a

Please sign in to comment.