fix(sw): bump cache to v5, add brand-catalog.js to precache

Update service worker cache name to nexus-pos-v5 to force cache
invalidation. Add brand-catalog.js to APP_SHELL precache list.
This should resolve stale cached JS causing parse errors.
This commit is contained in:
2026-05-14 22:11:13 +00:00
parent 4d6a7d9f32
commit 24db5eff43

View File

@@ -1,8 +1,8 @@
// /home/Autopartes/pos/static/pwa/sw.js // /home/Autopartes/pos/static/pwa/sw.js
// Nexus POS — Service Worker v3 // Nexus POS — Service Worker v5
// Self-contained vanilla JS. No external imports. // Self-contained vanilla JS. No external imports.
const CACHE_NAME = 'nexus-pos-v4'; const CACHE_NAME = 'nexus-pos-v5';
const APP_SHELL = [ const APP_SHELL = [
'/pos/login', '/pos/login',
@@ -31,6 +31,7 @@ const APP_SHELL = [
'/pos/static/js/reports.js', '/pos/static/js/reports.js',
'/pos/static/js/offline-banner.js', '/pos/static/js/offline-banner.js',
'/pos/static/js/sync-engine.js', '/pos/static/js/sync-engine.js',
'/pos/static/js/brand-catalog.js',
'/pos/static/pwa/manifest.json', '/pos/static/pwa/manifest.json',
'/pos/static/pwa/icon-192.png', '/pos/static/pwa/icon-192.png',
'/pos/static/pwa/icon-512.png' '/pos/static/pwa/icon-512.png'
@@ -126,12 +127,12 @@ self.addEventListener('fetch', function (event) {
return; return;
} }
// Never cache auth endpoints — tokens must always come from the server // Never cache auth endpoints
if (url.pathname.indexOf('/pos/api/auth/') !== -1) { if (url.pathname.indexOf('/pos/api/auth/') !== -1) {
return; return;
} }
// Don't cache login page — always fetch fresh to avoid stale redirects // Don't cache login page
if (url.pathname === '/pos/login' || url.pathname === '/pos/login/') { if (url.pathname === '/pos/login' || url.pathname === '/pos/login/') {
event.respondWith(networkFirst(req)); event.respondWith(networkFirst(req));
return; return;
@@ -143,7 +144,6 @@ self.addEventListener('fetch', function (event) {
fetch(req.clone()).then(function (response) { fetch(req.clone()).then(function (response) {
return response; return response;
}).catch(function () { }).catch(function () {
// Clone request body to store it for later retry
return req.clone().text().then(function (bodyText) { return req.clone().text().then(function (bodyText) {
var entry = { var entry = {
url: req.url, url: req.url,
@@ -156,19 +156,13 @@ self.addEventListener('fetch', function (event) {
}).then(function () { }).then(function () {
return new Response( return new Response(
JSON.stringify({ queued: true, message: 'Added to offline queue' }), JSON.stringify({ queued: true, message: 'Added to offline queue' }),
{ { status: 200, headers: { 'Content-Type': 'application/json' } }
status: 200,
headers: { 'Content-Type': 'application/json' }
}
); );
}).catch(function (err) { }).catch(function (err) {
console.error('[SW] Failed to queue offline cart request:', err); console.error('[SW] Failed to queue offline cart request:', err);
return new Response( return new Response(
JSON.stringify({ queued: false, message: 'Failed to queue request' }), JSON.stringify({ queued: false, message: 'Failed to queue request' }),
{ { status: 503, headers: { 'Content-Type': 'application/json' } }
status: 503,
headers: { 'Content-Type': 'application/json' }
}
); );
}) })
}) })
@@ -176,31 +170,31 @@ self.addEventListener('fetch', function (event) {
return; return;
} }
// API calls → network-first (except WhatsApp which must be real-time) // WhatsApp endpoints need fresh data
if (url.pathname.indexOf('/pos/api/whatsapp/') !== -1) { if (url.pathname.indexOf('/pos/api/whatsapp/') !== -1) {
// WhatsApp endpoints need fresh server data; skip SW caching
return; return;
} }
// API calls -> network-first
if (url.pathname.indexOf('/pos/api/') !== -1) { if (url.pathname.indexOf('/pos/api/') !== -1) {
event.respondWith(networkFirst(req)); event.respondWith(networkFirst(req));
return; return;
} }
// Everything else cache-first // Everything else -> cache-first
event.respondWith(cacheFirst(req)); event.respondWith(cacheFirst(req));
}); });
function cacheFirst(request) { function cacheFirst(request) {
return caches.match(request).then(function (cached) { return caches.match(request).then(function (cached) {
if (cached) { if (cached) {
// Update cache in background
fetch(request).then(function (response) { fetch(request).then(function (response) {
if (response && response.status === 200) { if (response && response.status === 200) {
caches.open(CACHE_NAME).then(function (cache) { caches.open(CACHE_NAME).then(function (cache) {
cache.put(request, response); cache.put(request, response);
}); });
} }
}).catch(function () { /* offline, ignore */ }); }).catch(function () {});
return cached; return cached;
} }
return fetch(request).then(function (response) { return fetch(request).then(function (response) {
@@ -230,7 +224,6 @@ function networkFirst(request) {
} }
// ─── Background Sync ───────────────────────────────────────────── // ─── Background Sync ─────────────────────────────────────────────
// Existing sync handler + new cart-specific sync
self.addEventListener('sync', function (event) { self.addEventListener('sync', function (event) {
if (event.tag === 'nexus-pos-sync') { if (event.tag === 'nexus-pos-sync') {
event.waitUntil( event.waitUntil(
@@ -250,8 +243,6 @@ self.addEventListener('sync', function (event) {
return; return;
} }
console.log('[SW] Syncing', entries.length, 'pending cart action(s)...'); console.log('[SW] Syncing', entries.length, 'pending cart action(s)...');
// Replay each pending request to the server
var syncPromises = entries.map(function (entry) { var syncPromises = entries.map(function (entry) {
return fetch(entry.url, { return fetch(entry.url, {
method: entry.method, method: entry.method,
@@ -268,7 +259,6 @@ self.addEventListener('sync', function (event) {
return { ok: false, id: entry.id }; return { ok: false, id: entry.id };
}); });
}); });
return Promise.all(syncPromises).then(function (results) { return Promise.all(syncPromises).then(function (results) {
var allOk = results.every(function (r) { return r.ok; }); var allOk = results.every(function (r) { return r.ok; });
if (allOk) { if (allOk) {
@@ -276,7 +266,6 @@ self.addEventListener('sync', function (event) {
console.log('[SW] All cart actions synced. Pending queue cleared.'); console.log('[SW] All cart actions synced. Pending queue cleared.');
}); });
} else { } else {
// Remove only successfully synced entries; failed ones will retry next time
var failedIds = results.filter(function (r) { return !r.ok; }).map(function (r) { return r.id; }); var failedIds = results.filter(function (r) { return !r.ok; }).map(function (r) { return r.id; });
console.warn('[SW] Some cart actions failed. Keeping', failedIds.length, 'entries for retry.'); console.warn('[SW] Some cart actions failed. Keeping', failedIds.length, 'entries for retry.');
} }
@@ -298,7 +287,6 @@ self.addEventListener('push', function (event) {
data = { title: event.data.text() }; data = { title: event.data.text() };
} }
} }
var title = data.title || 'Nexus POS'; var title = data.title || 'Nexus POS';
var options = { var options = {
body: data.body || 'Tienes una nueva notificación del POS.', body: data.body || 'Tienes una nueva notificación del POS.',
@@ -308,7 +296,6 @@ self.addEventListener('push', function (event) {
data: data.data || { url: '/pos/sale' }, data: data.data || { url: '/pos/sale' },
requireInteraction: false requireInteraction: false
}; };
event.waitUntil( event.waitUntil(
self.registration.showNotification(title, options) self.registration.showNotification(title, options)
); );
@@ -317,14 +304,11 @@ self.addEventListener('push', function (event) {
// ─── Notification Click ────────────────────────────────────────── // ─── Notification Click ──────────────────────────────────────────
self.addEventListener('notificationclick', function (event) { self.addEventListener('notificationclick', function (event) {
event.notification.close(); event.notification.close();
var targetUrl = event.notification.data && event.notification.data.url var targetUrl = event.notification.data && event.notification.data.url
? event.notification.data.url ? event.notification.data.url
: '/pos/sale'; : '/pos/sale';
event.waitUntil( event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (clientList) { self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (clientList) {
// Focus existing tab if it matches the target scope
for (var i = 0; i < clientList.length; i++) { for (var i = 0; i < clientList.length; i++) {
var client = clientList[i]; var client = clientList[i];
if (client.url.indexOf('/pos/') !== -1 && 'focus' in client) { if (client.url.indexOf('/pos/') !== -1 && 'focus' in client) {
@@ -335,7 +319,6 @@ self.addEventListener('notificationclick', function (event) {
}); });
} }
} }
// Otherwise open a new window
if (self.clients.openWindow) { if (self.clients.openWindow) {
return self.clients.openWindow(targetUrl); return self.clients.openWindow(targetUrl);
} }
@@ -343,28 +326,11 @@ self.addEventListener('notificationclick', function (event) {
); );
}); });
// ─── Periodic Background Sync (stub for future use) ──────────────
// This can be used to warm the cache daily or refresh catalog data
// in the background. Requires user permission and browser support.
// self.addEventListener('periodicsync', function (event) {
// if (event.tag === 'nexus-daily-sync') {
// event.waitUntil(
// // e.g. cache warming, catalog refresh, etc.
// caches.open(CACHE_NAME).then(function (cache) {
// return cache.add('/pos/api/catalog/refresh');
// }).catch(function (err) {
// console.error('[SW] periodicsync failed:', err);
// })
// );
// }
// });
// ─── Message handler ───────────────────────────────────────────── // ─── Message handler ─────────────────────────────────────────────
self.addEventListener('message', function (event) { self.addEventListener('message', function (event) {
if (event.data && event.data.type === 'SKIP_WAITING') { if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting(); self.skipWaiting();
} }
if (event.data && event.data.type === 'CLEAR_CACHES') { if (event.data && event.data.type === 'CLEAR_CACHES') {
event.waitUntil( event.waitUntil(
caches.keys().then(function (names) { caches.keys().then(function (names) {