Files
library/apps/frontend/src/service-worker.js
T
hikari e1fbbd4d7c fix: make service worker more resilient to prevent freezing
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
2026-02-20 01:02:59 -08:00

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))
);
})
);
}
});