fix: mobile indicator visibility, sequential pre-cache, and verified ready state

This commit is contained in:
2025-12-21 00:02:39 +01:00
parent 000f2582a3
commit 74a10b193c
2 changed files with 89 additions and 80 deletions

View File

@@ -1,8 +1,8 @@
const CACHE_NAME = 'whisky-vault-v8-bunker'; // Optimierter Bunker v8
const CACHE_NAME = 'whisky-vault-v10-bunker'; // Professional Bunker v10
// CONFIG: Kern-Seiten und Assets für den Bunker (sofortiges Pre-Caching)
// CONFIG: Core Pages & Static Assets
const CORE_PAGES = [
'/', // Dashboard / App Shell (Enthält Scan & Tasting)
'/',
];
const STATIC_ASSETS = [
@@ -12,12 +12,25 @@ const STATIC_ASSETS = [
'/favicon.ico',
];
// Helper: Fetch mit Timeout und Sauberen Abort
async function fetchWithTimeout(request, timeoutMs = 3000) {
// Helper: Broadcast to all clients (including those not yet controlled)
async function broadcast(message) {
try {
const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' });
clients.forEach(client => client.postMessage(message));
} catch (e) {
// Silently fail if no clients found
}
}
// Helper: Fetch with Timeout & AbortController
async function fetchWithTimeout(url, timeoutMs = 15000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(request, { signal: controller.signal });
const response = await fetch(url, {
signal: controller.signal,
cache: 'no-store' // Ensure we get fresh bits for the bunker
});
clearTimeout(id);
return response;
} catch (e) {
@@ -26,27 +39,25 @@ async function fetchWithTimeout(request, timeoutMs = 3000) {
}
}
// Helper: Broadcast an alle Clients
async function broadcast(message) {
const clients = await self.clients.matchAll();
clients.forEach(client => client.postMessage(message));
}
// Install: Lade alles Wichtige einzeln in den Bunker
// 🏗️ INSTALL: Build the Bunker
self.addEventListener('install', (event) => {
// Immediate takeover
self.skipWaiting();
event.waitUntil(
caches.open(CACHE_NAME).then(async (cache) => {
console.log('🏗️ PWA: Building bunker v8...');
const total = CORE_PAGES.length + STATIC_ASSETS.length;
console.log('🏗️ PWA: Building Bunker v10...');
const items = [...CORE_PAGES, ...STATIC_ASSETS];
const total = items.length;
let loaded = 0;
const promises = [...CORE_PAGES, ...STATIC_ASSETS].map(async (url) => {
// Sequential loading to avoid concurrent fetch limits on mobile
for (const url of items) {
try {
// Im Dev-Mode kann / lange dauern (Kompilierung). 10s Timeout.
const res = await fetchWithTimeout(url, 10000);
if (!res.ok) throw new Error(`Status ${res.status}`);
await cache.put(url, res);
const res = await fetchWithTimeout(url);
if (res && res.ok) {
await cache.put(url, res);
}
} catch (error) {
console.error(`⚠️ PWA: Pre-cache failed for ${url}:`, error);
} finally {
@@ -56,18 +67,15 @@ self.addEventListener('install', (event) => {
progress: Math.round((loaded / total) * 100)
});
}
});
}
await Promise.all(promises);
console.log('✅ PWA: Bunker build finished');
// Signal an Clients: Bunker ist bereit
broadcast({ type: 'PRECACHE_COMPLETE', version: CACHE_NAME });
})
);
});
// Activate: Alte Bunker räumen
// 🧹 ACTIVATE: Cleanup old bunkers
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
@@ -84,7 +92,7 @@ self.addEventListener('activate', (event) => {
self.clients.claim();
});
// Communication: Listen for status checks
// 💬 MESSAGE: Handle status checks
self.addEventListener('message', (event) => {
if (event.data?.type === 'CHECK_BUNKER_STATUS') {
event.source.postMessage({
@@ -95,36 +103,34 @@ self.addEventListener('message', (event) => {
}
});
// 🚀 FETCH: Bunker Strategy
self.addEventListener('fetch', (event) => {
// Nur GET-Requests cachen
if (event.request.method !== 'GET') return;
const url = new URL(event.request.url);
// 0. BYPASS: Auth & API (wird von App/Dexie direkt gehandelt)
// 0. BYPASS for Auth/API/Supabase
if (url.pathname.includes('/auth/') ||
url.pathname.includes('/api/') ||
url.hostname.includes('supabase.co')) {
return;
}
// 1. NEXT.JS DATA (RSC): Bunker & Fallback
// 1. RSC DATA (_next/data): Bunker with JSON Fallback
if (url.pathname.startsWith('/_next/data/')) {
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached;
return fetchWithTimeout(event.request, 2000)
.catch(() => {
return new Response(JSON.stringify({}), {
headers: { 'Content-Type': 'application/json' }
});
});
return fetchWithTimeout(event.request, 3000)
.catch(() => new Response(JSON.stringify({}), {
headers: { 'Content-Type': 'application/json' }
}));
})
);
return;
}
// 2. ASSETS & NAVIGATION: Stale-While-Revalidate (Der "Echte" Bunker Mode)
// 2. NAVIGATION & ASSETS: Bunker + SWR
const isNavigation = event.request.mode === 'navigate';
const isAsset = event.request.destination === 'style' ||
event.request.destination === 'script' ||
@@ -136,8 +142,8 @@ self.addEventListener('fetch', (event) => {
if (isNavigation || isAsset) {
event.respondWith(
caches.match(event.request).then(async (cachedResponse) => {
// Hintergrund-Update
const fetchPromise = fetchWithTimeout(event.request, 5000)
// Background update
const fetchPromise = fetchWithTimeout(event.request, 8000)
.then(async (networkResponse) => {
if (networkResponse && networkResponse.status === 200) {
const cache = await caches.open(CACHE_NAME);
@@ -145,7 +151,7 @@ self.addEventListener('fetch', (event) => {
}
return networkResponse;
})
.catch(() => { /* Silent in background */ });
.catch(() => { /* Background fail silent */ });
if (isNavigation) {
if (cachedResponse) return cachedResponse;