claude-desktop/pwa/client/src/service-worker.ts
Eddy 4e36b04cc9
All checks were successful
Build AppImage / build (push) Has been skipped
PWA Mobile-App: API-Server + SvelteKit-Frontend (Phase 1+2)
Backend (pwa/server/):
- Express + WebSocket API-Server auf Port 3100
- Claude Agent SDK Bridge mit Streaming
- Bearer-Token Authentifizierung
- REST: /api/status, /api/models, /api/sessions, /api/stop
- WebSocket: /ws mit Live-Text-Streaming
- Dockerfile für Container-Deployment

Frontend (pwa/client/):
- SvelteKit 5 PWA mit Dark Theme
- Mobil-optimierter Chat (WhatsApp/Telegram-Feeling)
- Message-Bubbles mit Markdown + Live-Streaming
- Session-Drawer (Swipe von links)
- Settings-Modal (Server/Token/Modell)
- Service Worker für Auto-Updates
- PWA-Manifest für "Add to Homescreen"
- Safe-Area-Insets für Notch-Handys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 06:38:12 +02:00

108 lines
2.9 KiB
TypeScript

/// <reference lib="webworker" />
// Claude Chat PWA — Service Worker
// Cache-First fuer statische Assets, Network-First fuer API-Calls
declare const self: ServiceWorkerGlobalScope;
const CACHE_NAME = 'claude-chat-v1';
// Statische Assets die gecacht werden sollen
const STATIC_ASSETS = [
'/',
'/favicon.png',
'/manifest.json',
];
// ============ Install — Statische Assets vorladen ============
self.addEventListener('install', (event: ExtendableEvent) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(STATIC_ASSETS);
})
);
// Sofort aktivieren, nicht auf alte Tabs warten
self.skipWaiting();
});
// ============ Activate — Alte Caches loeschen ============
self.addEventListener('activate', (event: ExtendableEvent) => {
event.waitUntil(
caches.keys().then((names) => {
return Promise.all(
names
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
// Sofort alle Clients uebernehmen
self.clients.claim();
});
// ============ Fetch — Caching-Strategie ============
self.addEventListener('fetch', (event: FetchEvent) => {
const url = new URL(event.request.url);
// WebSocket-Requests ignorieren
if (url.protocol === 'ws:' || url.protocol === 'wss:') {
return;
}
// API-Calls: Network-First mit Cache-Fallback
if (url.pathname.startsWith('/api/')) {
event.respondWith(networkFirst(event.request));
return;
}
// Statische Assets: Cache-First mit Network-Fallback
event.respondWith(cacheFirst(event.request));
});
/** Cache-First: Zuerst im Cache suchen, dann Netzwerk */
async function cacheFirst(request: Request): Promise<Response> {
const cached = await caches.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
// Erfolgreiche Responses cachen
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch {
// Offline-Fallback fuer HTML-Seiten
if (request.headers.get('accept')?.includes('text/html')) {
const cached = await caches.match('/');
if (cached) return cached;
}
return new Response('Offline', { status: 503 });
}
}
/** Network-First: Zuerst Netzwerk versuchen, dann Cache */
async function networkFirst(request: Request): Promise<Response> {
try {
const response = await fetch(request);
// Erfolgreiche GET-Responses cachen
if (response.ok && request.method === 'GET') {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch {
const cached = await caches.match(request);
if (cached) return cached;
return new Response(JSON.stringify({ error: 'Offline' }), {
status: 503,
headers: { 'Content-Type': 'application/json' },
});
}
}
export {};