/** * MYP Service Worker * ================= * * Service Worker für das Mercedes-Benz 3D-Druck-Management-System * Bietet Offline-Funktionalität und Caching für bessere Performance */ const CACHE_NAME = 'myp-v1.0.0'; const STATIC_CACHE_NAME = 'myp-static-v1.0.0'; const DYNAMIC_CACHE_NAME = 'myp-dynamic-v1.0.0'; // Statische Ressourcen, die immer gecacht werden sollen const STATIC_ASSETS = [ '/', '/static/css/tailwind.min.css', '/static/css/dark-light-unified.css', '/static/fontawesome/css/all.min.css', '/static/js/jobs-safety-fix.js', '/static/manifest.json', '/static/favicon.svg', '/static/icons/icon-192.png', '/static/icons/icon-512.png' ]; // API-Endpunkte, die gecacht werden können const CACHEABLE_APIS = [ '/api/printers', '/api/jobs', '/api/stats' ]; // Installation des Service Workers self.addEventListener('install', event => { console.log('🔧 MYP Service Worker wird installiert...'); event.waitUntil( caches.open(STATIC_CACHE_NAME) .then(cache => { console.log('📦 Statische Assets werden gecacht...'); return cache.addAll(STATIC_ASSETS); }) .then(() => { console.log('✅ MYP Service Worker erfolgreich installiert'); return self.skipWaiting(); }) .catch(error => { console.error('❌ Fehler bei Service Worker Installation:', error); }) ); }); // Aktivierung des Service Workers self.addEventListener('activate', event => { console.log('🚀 MYP Service Worker wird aktiviert...'); event.waitUntil( caches.keys() .then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { // Alte Caches löschen if (cacheName !== STATIC_CACHE_NAME && cacheName !== DYNAMIC_CACHE_NAME && cacheName !== CACHE_NAME) { console.log('🗑️ Lösche alten Cache:', cacheName); return caches.delete(cacheName); } }) ); }) .then(() => { console.log('✅ MYP Service Worker aktiviert'); return self.clients.claim(); }) ); }); // Fetch-Event Handler für Caching-Strategien self.addEventListener('fetch', event => { try { const request = event.request; // Basis-Validierung der Anfrage if (!request || !request.url) { console.warn('🚫 Ungültige Anfrage erhalten:', request); return; } const url = new URL(request.url); // Nur GET-Requests cachen if (request.method !== 'GET') { return; } // Bestimmte Dateitypen überspringen if (url.pathname.includes('.woff') || url.pathname.includes('.ttf') || url.pathname.includes('.eot') || url.pathname.endsWith('.map')) { return; } } catch (error) { console.error('❌ Fehler bei fetch event verarbeitung:', error); return; } // Statische Assets: Cache First if (STATIC_ASSETS.some(asset => url.pathname.endsWith(asset) || url.pathname === asset)) { event.respondWith(cacheFirst(request)); return; } // API-Endpunkte: Network First mit Cache Fallback if (CACHEABLE_APIS.some(api => url.pathname.startsWith(api))) { event.respondWith(networkFirst(request)); return; } // Statische Dateien (CSS, JS, Bilder): Cache First if (url.pathname.startsWith('/static/')) { event.respondWith(cacheFirst(request)); return; } // HTML-Seiten: Network First if (request.headers.get('accept') && request.headers.get('accept').includes('text/html')) { event.respondWith(networkFirst(request)); return; } // Alle anderen Requests: Network Only mit besserer Error-Behandlung event.respondWith( fetch(request).catch(error => { console.warn('🌐 Network request failed:', request.url, error); // Vermeide Fehler-Loops bei Service Worker eigenen Requests if (request.url.includes('sw.js') || request.url.includes('service-worker')) { throw error; } // Für kritische Fehler eine Fallback-Response zurückgeben if (request.destination === 'document') { return new Response('Service temporarily unavailable', { status: 503, statusText: 'Service Unavailable', headers: { 'Content-Type': 'text/html' } }); } // Für API-Requests if (request.url.includes('/api/')) { return new Response(JSON.stringify({ success: false, error: 'Network error', message: 'Service temporarily unavailable' }), { status: 503, statusText: 'Service Unavailable', headers: { 'Content-Type': 'application/json' } }); } // Für andere Requests: Fehler weiterwerfen throw error; }) ); }); /** * Cache First Strategie * Versucht zuerst aus dem Cache zu laden, dann aus dem Netzwerk */ async function cacheFirst(request) { try { // Validierung der Anfrage if (!request || !request.url) { throw new Error('Ungültige Anfrage für Cache First'); } const cache = await caches.open(STATIC_CACHE_NAME); const cachedResponse = await cache.match(request); if (cachedResponse && cachedResponse.ok) { console.log('📦 Aus Cache geladen:', request.url); return cachedResponse; } const networkResponse = await fetch(request); if (networkResponse && networkResponse.ok) { try { await cache.put(request, networkResponse.clone()); console.log('🌐 Aus Netzwerk geladen und gecacht:', request.url); } catch (cacheError) { console.warn('⚠️ Cache-Speicherung fehlgeschlagen:', cacheError); } } return networkResponse; } catch (error) { console.error('❌ Cache First Fehler:', error); // Erweiterte Fallback-Strategien if (request.url.includes('tailwind.min.css') || request.url.includes('.css')) { return new Response('/* Offline CSS Fallback */', { headers: { 'Content-Type': 'text/css' }, status: 200 }); } if (request.url.includes('.js')) { return new Response('// Offline JS Fallback', { headers: { 'Content-Type': 'application/javascript' }, status: 200 }); } // Für andere Ressourcen: Netzwerk-Error weiterwerfen throw error; } } /** * Network First Strategie * Versucht zuerst aus dem Netzwerk zu laden, dann aus dem Cache */ async function networkFirst(request) { try { // Validierung der Anfrage if (!request || !request.url) { throw new Error('Ungültige Anfrage für Network First'); } const networkResponse = await fetch(request); if (networkResponse && networkResponse.ok) { try { const cache = await caches.open(DYNAMIC_CACHE_NAME); await cache.put(request, networkResponse.clone()); console.log('🌐 Aus Netzwerk geladen:', request.url); } catch (cacheError) { console.warn('⚠️ Cache-Speicherung bei Network First fehlgeschlagen:', cacheError); } return networkResponse; } throw new Error(`Network response not ok: ${networkResponse?.status || 'unknown'}`); } catch (error) { console.log('📦 Netzwerk nicht verfügbar, versuche Cache:', request.url); try { const cache = await caches.open(DYNAMIC_CACHE_NAME); const cachedResponse = await cache.match(request); if (cachedResponse && cachedResponse.ok) { console.log('📦 Aus Cache geladen (Offline):', request.url); return cachedResponse; } } catch (cacheError) { console.error('❌ Cache-Zugriff fehlgeschlagen:', cacheError); } // Offline-Fallback für API-Requests if (request.url.includes('/api/')) { return new Response(JSON.stringify({ success: false, error: 'Offline - Keine Verbindung zum Server', offline: true }), { status: 503, headers: { 'Content-Type': 'application/json' } }); } // Offline-Fallback für HTML-Seiten if (request.headers.get('accept').includes('text/html')) { const offlineHtml = ` MYP - Offline
📡

