162 lines
4.1 KiB
JavaScript
162 lines
4.1 KiB
JavaScript
|
// Local Consts
|
||
|
const SW_VERSION = 1;
|
||
|
const SW_CACHE_NAME = "OFFLINE_V1";
|
||
|
|
||
|
// Message Consts
|
||
|
const PURGE_SW_CACHE = "PURGE_SW_CACHE";
|
||
|
const DEL_SW_CACHE = "DEL_SW_CACHE";
|
||
|
const GET_SW_CACHE = "GET_SW_CACHE";
|
||
|
const GET_SW_VERSION = "GET_SW_VERSION";
|
||
|
|
||
|
// Assets
|
||
|
const ASSETS_DOCUMENT = /^\/documents\/[a-zA-Z0-9]{32}\/(cover|file|progress)$/;
|
||
|
const ASSETS_OFFLINE = [
|
||
|
// Offline Resources
|
||
|
"/offline",
|
||
|
"/assets/offline/index.js",
|
||
|
"/assets/reader/index.js",
|
||
|
"/assets/images/no-cover.jpg",
|
||
|
|
||
|
// App Style
|
||
|
"/manifest.json",
|
||
|
"/assets/style.css",
|
||
|
|
||
|
// Reader & Offline Libraries
|
||
|
"/assets/js/platform.js",
|
||
|
"/assets/js/jszip.min.js",
|
||
|
"/assets/js/epub.min.js",
|
||
|
"/assets/js/no-sleep.js",
|
||
|
"/assets/js/idb-keyval.js",
|
||
|
];
|
||
|
|
||
|
function wantCache(request) {
|
||
|
let urlPath = new URL(request.url).pathname;
|
||
|
if (ASSETS_OFFLINE.includes(urlPath)) return true;
|
||
|
if (urlPath.match(ASSETS_DOCUMENT)) return true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Nuke Cache
|
||
|
**/
|
||
|
function purgeCache() {
|
||
|
return caches.keys().then(function (names) {
|
||
|
for (let name of names) caches.delete(name);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update Cache
|
||
|
**/
|
||
|
async function updateCache(request) {
|
||
|
let cache = await caches.open(SW_CACHE_NAME);
|
||
|
|
||
|
console.log("UPDATING CACHE:", request.url);
|
||
|
|
||
|
return fetch(request).then((response) => {
|
||
|
const resClone = response.clone();
|
||
|
if (response.status < 400) cache.put(request, resClone);
|
||
|
return response;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pre-Cache Resources on Install
|
||
|
**/
|
||
|
function cacheOfflineResources() {
|
||
|
return caches.open(SW_CACHE_NAME).then(function (cache) {
|
||
|
return cache.addAll(ASSETS_OFFLINE);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Install & Update Listener -> Cache Offline Resources
|
||
|
**/
|
||
|
self.addEventListener("install", function (event) {
|
||
|
console.log("INSTALL:", event);
|
||
|
event.waitUntil(cacheOfflineResources());
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Message Listener -> Communication Channel Page <-> SW
|
||
|
**/
|
||
|
self.addEventListener("message", (event) => {
|
||
|
console.log("MESSAGE:", event);
|
||
|
let { id, data } = event.data;
|
||
|
|
||
|
if (data.type === GET_SW_VERSION) {
|
||
|
event.source.postMessage({ id, data: SW_VERSION });
|
||
|
} else if (data.type === PURGE_SW_CACHE) {
|
||
|
purgeCache()
|
||
|
.then(() => event.source.postMessage({ id, data: "SUCCESS" }))
|
||
|
.catch(() => event.source.postMessage({ id, data: "FAILURE" }));
|
||
|
} else if (data.type === GET_SW_CACHE) {
|
||
|
caches.open(SW_CACHE_NAME).then(async (cache) => {
|
||
|
let allKeys = await cache.keys();
|
||
|
|
||
|
let docResources = allKeys
|
||
|
.map((item) => new URL(item.url).pathname)
|
||
|
.filter((item) => item.startsWith("/documents/"));
|
||
|
|
||
|
let documentIDs = Array.from(
|
||
|
new Set(docResources.map((item) => item.split("/")[2]))
|
||
|
);
|
||
|
|
||
|
let cachedDocuments = await Promise.all(
|
||
|
documentIDs
|
||
|
.filter(
|
||
|
(id) =>
|
||
|
docResources.includes("/documents/" + id + "/file") &&
|
||
|
docResources.includes("/documents/" + id + "/progress")
|
||
|
)
|
||
|
.map(async (id) => {
|
||
|
let resp = await cache.match("/documents/" + id + "/progress");
|
||
|
return resp.json();
|
||
|
})
|
||
|
);
|
||
|
|
||
|
event.source.postMessage({ id, data: cachedDocuments });
|
||
|
});
|
||
|
|
||
|
// TODO
|
||
|
} else if (data.type === DEL_SW_CACHE) {
|
||
|
// TODO
|
||
|
} else {
|
||
|
event.source.postMessage({ id, data: { pong: 1 } });
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Fetch Listener -> Cache
|
||
|
* - Covers
|
||
|
* - Files
|
||
|
* - Assets (Styles, JS Libraries)
|
||
|
*
|
||
|
* NOTE: We do not cache regular app resources. We will fallback to the
|
||
|
* offline reader.
|
||
|
**/
|
||
|
self.addEventListener("fetch", (event) => {
|
||
|
event.respondWith(
|
||
|
(async function () {
|
||
|
// Bypass Lazy Caching
|
||
|
if (event.request.url.endsWith("/progress")) {
|
||
|
return updateCache(event.request).catch((e) =>
|
||
|
caches.match(event.request)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Get Potential Cache
|
||
|
let cachedResponse = await caches.match(event.request);
|
||
|
|
||
|
// Update Cache Asynchronously (If Wanted)
|
||
|
let newResponse = (
|
||
|
wantCache(event.request)
|
||
|
? updateCache(event.request)
|
||
|
: fetch(event.request)
|
||
|
).catch((e) => caches.match("/offline"));
|
||
|
|
||
|
return cachedResponse || newResponse;
|
||
|
})()
|
||
|
);
|
||
|
});
|