52f801675e
Root cause: AbortController signals don't properly propagate through Service Worker fetch handlers (known Chrome/Safari bug), causing the fetch to hang indefinitely on mobile browsers instead of timing out. Fix: replace AbortController-based timeout in api() with Promise.race against a simple setTimeout. Also: - Stop all MediaStream tracks explicitly in stopScanning() - Add 200ms yield point before API call for hardware release - Bypass SW for API requests entirely (no caching needed for lookups) - Bump SW cache to v11 - Add outer try/catch safety net in handleBarcode
112 lines
4.2 KiB
JavaScript
112 lines
4.2 KiB
JavaScript
// ═══════════════════════════════════════════════════════════════════════════
|
|
// Canteen Asset Tracker — Service Worker
|
|
// Caches app shell + CDN deps. Network-first for API, cache fallback for static.
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
const CACHE_NAME = 'canteen-v11';
|
|
|
|
// App shell — core resources needed to boot the PWA
|
|
const APP_SHELL = [
|
|
'/',
|
|
'/index.html',
|
|
];
|
|
|
|
// CDN resources we want cached for offline resilience
|
|
const CDN_URLS = [
|
|
'https://cdn.jsdelivr.net/npm/@zxing/library@0.20.0/umd/index.min.js',
|
|
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
|
|
'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css',
|
|
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
|
|
'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js',
|
|
'https://unpkg.com/leaflet.heat@0.2.0/dist/leaflet-heat.js',
|
|
];
|
|
|
|
// ── Install: pre-cache app shell + CDN resources ──────────────────────────
|
|
|
|
self.addEventListener('install', (event) => {
|
|
console.log('[SW] Install — caching app shell + CDN assets');
|
|
event.waitUntil(
|
|
caches.open(CACHE_NAME).then((cache) => {
|
|
return cache.addAll([...APP_SHELL, ...CDN_URLS]).catch((err) => {
|
|
console.warn('[SW] Some resources failed to cache (non-fatal):', err.message);
|
|
});
|
|
})
|
|
);
|
|
// Activate immediately — don't wait for old SW to release
|
|
self.skipWaiting();
|
|
});
|
|
|
|
// ── Activate: purge old caches ────────────────────────────────────────────
|
|
|
|
self.addEventListener('activate', (event) => {
|
|
console.log('[SW] Activate — purging old caches');
|
|
event.waitUntil(
|
|
caches.keys().then((keys) => {
|
|
return Promise.all(
|
|
keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))
|
|
);
|
|
})
|
|
);
|
|
// Take control of all clients immediately
|
|
self.clients.claim();
|
|
});
|
|
|
|
// ── Fetch: network-first for everything, cache fallback ───────────────────
|
|
|
|
self.addEventListener('fetch', (event) => {
|
|
const url = new URL(event.request.url);
|
|
|
|
// Navigation requests: network-first, fallback to cached index.html
|
|
if (event.request.mode === 'navigate') {
|
|
event.respondWith(
|
|
fetch(event.request)
|
|
.then((response) => {
|
|
// Cache the latest version
|
|
const clone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
return caches.match(event.request).then((cached) => cached || caches.match('/'));
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// API requests: pass through — don't intercept at all.
|
|
// The SW cache doesn't help with real-time lookups, and SW interception
|
|
// combined with main-thread timeouts can cause indefinite hangs on mobile
|
|
// (AbortController signal doesn't propagate through SW fetch handler).
|
|
if (url.pathname.startsWith('/api/')) {
|
|
return; // Don't call event.respondWith — let the request go direct
|
|
}
|
|
|
|
// Static assets (local + CDN): network-first, cache fallback
|
|
event.respondWith(
|
|
fetch(event.request)
|
|
.then((response) => {
|
|
if (response.ok) {
|
|
const clone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
return caches.match(event.request);
|
|
})
|
|
);
|
|
});
|
|
|
|
// ── Message handler: allow main thread to trigger queue sync ──────────────
|
|
|
|
self.addEventListener('message', (event) => {
|
|
if (event.data && event.data.type === 'SYNC_QUEUE') {
|
|
// Forward sync request to all controlled clients
|
|
self.clients.matchAll().then((clients) => {
|
|
clients.forEach((client) => {
|
|
client.postMessage({ type: 'PROCESS_OFFLINE_QUEUE' });
|
|
});
|
|
});
|
|
}
|
|
});
|