Files
tomsjavajive-app/sw.js
T
2026-05-16 23:00:37 -05:00

270 lines
7.0 KiB
JavaScript

const CACHE_NAME = 'tomsjavajive-v1';
const STATIC_CACHE = 'tomsjavajive-static-v1';
const DYNAMIC_CACHE = 'tomsjavajive-dynamic-v1';
// Static assets to cache immediately
const STATIC_ASSETS = [
'/',
'/shop.php',
'/cart.php',
'/assets/css/style.css',
'/assets/js/main.js',
'/assets/images/logo.png',
'/manifest.json',
'/offline.html'
];
// Install event - cache static assets
self.addEventListener('install', (event) => {
console.log('[Service Worker] Installing...');
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => {
console.log('[Service Worker] Caching static assets');
return cache.addAll(STATIC_ASSETS.map(url => {
return new Request(url, { cache: 'no-cache' });
})).catch(err => {
console.log('[Service Worker] Some assets failed to cache:', err);
return Promise.resolve();
});
})
.then(() => self.skipWaiting())
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('[Service Worker] Activating...');
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((cacheName) => {
return cacheName !== STATIC_CACHE &&
cacheName !== DYNAMIC_CACHE &&
cacheName.startsWith('tomsjavajive-');
})
.map((cacheName) => {
console.log('[Service Worker] Deleting old cache:', cacheName);
return caches.delete(cacheName);
})
);
})
.then(() => self.clients.claim())
);
});
// Fetch event - serve from cache or network
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Skip admin panel
if (url.pathname.startsWith('/admin')) {
return;
}
// Skip API requests (always network)
if (url.pathname.startsWith('/api')) {
return;
}
// Handle navigation requests
if (request.mode === 'navigate') {
event.respondWith(
fetch(request)
.then((response) => {
// Clone and cache the response
const responseClone = response.clone();
caches.open(DYNAMIC_CACHE).then((cache) => {
cache.put(request, responseClone);
});
return response;
})
.catch(() => {
// Try to serve from cache
return caches.match(request)
.then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
// Serve offline page
return caches.match('/offline.html');
});
})
);
return;
}
// Handle static assets (cache-first strategy)
if (isStaticAsset(url.pathname)) {
event.respondWith(
caches.match(request)
.then((cachedResponse) => {
if (cachedResponse) {
// Fetch in background to update cache
fetch(request).then((response) => {
if (response.ok) {
caches.open(STATIC_CACHE).then((cache) => {
cache.put(request, response);
});
}
}).catch(() => {});
return cachedResponse;
}
return fetch(request).then((response) => {
if (response.ok) {
const responseClone = response.clone();
caches.open(STATIC_CACHE).then((cache) => {
cache.put(request, responseClone);
});
}
return response;
});
})
);
return;
}
// Handle images (cache-first with network fallback)
if (isImageRequest(request)) {
event.respondWith(
caches.match(request)
.then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(request)
.then((response) => {
if (response.ok) {
const responseClone = response.clone();
caches.open(DYNAMIC_CACHE).then((cache) => {
cache.put(request, responseClone);
});
}
return response;
})
.catch(() => {
// Return placeholder image
return caches.match('/assets/images/placeholder-product.svg');
});
})
);
return;
}
// Default: network-first with cache fallback
event.respondWith(
fetch(request)
.then((response) => {
if (response.ok) {
const responseClone = response.clone();
caches.open(DYNAMIC_CACHE).then((cache) => {
cache.put(request, responseClone);
});
}
return response;
})
.catch(() => {
return caches.match(request);
})
);
});
// Helper functions
function isStaticAsset(pathname) {
return pathname.match(/\.(css|js|woff|woff2|ttf|eot)$/i);
}
function isImageRequest(request) {
return request.destination === 'image' ||
request.url.match(/\.(png|jpg|jpeg|gif|svg|webp|ico)$/i);
}
// Push notification handling
self.addEventListener('push', (event) => {
console.log('[Service Worker] Push received');
let data = { title: "Tom's Java Jive", body: 'You have a new notification!' };
if (event.data) {
try {
data = event.data.json();
} catch (e) {
data.body = event.data.text();
}
}
const options = {
body: data.body,
icon: '/assets/icons/icon-192.png',
badge: '/assets/icons/badge-72.png',
vibrate: [100, 50, 100],
data: {
url: data.url || '/'
},
actions: [
{ action: 'view', title: 'View' },
{ action: 'close', title: 'Close' }
]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// Notification click handling
self.addEventListener('notificationclick', (event) => {
console.log('[Service Worker] Notification clicked');
event.notification.close();
if (event.action === 'close') {
return;
}
const url = event.notification.data?.url || '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then((clientList) => {
// Focus existing window if available
for (const client of clientList) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// Open new window
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
// Background sync for cart/orders
self.addEventListener('sync', (event) => {
console.log('[Service Worker] Background sync:', event.tag);
if (event.tag === 'sync-cart') {
event.waitUntil(syncCart());
}
});
async function syncCart() {
// Get pending cart actions from IndexedDB
// and sync with server
console.log('[Service Worker] Syncing cart...');
}