Files
Dramlog-Prod/public/sw.js
2025-12-20 23:33:50 +01:00

153 lines
5.8 KiB
JavaScript

const CACHE_NAME = 'whisky-vault-v6-bunker'; // Neue Version erzwingen!
// CONFIG: Core pages and assets to pre-cache immediately on install
// WICHTIG: Hier muss die URL für "Neues Tasting" rein, damit sie offline da ist!
const CORE_PAGES = [
'/', // Dashboard / App Shell
'/sessions', // Tasting Sessions
//'/scan', // Scan Page (falls URL existiert)
// '/tasting/new' // <--- FÜGE HIER DEINE TASTING URL HINZU, falls sie existiert!
];
const STATIC_ASSETS = [
'/manifest.json',
'/icon-192.png',
'/icon-512.png',
'/favicon.ico',
];
// Install: Lade alles Wichtige sofort in den Keller
self.addEventListener('install', (event) => {
self.skipWaiting(); // Sofortiger Wechsel zum neuen SW
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('⚡ PWA: Pre-caching core pages and assets...');
return cache.addAll([...CORE_PAGES, ...STATIC_ASSETS]);
})
);
});
// Activate: Alten Müll rauswerfen
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('🧹 PWA: Clearing old cache', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim(); // Sofort Kontrolle übernehmen
});
// Helper: Network mit Timeout (für den Notfall)
function fetchWithTimeout(request, timeoutMs = 3000) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => reject(new Error('Network timeout')), timeoutMs);
fetch(request).then(
(res) => { clearTimeout(timeoutId); resolve(res); },
(err) => { clearTimeout(timeoutId); reject(err); }
);
});
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// 0. BYPASS: Auth & API immer durchlassen (werden von Dexie/App handled)
if (url.pathname.includes('/auth/') ||
url.pathname.includes('/api/') ||
url.hostname.includes('supabase.co')) {
return;
}
// 1. NEXT.JS DATA (RSC): Swallow Errors
// Verhindert "Application Error" wenn JSON fehlt
if (url.pathname.startsWith('/_next/data/')) {
event.respondWith(
caches.match(event.request).then((cached) => {
// Wenn im Cache (selten bei RSC), nimm es
if (cached) return cached;
// Sonst Netzwerk mit Timeout
return fetchWithTimeout(event.request, 2000)
.then(res => {
// Optional: RSC cachen für später?
// Vorsicht: RSC Daten veralten schnell. Hier cachen wir NICHT.
return res;
})
.catch(() => {
// Fallback: Leeres JSON, damit React nicht crasht
return new Response(JSON.stringify({}), {
headers: { 'Content-Type': 'application/json' }
});
});
})
);
return;
}
// 2. ASSETS (JS/CSS/Images): CACHE FIRST (Der "Bunker" Fix)
// Wenn wir die Datei haben, nutzen wir sie. Keine Diskussion mit dem Netzwerk.
const isAsset = event.request.destination === 'style' ||
event.request.destination === 'script' ||
event.request.destination === 'worker' ||
event.request.destination === 'font' ||
event.request.destination === 'image' ||
url.pathname.startsWith('/_next/static');
if (isAsset) {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) {
// TREFFER: Sofort zurückgeben. Kein Fetch!
// Das verhindert den "Network Changed" Crash.
return cachedResponse;
}
// MISS: Nur dann zum Netzwerk gehen
return fetch(event.request).then((networkResponse) => {
if (networkResponse && networkResponse.status === 200) {
const responseClone = networkResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
}
return networkResponse;
});
})
);
return;
}
// 3. NAVIGATION (HTML): App Shell / Cache First
// Wenn offline, gib die Startseite zurück und lass Next.js routen.
if (event.request.mode === 'navigate') {
event.respondWith(
(async () => {
try {
// A. Suche exakte Seite im Cache
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// B. Suche Startseite (App Shell) im Cache
// Das ist der wichtigste Fallback für SPAs!
const shellResponse = await caches.match('/');
if (shellResponse) return shellResponse;
// C. Wenn nichts im Cache ist (ganz neuer User): Netzwerk
return await fetchWithTimeout(event.request, 3000);
} catch (error) {
console.log('[SW] Offline fallback failed:', error);
// D. Letzter Ausweg (Text anzeigen)
return new Response('<h1>Offline</h1><p>Bitte App neu laden.</p>', {
headers: { 'Content-Type': 'text/html' }
});
}
})()
);
}
});