Newwebshop/public/sw.js

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