movmaker-webui/sw.js

141 lines
4.7 KiB
JavaScript

const MVLOG_CACHE_PREFIX = 'mvlog-pwa-';
const MVLOG_CACHE_VERSION = 'mvlog-pwa-v1';
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);
}));
});