mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-05-10 00:44:13 +08:00
Merge pull request #51 from Moe-Sakura/dev
Refactor service worker implementation and update caching strategies
This commit is contained in:
363
public/sw.js
363
public/sw.js
@@ -1,300 +1,191 @@
|
||||
// Service Worker - PWA 支持(增强缓存版)
|
||||
// 尽可能多地缓存文件以提升加载速度
|
||||
const SW_VERSION = self.__SW_VERSION__ || Date.now().toString(36);
|
||||
const CACHE_NAME = `searchgal-cache-${SW_VERSION}`;
|
||||
// Service Worker - PWA 支持
|
||||
// 版本由构建工具注入,开发时使用时间戳
|
||||
const SW_VERSION = self.__SW_VERSION__ || Date.now().toString(36)
|
||||
const CACHE_NAME = `searchgal-${SW_VERSION}`
|
||||
|
||||
// 缓存静态资源(尽可能多)
|
||||
const CACHEABLE_PATTERNS = [
|
||||
/\/assets\/.*\.(js|css|mjs)(\?.*)?$/i, // Vite 构建的静态资源
|
||||
/\.(woff2?|ttf|otf|eot)(\?.*)?$/i, // 字体
|
||||
/\.(png|jpg|jpeg|gif|webp|svg|ico|avif)(\?.*)?$/i, // 图片
|
||||
/\.(mp3|wav|ogg|m4a|aac|flac)(\?.*)?$/i, // 音频(snd-lib 音效)
|
||||
/\.(mp4|webm|ogv)(\?.*)?$/i, // 视频
|
||||
/\.(json|xml|txt)(\?.*)?$/i, // 数据文件
|
||||
/\.(wasm)(\?.*)?$/i, // WebAssembly
|
||||
/\/manifest\.json$/i, // PWA manifest
|
||||
/\/favicon/i, // Favicon 相关
|
||||
/\/apple-touch-icon/i, // iOS 图标
|
||||
/\/android-chrome/i, // Android 图标
|
||||
];
|
||||
|
||||
// 预缓存的核心资源(安装时缓存)
|
||||
const PRECACHE_URLS = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/manifest.json',
|
||||
];
|
||||
// 可缓存的静态资源模式
|
||||
const CACHEABLE_EXTS = /\.(js|css|mjs|woff2?|ttf|otf|eot|png|jpg|jpeg|gif|webp|svg|ico|avif|mp3|wav|ogg|m4a|aac|flac|wasm)(\?.*)?$/i
|
||||
const CACHEABLE_PATHS = /\/(manifest\.json|favicon|apple-touch-icon|android-chrome)/i
|
||||
|
||||
// 永不缓存
|
||||
const NO_CACHE_PATTERNS = [
|
||||
/\/api\//,
|
||||
/\/sw\.js$/,
|
||||
/sockjs-node/,
|
||||
/__vite/,
|
||||
/hot-update/,
|
||||
/\.map$/, // Source maps
|
||||
];
|
||||
const NO_CACHE = /\/(api|sw\.js|sockjs-node|__vite|hot-update)|\.map$/
|
||||
|
||||
// 预缓存资源
|
||||
const PRECACHE = ['/', '/index.html', '/manifest.json']
|
||||
|
||||
// ============================================
|
||||
// 安装事件 - 预缓存核心资源
|
||||
// 生命周期事件
|
||||
// ============================================
|
||||
self.addEventListener('install', (event) => {
|
||||
console.log(`[SW] Installing version ${SW_VERSION}`);
|
||||
|
||||
// 预缓存失败不应阻止 service worker 安装
|
||||
const precachePromise = caches.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
console.log('[SW] Precaching core resources');
|
||||
return cache.addAll(PRECACHE_URLS);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('[SW] Precache failed:', err);
|
||||
});
|
||||
|
||||
event.waitUntil(
|
||||
precachePromise.then(() => self.skipWaiting())
|
||||
);
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 激活事件 - 清理旧缓存
|
||||
// ============================================
|
||||
self.addEventListener('activate', (event) => {
|
||||
console.log(`[SW] Activating version ${SW_VERSION}`);
|
||||
|
||||
event.waitUntil(
|
||||
self.addEventListener('install', (e) => {
|
||||
console.log(`[SW] Installing v${SW_VERSION}`)
|
||||
e.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then((c) => c.addAll(PRECACHE).catch(() => {}))
|
||||
.then(() => self.skipWaiting())
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener('activate', (e) => {
|
||||
console.log(`[SW] Activating v${SW_VERSION}`)
|
||||
e.waitUntil(
|
||||
caches.keys()
|
||||
.then((names) => Promise.all(
|
||||
names
|
||||
.filter((name) => name.startsWith('searchgal-cache-') && name !== CACHE_NAME)
|
||||
.map((name) => caches.delete(name))
|
||||
.then((keys) => Promise.all(
|
||||
keys.filter((k) => k.startsWith('searchgal-') && k !== CACHE_NAME).map((k) => caches.delete(k))
|
||||
))
|
||||
.then(() => self.clients.claim())
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
// ============================================
|
||||
// 消息处理
|
||||
// ============================================
|
||||
self.addEventListener('message', (event) => {
|
||||
const { type } = event.data || {};
|
||||
|
||||
|
||||
self.addEventListener('message', (e) => {
|
||||
const { type } = e.data || {}
|
||||
if (type === 'GET_VERSION') {
|
||||
event.ports[0]?.postMessage({ version: SW_VERSION });
|
||||
e.ports[0]?.postMessage({ version: SW_VERSION })
|
||||
} else if (type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
self.skipWaiting()
|
||||
} else if (type === 'CLEAR_CACHE') {
|
||||
event.waitUntil(
|
||||
e.waitUntil(
|
||||
caches.keys()
|
||||
.then((names) => Promise.all(names.map((name) => caches.delete(name))))
|
||||
.then(() => event.ports[0]?.postMessage({ success: true }))
|
||||
);
|
||||
.then((keys) => Promise.all(keys.map((k) => caches.delete(k))))
|
||||
.then(() => e.ports[0]?.postMessage({ success: true }))
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// ============================================
|
||||
// 请求拦截
|
||||
// ============================================
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
const url = new URL(request.url);
|
||||
|
||||
// 跳过非 GET 请求
|
||||
if (request.method !== 'GET') return;
|
||||
|
||||
// 跳过非 HTTP(S) 请求
|
||||
if (!url.protocol.startsWith('http')) return;
|
||||
|
||||
// 跳过永不缓存的模式
|
||||
if (NO_CACHE_PATTERNS.some((p) => p.test(url.href))) return;
|
||||
self.addEventListener('fetch', (e) => {
|
||||
const { request } = e
|
||||
const url = new URL(request.url)
|
||||
|
||||
// 同源请求
|
||||
if (url.origin === location.origin) {
|
||||
// HTML 页面请求:网络优先,离线时显示提示
|
||||
if (request.destination === 'document' || url.pathname === '/' || url.pathname.endsWith('.html')) {
|
||||
event.respondWith(handlePageRequest(request));
|
||||
return;
|
||||
// 跳过:非 GET、非 HTTP、永不缓存、跨域图片
|
||||
if (
|
||||
request.method !== 'GET' ||
|
||||
!url.protocol.startsWith('http') ||
|
||||
NO_CACHE.test(url.href) ||
|
||||
(url.origin !== location.origin && request.destination === 'image')
|
||||
) return
|
||||
|
||||
const isSameOrigin = url.origin === location.origin
|
||||
const isDocument = request.destination === 'document' || url.pathname === '/' || url.pathname.endsWith('.html')
|
||||
const isCacheable = CACHEABLE_EXTS.test(url.pathname) || CACHEABLE_PATHS.test(url.pathname)
|
||||
|
||||
if (isSameOrigin) {
|
||||
if (isDocument) {
|
||||
// HTML:网络优先,离线返回提示页
|
||||
e.respondWith(networkFirstWithOffline(request))
|
||||
} else if (isCacheable) {
|
||||
// 静态资源:缓存优先
|
||||
e.respondWith(cacheFirst(request))
|
||||
} else {
|
||||
// 其他:网络优先
|
||||
e.respondWith(networkFirst(request))
|
||||
}
|
||||
|
||||
// 可缓存的静态资源:缓存优先(加速)
|
||||
if (CACHEABLE_PATTERNS.some((p) => p.test(url.pathname) || p.test(url.href))) {
|
||||
event.respondWith(cacheFirst(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// 同源其他资源:网络优先,但也缓存
|
||||
event.respondWith(networkFirst(request));
|
||||
return;
|
||||
} else if (isCacheable) {
|
||||
// 跨域可缓存资源
|
||||
e.respondWith(cacheFirstCrossOrigin(request))
|
||||
}
|
||||
})
|
||||
|
||||
// 跨域资源:仅缓存可缓存的静态资源(如 CDN 资源)
|
||||
if (CACHEABLE_PATTERNS.some((p) => p.test(url.pathname) || p.test(url.href))) {
|
||||
event.respondWith(cacheFirstCrossOrigin(request));
|
||||
return;
|
||||
}
|
||||
});
|
||||
// ============================================
|
||||
// 缓存策略
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 处理页面请求 - 离线时显示联网提示
|
||||
*/
|
||||
async function handlePageRequest(request) {
|
||||
async function cacheFirst(req) {
|
||||
const cached = await caches.match(req)
|
||||
if (cached) return cached
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
return response;
|
||||
const res = await fetch(req)
|
||||
if (res.ok) {
|
||||
const cache = await caches.open(CACHE_NAME)
|
||||
cache.put(req, res.clone())
|
||||
}
|
||||
return res
|
||||
} catch {
|
||||
// 离线 - 返回联网提示页面
|
||||
return createOfflinePage();
|
||||
return new Response('', { status: 503 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存优先(用于静态资源加速)
|
||||
*/
|
||||
async function cacheFirst(request) {
|
||||
const cached = await caches.match(request);
|
||||
if (cached) return cached;
|
||||
|
||||
async function networkFirst(req) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
cache.put(request, response.clone());
|
||||
const res = await fetch(req)
|
||||
if (res.ok) {
|
||||
const cache = await caches.open(CACHE_NAME)
|
||||
cache.put(req, res.clone())
|
||||
}
|
||||
return response;
|
||||
return res
|
||||
} catch {
|
||||
return new Response('', { status: 503 });
|
||||
return (await caches.match(req)) || new Response('', { status: 503 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络优先
|
||||
*/
|
||||
async function networkFirst(request) {
|
||||
async function networkFirstWithOffline(req) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
return await fetch(req)
|
||||
} catch {
|
||||
const cached = await caches.match(request);
|
||||
return cached || new Response('', { status: 503 });
|
||||
return offlinePage()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存优先(跨域资源)
|
||||
*/
|
||||
async function cacheFirstCrossOrigin(request) {
|
||||
const cached = await caches.match(request);
|
||||
if (cached) return cached;
|
||||
|
||||
async function cacheFirstCrossOrigin(req) {
|
||||
const cached = await caches.match(req)
|
||||
if (cached) return cached
|
||||
try {
|
||||
// 跨域请求需要设置 mode
|
||||
const response = await fetch(request, { mode: 'cors', credentials: 'omit' });
|
||||
if (response.ok && response.type !== 'opaque') {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
cache.put(request, response.clone());
|
||||
const res = await fetch(req, { mode: 'cors', credentials: 'omit' })
|
||||
if (res.ok && res.type !== 'opaque') {
|
||||
const cache = await caches.open(CACHE_NAME)
|
||||
cache.put(req, res.clone())
|
||||
}
|
||||
return response;
|
||||
return res
|
||||
} catch {
|
||||
// 跨域失败时返回空响应
|
||||
return new Response('', { status: 503 });
|
||||
return new Response('', { status: 503 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 离线提示页面
|
||||
*/
|
||||
function createOfflinePage() {
|
||||
return new Response(
|
||||
`<!DOCTYPE html>
|
||||
// ============================================
|
||||
// 离线页面
|
||||
// ============================================
|
||||
|
||||
function offlinePage() {
|
||||
return new Response(`<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>请连接网络 - SearchGal</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>离线 - SearchGal</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #fff5fa 0%, #ffe4f0 100%);
|
||||
color: #333;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
.icon {
|
||||
font-size: 5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background: linear-gradient(135deg, #ff1493, #d946ef);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
p {
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
button {
|
||||
padding: 1rem 2.5rem;
|
||||
background: linear-gradient(135deg, #ff1493, #d946ef);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(255, 20, 147, 0.3);
|
||||
}
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(255, 20, 147, 0.4);
|
||||
}
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.hint {
|
||||
margin-top: 2rem;
|
||||
font-size: 0.875rem;
|
||||
color: #999;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:system-ui,-apple-system,sans-serif;background:linear-gradient(135deg,#fff5fa,#ffe4f0);padding:1.5rem}
|
||||
.c{text-align:center;max-width:360px}
|
||||
.i{font-size:4rem;margin-bottom:1rem;animation:f 2s ease-in-out infinite}
|
||||
@keyframes f{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
|
||||
h1{font-size:1.5rem;margin-bottom:.5rem;background:linear-gradient(135deg,#ff1493,#d946ef);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
||||
p{color:#666;line-height:1.5;margin-bottom:1.5rem}
|
||||
button{padding:.875rem 2rem;background:linear-gradient(135deg,#ff1493,#d946ef);color:#fff;border:none;border-radius:9999px;font-size:.9rem;font-weight:500;cursor:pointer;box-shadow:0 4px 12px rgba(255,20,147,.3);transition:transform .2s,box-shadow .2s}
|
||||
button:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(255,20,147,.4)}
|
||||
button:active{transform:translateY(0)}
|
||||
.h{margin-top:1.5rem;font-size:.8rem;color:#999}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="icon">🌐</div>
|
||||
<div class="c">
|
||||
<div class="i">🌐</div>
|
||||
<h1>需要网络连接</h1>
|
||||
<p>SearchGal 是一个在线搜索服务,<br>请连接网络后使用</p>
|
||||
<p>SearchGal 是在线搜索服务,请连接网络后使用</p>
|
||||
<button onclick="location.reload()">重新连接</button>
|
||||
<p class="hint">请检查你的网络是否正常连接</p>
|
||||
<p class="h">检查网络是否正常</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`,
|
||||
{
|
||||
status: 503,
|
||||
statusText: 'Offline',
|
||||
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
||||
}
|
||||
);
|
||||
</html>`, {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/html;charset=utf-8' }
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`[SW] Service Worker loaded, version: ${SW_VERSION}`);
|
||||
console.log(`[SW] Loaded v${SW_VERSION}`)
|
||||
|
||||
@@ -793,7 +793,7 @@ function resetAdvancedApiSettings(playSound = true) {
|
||||
|
||||
/* IDE 风格代码编辑器 */
|
||||
.code-editor {
|
||||
font-family: "JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace !important;
|
||||
font-family: Consolas, "Monaco", "JetBrains Mono", "Fira Code", monospace !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.5 !important;
|
||||
min-height: 240px !important;
|
||||
|
||||
129
src/main.ts
129
src/main.ts
@@ -55,117 +55,54 @@ createProgressFetch()
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
// Service Worker 更新检测
|
||||
// 当前激活的 SW 版本(运行时获取)
|
||||
let activatedSwVersion: string | null = null
|
||||
// ============================================
|
||||
// Service Worker 注册与更新
|
||||
// ============================================
|
||||
|
||||
// 获取 SW 版本
|
||||
async function getSwVersion(sw: ServiceWorker): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
const messageChannel = new MessageChannel()
|
||||
messageChannel.port1.onmessage = (event) => {
|
||||
resolve(event.data?.version || null)
|
||||
}
|
||||
sw.postMessage({ type: 'GET_VERSION' }, [messageChannel.port2])
|
||||
|
||||
// 超时处理
|
||||
setTimeout(() => resolve(null), 2000)
|
||||
})
|
||||
}
|
||||
|
||||
// 检查是否有新版本
|
||||
async function checkForNewVersion(registration: ServiceWorkerRegistration): Promise<boolean> {
|
||||
const sw = registration.active
|
||||
if (!sw) {return false}
|
||||
|
||||
const currentVersion = await getSwVersion(sw)
|
||||
|
||||
// 首次获取版本时记录
|
||||
if (!activatedSwVersion && currentVersion) {
|
||||
activatedSwVersion = currentVersion
|
||||
console.log(`[SW] Current version: ${activatedSwVersion}`)
|
||||
return false
|
||||
}
|
||||
|
||||
// 版本不同则有更新
|
||||
if (currentVersion && activatedSwVersion && currentVersion !== activatedSwVersion) {
|
||||
console.log(`[SW] New version available: ${currentVersion} (was: ${activatedSwVersion})`)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 触发 SW 更新
|
||||
function triggerSwUpdate(registration: ServiceWorkerRegistration) {
|
||||
const waitingSw = registration.waiting
|
||||
if (waitingSw) {
|
||||
waitingSw.postMessage({ type: 'SKIP_WAITING' })
|
||||
}
|
||||
// 刷新页面
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
// 自动应用更新(不显示弹窗)
|
||||
function autoApplyUpdate(registration: ServiceWorkerRegistration) {
|
||||
console.log('[SW] Auto-applying update...')
|
||||
triggerSwUpdate(registration)
|
||||
}
|
||||
|
||||
// 注册 Service Worker (PWA 支持)
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', async () => {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register('/sw.js')
|
||||
const reg = await navigator.serviceWorker.register('/sw.js')
|
||||
console.log('[SW] Registered')
|
||||
|
||||
// 检查更新
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration.installing
|
||||
if (newWorker) {
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
// 有新版本可用,自动更新
|
||||
autoApplyUpdate(registration)
|
||||
}
|
||||
})
|
||||
// 新版本检测
|
||||
reg.addEventListener('updatefound', () => {
|
||||
const worker = reg.installing
|
||||
if (!worker) {
|
||||
return
|
||||
}
|
||||
|
||||
worker.addEventListener('statechange', () => {
|
||||
// 新 SW 安装完成且有旧 SW 控制页面 = 有更新
|
||||
if (worker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
console.log('[SW] Update available, activating...')
|
||||
worker.postMessage({ type: 'SKIP_WAITING' })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 首次获取当前版本
|
||||
if (registration.active) {
|
||||
activatedSwVersion = await getSwVersion(registration.active)
|
||||
console.log(`[SW] Registered, version: ${activatedSwVersion || 'unknown'}`)
|
||||
}
|
||||
|
||||
// 定期检查版本(每 5 分钟)
|
||||
setInterval(async () => {
|
||||
try {
|
||||
await registration.update()
|
||||
const hasNewVersion = await checkForNewVersion(registration)
|
||||
if (hasNewVersion) {
|
||||
autoApplyUpdate(registration)
|
||||
}
|
||||
} catch {
|
||||
// 静默处理检查失败
|
||||
// 新 SW 激活后刷新页面
|
||||
let refreshing = false
|
||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||
if (refreshing) {
|
||||
return
|
||||
}
|
||||
}, 5 * 60 * 1000)
|
||||
refreshing = true
|
||||
console.log('[SW] New version activated, reloading...')
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
// 定期检查更新(5 分钟)
|
||||
setInterval(() => reg.update().catch(() => {}), 5 * 60 * 1000)
|
||||
|
||||
// 页面可见时检查更新
|
||||
document.addEventListener('visibilitychange', async () => {
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
try {
|
||||
await registration.update()
|
||||
} catch {
|
||||
// 静默处理
|
||||
}
|
||||
reg.update().catch(() => {})
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
// 静默处理注册失败
|
||||
// 静默处理
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 导出更新函数供组件使用
|
||||
;(window as Window & { triggerSwUpdate?: typeof triggerSwUpdate }).triggerSwUpdate = triggerSwUpdate
|
||||
|
||||
Reference in New Issue
Block a user