generated from nhcarrigan/template
e1fbbd4d7c
The service worker was causing 503 errors and freezing requests because: - Pre-caching was trying to cache non-existent files - Fetch handler was too aggressive with interception - Failed cache operations blocked all requests Fixes: - Made pre-caching optional (logs errors but doesn't fail) - More lenient fetch handler (only caches successful responses) - Proper error handling for cache operations - Only intercepts GET requests - Falls back to network if cache fails
173 lines
4.8 KiB
JavaScript
173 lines
4.8 KiB
JavaScript
/**
|
|
* @copyright 2026 NHCarrigan
|
|
* @license Naomi's Public License
|
|
* @author Hikari
|
|
*/
|
|
|
|
const CACHE_VERSION = 'library-v1';
|
|
const STATIC_CACHE = `${CACHE_VERSION}-static`;
|
|
const DYNAMIC_CACHE = `${CACHE_VERSION}-dynamic`;
|
|
const IMAGE_CACHE = `${CACHE_VERSION}-images`;
|
|
|
|
// Static assets to cache on install
|
|
const STATIC_ASSETS = [
|
|
'/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).catch((err) => {
|
|
console.error('[Service Worker] Failed to cache static assets:', err);
|
|
// Don't fail installation if caching fails
|
|
return Promise.resolve();
|
|
});
|
|
}).then(() => {
|
|
return 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.startsWith('library-') && cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE && cacheName !== IMAGE_CACHE;
|
|
})
|
|
.map((cacheName) => {
|
|
console.log('[Service Worker] Deleting old cache:', cacheName);
|
|
return caches.delete(cacheName);
|
|
})
|
|
);
|
|
}).then(() => {
|
|
return self.clients.claim();
|
|
})
|
|
);
|
|
});
|
|
|
|
// Fetch event - implement caching strategies
|
|
self.addEventListener('fetch', (event) => {
|
|
const { request } = event;
|
|
const url = new URL(request.url);
|
|
|
|
// Skip non-GET requests
|
|
if (request.method !== 'GET') {
|
|
return;
|
|
}
|
|
|
|
// Skip Chrome extensions and other protocols
|
|
if (!url.protocol.startsWith('http')) {
|
|
return;
|
|
}
|
|
|
|
// API requests - Network first, cache fallback
|
|
if (url.pathname.startsWith('/api/')) {
|
|
event.respondWith(
|
|
fetch(request)
|
|
.then((response) => {
|
|
// Clone the response before caching
|
|
const responseClone = response.clone();
|
|
caches.open(DYNAMIC_CACHE).then((cache) => {
|
|
cache.put(request, responseClone);
|
|
});
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
return caches.match(request).then((cachedResponse) => {
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
// Return offline page for failed API requests
|
|
return caches.match('/offline.html');
|
|
});
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Images - Cache first, network fallback
|
|
if (request.destination === 'image' || url.pathname.match(/\.(jpg|jpeg|png|gif|webp|svg)$/)) {
|
|
event.respondWith(
|
|
caches.match(request).then((cachedResponse) => {
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
return fetch(request).then((response) => {
|
|
const responseClone = response.clone();
|
|
caches.open(IMAGE_CACHE).then((cache) => {
|
|
cache.put(request, responseClone);
|
|
});
|
|
return response;
|
|
});
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Static assets - Cache first, network fallback
|
|
if (url.pathname.match(/\.(js|css|woff|woff2|ttf|eot)$/)) {
|
|
event.respondWith(
|
|
caches.match(request).then((cachedResponse) => {
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
return fetch(request).then((response) => {
|
|
const responseClone = response.clone();
|
|
caches.open(STATIC_CACHE).then((cache) => {
|
|
cache.put(request, responseClone);
|
|
});
|
|
return response;
|
|
});
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// HTML pages - Network first, cache fallback
|
|
event.respondWith(
|
|
fetch(request)
|
|
.then((response) => {
|
|
// Only cache successful responses
|
|
if (response.ok) {
|
|
const responseClone = response.clone();
|
|
caches.open(DYNAMIC_CACHE).then((cache) => {
|
|
cache.put(request, responseClone);
|
|
});
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
return caches.match(request).then((cachedResponse) => {
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
// Return offline page as fallback
|
|
return caches.match('/offline.html');
|
|
});
|
|
})
|
|
);
|
|
});
|
|
|
|
// Message event - handle cache clearing requests
|
|
self.addEventListener('message', (event) => {
|
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
|
self.skipWaiting();
|
|
}
|
|
if (event.data && event.data.type === 'CLEAR_CACHE') {
|
|
event.waitUntil(
|
|
caches.keys().then((cacheNames) => {
|
|
return Promise.all(
|
|
cacheNames.map((cacheName) => caches.delete(cacheName))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
});
|