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