fix: robust SW status tracking and polling for mobile production

This commit is contained in:
2025-12-21 00:33:50 +01:00
parent ab8f0fe3ef
commit 4e8af60488
2 changed files with 61 additions and 28 deletions

View File

@@ -1,4 +1,4 @@
const CACHE_NAME = 'whisky-vault-v12-offline';
const CACHE_NAME = 'whisky-vault-v13-offline';
// CONFIG: Assets
const STATIC_ASSETS = [
@@ -12,15 +12,21 @@ const CORE_PAGES = [
'/',
];
// Global state to track progress even when UI is not listening
// Global state to track progress
let currentProgress = 0;
let isPrecacheFinished = false;
// Helper: Broadcast to all clients
// Helper: Broadcast to ALL clients (including those being installed)
async function broadcast(message) {
try {
const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' });
clients.forEach(client => client.postMessage(message));
clients.forEach(client => {
try {
client.postMessage(message);
} catch (err) {
// Ignore message delivery errors
}
});
} catch (e) { }
}
@@ -43,23 +49,22 @@ async function fetchWithTimeout(url, timeoutMs = 30000) {
// 🏗️ INSTALL: Build the Offline-Modus
self.addEventListener('install', (event) => {
// Take over immediately
self.skipWaiting();
event.waitUntil(
caches.open(CACHE_NAME).then(async (cache) => {
console.log('🏗️ PWA: Building Offline-Modus v12...');
// 💡 WAIT A MOMENT: Give the UI time to mount and register listeners
// In dev mode, the app takes a second to boot up.
await new Promise(resolve => setTimeout(resolve, 1500));
console.log('🏗️ PWA: Building Offline-Modus v13...');
const items = [...STATIC_ASSETS, ...CORE_PAGES];
const total = items.length;
let loaded = 0;
// Start broadcasting 0% immediately
broadcast({ type: 'OFFLINE_PROGRESS', progress: 0 });
for (const url of items) {
try {
// Start with manifest and icons (fast) then the app shell (slow in dev)
const res = await fetchWithTimeout(url);
if (res && res.ok) {
await cache.put(url, res);

View File

@@ -10,8 +10,18 @@ export default function OfflineIndicator() {
useEffect(() => {
setIsOffline(!navigator.onLine);
const savedReady = localStorage.getItem('whisky_offline_ready') === 'true';
setIsReady(savedReady);
// 🧼 Clean up legacy keys and check for ready state
const checkLocalReady = () => {
const ready = localStorage.getItem('whisky_offline_ready') === 'true' ||
localStorage.getItem('whisky_bunker_ready') === 'true';
if (ready) {
setIsReady(true);
// Standardize on the new key
localStorage.setItem('whisky_offline_ready', 'true');
}
};
checkLocalReady();
const handleOnline = () => setIsOffline(false);
const handleOffline = () => setIsOffline(true);
@@ -37,30 +47,48 @@ export default function OfflineIndicator() {
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
const queryStatus = async () => {
if (!('serviceWorker' in navigator)) return;
try {
const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
// Try to talk to ANY available worker (installing, waiting, or active)
const sw = registration.installing || registration.waiting || registration.active;
if (sw) {
sw.postMessage({ type: 'CHECK_OFFLINE_STATUS' });
}
}
} catch (e) {
console.error('PWA: Status query failed', e);
}
};
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', handleMessage);
queryStatus();
// Proactive status check
navigator.serviceWorker.ready.then(() => {
// If there's an active or installing worker, ask for status
const sw = navigator.serviceWorker.controller ||
(navigator.serviceWorker as any).installing ||
(navigator.serviceWorker as any).waiting;
if (sw) {
sw.postMessage({ type: 'CHECK_OFFLINE_STATUS' });
// 🔄 ROBUST POLLING: If not ready and progress is 0, query periodically
// This handles cases where the SW was suspended or the first broadcast was missed.
const interval = setInterval(() => {
if (!isReady && progress < 100) {
queryStatus();
}
});
}, 3000);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
navigator.serviceWorker.removeEventListener('message', handleMessage);
clearInterval(interval);
};
}
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
if ('serviceWorker' in navigator) {
navigator.serviceWorker.removeEventListener('message', handleMessage);
}
};
}, []);
}, [isReady, progress]);
if (isOffline) {
return (
@@ -84,10 +112,10 @@ export default function OfflineIndicator() {
}
return (
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-amber-600/10 border border-amber-600/20 rounded-full">
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-amber-600/10 border border-amber-600/20 rounded-full animate-in fade-in slide-in-from-right-2">
<Loader2 size={10} className="text-amber-600 animate-spin" />
<span className="text-[9px] font-black uppercase tracking-widest text-amber-600">
{progress > 0 ? `Lade Offline-Daten... ${progress}%` : 'Vorbereiten...'}
{progress > 0 ? `Vorbereitung... ${progress}%` : 'Warte auf System...'}
</span>
</div>
);