← back to Wine Finder Next
public/service-worker.js
138 lines
// Service Worker for offline capability and caching
const CACHE_NAME = 'wine-finder-v1';
const urlsToCache = [
'/',
'/membership',
'/offline.html',
'/_next/static/css/',
'/_next/static/js/',
];
// Install event - cache essential resources
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
self.skipWaiting();
});
// Activate event - clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((cacheName) => cacheName !== CACHE_NAME)
.map((cacheName) => caches.delete(cacheName))
);
})
);
self.clients.claim();
});
// Fetch event - serve from cache when possible
self.addEventListener('fetch', (event) => {
const { request } = event;
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Handle API requests differently
if (request.url.includes('/api/')) {
event.respondWith(
fetch(request)
.then((response) => {
// Clone the response before caching
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache);
});
return response;
})
.catch(() => {
// Return cached API response if offline
return caches.match(request);
})
);
return;
}
// Network first, cache fallback for pages
if (request.mode === 'navigate') {
event.respondWith(
fetch(request)
.then((response) => {
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache);
});
return response;
})
.catch(() => {
return caches.match(request).then((response) => {
return response || caches.match('/offline.html');
});
})
);
return;
}
// Cache first for static assets
event.respondWith(
caches.match(request).then((response) => {
if (response) {
return response;
}
return fetch(request).then((response) => {
// Don't cache non-successful responses
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache);
});
return response;
});
})
);
});
// Background sync for offline actions
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-votes') {
event.waitUntil(syncVotes());
}
});
async function syncVotes() {
// Sync any offline votes when connection is restored
const cache = await caches.open(CACHE_NAME);
const requests = await cache.keys();
const voteRequests = requests.filter(req =>
req.url.includes('/api/membership/votes') && req.method === 'POST'
);
for (const request of voteRequests) {
try {
await fetch(request);
await cache.delete(request);
} catch (error) {
console.error('Failed to sync vote:', error);
}
}
}