// ==================================================== // ⚡ SERVICE WORKER — Ultra Elite Dashboard // Ermöglicht Offline-Nutzung auf allen Geräten // ==================================================== const CACHE_NAME = 'elite-dashboard-v6'; const OFFLINE_URL = './index.html'; // Dateien die IMMER gecacht werden (Offline verfügbar) const PRECACHE_ASSETS = [ './', './index.html', './manifest.json', './icon-192.png', './icon-512.png', // Externe Bibliotheken 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap', 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js', 'https://unpkg.com/docx@8.2.3/build/index.js', 'https://unpkg.com/file-saver@2.0.5/dist/FileSaver.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js', 'https://cdn.jsdelivr.net/npm/pannellum@2.5.7/build/pannellum.js', 'https://cdn.jsdelivr.net/npm/pannellum@2.5.7/build/pannellum.css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' ]; // ===== INSTALL: Dateien vorab cachen ===== self.addEventListener('install', event => { console.log('🔧 Service Worker: Installing...'); event.waitUntil( caches.open(CACHE_NAME) .then(cache => { console.log('📦 Caching core assets...'); // Cache each individually to avoid one failure blocking all return Promise.allSettled( PRECACHE_ASSETS.map(url => cache.add(url).catch(err => console.warn('⚠️ Failed to cache:', url, err) ) ) ); }) .then(() => self.skipWaiting()) // Sofort aktivieren ); }); // ===== ACTIVATE: Alte Caches löschen ===== self.addEventListener('activate', event => { console.log('✅ Service Worker: Activated!'); event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames .filter(name => name !== CACHE_NAME) .map(name => { console.log('🗑️ Deleting old cache:', name); return caches.delete(name); }) ); }).then(() => self.clients.claim()) // Alle Tabs übernehmen ); }); // ===== FETCH: Anfragen abfangen & aus Cache bedienen ===== self.addEventListener('fetch', event => { const request = event.request; // Nur GET-Anfragen cachen if (request.method !== 'GET') return; // API-Anfragen: Network First (z.B. Gebetszeiten, Währung) if (request.url.includes('api.') || request.url.includes('aladhan.com') || request.url.includes('frankfurter.app') || request.url.includes('mymemory.translated') || request.url.includes('nominatim.openstreetmap') || request.url.includes('openai.com')) { event.respondWith( fetch(request) .then(response => { // API-Antwort auch cachen für Offline-Zugriff const responseClone = response.clone(); caches.open(CACHE_NAME).then(cache => { cache.put(request, responseClone); }); return response; }) .catch(() => { // Offline: Aus Cache holen falls vorhanden return caches.match(request); }) ); return; } // Alles andere: Cache First, Network Fallback event.respondWith( caches.match(request) .then(cachedResponse => { if (cachedResponse) { // Im Hintergrund aktualisieren (Stale While Revalidate) fetch(request).then(networkResponse => { if (networkResponse && networkResponse.status === 200) { caches.open(CACHE_NAME).then(cache => { cache.put(request, networkResponse); }); } }).catch(() => {}); // Netzwerkfehler ignorieren return cachedResponse; } // Nicht im Cache → Aus Netzwerk holen & cachen return fetch(request) .then(networkResponse => { if (!networkResponse || networkResponse.status !== 200) { return networkResponse; } const responseClone = networkResponse.clone(); caches.open(CACHE_NAME).then(cache => { cache.put(request, responseClone); }); return networkResponse; }) .catch(() => { // Komplett Offline: Fallback zur Hauptseite if (request.destination === 'document') { return caches.match(OFFLINE_URL); } // Für Bilder: leeres transparentes Pixel if (request.destination === 'image') { return new Response( '', { headers: { 'Content-Type': 'image/svg+xml' } } ); } }); }) ); }); // ===== BACKGROUND SYNC (optional) ===== self.addEventListener('sync', event => { if (event.tag === 'sync-data') { console.log('🔄 Background sync triggered'); } }); // ===== PUSH NOTIFICATIONS (optional) ===== self.addEventListener('push', event => { const data = event.data ? event.data.json() : {}; self.registration.showNotification(data.title || 'Dashboard', { body: data.body || 'Neue Benachrichtigung', icon: './icon-192.png', badge: './icon-192.png' }); });