167 lines
5.0 KiB
JavaScript
167 lines
5.0 KiB
JavaScript
'use strict';
|
|
const OFFLINE_DATA_FILE = 'offline.json',
|
|
CACHE_NAME_PREFIX = 'c3offline',
|
|
BROADCASTCHANNEL_NAME = 'offline',
|
|
CONSOLE_PREFIX = '[SW] ',
|
|
LAZYLOAD_KEYNAME = '',
|
|
broadcastChannel =
|
|
'undefined' == typeof BroadcastChannel ? null : new BroadcastChannel('offline');
|
|
function PostBroadcastMessage(a) {
|
|
broadcastChannel && setTimeout(() => broadcastChannel.postMessage(a), 3e3);
|
|
}
|
|
function Broadcast(a) {
|
|
PostBroadcastMessage({ type: a });
|
|
}
|
|
function BroadcastDownloadingUpdate(a) {
|
|
PostBroadcastMessage({ type: 'downloading-update', version: a });
|
|
}
|
|
function BroadcastUpdateReady(a) {
|
|
PostBroadcastMessage({ type: 'update-ready', version: a });
|
|
}
|
|
function IsUrlInLazyLoadList(a, b) {
|
|
if (!b) return !1;
|
|
try {
|
|
for (const c of b) if (new RegExp(c).test(a)) return !0;
|
|
} catch (a) {
|
|
console.error('[SW] Error matching in lazy-load list: ', a);
|
|
}
|
|
return !1;
|
|
}
|
|
function WriteLazyLoadListToStorage(a) {
|
|
return 'undefined' == typeof localforage
|
|
? Promise.resolve()
|
|
: localforage.setItem(LAZYLOAD_KEYNAME, a);
|
|
}
|
|
function ReadLazyLoadListFromStorage() {
|
|
return 'undefined' == typeof localforage
|
|
? Promise.resolve([])
|
|
: localforage.getItem(LAZYLOAD_KEYNAME);
|
|
}
|
|
function GetCacheBaseName() {
|
|
return 'c3offline-' + self.registration.scope;
|
|
}
|
|
function GetCacheVersionName(a) {
|
|
return GetCacheBaseName() + '-v' + a;
|
|
}
|
|
async function GetAvailableCacheNames() {
|
|
const a = await caches.keys(),
|
|
b = GetCacheBaseName();
|
|
return a.filter((a) => a.startsWith(b));
|
|
}
|
|
async function IsUpdatePending() {
|
|
const a = await GetAvailableCacheNames();
|
|
return 2 <= a.length;
|
|
}
|
|
async function GetMainPageUrl() {
|
|
const a = await clients.matchAll({ includeUncontrolled: !0, type: 'window' });
|
|
for (const b of a) {
|
|
let a = b.url;
|
|
if (
|
|
(a.startsWith(self.registration.scope) && (a = a.substring(self.registration.scope.length)),
|
|
a && '/' !== a)
|
|
)
|
|
return a.startsWith('?') && (a = '/' + a), a;
|
|
}
|
|
return '';
|
|
}
|
|
function fetchWithBypass(a, b) {
|
|
return (
|
|
'string' == typeof a && (a = new Request(a)),
|
|
b
|
|
? fetch(a.url, {
|
|
headers: a.headers,
|
|
mode: a.mode,
|
|
credentials: a.credentials,
|
|
redirect: a.redirect,
|
|
cache: 'no-store'
|
|
})
|
|
: fetch(a)
|
|
);
|
|
}
|
|
async function CreateCacheFromFileList(a, b, c) {
|
|
const d = await Promise.all(b.map((a) => fetchWithBypass(a, c)));
|
|
let e = !0;
|
|
for (const f of d)
|
|
f.ok ||
|
|
((e = !1),
|
|
console.error("[SW] Error fetching '" + f.url + "' (" + f.status + ' ' + f.statusText + ')'));
|
|
if (!e) throw new Error('not all resources were fetched successfully');
|
|
const f = await caches.open(a);
|
|
try {
|
|
return await Promise.all(d.map((a, c) => f.put(b[c], a)));
|
|
} catch (b) {
|
|
throw (console.error('[SW] Error writing cache entries: ', b), caches.delete(a), b);
|
|
}
|
|
}
|
|
async function UpdateCheck(a) {
|
|
try {
|
|
const b = await fetchWithBypass(OFFLINE_DATA_FILE, !0);
|
|
if (!b.ok) throw new Error('offline.json responded with ' + b.status + ' ' + b.statusText);
|
|
const c = await b.json(),
|
|
d = c.version,
|
|
e = c.fileList,
|
|
f = c.lazyLoad,
|
|
g = GetCacheVersionName(d),
|
|
h = await caches.has(g);
|
|
if (h) {
|
|
const a = await IsUpdatePending();
|
|
return void (a
|
|
? (console.log('[SW] Update pending'), Broadcast('update-pending'))
|
|
: (console.log('[SW] Up to date'), Broadcast('up-to-date')));
|
|
}
|
|
const i = await GetMainPageUrl();
|
|
e.unshift('./'),
|
|
i && -1 === e.indexOf(i) && e.unshift(i),
|
|
console.log('[SW] Caching ' + e.length + ' files for offline use'),
|
|
a ? Broadcast('downloading') : BroadcastDownloadingUpdate(d),
|
|
f && (await WriteLazyLoadListToStorage(f)),
|
|
await CreateCacheFromFileList(g, e, !a);
|
|
const j = await IsUpdatePending();
|
|
j
|
|
? (console.log('[SW] All resources saved, update ready'), BroadcastUpdateReady(d))
|
|
: (console.log('[SW] All resources saved, offline support ready'),
|
|
Broadcast('offline-ready'));
|
|
} catch (a) {
|
|
console.warn('[SW] Update check failed: ', a);
|
|
}
|
|
}
|
|
self.addEventListener('install', (a) => {
|
|
a.waitUntil(UpdateCheck(!0).catch(() => null));
|
|
});
|
|
async function GetCacheNameToUse(a, b) {
|
|
if (1 === a.length || !b) return a[0];
|
|
const c = await clients.matchAll();
|
|
if (1 < c.length) return a[0];
|
|
const d = a[a.length - 1];
|
|
return (
|
|
console.log('[SW] Updating to new version'),
|
|
await Promise.all(a.slice(0, -1).map((a) => caches.delete(a))),
|
|
d
|
|
);
|
|
}
|
|
async function HandleFetch(a, b) {
|
|
const c = await GetAvailableCacheNames();
|
|
if (!c.length) return fetch(a.request);
|
|
const d = await GetCacheNameToUse(c, b),
|
|
e = await caches.open(d),
|
|
f = await e.match(a.request);
|
|
if (f) return f;
|
|
const g = await Promise.all([fetch(a.request), ReadLazyLoadListFromStorage()]),
|
|
h = g[0],
|
|
i = g[1];
|
|
if (IsUrlInLazyLoadList(a.request.url, i))
|
|
try {
|
|
await e.put(a.request, h.clone());
|
|
} catch (b) {
|
|
console.warn("[SW] Error caching '" + a.request.url + "': ", b);
|
|
}
|
|
return h;
|
|
}
|
|
self.addEventListener('fetch', (a) => {
|
|
if (new URL(a.request.url).origin === location.origin) {
|
|
const b = 'navigate' === a.request.mode,
|
|
c = HandleFetch(a, b);
|
|
b && a.waitUntil(c.then(() => UpdateCheck(!1))), a.respondWith(c);
|
|
}
|
|
});
|