95 lines
3.4 KiB
JavaScript
95 lines
3.4 KiB
JavaScript
const CACHE = 'hamham-public-v1';
|
|
async function savePage(address) {
|
|
const url = new URL(address, self.location.origin);
|
|
if (url.origin !== self.location.origin) return;
|
|
const match = /^\/rezepte\/([a-f0-9-]{36})(?:\/kochen)?$/.exec(url.pathname);
|
|
if (match) {
|
|
const recipe = await fetch(`/api/recipes/${match[1]}`).then((r) => (r.ok ? r.json() : null));
|
|
if (!recipe?.published) return;
|
|
} else if (!['/', '/favoriten', '/einkaufsliste'].includes(url.pathname)) return;
|
|
const response = await fetch(url.href, { headers: { Accept: 'text/html' } });
|
|
if (!response.ok || !response.headers.get('content-type')?.includes('text/html')) return;
|
|
const cache = await caches.open(CACHE),
|
|
html = await response.clone().text();
|
|
await cache.put(url.href, response);
|
|
const paths = [
|
|
...new Set(
|
|
Array.from(
|
|
html.matchAll(/(?:src|href)="(\/(?:_next\/static\/|images\/|icons\/|media\/)[^"<>]+)"/g),
|
|
(m) => m[1].replaceAll('&', '&'),
|
|
),
|
|
),
|
|
];
|
|
await Promise.all(
|
|
paths.map(async (path) => {
|
|
try {
|
|
const r = await fetch(path);
|
|
if (r.ok) await cache.put(path, r);
|
|
} catch {}
|
|
}),
|
|
);
|
|
}
|
|
self.addEventListener('message', (event) => {
|
|
if (event.data?.type === 'CACHE_PAGE' && typeof event.data.url === 'string')
|
|
event.waitUntil(savePage(event.data.url).catch(() => {}));
|
|
});
|
|
self.addEventListener('install', (event) => {
|
|
event.waitUntil(
|
|
caches.open(CACHE).then((cache) => cache.addAll(['/offline.html', '/icons/icon-192.png'])),
|
|
);
|
|
self.skipWaiting();
|
|
});
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil(
|
|
caches
|
|
.keys()
|
|
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))),
|
|
);
|
|
self.clients.claim();
|
|
});
|
|
self.addEventListener('fetch', (event) => {
|
|
const req = event.request,
|
|
url = new URL(req.url);
|
|
if (
|
|
req.method !== 'GET' ||
|
|
url.origin !== self.location.origin ||
|
|
url.pathname.startsWith('/admin') ||
|
|
url.pathname.startsWith('/api') ||
|
|
url.searchParams.has('_rsc') ||
|
|
req.headers.get('rsc')
|
|
)
|
|
return;
|
|
const asset = /^\/(?:_next\/static\/|images\/|icons\/|media\/)/.test(url.pathname);
|
|
const recipe = /^\/rezepte\/([a-f0-9-]{36})(?:\/kochen)?$/.exec(url.pathname);
|
|
if (!asset && req.mode !== 'navigate') return;
|
|
event.respondWith(
|
|
(async () => {
|
|
const cache = await caches.open(CACHE);
|
|
if (asset) {
|
|
const cached = await cache.match(req);
|
|
if (cached) return cached;
|
|
}
|
|
try {
|
|
const response = await fetch(req);
|
|
if (response.ok && asset) await cache.put(req, response.clone());
|
|
if (response.ok && recipe) {
|
|
// Never persist a draft or an administrator page. Only published recipes.
|
|
const data = await fetch(`/api/recipes/${recipe[1]}`)
|
|
.then((r) => (r.ok ? r.json() : null))
|
|
.catch(() => null);
|
|
if (data?.published) await cache.put(req, response.clone());
|
|
else await cache.delete(req);
|
|
}
|
|
return response;
|
|
} catch {
|
|
const saved =
|
|
(await cache.match(req)) ??
|
|
(req.mode === 'navigate' ? await cache.match(req, { ignoreSearch: true }) : null);
|
|
if (saved) return saved;
|
|
if (req.mode === 'navigate') return await cache.match('/offline.html');
|
|
return new Response('', { status: 503 });
|
|
}
|
|
})(),
|
|
);
|
|
});
|