Files
InfoGenie/InfoGenie-frontend/public/sw.js

151 lines
4.2 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Service Worker for InfoGenie App (PWA)
// 注意PWA 必须在 HTTPS或 localhost下才能生效
const CACHE_VERSION = 'v2';
const PRECACHE_NAME = `infogenie-precache-${CACHE_VERSION}`;
const RUNTIME_NAME = `infogenie-runtime-${CACHE_VERSION}`;
const CORE_ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/icons/icon-192.png',
'/icons/icon-512.png',
'/icons/icon-192-maskable.png',
'/icons/icon-512-maskable.png',
'/icons/apple-touch-icon.png',
'/icons/favicon-32.png',
'/icons/favicon-16.png'
];
async function safeAddAll(cache, urls) {
const uniqueUrls = Array.from(new Set(urls)).filter(Boolean);
const results = await Promise.allSettled(uniqueUrls.map(url => cache.add(url)));
const failures = results.filter(r => r.status === 'rejected');
if (failures.length > 0) {
console.warn('[SW] Some assets failed to cache:', failures.length);
}
}
async function precacheEntrypoints(cache) {
try {
const res = await fetch('/asset-manifest.json', { cache: 'no-store' });
if (!res.ok) return;
const manifest = await res.json();
const filesObj = manifest && typeof manifest === 'object' ? manifest.files : undefined;
const files = filesObj && typeof filesObj === 'object' ? Object.values(filesObj) : [];
const entrypoints = Array.isArray(manifest.entrypoints) ? manifest.entrypoints : [];
const urls = [...files, ...entrypoints]
.filter(p => typeof p === 'string')
.map(p => (p.startsWith('/') ? p : `/${p}`))
.filter(p => !p.endsWith('.map'));
await safeAddAll(cache, urls);
} catch (err) {
console.warn('[SW] Failed to precache entrypoints:', err);
}
}
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
(async () => {
const cache = await caches.open(PRECACHE_NAME);
await safeAddAll(cache, CORE_ASSETS);
await precacheEntrypoints(cache);
})()
);
});
self.addEventListener('activate', event => {
event.waitUntil(
(async () => {
const cacheNames = await caches.keys();
await Promise.all(
cacheNames.map(name => {
if (name !== PRECACHE_NAME && name !== RUNTIME_NAME) {
return caches.delete(name);
}
return undefined;
})
);
await self.clients.claim();
})()
);
});
function isNavigationRequest(request) {
return request.mode === 'navigate' || request.destination === 'document';
}
function shouldHandleRequest(url, request) {
if (request.method !== 'GET') return false;
if (url.origin !== self.location.origin) return false;
return true;
}
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (!shouldHandleRequest(url, event.request)) return;
// 不缓存后端 API如需缓存请在这里加规则
if (url.pathname.startsWith('/api')) return;
// 页面请求:优先网络,离线回退到缓存
if (isNavigationRequest(event.request)) {
event.respondWith(
(async () => {
try {
const networkResponse = await fetch(event.request);
const cache = await caches.open(RUNTIME_NAME);
cache.put(event.request, networkResponse.clone());
return networkResponse;
} catch (err) {
const cached = await caches.match(event.request);
return cached || caches.match('/index.html');
}
})()
);
return;
}
// 静态资源stale-while-revalidate
event.respondWith(
(async () => {
const cached = await caches.match(event.request);
const cache = await caches.open(RUNTIME_NAME);
const networkFetch = (async () => {
try {
const response = await fetch(event.request);
if (response && response.ok) {
cache.put(event.request, response.clone());
}
return response;
} catch (err) {
return undefined;
}
})();
if (cached) {
event.waitUntil(networkFetch);
return cached;
}
const response = await networkFetch;
if (response) return response;
return new Response('', { status: 504, statusText: 'Offline' });
})()
);
});
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});