432 lines
12 KiB
JavaScript
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');
|