**Änderungen:** - ✅ Aktualisierte Schulungsdokumentation für den Gastzugang, um den Workflow klarer darzustellen. - ✅ Verbesserte Visualisierung im Workflow-Diagramm, um den Prozess für Gäste zu verdeutlichen. - ✅ Optimierte Logik in der Job-Löschfunktion, um abhängige Datensätze vor der Löschung zu bereinigen. **Ergebnis:** - Klarere Anleitungen für Benutzer zur Nutzung des Gastzugangs. - Verbesserte Nachvollziehbarkeit des Workflows durch aktualisierte Diagramme. - Erhöhte Datenintegrität durch Bereinigung abhängiger Datensätze vor der Job-Löschung. 🤖 Generated with [Claude Code](https://claude.ai/code)
393 lines
13 KiB
JavaScript
393 lines
13 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>MYP - Offline</title>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<style>
|
|
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
|
|
.offline-message { max-width: 500px; margin: 0 auto; }
|
|
.icon { font-size: 64px; margin-bottom: 20px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="offline-message">
|
|
<div class="icon">📡</div>
|
|
<h1>Offline</h1>
|
|
<p>Sie sind derzeit offline. Bitte überprüfen Sie Ihre Internetverbindung.</p>
|
|
<button onclick="window.location.reload()">Erneut versuchen</button>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
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');
|