Files
bitfielder/public/sw.js
2025-07-14 21:08:21 +02:00

201 lines
5.6 KiB
JavaScript

const CACHE_NAME = 'bitfielder-v2';
const STATIC_CACHE = 'bitfielder-static-v2';
const DYNAMIC_CACHE = 'bitfielder-dynamic-v2';
// Core app files that should always be cached
const CORE_ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/icon-192.png',
'/icon-512.png'
];
// Assets that can be cached dynamically
const DYNAMIC_ASSETS_PATTERNS = [
/\/src\/.+\.(ts|tsx|js|jsx)$/,
/\/src\/.+\.css$/,
/\/assets\/.+\.(js|css)$/,
/fonts\.googleapis\.com/,
/fonts\.gstatic\.com/
];
// Install event - cache core resources
self.addEventListener('install', event => {
console.log('Service Worker installing...');
event.waitUntil(
Promise.all([
caches.open(STATIC_CACHE).then(cache => {
console.log('Caching core assets');
return cache.addAll(CORE_ASSETS);
}),
caches.open(DYNAMIC_CACHE).then(cache => {
console.log('Dynamic cache initialized');
// Pre-cache critical assets if they exist
return cache.addAll([]).catch(() => {
console.log('No additional assets to pre-cache');
});
})
]).then(() => {
console.log('Service Worker installed successfully');
return self.skipWaiting();
})
);
});
// Activate event - clean up old caches and take control
self.addEventListener('activate', event => {
console.log('Service Worker activating...');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all([
...cacheNames.map(cacheName => {
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
}),
self.clients.claim()
]);
}).then(() => {
console.log('Service Worker activated successfully');
})
);
});
// Fetch event - sophisticated caching strategy
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Skip external APIs and chrome-extension
if (!url.origin.includes(self.location.origin) && !url.hostname.includes('googleapis') && !url.hostname.includes('gstatic')) {
return;
}
event.respondWith(handleFetch(request));
});
async function handleFetch(request) {
const url = new URL(request.url);
try {
// Strategy 1: Core assets - Cache First
if (CORE_ASSETS.some(asset => url.pathname === asset || url.pathname.endsWith(asset))) {
return await cacheFirst(request, STATIC_CACHE);
}
// Strategy 2: Dynamic assets - Stale While Revalidate
if (DYNAMIC_ASSETS_PATTERNS.some(pattern => pattern.test(url.pathname))) {
return await staleWhileRevalidate(request, DYNAMIC_CACHE);
}
// Strategy 3: Fonts - Cache First with longer TTL
if (url.hostname.includes('googleapis') || url.hostname.includes('gstatic')) {
return await cacheFirst(request, DYNAMIC_CACHE);
}
// Strategy 4: Everything else - Network First
return await networkFirst(request, DYNAMIC_CACHE);
} catch (error) {
console.error('Fetch error:', error);
// Fallback for navigation requests
if (request.mode === 'navigate') {
const cache = await caches.open(STATIC_CACHE);
return await cache.match('/index.html') || new Response('App offline', { status: 503 });
}
return new Response('Resource not available offline', { status: 503 });
}
}
// Cache First strategy
async function cacheFirst(request, cacheName) {
const cache = await caches.open(cacheName);
const cached = await cache.match(request);
if (cached) {
return cached;
}
const response = await fetch(request);
if (response.status === 200) {
cache.put(request, response.clone());
}
return response;
}
// Network First strategy
async function networkFirst(request, cacheName) {
const cache = await caches.open(cacheName);
try {
const response = await fetch(request);
if (response.status === 200) {
cache.put(request, response.clone());
}
return response;
} catch (error) {
const cached = await cache.match(request);
if (cached) {
return cached;
}
throw error;
}
}
// Stale While Revalidate strategy
async function staleWhileRevalidate(request, cacheName) {
const cache = await caches.open(cacheName);
const cached = await cache.match(request);
// Always try to fetch and update cache in background
const fetchPromise = fetch(request).then(response => {
if (response.status === 200) {
cache.put(request, response.clone());
}
return response;
}).catch(() => null);
// Return cached version immediately if available, otherwise wait for network
return cached || await fetchPromise;
}
// Handle background sync for offline actions
self.addEventListener('sync', event => {
console.log('Background sync triggered:', event.tag);
if (event.tag === 'shader-save') {
event.waitUntil(syncShaderData());
}
});
async function syncShaderData() {
// This would handle syncing shader data when coming back online
// For now, just log that sync is available
console.log('Shader data sync available for future implementation');
}
// Handle push notifications (for future features)
self.addEventListener('push', event => {
if (event.data) {
const data = event.data.json();
console.log('Push notification received:', data);
// Could be used for shader sharing notifications, etc.
}
});
// Provide offline status
self.addEventListener('message', event => {
if (event.data && event.data.type === 'GET_VERSION') {
event.ports[0].postMessage({ version: CACHE_NAME });
}
});