const BASE_ITEM = `
`; const GET_SW_CACHE = "GET_SW_CACHE"; const DEL_SW_CACHE = "DEL_SW_CACHE"; async function initOffline() { if (document.location.pathname !== "/offline") window.history.replaceState(null, null, "/offline"); await SW.install().catch((e) => { console.log("Service Worker Install Error:", e); }); console.log("Registered"); getCachedDocuments().then(populateDOM); } /** * Ask service worker for list of cached documents. **/ async function getCachedDocuments() { let cachedDocuments = await IDB.find(/^DOCUMENT-/, true); let cachedSWData = await SW.send({ type: GET_SW_CACHE }); return cachedSWData; } async function sendSWMessage(data) { let swReg = await navigator.serviceWorker.ready; if (!swReg.active) return; function randomID() { return "00000000000000000000000000000000".replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))) .toString(16) .toUpperCase() ); } let id = randomID(); navigator.serviceWorker.addEventListener(id, (event) => { console.log("Response:", event); navigator.serviceWorker.removeEventListener(id); }); swReg.active.postMessage({ id, data }); } /** * Populate DOM with cached documents. **/ function populateDOM(data) { let allDocuments = document.querySelector("#container div"); data.forEach((item) => { // Create Main Element let baseEl = document.createElement("div"); baseEl.innerHTML = BASE_ITEM; baseEl = baseEl.firstElementChild; // Get Elements let coverEl = baseEl.querySelector("a img"); let [titleEl, authorEl, percentageEl] = baseEl.querySelectorAll("p + p"); let downloadEl = baseEl.querySelector("svg").parentElement; // Set Variables downloadEl.setAttribute("href", "/documents/" + item.id + "/file"); coverEl.setAttribute("src", "/documents/" + item.id + "/cover"); coverEl.parentElement.setAttribute("href", "/reader?id=" + item.id); titleEl.textContent = item.title; authorEl.textContent = item.author; percentageEl.textContent = item.percentage + "%"; allDocuments.append(baseEl); }); } /** * Allow adding file to offline reader. Add to IndexedDB, * and later upload? Add style indicating external file? **/ function handleFileAdd() {} const SW = (function () { // Helper Function function randomID() { return "00000000000000000000000000000000".replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))) .toString(16) .toUpperCase() ); } // Variables let swInstance = null; let outstandingMessages = {}; navigator.serviceWorker.addEventListener("message", ({ data }) => { let { id } = data; data = data.data; console.log("SW Message:", id, data); if (!outstandingMessages[id]) return console.warn("Invalid Outstanding Message:", { id, data }); outstandingMessages[id](data); delete outstandingMessages[id]; }); return { async install() { // Register Service Worker swInstance = await navigator.serviceWorker.register("/sw.js"); console.log(swInstance); swInstance.onupdatefound = (data) => console.log("Update Found:", data); // Wait for Registration / Update let serviceWorker = swInstance.installing || swInstance.waiting || swInstance.active; if (serviceWorker.state == "activated") return; await new Promise((resolve) => { serviceWorker.onstatechange = (data) => { console.log("State Change:", serviceWorker.state); if (serviceWorker.state == "activated") resolve(); }; }); }, send(data) { if (!swInstance?.active) return Promise.reject("Inactive Service Worker"); let id = randomID(); let msgPromise = new Promise((resolve) => { outstandingMessages[id] = resolve; }); swInstance.active.postMessage({ id, data }); return msgPromise; }, }; })(); // Initialize window.addEventListener("DOMContentLoaded", initOffline);