2026-01-03 16:57:14 -08:00
|
|
|
// MacroPad PWA Service Worker
|
2026-07-17 10:24:14 -07:00
|
|
|
const CACHE_NAME = 'macropad-v4';
|
2026-01-03 16:57:14 -08:00
|
|
|
const ASSETS_TO_CACHE = [
|
|
|
|
|
'/',
|
|
|
|
|
'/static/css/styles.css',
|
|
|
|
|
'/static/js/app.js',
|
|
|
|
|
'/static/icons/icon-192.png',
|
|
|
|
|
'/static/icons/icon-512.png',
|
|
|
|
|
'/manifest.json'
|
|
|
|
|
];
|
|
|
|
|
|
2026-07-17 10:24:14 -07:00
|
|
|
// Install event - pre-cache the app shell
|
2026-01-03 16:57:14 -08:00
|
|
|
self.addEventListener('install', (event) => {
|
|
|
|
|
event.waitUntil(
|
|
|
|
|
caches.open(CACHE_NAME)
|
|
|
|
|
.then((cache) => {
|
|
|
|
|
console.log('Caching app assets');
|
|
|
|
|
return cache.addAll(ASSETS_TO_CACHE);
|
|
|
|
|
})
|
|
|
|
|
.then(() => self.skipWaiting())
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Activate event - clean old caches
|
|
|
|
|
self.addEventListener('activate', (event) => {
|
|
|
|
|
event.waitUntil(
|
|
|
|
|
caches.keys().then((cacheNames) => {
|
|
|
|
|
return Promise.all(
|
|
|
|
|
cacheNames
|
|
|
|
|
.filter((name) => name !== CACHE_NAME)
|
|
|
|
|
.map((name) => caches.delete(name))
|
|
|
|
|
);
|
|
|
|
|
}).then(() => self.clients.claim())
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-17 10:24:14 -07:00
|
|
|
// Network-first for navigations + app code so deployed fixes reach clients.
|
|
|
|
|
async function networkFirst(request) {
|
|
|
|
|
const cache = await caches.open(CACHE_NAME);
|
|
|
|
|
try {
|
|
|
|
|
const networkResponse = await fetch(request);
|
|
|
|
|
if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') {
|
|
|
|
|
cache.put(request, networkResponse.clone());
|
|
|
|
|
}
|
|
|
|
|
return networkResponse;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const cached = await cache.match(request);
|
|
|
|
|
if (cached) {
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
// Offline fallback for navigations: serve the cached shell.
|
|
|
|
|
if (request.mode === 'navigate') {
|
|
|
|
|
const shell = await cache.match('/');
|
|
|
|
|
if (shell) {
|
|
|
|
|
return shell;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return new Response('Offline', {
|
|
|
|
|
status: 503,
|
|
|
|
|
statusText: 'Offline',
|
|
|
|
|
headers: { 'Content-Type': 'text/plain' }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-03 16:57:14 -08:00
|
|
|
self.addEventListener('fetch', (event) => {
|
2026-07-17 10:24:14 -07:00
|
|
|
const request = event.request;
|
2026-01-03 16:57:14 -08:00
|
|
|
|
2026-07-17 10:24:14 -07:00
|
|
|
// Only handle GET; let the browser deal with POST/PUT/DELETE directly.
|
|
|
|
|
if (request.method !== 'GET') {
|
2026-01-03 16:57:14 -08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-17 10:24:14 -07:00
|
|
|
const url = new URL(request.url);
|
|
|
|
|
|
|
|
|
|
// API requests always go to the network; never serve stale data.
|
|
|
|
|
// Return a synthetic offline JSON response instead of an unhandled undefined.
|
|
|
|
|
if (url.pathname.startsWith('/api/')) {
|
|
|
|
|
event.respondWith(
|
|
|
|
|
fetch(request).catch(() => new Response(
|
|
|
|
|
JSON.stringify({ error: 'offline', detail: 'Network unavailable' }),
|
|
|
|
|
{ status: 503, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
))
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Navigations and static app assets: network-first with cache fallback.
|
|
|
|
|
event.respondWith(networkFirst(request));
|
2026-01-03 16:57:14 -08:00
|
|
|
});
|