356 lines
10 KiB
JavaScript
356 lines
10 KiB
JavaScript
/**
|
|
* Service Worker für Webshop System
|
|
* Offline-Funktionalität und Caching
|
|
*/
|
|
|
|
const CACHE_NAME = 'webshop-v1';
|
|
const STATIC_CACHE = 'webshop-static-v1';
|
|
const DYNAMIC_CACHE = 'webshop-dynamic-v1';
|
|
|
|
// Assets für Offline-Cache
|
|
const STATIC_ASSETS = [
|
|
'/',
|
|
'/css/bootstrap.min.css',
|
|
'/css/custom.css',
|
|
'/js/bootstrap.bundle.min.js',
|
|
'/js/app.js',
|
|
'/img/logo.png',
|
|
'/img/icons/icon-192x192.png',
|
|
'/img/icons/icon-512x512.png',
|
|
'/offline.html'
|
|
];
|
|
|
|
// API-Endpoints für Offline-Cache
|
|
const API_CACHE = [
|
|
'/api/products',
|
|
'/api/categories',
|
|
'/api/stats'
|
|
];
|
|
|
|
// Install Event - Cache statische Assets
|
|
self.addEventListener('install', event => {
|
|
console.log('Service Worker: Installing...');
|
|
|
|
event.waitUntil(
|
|
caches.open(STATIC_CACHE)
|
|
.then(cache => {
|
|
console.log('Service Worker: Caching static assets');
|
|
return cache.addAll(STATIC_ASSETS);
|
|
})
|
|
.then(() => {
|
|
console.log('Service Worker: Static assets cached');
|
|
return self.skipWaiting();
|
|
})
|
|
.catch(error => {
|
|
console.error('Service Worker: Error caching static assets', error);
|
|
})
|
|
);
|
|
});
|
|
|
|
// Activate Event - Cleanup alte 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 - Cache-Strategie
|
|
self.addEventListener('fetch', event => {
|
|
const { request } = event;
|
|
const url = new URL(request.url);
|
|
|
|
// Skip non-GET requests
|
|
if (request.method !== 'GET') {
|
|
return;
|
|
}
|
|
|
|
// Skip Chrome extensions
|
|
if (url.protocol === 'chrome-extension:') {
|
|
return;
|
|
}
|
|
|
|
// Cache-Strategie basierend auf URL
|
|
if (url.pathname.startsWith('/api/')) {
|
|
// API-Requests: Network-First mit Cache-Fallback
|
|
event.respondWith(handleApiRequest(request));
|
|
} else if (isStaticAsset(url.pathname)) {
|
|
// Statische Assets: Cache-First
|
|
event.respondWith(handleStaticAsset(request));
|
|
} else {
|
|
// HTML-Seiten: Network-First mit Cache-Fallback
|
|
event.respondWith(handlePageRequest(request));
|
|
}
|
|
});
|
|
|
|
// API-Request Handler
|
|
async function handleApiRequest(request) {
|
|
try {
|
|
// Versuche Network-Request
|
|
const networkResponse = await fetch(request);
|
|
|
|
if (networkResponse.ok) {
|
|
// Cache erfolgreiche API-Responses
|
|
const cache = await caches.open(DYNAMIC_CACHE);
|
|
cache.put(request, networkResponse.clone());
|
|
}
|
|
|
|
return networkResponse;
|
|
} catch (error) {
|
|
console.log('Service Worker: Network failed, trying cache', request.url);
|
|
|
|
// Fallback zu Cache
|
|
const cachedResponse = await caches.match(request);
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
|
|
// Offline-Fallback für API-Requests
|
|
return new Response(JSON.stringify({
|
|
error: 'Offline',
|
|
message: 'Keine Internetverbindung verfügbar'
|
|
}), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|
|
|
|
// Statische Asset Handler
|
|
async function handleStaticAsset(request) {
|
|
const cachedResponse = await caches.match(request);
|
|
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
|
|
try {
|
|
const networkResponse = await fetch(request);
|
|
|
|
if (networkResponse.ok) {
|
|
const cache = await caches.open(STATIC_CACHE);
|
|
cache.put(request, networkResponse.clone());
|
|
}
|
|
|
|
return networkResponse;
|
|
} catch (error) {
|
|
console.log('Service Worker: Static asset not found', request.url);
|
|
return new Response('Asset not found', { status: 404 });
|
|
}
|
|
}
|
|
|
|
// Page Request Handler
|
|
async function handlePageRequest(request) {
|
|
try {
|
|
// Versuche Network-Request
|
|
const networkResponse = await fetch(request);
|
|
|
|
if (networkResponse.ok) {
|
|
// Cache erfolgreiche Page-Responses
|
|
const cache = await caches.open(DYNAMIC_CACHE);
|
|
cache.put(request, networkResponse.clone());
|
|
}
|
|
|
|
return networkResponse;
|
|
} catch (error) {
|
|
console.log('Service Worker: Network failed, trying cache', request.url);
|
|
|
|
// Fallback zu Cache
|
|
const cachedResponse = await caches.match(request);
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
|
|
// Offline-Seite für HTML-Requests
|
|
if (request.headers.get('accept').includes('text/html')) {
|
|
return caches.match('/offline.html');
|
|
}
|
|
|
|
return new Response('Page not found', { status: 404 });
|
|
}
|
|
}
|
|
|
|
// Prüfe ob es sich um ein statisches Asset handelt
|
|
function isStaticAsset(pathname) {
|
|
return pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$/);
|
|
}
|
|
|
|
// Background Sync für Offline-Aktionen
|
|
self.addEventListener('sync', event => {
|
|
console.log('Service Worker: Background sync', event.tag);
|
|
|
|
if (event.tag === 'background-sync') {
|
|
event.waitUntil(doBackgroundSync());
|
|
}
|
|
|
|
if (event.tag === 'cart-sync') {
|
|
event.waitUntil(syncCartData());
|
|
}
|
|
});
|
|
|
|
// Background Sync Implementation
|
|
async function doBackgroundSync() {
|
|
try {
|
|
// Synchronisiere Offline-Daten
|
|
const offlineData = await getOfflineData();
|
|
|
|
for (const data of offlineData) {
|
|
await syncToServer(data);
|
|
}
|
|
|
|
console.log('Service Worker: Background sync completed');
|
|
} catch (error) {
|
|
console.error('Service Worker: Background sync failed', error);
|
|
}
|
|
}
|
|
|
|
// Warenkorb-Synchronisation
|
|
async function syncCartData() {
|
|
try {
|
|
const cartData = await getCartData();
|
|
|
|
if (cartData && cartData.items.length > 0) {
|
|
await syncCartToServer(cartData);
|
|
}
|
|
|
|
console.log('Service Worker: Cart sync completed');
|
|
} catch (error) {
|
|
console.error('Service Worker: Cart sync failed', error);
|
|
}
|
|
}
|
|
|
|
// Push-Benachrichtigungen
|
|
self.addEventListener('push', event => {
|
|
console.log('Service Worker: Push notification received');
|
|
|
|
const options = {
|
|
body: event.data ? event.data.text() : 'Neue Nachricht verfügbar',
|
|
icon: '/img/icons/icon-192x192.png',
|
|
badge: '/img/icons/badge-72x72.png',
|
|
vibrate: [100, 50, 100],
|
|
data: {
|
|
dateOfArrival: Date.now(),
|
|
primaryKey: 1
|
|
},
|
|
actions: [
|
|
{
|
|
action: 'explore',
|
|
title: 'Anzeigen',
|
|
icon: '/img/icons/action-explore.png'
|
|
},
|
|
{
|
|
action: 'close',
|
|
title: 'Schließen',
|
|
icon: '/img/icons/action-close.png'
|
|
}
|
|
]
|
|
};
|
|
|
|
event.waitUntil(
|
|
self.registration.showNotification('Webshop System', options)
|
|
);
|
|
});
|
|
|
|
// Notification Click Handler
|
|
self.addEventListener('notificationclick', event => {
|
|
console.log('Service Worker: Notification clicked', event.action);
|
|
|
|
event.notification.close();
|
|
|
|
if (event.action === 'explore') {
|
|
event.waitUntil(
|
|
clients.openWindow('/')
|
|
);
|
|
} else if (event.action === 'close') {
|
|
// Notification schließen
|
|
return;
|
|
} else {
|
|
// Standard-Aktion: App öffnen
|
|
event.waitUntil(
|
|
clients.openWindow('/')
|
|
);
|
|
}
|
|
});
|
|
|
|
// Message Handler für Client-Kommunikation
|
|
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(
|
|
caches.open(STATIC_CACHE)
|
|
.then(cache => cache.addAll(event.data.urls))
|
|
);
|
|
}
|
|
|
|
if (event.data && event.data.type === 'GET_CACHE_SIZE') {
|
|
event.waitUntil(
|
|
caches.keys()
|
|
.then(cacheNames => {
|
|
return Promise.all(
|
|
cacheNames.map(name => caches.open(name).then(cache => cache.keys()))
|
|
);
|
|
})
|
|
.then(requests => {
|
|
const totalSize = requests.reduce((size, requests) => size + requests.length, 0);
|
|
event.ports[0].postMessage({ size: totalSize });
|
|
})
|
|
);
|
|
}
|
|
});
|
|
|
|
// Hilfsfunktionen für Offline-Daten
|
|
async function getOfflineData() {
|
|
// Implementierung für Offline-Daten aus IndexedDB
|
|
return [];
|
|
}
|
|
|
|
async function syncToServer(data) {
|
|
// Implementierung für Server-Synchronisation
|
|
console.log('Syncing data to server:', data);
|
|
}
|
|
|
|
async function getCartData() {
|
|
// Implementierung für Warenkorb-Daten
|
|
return null;
|
|
}
|
|
|
|
async function syncCartToServer(cartData) {
|
|
// Implementierung für Warenkorb-Synchronisation
|
|
console.log('Syncing cart to server:', cartData);
|
|
}
|
|
|
|
// Cache-Bereinigung
|
|
async function cleanOldCaches() {
|
|
const cacheNames = await caches.keys();
|
|
const currentCaches = [STATIC_CACHE, DYNAMIC_CACHE];
|
|
|
|
for (const cacheName of cacheNames) {
|
|
if (!currentCaches.includes(cacheName)) {
|
|
await caches.delete(cacheName);
|
|
console.log('Service Worker: Deleted old cache', cacheName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Periodische Cache-Bereinigung
|
|
setInterval(cleanOldCaches, 24 * 60 * 60 * 1000); // Täglich
|