- Update to @tabler/icons-svelte-runes for a more 'Svente 5' experience. - Adjust error handling in load functions, plain error thrown for handling in the error handler. - Fix the warning regarding a click handler attached to an <img> on the About page - Re-organise component directory - Remove unused font declarations from global.css Features: - Add service-worker caching of FilterData API response to allow for filtering to work fully offline -
122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
/// <reference types="@sveltejs/kit" />
|
|
/// <reference lib="webworker" />
|
|
|
|
import type { ApiEnvelope } from '@owlboard/api-schema-types';
|
|
import { build, files, version } from '$service-worker';
|
|
|
|
const sw = self as unknown as ServiceWorkerGlobalScope;
|
|
const CACHE_NAME = `owlboard-cache-${version}`;
|
|
const ASSETS = [
|
|
...build,
|
|
...files.filter((file) => !file.endsWith('manifest.json') && !file.startsWith('/icons/')),
|
|
'/'
|
|
];
|
|
|
|
sw.addEventListener('install', (event) => {
|
|
sw.skipWaiting();
|
|
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)));
|
|
});
|
|
|
|
sw.addEventListener('activate', (event) => {
|
|
event.waitUntil(
|
|
Promise.all([
|
|
caches.keys().then((keys) => {
|
|
return Promise.all(
|
|
keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
|
|
);
|
|
}),
|
|
sw.clients.claim()
|
|
])
|
|
);
|
|
});
|
|
|
|
sw.addEventListener('fetch', (event) => {
|
|
const { request } = event;
|
|
if (request.method !== 'GET') return;
|
|
|
|
const url = new URL(request.url);
|
|
|
|
// Uncached static assets
|
|
if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) {
|
|
event.respondWith(fetch(request));
|
|
return;
|
|
}
|
|
|
|
// Static Assets (Cache-First)
|
|
if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) {
|
|
event.respondWith(caches.match(request).then((res) => res || fetch(request)));
|
|
return;
|
|
}
|
|
|
|
// Cachable API Responses - stale-while-revalidate
|
|
if (url.pathname === '/api/v3/locationFilter/data') {
|
|
event.respondWith(
|
|
caches.open('ob-dynamic-cache').then(async (cache) => {
|
|
const cachedResponse = await cache.match(request);
|
|
|
|
// Ensure fallback response exists
|
|
const makeOfflineFallback = () => {
|
|
const errorObject: ApiEnvelope.Envelope = {
|
|
e: {
|
|
code: 'NETWORK_DISCONNECTED',
|
|
msg: 'Cannot connect to the OwlBoard server'
|
|
},
|
|
t: Math.floor(Date.now() / 1000)
|
|
};
|
|
return new Response(JSON.stringify(errorObject), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
};
|
|
|
|
const networkFetch = fetch(request)
|
|
.then((networkResponse) => {
|
|
if (networkResponse.ok) {
|
|
cache.put(request, networkResponse.clone());
|
|
}
|
|
return networkResponse;
|
|
})
|
|
.catch(() => {
|
|
// If offline & cache empty, return fallback
|
|
return makeOfflineFallback();
|
|
});
|
|
|
|
// If cachedResponse is undefined, networkFetch safely resolves to a Response
|
|
return cachedResponse || networkFetch;
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Offline API Fallback (Network-First)
|
|
if (url.pathname.startsWith('/api') || url.hostname.includes('api')) {
|
|
event.respondWith(
|
|
fetch(request).catch(() => {
|
|
const errorObject: ApiEnvelope.Envelope = {
|
|
e: {
|
|
code: 'NETWORK_DISCONNECTED',
|
|
msg: 'Cannot connect to the OwlBoard server'
|
|
},
|
|
t: Math.floor(Date.now() / 1000)
|
|
};
|
|
return new Response(JSON.stringify(errorObject), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Nav fallback - return fallback page, if offline and page requested
|
|
if (request.mode === 'navigate' || request.headers.get('accept')?.includes('text/html')) {
|
|
event.respondWith(
|
|
fetch(request).catch(() => {
|
|
// Serve svelte fallback page
|
|
return caches.match('/') as Promise<Response>;
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
});
|