2025-06-04 10:03:22 +02:00

432 lines
12 KiB
JavaScript

/**
* MYP Platform Service Worker
* Offline-First Caching Strategy
*/
// MYP Platform Service Worker
const CACHE_NAME = 'myp-platform-cache-v1';
const STATIC_CACHE = 'myp-static-v1';
const DYNAMIC_CACHE = 'myp-dynamic-v1';
const ASSETS_TO_CACHE = [
'/',
'/dashboard',
'/static/css/tailwind.min.css',
'/static/css/tailwind-dark.min.css',
'/static/js/ui-components.js',
'/static/js/offline-app.js',
'/static/icons/mercedes-logo.svg',
'/static/icons/icon-144x144.png',
'/static/favicon.ico'
];
// Static files patterns
const STATIC_PATTERNS = [
/\.css$/,
/\.js$/,
/\.svg$/,
/\.png$/,
/\.ico$/,
/\.woff2?$/
];
// API request patterns to avoid caching
const API_PATTERNS = [
/^\/api\//,
/^\/auth\//,
/^\/api\/jobs/,
/^\/api\/printers/,
/^\/api\/stats/
];
// Install event - cache core assets
self.addEventListener('install', (event) => {
console.log('Service Worker: Installing...');
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => {
console.log('Service Worker: Caching static files');
return cache.addAll(ASSETS_TO_CACHE);
})
.then(() => {
console.log('Service Worker: Static files cached');
return self.skipWaiting();
})
.catch((error) => {
console.error('Service Worker: Error caching static files', error);
})
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('Service Worker: Activating...');
event.waitUntil(
caches.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
console.log('Service Worker: Deleting old cache', cacheName);
return caches.delete(cacheName);
}
})
);
})
.then(() => {
console.log('Service Worker: Activated');
return self.clients.claim();
})
);
});
// Fetch event - handle requests
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Unterstütze sowohl HTTP als auch HTTPS
if (request.method !== 'GET' ||
(url.protocol !== 'http:' && url.protocol !== 'https:')) {
return;
}
// Simple network-first approach
event.respondWith(
fetch(request)
.then((response) => {
// Cache successful static responses
if (response && response.status === 200 && isStaticFile(url.pathname) &&
(url.protocol === 'http:' || url.protocol === 'https:')) {
const responseClone = response.clone();
caches.open(STATIC_CACHE).then((cache) => {
cache.put(request, responseClone);
}).catch(err => {
console.warn('Failed to cache response:', err);
});
}
return response;
})
.catch(() => {
// Fallback to cache if network fails
return caches.match(request);
})
);
});
// Check if request is for a static file
function isStaticFile(pathname) {
return STATIC_PATTERNS.some(pattern => pattern.test(pathname));
}
// Check if request is an API request
function isAPIRequest(pathname) {
return API_PATTERNS.some(pattern => pattern.test(pathname));
}
// Check if request is for a page
function isPageRequest(request) {
return request.mode === 'navigate';
}
// MYP Platform Service Worker
const STATIC_FILES = [
'/',
'/static/css/tailwind.min.css',
'/static/css/tailwind-dark.min.css',
'/static/js/ui-components.js',
'/static/js/offline-app.js',
'/login',
'/dashboard'
];
// API endpoints to cache
const API_CACHE_PATTERNS = [
/^\/api\/dashboard/,
/^\/api\/printers/,
/^\/api\/jobs/,
/^\/api\/stats/
];
// Handle static file requests - Cache First strategy
async function handleStaticFile(request) {
try {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
const networkResponse = await fetch(request);
// Cache successful responses
if (networkResponse.ok) {
const cache = await caches.open(STATIC_CACHE);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.error('Service Worker: Error handling static file', error);
// Return cached version if available
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Return offline fallback
return new Response('Offline - Datei nicht verfügbar', {
status: 503,
statusText: 'Service Unavailable'
});
}
}
// Handle API requests - Network First with cache fallback
async function handleAPIRequest(request) {
const url = new URL(request.url);
// Skip caching for chrome-extension URLs
if (url.protocol === 'chrome-extension:') {
try {
return await fetch(request);
} catch (error) {
console.error('Failed to fetch from chrome-extension:', error);
return new Response(JSON.stringify({
error: 'Fehler beim Zugriff auf chrome-extension',
offline: true
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
}
try {
// Try network first
const networkResponse = await fetch(request);
if (networkResponse.ok) {
// Cache successful GET responses for specific endpoints
if (request.method === 'GET' && shouldCacheAPIResponse(url.pathname)) {
const cache = await caches.open(DYNAMIC_CACHE);
cache.put(request, networkResponse.clone());
}
return networkResponse;
}
throw new Error(`HTTP ${networkResponse.status}`);
} catch (error) {
console.log('Service Worker: Network failed for API request, trying cache');
// Try cache fallback for GET requests
const cachedResponse = await caches.match(request);
if (cachedResponse) {
// Add offline header to indicate cached response
const response = cachedResponse.clone();
response.headers.set('X-Served-By', 'ServiceWorker-Cache');
return response;
}
// Return offline response
return new Response(JSON.stringify({
error: 'Offline - Daten nicht verfügbar',
offline: true,
timestamp: new Date().toISOString()
}), {
status: 503,
statusText: 'Service Unavailable',
headers: {
'Content-Type': 'application/json',
'X-Served-By': 'ServiceWorker-Offline'
}
});
}
}
// Handle page requests - Network First with offline fallback
async function handlePageRequest(request) {
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
// Cache successful page responses
const cache = await caches.open(DYNAMIC_CACHE);
cache.put(request, networkResponse.clone());
return networkResponse;
}
throw new Error(`HTTP ${networkResponse.status}`);
} catch (error) {
console.log('Service Worker: Network failed for page request, trying cache');
// Try cache fallback
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Fallback offline response
return caches.match('/offline.html') || new Response(
'<html><body><h1>Offline</h1><p>Sie sind momentan offline. Bitte überprüfen Sie Ihre Internetverbindung.</p></body></html>',
{
status: 200,
headers: { 'Content-Type': 'text/html' }
}
);
}
}
// Check if API response should be cached
function shouldCacheAPIResponse(pathname) {
return API_CACHE_PATTERNS.some(pattern => pattern.test(pathname));
}
// Background sync for offline actions
self.addEventListener('sync', (event) => {
console.log('Service Worker: Background sync triggered', event.tag);
if (event.tag === 'background-sync') {
event.waitUntil(doBackgroundSync());
}
});
// Perform background sync
async function doBackgroundSync() {
try {
// Get pending requests from IndexedDB or localStorage
const pendingRequests = await getPendingRequests();
for (const request of pendingRequests) {
try {
await fetch(request.url, request.options);
await removePendingRequest(request.id);
console.log('Service Worker: Synced request', request.url);
} catch (error) {
console.error('Service Worker: Failed to sync request', request.url, error);
}
}
} catch (error) {
console.error('Service Worker: Background sync failed', error);
}
}
// Get pending requests (placeholder - implement with IndexedDB)
async function getPendingRequests() {
// This would typically use IndexedDB to store pending requests
// For now, return empty array
return [];
}
// Remove pending request (placeholder - implement with IndexedDB)
async function removePendingRequest(id) {
// This would typically remove the request from IndexedDB
console.log('Service Worker: Removing pending request', id);
}
// Push notification handling
self.addEventListener('push', (event) => {
console.log('Service Worker: Push notification received');
const options = {
body: 'Sie haben neue Benachrichtigungen',
icon: '/static/icons/icon-192x192.png',
badge: '/static/icons/badge-72x72.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: 'Anzeigen',
icon: '/static/icons/checkmark.png'
},
{
action: 'close',
title: 'Schließen',
icon: '/static/icons/xmark.png'
}
]
};
event.waitUntil(
self.registration.showNotification('MYP Platform', options)
);
});
// Notification click handling
self.addEventListener('notificationclick', (event) => {
console.log('Service Worker: Notification clicked');
event.notification.close();
if (event.action === 'explore') {
event.waitUntil(
clients.openWindow('/dashboard')
);
}
});
// Message handling from main thread
self.addEventListener('message', (event) => {
console.log('Service Worker: Message received', event.data);
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
if (event.data && event.data.type === 'CACHE_URLS') {
event.waitUntil(
cacheUrls(event.data.urls)
);
}
});
// Cache specific URLs
async function cacheUrls(urls) {
try {
const cache = await caches.open(DYNAMIC_CACHE);
await cache.addAll(urls);
console.log('Service Worker: URLs cached', urls);
} catch (error) {
console.error('Service Worker: Error caching URLs', error);
}
}
// Periodic background sync (if supported)
self.addEventListener('periodicsync', (event) => {
console.log('Service Worker: Periodic sync triggered', event.tag);
if (event.tag === 'content-sync') {
event.waitUntil(syncContent());
}
});
// Sync content periodically
async function syncContent() {
try {
// Sync critical data in background
const endpoints = ['/api/dashboard', '/api/jobs'];
for (const endpoint of endpoints) {
try {
const response = await fetch(endpoint);
if (response.ok) {
const cache = await caches.open(DYNAMIC_CACHE);
cache.put(endpoint, response.clone());
}
} catch (error) {
console.error('Service Worker: Error syncing', endpoint, error);
}
}
} catch (error) {
console.error('Service Worker: Content sync failed', error);
}
}
console.log('Service Worker: Script loaded');