Files
Juan DenisandClaude Opus 4.6 2b2afd4ebc Add PWA mobile support with push notifications and offline mode
Phase 37: Progressive Web App manifest, service worker for offline caching,
push notification registration, quick actions API, mobile summary endpoint,
and offline data caching for server status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 02:28:54 -04:00

77 lines
2.0 KiB
JavaScript

// ServerKit Service Worker for PWA / Offline support
const CACHE_NAME = 'serverkit-v1';
const OFFLINE_URL = '/';
// Assets to cache on install
const PRECACHE_ASSETS = [
'/',
'/favicon.svg',
'/manifest.json',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(PRECACHE_ASSETS);
})
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
);
})
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
// Skip API requests — always go to network
if (event.request.url.includes('/api/')) {
return;
}
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(event.request).then((cached) => {
return cached || caches.match(OFFLINE_URL);
});
})
);
});
// Push notification handler
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : {};
const title = data.title || 'ServerKit';
const options = {
body: data.body || 'New notification',
icon: '/favicon.svg',
badge: '/favicon.svg',
data: data.url || '/',
actions: data.actions || [],
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const url = event.notification.data || '/';
event.waitUntil(
self.clients.matchAll({ type: 'window' }).then((clients) => {
for (const client of clients) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
return self.clients.openWindow(url);
})
);
});