init
This commit is contained in:
235
animex/sw.js
Normal file
235
animex/sw.js
Normal file
@@ -0,0 +1,235 @@
|
||||
/* sw.js
|
||||
PWA offline service worker
|
||||
- precaches core app shell + important pages
|
||||
- runtime caches images/videos/pdf with limits
|
||||
- navigation fallback to /offline.html
|
||||
- supports skipWaiting and downloadOffline messages
|
||||
*/
|
||||
|
||||
const CACHE_VERSION = '2025-11-20-8'; // bump when you change assets
|
||||
const PRECACHE = `precache-${CACHE_VERSION}`;
|
||||
const RUNTIME = `runtime-${CACHE_VERSION}`;
|
||||
|
||||
// Offline fallback page
|
||||
const OFFLINE_URL = '/offline.html';
|
||||
const PRECACHE_URLS = [
|
||||
'/', // index
|
||||
'/index.html',
|
||||
'/Launch.html',
|
||||
'/about.html',
|
||||
'/in.html',
|
||||
'/intro.html',
|
||||
'/login.html',
|
||||
'/manga.html',
|
||||
'/library.html',
|
||||
'/offline.html',
|
||||
'/portal.html',
|
||||
'/reader.html',
|
||||
'/video_player.html',
|
||||
'/pdf.html',
|
||||
'/view.html',
|
||||
'/search.html',
|
||||
'/lists.html',
|
||||
'/settings.html',
|
||||
'/Resources/manifest.json',
|
||||
'/Resources/styles.css',
|
||||
'/Resources/manga.css',
|
||||
'/Resources/series.css',
|
||||
'/Resources/favicon.png',
|
||||
'/Resources/Images/Launch.png',
|
||||
'/Resources/Images/Launch_screen.png',
|
||||
'/Resources/Images/logo-256.png',
|
||||
'/Resources/Images/logo-512.png',
|
||||
'/sw.js'
|
||||
];
|
||||
|
||||
// Runtime cache limits
|
||||
const MAX_IMAGE_ITEMS = 80;
|
||||
const MAX_VIDEO_ITEMS = 15;
|
||||
const MAX_PDF_ITEMS = 20;
|
||||
|
||||
/* Utility: trim cache to max items (LRU-ish by deleting oldest) */
|
||||
async function trimCache(cacheName, maxItems) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const keys = await cache.keys();
|
||||
if (keys.length <= maxItems) return;
|
||||
const removeCount = keys.length - maxItems;
|
||||
for (let i = 0; i < removeCount; i++) {
|
||||
await cache.delete(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Install: precache app shell */
|
||||
self.addEventListener('install', event => {
|
||||
self.skipWaiting(); // activate worker immediately (be careful with breaking changes)
|
||||
event.waitUntil(
|
||||
caches.open(PRECACHE)
|
||||
.then(cache => cache.addAll(PRECACHE_URLS))
|
||||
.catch(err => {
|
||||
console.error('Precache failed:', err);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/* Activate: clean old caches */
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil((async () => {
|
||||
const names = await caches.keys();
|
||||
await Promise.all(
|
||||
names.filter(name => name !== PRECACHE && name !== RUNTIME)
|
||||
.map(name => caches.delete(name))
|
||||
);
|
||||
// Immediately take control of the pages
|
||||
await self.clients.claim();
|
||||
})());
|
||||
});
|
||||
|
||||
/* Fetch handler */
|
||||
self.addEventListener('fetch', event => {
|
||||
const req = event.request;
|
||||
const url = new URL(req.url);
|
||||
|
||||
// Only handle same-origin requests (adjust if assets are on a CDN)
|
||||
const sameOrigin = url.origin === self.location.origin;
|
||||
|
||||
// 1) Navigation requests -> network-first, fallback to cache -> offline page
|
||||
if (req.mode === 'navigate') {
|
||||
event.respondWith(networkFirstFallbackToCache(req));
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) API / JSON/XHR requests -> network-first (don't cache large dynamic responses)
|
||||
if (sameOrigin && url.pathname.startsWith('/api')) {
|
||||
event.respondWith(networkFirst(req));
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) Images -> cache-first with size limit
|
||||
if (req.destination === 'image' || /\.(?:png|jpg|jpeg|gif|webp|svg)$/i.test(url.pathname)) {
|
||||
event.respondWith(cacheFirstWithRuntime(req, 'images-cache', MAX_IMAGE_ITEMS));
|
||||
return;
|
||||
}
|
||||
|
||||
// 4) Video files -> cache-first but avoid precaching; runtime cache with small limit
|
||||
if (/\.(?:mp4|webm|m4v|mov)$/i.test(url.pathname)) {
|
||||
event.respondWith(cacheFirstWithRuntime(req, 'videos-cache', MAX_VIDEO_ITEMS));
|
||||
return;
|
||||
}
|
||||
|
||||
// 5) PDFs and other documents -> cache-first with limit
|
||||
if (/\.(?:pdf|epub|mobi)$/i.test(url.pathname) || req.destination === 'document') {
|
||||
event.respondWith(cacheFirstWithRuntime(req, 'docs-cache', MAX_PDF_ITEMS));
|
||||
return;
|
||||
}
|
||||
|
||||
// 6) CSS/JS/font -> stale-while-revalidate strategy
|
||||
if (/\.(?:js|css|woff2?|ttf|otf)$/i.test(url.pathname) || req.destination === 'script' || req.destination === 'style' || req.destination === 'font') {
|
||||
event.respondWith(staleWhileRevalidate(req));
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: try cache first then network
|
||||
event.respondWith(
|
||||
caches.match(req).then(cached => cached || fetch(req).catch(() => {
|
||||
// if fetch failed and request is a navigation or HTML, show offline page
|
||||
if (req.headers.get('accept') && req.headers.get('accept').includes('text/html')) {
|
||||
return caches.match(OFFLINE_URL);
|
||||
}
|
||||
return new Response(null, { status: 503, statusText: 'Service Unavailable' });
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
/* Strategies */
|
||||
|
||||
async function networkFirstFallbackToCache(request) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
// Put navigation responses in runtime cache for offline use
|
||||
const cache = await caches.open(RUNTIME);
|
||||
cache.put(request, response.clone()).catch(() => {});
|
||||
return response;
|
||||
} catch (err) {
|
||||
// network failed -> try cache
|
||||
const cached = await caches.match(request);
|
||||
if (cached) return cached;
|
||||
// finally fallback to offline page
|
||||
return caches.match(OFFLINE_URL);
|
||||
}
|
||||
}
|
||||
|
||||
async function networkFirst(request) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
// Update runtime cache
|
||||
const cache = await caches.open(RUNTIME);
|
||||
cache.put(request, response.clone()).catch(() => {});
|
||||
return response;
|
||||
} catch (err) {
|
||||
const cached = await caches.match(request);
|
||||
if (cached) return cached;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function cacheFirstWithRuntime(request, cacheName, maxItems = 50) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const cached = await cache.match(request);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
// Only cache successful responses (200)
|
||||
if (response && response.status === 200) {
|
||||
cache.put(request, response.clone()).catch(() => {});
|
||||
// trim if necessary
|
||||
trimCache(cacheName, maxItems).catch(() => {});
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
// fallback to precache or offline
|
||||
const fallback = await caches.match(request);
|
||||
if (fallback) return fallback;
|
||||
if (request.headers.get('accept') && request.headers.get('accept').includes('text/html')) {
|
||||
return caches.match(OFFLINE_URL);
|
||||
}
|
||||
return new Response(null, { status: 503, statusText: 'Service Unavailable' });
|
||||
}
|
||||
}
|
||||
|
||||
async function staleWhileRevalidate(request) {
|
||||
const cache = await caches.open(RUNTIME);
|
||||
const cached = await cache.match(request);
|
||||
const networkFetch = fetch(request).then(response => {
|
||||
if (response && response.status === 200) {
|
||||
cache.put(request, response.clone()).catch(() => {});
|
||||
}
|
||||
return response;
|
||||
}).catch(() => null);
|
||||
return cached || networkFetch;
|
||||
}
|
||||
|
||||
/* Listen for messages from the page to trigger SW actions (skipWaiting, downloadOffline) */
|
||||
self.addEventListener('message', event => {
|
||||
const data = event.data;
|
||||
if (!data) return;
|
||||
|
||||
if (data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
if (data.type === 'DOWNLOAD_OFFLINE') {
|
||||
// Cache urls that are missing from PRECACHE
|
||||
downloadOffline();
|
||||
}
|
||||
});
|
||||
|
||||
/* Pre-cache any resources that aren't yet cached */
|
||||
async function downloadOffline() {
|
||||
const cache = await caches.open(PRECACHE);
|
||||
const cachedRequests = await cache.keys();
|
||||
const cachedUrls = cachedRequests.map(r => new URL(r.url).pathname);
|
||||
const toCache = PRECACHE_URLS.filter(url => !cachedUrls.includes(url));
|
||||
return cache.addAll(toCache);
|
||||
}
|
||||
Reference in New Issue
Block a user