const MVLOG_CACHE_PREFIX = 'mvlog-pwa-'; const MVLOG_CACHE_VERSION = 'mvlog-pwa-v6'; const APP_SHELL_CACHE = `${MVLOG_CACHE_VERSION}-app-shell`; const RUNTIME_CACHE = `${MVLOG_CACHE_VERSION}-runtime`; const APP_SHELL_URLS = [ '/', '/offline.html', '/style.css', '/js/map_lightbox.js', '/assets/fonts/BebasNeue-Regular.ttf', '/assets/fonts/IBMPlexSans-SemiBold.ttf', '/assets/fonts/NotoSans-Bold.ttf', '/assets/img/background.jpg', '/assets/img/moto_travel.png', '/assets/img/pwa-icon-192.png', '/assets/img/pwa-icon-512.png', '/assets/img/pwa-maskable-512.png', '/assets/img/apple-touch-icon.png', '/manifest.json' ]; self.addEventListener('install', event => { event.waitUntil((async () => { const cache = await caches.open(APP_SHELL_CACHE); await Promise.all(APP_SHELL_URLS.map(url => cache.add(new Request(url, { cache: 'reload' })).catch(() => undefined) )); await self.skipWaiting(); })()); }); self.addEventListener('activate', event => { event.waitUntil((async () => { const names = await caches.keys(); await Promise.all(names.map(name => { if (name.startsWith(MVLOG_CACHE_PREFIX) && ![APP_SHELL_CACHE, RUNTIME_CACHE].includes(name)) { return caches.delete(name); } return undefined; })); await clients.claim(); })()); }); function shouldBypassFetch(request, url) { if (request.method !== 'GET') return true; if (!['http:', 'https:'].includes(url.protocol)) return true; if (url.origin !== self.location.origin) return false; const path = url.pathname; if (path === '/admin.php' || path === '/login.php' || path === '/logout.php') return true; if (path === '/push_config.php' || path === '/push_subscribe.php' || path === '/job_status.php') return true; if (path.startsWith('/api/') || path.startsWith('/lib/') || path.startsWith('/cache/') || path.startsWith('/in-dir/')) return true; if (path.startsWith('/out-dir/') && /\.(mp4|m4v|mov|webm)$/i.test(path)) return true; if (/\.(mp4|m4v|mov|webm)$/i.test(path)) return true; return false; } function isStaticAsset(url) { if (url.origin !== self.location.origin) return false; const path = url.pathname; return path === '/style.css' || path === '/manifest.json' || path === '/offline.html' || path.startsWith('/assets/') || path.startsWith('/js/'); } async function networkFirstNavigation(request) { try { const response = await fetch(request); if (response && response.ok) { const cache = await caches.open(RUNTIME_CACHE); cache.put(request, response.clone()).catch(() => undefined); } return response; } catch (error) { const exact = await caches.match(request); if (exact) return exact; const offline = await caches.match('/offline.html'); if (offline) return offline; const home = await caches.match('/'); if (home) return home; return new Response('Offline', { status: 503, headers: { 'Content-Type': 'text/plain; charset=utf-8' } }); } } async function staleWhileRevalidate(request) { const cached = await caches.match(request); const cache = await caches.open(RUNTIME_CACHE); const network = fetch(request).then(response => { if (response && response.ok) { cache.put(request, response.clone()).catch(() => undefined); } return response; }).catch(() => cached); return cached || network; } self.addEventListener('fetch', event => { const request = event.request; const url = new URL(request.url); if (shouldBypassFetch(request, url)) return; if (request.mode === 'navigate') { event.respondWith(networkFirstNavigation(request)); return; } if (isStaticAsset(url)) { event.respondWith(staleWhileRevalidate(request)); } }); self.addEventListener('push', event => { let data = {}; try { data = event.data ? event.data.json() : {}; } catch (e) { data = { title: 'MVLog', body: event.data ? event.data.text() : 'New article @ MVLog!' }; } const title = data.title || 'MVLog'; const options = { body: data.body || 'New article @ MVLog!', icon: 'assets/img/moto_travel.png', badge: 'assets/img/moto_travel.png', data: { url: data.url || './' }, tag: data.tag || 'mvlog-new-video', renotify: true }; event.waitUntil(self.registration.showNotification(title, options)); }); self.addEventListener('notificationclick', event => { event.notification.close(); const url = event.notification.data && event.notification.data.url ? event.notification.data.url : './'; event.waitUntil(clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => { for (const client of clientList) { if (client.url === url && 'focus' in client) return client.focus(); } return clients.openWindow(url); })); });