Updated service worker cache version from 0.1.1 to 0.1.2 to force mobile browsers to invalidate old cached assets. This ensures users see the latest calendar.js with enhanced error handling. Also updated version.json to match with relevant changelog entries for mobile calendar fixes and DeepL integration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
197 lines
5.5 KiB
JavaScript
197 lines
5.5 KiB
JavaScript
/**
|
|
* Tractatus Service Worker
|
|
* - Version management and update notifications
|
|
* - Cache management for offline support
|
|
* - PWA functionality
|
|
*/
|
|
|
|
const CACHE_VERSION = '0.1.2';
|
|
const CACHE_NAME = `tractatus-v${CACHE_VERSION}`;
|
|
const VERSION_CHECK_INTERVAL = 3600000; // 1 hour in milliseconds
|
|
|
|
// Assets to cache immediately on install
|
|
const CRITICAL_ASSETS = [
|
|
'/',
|
|
'/index.html',
|
|
'/css/tailwind.css',
|
|
'/js/components/navbar.js',
|
|
'/images/tractatus-icon.svg',
|
|
'/favicon.svg'
|
|
];
|
|
|
|
// Install event - cache critical assets
|
|
self.addEventListener('install', (event) => {
|
|
event.waitUntil(
|
|
caches.open(CACHE_NAME).then((cache) => {
|
|
console.log('[Service Worker] Caching critical assets');
|
|
return cache.addAll(CRITICAL_ASSETS);
|
|
}).then(() => {
|
|
// Force activation of new service worker
|
|
return self.skipWaiting();
|
|
})
|
|
);
|
|
});
|
|
|
|
// Activate event - clean up old caches
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil(
|
|
caches.keys().then((cacheNames) => {
|
|
return Promise.all(
|
|
cacheNames
|
|
.filter((name) => name !== CACHE_NAME)
|
|
.map((name) => {
|
|
console.log('[Service Worker] Deleting old cache:', name);
|
|
return caches.delete(name);
|
|
})
|
|
);
|
|
}).then(() => {
|
|
// Take control of all clients immediately
|
|
return self.clients.claim();
|
|
})
|
|
);
|
|
});
|
|
|
|
// Fetch event - network-first strategy with cache fallback
|
|
self.addEventListener('fetch', (event) => {
|
|
const { request } = event;
|
|
const url = new URL(request.url);
|
|
|
|
// Skip chrome-extension and other non-http requests
|
|
if (!url.protocol.startsWith('http')) {
|
|
return;
|
|
}
|
|
|
|
// HTML files: Network-ONLY (never cache, always fetch fresh)
|
|
// This ensures users always get the latest content without cache refresh
|
|
if (request.destination === 'document' || url.pathname.endsWith('.html')) {
|
|
event.respondWith(
|
|
fetch(request)
|
|
.catch(() => {
|
|
// Only for offline fallback: serve cached index.html
|
|
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
return caches.match('/index.html');
|
|
}
|
|
// All other HTML: network only, fail if offline
|
|
throw new Error('Network required for HTML pages');
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Static assets (CSS, JS, images): Network-first for versioned URLs, cache-first for others
|
|
if (
|
|
request.destination === 'style' ||
|
|
request.destination === 'script' ||
|
|
request.destination === 'image' ||
|
|
request.destination === 'font'
|
|
) {
|
|
// If URL has version parameter, always fetch fresh (network-first)
|
|
const hasVersionParam = url.searchParams.has('v');
|
|
|
|
if (hasVersionParam) {
|
|
// Network-first for versioned assets (ensures cache-busting works)
|
|
event.respondWith(
|
|
fetch(request).then((response) => {
|
|
// Cache the response for offline use
|
|
const responseClone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => {
|
|
cache.put(request, responseClone);
|
|
});
|
|
return response;
|
|
}).catch(() => {
|
|
// Fallback to cache if offline
|
|
return caches.match(request);
|
|
})
|
|
);
|
|
} else {
|
|
// Cache-first for non-versioned assets
|
|
event.respondWith(
|
|
caches.match(request).then((cachedResponse) => {
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
return fetch(request).then((response) => {
|
|
const responseClone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => {
|
|
cache.put(request, responseClone);
|
|
});
|
|
return response;
|
|
});
|
|
})
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// API calls and other requests: Network-first
|
|
event.respondWith(
|
|
fetch(request)
|
|
.then((response) => {
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
return caches.match(request);
|
|
})
|
|
);
|
|
});
|
|
|
|
// Message event - handle version checks from clients
|
|
self.addEventListener('message', (event) => {
|
|
if (event.data.type === 'CHECK_VERSION') {
|
|
checkVersion().then((versionInfo) => {
|
|
event.ports[0].postMessage({
|
|
type: 'VERSION_INFO',
|
|
...versionInfo
|
|
});
|
|
});
|
|
}
|
|
|
|
if (event.data.type === 'SKIP_WAITING') {
|
|
self.skipWaiting();
|
|
}
|
|
});
|
|
|
|
// Check for version updates
|
|
async function checkVersion() {
|
|
try {
|
|
const response = await fetch('/version.json', { cache: 'no-store' });
|
|
const serverVersion = await response.json();
|
|
|
|
return {
|
|
currentVersion: CACHE_VERSION,
|
|
serverVersion: serverVersion.version,
|
|
updateAvailable: CACHE_VERSION !== serverVersion.version,
|
|
forceUpdate: serverVersion.forceUpdate,
|
|
changelog: serverVersion.changelog
|
|
};
|
|
} catch (error) {
|
|
console.error('[Service Worker] Version check failed:', error);
|
|
return {
|
|
currentVersion: CACHE_VERSION,
|
|
serverVersion: null,
|
|
updateAvailable: false,
|
|
error: true
|
|
};
|
|
}
|
|
}
|
|
|
|
// Periodic background sync for version checks (if supported)
|
|
self.addEventListener('periodicsync', (event) => {
|
|
if (event.tag === 'version-check') {
|
|
event.waitUntil(
|
|
checkVersion().then((versionInfo) => {
|
|
if (versionInfo.updateAvailable) {
|
|
// Notify all clients about update
|
|
self.clients.matchAll().then((clients) => {
|
|
clients.forEach((client) => {
|
|
client.postMessage({
|
|
type: 'UPDATE_AVAILABLE',
|
|
...versionInfo
|
|
});
|
|
});
|
|
});
|
|
}
|
|
})
|
|
);
|
|
}
|
|
});
|