Files
synapbus/web/static/sw.js
T
Algis DumbrisandClaude Opus 4.6 30d62de350 feat: v0.7.0 — analytics dashboard, PWA, UX fixes, MCP prompts
Analytics: time-series message graph with 5 time spans (1h/4h/24h/7d/30d),
top-5 agents and channels leaderboards, summary cards. 4 new REST endpoints.

PWA: web app manifest, service worker with cache-first static/network-only
API strategy, push notifications via Web Push API with VAPID keys, push
subscription management endpoints, SQLite migration for subscriptions.

UX fixes: auto-resize compose textarea (3-12 lines), inline editable agent
display name, editable human display name in settings, smart mention/channel
highlighting (existing→link, deleted→inactive badge, unknown→plain text),
font size -/+ preference (12-24px persisted in localStorage), version footer
with GitHub link.

MCP: 4 prompts — daily-digest, agent-health-check, channel-overview,
debug-agent. Registered with prompt capabilities enabled.

Code review fixes: scoped push unsubscribe to user, capped analytics limit
at 100, hardened HTML strip regex, bounded SW cache, backend push unsub on
disable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 10:36:58 +02:00

90 lines
2.9 KiB
JavaScript

const CACHE_NAME = 'synapbus-v1';
const STATIC_ASSETS = ['/', '/index.html'];
// Install: pre-cache app shell
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
);
self.skipWaiting();
});
// Activate: clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(names.filter((n) => n !== CACHE_NAME).map((n) => caches.delete(n)))
)
);
self.clients.claim();
});
// Fetch: network-only for API, cache-first for static
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Network-only for API, auth, MCP, SSE
if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/auth/') ||
url.pathname.startsWith('/mcp') || url.pathname.startsWith('/a2a')) {
return; // Let the browser handle normally
}
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached;
return fetch(event.request).then((response) => {
// Only cache static assets (SvelteKit chunks, icons, fonts)
if (response.ok && event.request.method === 'GET' &&
(url.pathname.startsWith('/_app/') || url.pathname.startsWith('/icons/') ||
url.pathname === '/' || url.pathname === '/index.html')) {
const clone = response.clone();
caches.open(CACHE_NAME).then(async (cache) => {
cache.put(event.request, clone);
// Evict oldest entries if cache exceeds 80 items
const keys = await cache.keys();
if (keys.length > 80) {
for (let i = 0; i < keys.length - 80; i++) {
cache.delete(keys[i]);
}
}
});
}
return response;
}).catch(() => {
// Offline fallback for navigation requests
if (event.request.mode === 'navigate') {
return caches.match('/index.html');
}
});
})
);
});
// Push notification handling
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : {};
const title = data.title || 'SynapBus';
const options = {
body: data.body || 'New notification',
icon: '/icons/icon-192.png',
badge: '/icons/icon-192.png',
tag: data.tag || 'synapbus-notification',
data: { url: data.url || '/' }
};
event.waitUntil(self.registration.showNotification(title, options));
});
// Notification click: open the relevant page
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const url = event.notification.data?.url || '/';
event.waitUntil(
clients.matchAll({ type: 'window' }).then((clientList) => {
for (const client of clientList) {
if (client.url.includes(url) && 'focus' in client) return client.focus();
}
return clients.openWindow(url);
})
);
});