All checks were successful
Build AppImage / build (push) Has been skipped
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>
108 lines
2.9 KiB
TypeScript
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 {};
|