Offline

Sie sind derzeit offline. Bitte überprüfen Sie Ihre Internetverbindung.

`; return new Response(offlineHtml, { headers: { 'Content-Type': 'text/html' } }); } throw error; } } // Background Sync für Offline-Aktionen self.addEventListener('sync', event => { console.log('🔄 Background Sync Event:', event.tag); if (event.tag === 'background-sync-jobs') { event.waitUntil(syncJobs()); } }); /** * Synchronisiert Jobs im Hintergrund */ async function syncJobs() { try { console.log('🔄 Synchronisiere Jobs im Hintergrund...'); // Hier könnten offline gespeicherte Aktionen synchronisiert werden const response = await fetch('/api/jobs'); if (response.ok) { console.log('✅ Jobs erfolgreich synchronisiert'); } } catch (error) { console.error('❌ Fehler bei Job-Synchronisation:', error); } } // Push-Benachrichtigungen self.addEventListener('push', event => { console.log('📬 Push-Benachrichtigung erhalten'); const options = { body: 'Sie haben eine neue Benachrichtigung', icon: '/static/icons/icon-192.png', badge: '/static/icons/icon-192.png', vibrate: [100, 50, 100], data: { dateOfArrival: Date.now(), primaryKey: 1 }, actions: [ { action: 'explore', title: 'Anzeigen', icon: '/static/icons/icon-192.png' }, { action: 'close', title: 'Schließen', icon: '/static/icons/icon-192.png' } ] }; if (event.data) { const data = event.data.json(); options.body = data.message || options.body; options.title = data.title || 'MYP Benachrichtigung'; } event.waitUntil( self.registration.showNotification('MYP System', options) ); }); // Benachrichtigung-Click Handler self.addEventListener('notificationclick', event => { console.log('🔔 Benachrichtigung geklickt:', event.action); event.notification.close(); if (event.action === 'explore') { event.waitUntil( clients.openWindow('/') ); } }); console.log('🚀 MYP Service Worker geladen');