Files
the-nexus-fork/sw.js

219 lines
5.4 KiB
JavaScript
Raw Normal View History

/**
* The Nexus Service Worker
* Provides offline capability and home screen install support
* Strategy: Cache-first for local assets, stale-while-revalidate for CDN
*/
const CACHE_VERSION = 'nexus-v1';
const STATIC_CACHE = `${CACHE_VERSION}-static`;
const CDN_CACHE = `${CACHE_VERSION}-cdn`;
const OFFLINE_PAGE = '/offline.html';
// Core local assets that must be cached
const CORE_ASSETS = [
'/',
'/index.html',
'/style.css',
'/app.js',
'/manifest.json',
'/icons/nexus-icon.svg',
'/icons/nexus-maskable.svg',
OFFLINE_PAGE
];
// CDN resources that benefit from caching but can be stale
const CDN_PATTERNS = [
/^https:\/\/unpkg\.com/,
/^https:\/\/cdn\.jsdelivr\.net/,
/^https:\/\/fonts\.googleapis\.com/,
/^https:\/\/fonts\.gstatic\.com/,
/^https:\/\/cdn\.threejs\.org/
];
/**
* Check if a URL matches any CDN pattern
*/
function isCdnResource(url) {
return CDN_PATTERNS.some(pattern => pattern.test(url));
}
/**
* Install event - cache core assets
*/
self.addEventListener('install', (event) => {
console.log('[Nexus SW] Installing...');
event.waitUntil(
caches.open(STATIC_CACHE)
.then(cache => {
console.log('[Nexus SW] Caching core assets');
return cache.addAll(CORE_ASSETS);
})
.then(() => {
console.log('[Nexus SW] Core assets cached');
return self.skipWaiting();
})
.catch(err => {
console.error('[Nexus SW] Cache failed:', err);
})
);
});
/**
* Activate event - clean up old caches
*/
self.addEventListener('activate', (event) => {
console.log('[Nexus SW] Activating...');
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name.startsWith('nexus-') && name !== STATIC_CACHE && name !== CDN_CACHE)
.map(name => {
console.log('[Nexus SW] Deleting old cache:', name);
return caches.delete(name);
})
);
})
.then(() => {
console.log('[Nexus SW] Activated');
return self.clients.claim();
})
);
});
/**
* Fetch event - handle requests with appropriate strategy
*/
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Skip cross-origin requests that aren't CDN resources
if (url.origin !== self.location.origin && !isCdnResource(url.href)) {
return;
}
// Handle CDN resources with stale-while-revalidate
if (isCdnResource(url.href)) {
event.respondWith(handleCdnRequest(request));
return;
}
// Handle local assets with cache-first strategy
event.respondWith(handleLocalRequest(request));
});
/**
* Cache-first strategy for local assets
* Fastest response, updates cache in background
*/
async function handleLocalRequest(request) {
try {
const cache = await caches.open(STATIC_CACHE);
const cached = await cache.match(request);
// Return cached version immediately if available
if (cached) {
// Revalidate in background for next time
fetch(request)
.then(response => {
if (response.ok) {
cache.put(request, response.clone());
}
})
.catch(() => {
// Network failed, cached version is already being used
});
return cached;
}
// Not in cache - fetch from network
const response = await fetch(request);
if (response.ok) {
cache.put(request, response.clone());
}
return response;
} catch (error) {
console.error('[Nexus SW] Local request failed:', error);
// Return offline page for navigation requests
if (request.mode === 'navigate') {
const cache = await caches.open(STATIC_CACHE);
const offlinePage = await cache.match(OFFLINE_PAGE);
if (offlinePage) {
return offlinePage;
}
}
throw error;
}
}
/**
* Stale-while-revalidate strategy for CDN resources
* Serves stale content while updating in background
*/
async function handleCdnRequest(request) {
const cache = await caches.open(CDN_CACHE);
const cached = await cache.match(request);
// Always try to fetch fresh version
const fetchPromise = fetch(request)
.then(response => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
})
.catch(error => {
console.error('[Nexus SW] CDN fetch failed:', error);
throw error;
});
// Return cached version immediately if available, otherwise wait for network
if (cached) {
// Return stale but revalidate for next time
fetchPromise.catch(() => {}); // Swallow errors, we have cached version
return cached;
}
// No cache - must wait for network
return fetchPromise;
}
/**
* Message handler for runtime cache updates
*/
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
if (event.data && event.data.type === 'GET_VERSION') {
event.ports[0].postMessage({ version: CACHE_VERSION });
}
});
/**
* Background sync for deferred actions (future enhancement)
*/
self.addEventListener('sync', (event) => {
if (event.tag === 'nexus-sync') {
console.log('[Nexus SW] Background sync triggered');
// Future: sync chat messages, state updates, etc.
}
});
console.log('[Nexus SW] Service worker loaded');