AnthoLume/assets/sw.js

162 lines
4.1 KiB
JavaScript
Raw Normal View History

// 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;
})()
);
});