Files
claudecodeui/public/sw.js
Simos Mikelatos 45e71a0e73 feat: introduce notification system and claude notifications (#450)
* feat: introduce notification system and claude notifications

* fix(sw): prevent caching of API requests and WebSocket upgrades

* default to false for webpush notifications and translations for the button

* fix: notifications orchestrator and add a notification when  first enabled

* fix: remove unused state update and dependency in settings controller hook

* fix: show notifications settings tab

* fix: add notifications for response completion for all providers

* feat: show session name in notification and don't reload tab on clicking
--- the notification

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Haileyesus <something@gmail.com>
2026-03-13 18:59:09 +03:00

106 lines
2.5 KiB
JavaScript
Executable File

// Service Worker for Claude Code UI PWA
const CACHE_NAME = 'claude-ui-v1';
const urlsToCache = [
'/',
'/index.html',
'/manifest.json'
];
// Install event
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
})
);
self.skipWaiting();
});
// Fetch event
self.addEventListener('fetch', event => {
// Never cache API requests or WebSocket upgrades
if (event.request.url.includes('/api/') || event.request.url.includes('/ws')) {
return;
}
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
// Activate event
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});
// Push notification event
self.addEventListener('push', event => {
if (!event.data) return;
let payload;
try {
payload = event.data.json();
} catch {
payload = { title: 'Claude Code UI', body: event.data.text() };
}
const options = {
body: payload.body || '',
icon: '/logo-256.png',
badge: '/logo-128.png',
data: payload.data || {},
tag: payload.data?.tag || `${payload.data?.sessionId || 'global'}:${payload.data?.code || 'default'}`,
renotify: true
};
event.waitUntil(
self.registration.showNotification(payload.title || 'Claude Code UI', options)
);
});
// Notification click event
self.addEventListener('notificationclick', event => {
event.notification.close();
const sessionId = event.notification.data?.sessionId;
const provider = event.notification.data?.provider || null;
const urlPath = sessionId ? `/session/${sessionId}` : '/';
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(async clientList => {
for (const client of clientList) {
if (client.url.includes(self.location.origin)) {
await client.focus();
client.postMessage({
type: 'notification:navigate',
sessionId: sessionId || null,
provider,
urlPath
});
return;
}
}
return self.clients.openWindow(urlPath);
})
);
});