const CACHE_NAME = 'blue-mobile-v1';
const ASSETS_TO_CACHE = [
'./',
'./index.html',
'./offline.html',
'./styles.css',
'./app.js',
'./products.json',
'./icons/icon-192.png',
'./icons/icon-512.png',
'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(ASSETS_TO_CACHE);
})
);
self.skipWaiting();
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
))
);
self.clients.claim();
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// Network-first for API (products.json) with cache fallback
if (url.pathname.endsWith('/products.json')) {
event.respondWith(
fetch(event.request).then(resp => {
const copy = resp.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, copy));
return resp;
}).catch(() => caches.match(event.request))
);
return;
}
// Navigation requests: try network first, fallback to cached offline page
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).then(resp => {
// update cache for navigation
const copy = resp.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, copy));
return resp;
}).catch(() => caches.match('./offline.html'))
);
return;
}
// For images, styles, scripts: cache-first (fast)
if (event.request.destination === 'image') {
// Try to serve WebP variant when possible
const url = new URL(event.request.url);
const webpUrl = url.pathname + '.webp';
event.respondWith(
caches.match(webpUrl).then(cachedWebp => {
if (cachedWebp) return cachedWebp;
return caches.match(event.request).then(cached => cached || fetch(event.request).then(resp => {
const copy = resp.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, copy));
return resp;
}));
})
);
return;
}
if (event.request.destination === 'style' || event.request.destination === 'script' || event.request.destination === 'font') {
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request).then(resp => {
const copy = resp.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, copy));
return resp;
}))
);
return;
}
// Default: try cache, then network
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request)).catch(() => {})
);
});