196 lines
5.4 KiB
JavaScript
196 lines
5.4 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$/,
|
|
/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');
|
|
})
|
|
]).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 });
|
|
}
|
|
}); |