287 lines
7.1 KiB
JavaScript
287 lines
7.1 KiB
JavaScript
|
|
// Service Worker for PWA functionality
|
||
|
|
const CACHE_NAME = 'it-hardware-ranking-v1'
|
||
|
|
const RUNTIME_CACHE = 'it-hardware-ranking-runtime'
|
||
|
|
|
||
|
|
// 需要预缓存的资源列表
|
||
|
|
const STATIC_CACHE_URLS = [
|
||
|
|
'/',
|
||
|
|
'/index.html',
|
||
|
|
'/manifest.json',
|
||
|
|
'/favicon.ico',
|
||
|
|
// 添加其他需要预缓存的静态资源
|
||
|
|
]
|
||
|
|
|
||
|
|
// 需要网络优先的资源
|
||
|
|
const NETWORK_FIRST_URLS = [
|
||
|
|
'/api/',
|
||
|
|
// 添加其他需要网络优先的API路径
|
||
|
|
]
|
||
|
|
|
||
|
|
// 需要缓存优先的资源
|
||
|
|
const CACHE_FIRST_URLS = [
|
||
|
|
'/static/',
|
||
|
|
'/assets/',
|
||
|
|
// 添加其他需要缓存优先的静态资源路径
|
||
|
|
]
|
||
|
|
|
||
|
|
// 安装事件 - 预缓存静态资源
|
||
|
|
self.addEventListener('install', (event) => {
|
||
|
|
console.log('[SW] Install event triggered')
|
||
|
|
|
||
|
|
event.waitUntil(
|
||
|
|
caches.open(CACHE_NAME)
|
||
|
|
.then((cache) => {
|
||
|
|
console.log('[SW] Caching static resources')
|
||
|
|
return cache.addAll(STATIC_CACHE_URLS)
|
||
|
|
})
|
||
|
|
.then(() => {
|
||
|
|
console.log('[SW] Static resources cached successfully')
|
||
|
|
// 强制激活新的Service Worker
|
||
|
|
return self.skipWaiting()
|
||
|
|
})
|
||
|
|
.catch((error) => {
|
||
|
|
console.error('[SW] Failed to cache static resources:', error)
|
||
|
|
})
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
// 激活事件 - 清理旧缓存
|
||
|
|
self.addEventListener('activate', (event) => {
|
||
|
|
console.log('[SW] Activate event triggered')
|
||
|
|
|
||
|
|
event.waitUntil(
|
||
|
|
caches.keys()
|
||
|
|
.then((cacheNames) => {
|
||
|
|
return Promise.all(
|
||
|
|
cacheNames.map((cacheName) => {
|
||
|
|
// 删除旧版本的缓存
|
||
|
|
if (cacheName !== CACHE_NAME && cacheName !== RUNTIME_CACHE) {
|
||
|
|
console.log('[SW] Deleting old cache:', cacheName)
|
||
|
|
return caches.delete(cacheName)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
)
|
||
|
|
})
|
||
|
|
.then(() => {
|
||
|
|
console.log('[SW] Old caches cleaned up')
|
||
|
|
// 立即控制所有客户端
|
||
|
|
return self.clients.claim()
|
||
|
|
})
|
||
|
|
.catch((error) => {
|
||
|
|
console.error('[SW] Failed to clean up old caches:', error)
|
||
|
|
})
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
// 网络请求拦截
|
||
|
|
self.addEventListener('fetch', (event) => {
|
||
|
|
const { request } = event
|
||
|
|
const url = new URL(request.url)
|
||
|
|
|
||
|
|
// 跳过非HTTP(S)请求
|
||
|
|
if (!url.protocol.startsWith('http')) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// 跳过Chrome扩展请求
|
||
|
|
if (url.protocol === 'chrome-extension:') {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// 根据请求URL选择缓存策略
|
||
|
|
if (isNetworkFirst(url)) {
|
||
|
|
// 网络优先策略
|
||
|
|
event.respondWith(networkFirst(request))
|
||
|
|
} else if (isCacheFirst(url)) {
|
||
|
|
// 缓存优先策略
|
||
|
|
event.respondWith(cacheFirst(request))
|
||
|
|
} else {
|
||
|
|
// 缓存优先,网络作为后备策略
|
||
|
|
event.respondWith(staleWhileRevalidate(request))
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// 判断是否使用网络优先策略
|
||
|
|
function isNetworkFirst(url) {
|
||
|
|
return NETWORK_FIRST_URLS.some(path => url.pathname.startsWith(path))
|
||
|
|
}
|
||
|
|
|
||
|
|
// 判断是否使用缓存优先策略
|
||
|
|
function isCacheFirst(url) {
|
||
|
|
return CACHE_FIRST_URLS.some(path => url.pathname.startsWith(path))
|
||
|
|
}
|
||
|
|
|
||
|
|
// 网络优先策略
|
||
|
|
async function networkFirst(request) {
|
||
|
|
const cache = await caches.open(RUNTIME_CACHE)
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 尝试从网络获取
|
||
|
|
const response = await fetch(request)
|
||
|
|
|
||
|
|
// 如果响应成功,缓存它
|
||
|
|
if (response.ok) {
|
||
|
|
cache.put(request, response.clone())
|
||
|
|
}
|
||
|
|
|
||
|
|
return response
|
||
|
|
} catch (error) {
|
||
|
|
console.log('[SW] Network request failed, trying cache:', error)
|
||
|
|
|
||
|
|
// 网络失败,尝试从缓存获取
|
||
|
|
const cachedResponse = await cache.match(request)
|
||
|
|
|
||
|
|
if (cachedResponse) {
|
||
|
|
return cachedResponse
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果缓存也没有,返回离线页面
|
||
|
|
return new Response('离线状态,请检查网络连接', {
|
||
|
|
status: 503,
|
||
|
|
statusText: 'Service Unavailable'
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 缓存优先策略
|
||
|
|
async function cacheFirst(request) {
|
||
|
|
const cache = await caches.open(RUNTIME_CACHE)
|
||
|
|
const cachedResponse = await cache.match(request)
|
||
|
|
|
||
|
|
if (cachedResponse) {
|
||
|
|
return cachedResponse
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 缓存中没有,从网络获取
|
||
|
|
const response = await fetch(request)
|
||
|
|
|
||
|
|
// 如果响应成功,缓存它
|
||
|
|
if (response.ok) {
|
||
|
|
cache.put(request, response.clone())
|
||
|
|
}
|
||
|
|
|
||
|
|
return response
|
||
|
|
} catch (error) {
|
||
|
|
console.log('[SW] Network request failed:', error)
|
||
|
|
|
||
|
|
// 返回错误响应
|
||
|
|
return new Response('网络请求失败', {
|
||
|
|
status: 500,
|
||
|
|
statusText: 'Internal Server Error'
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 缓存优先,网络作为后备策略
|
||
|
|
async function staleWhileRevalidate(request) {
|
||
|
|
const cache = await caches.open(RUNTIME_CACHE)
|
||
|
|
const cachedResponse = await cache.match(request)
|
||
|
|
|
||
|
|
// 在后台发起网络请求
|
||
|
|
const fetchPromise = fetch(request).then((response) => {
|
||
|
|
// 如果响应成功,更新缓存
|
||
|
|
if (response.ok) {
|
||
|
|
cache.put(request, response.clone())
|
||
|
|
}
|
||
|
|
return response
|
||
|
|
}).catch((error) => {
|
||
|
|
console.log('[SW] Background fetch failed:', error)
|
||
|
|
// 返回错误响应,但不影响缓存的响应
|
||
|
|
return new Response('网络请求失败', {
|
||
|
|
status: 500,
|
||
|
|
statusText: 'Internal Server Error'
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
// 如果有缓存,立即返回缓存
|
||
|
|
if (cachedResponse) {
|
||
|
|
return cachedResponse
|
||
|
|
}
|
||
|
|
|
||
|
|
// 没有缓存,等待网络请求
|
||
|
|
return fetchPromise
|
||
|
|
}
|
||
|
|
|
||
|
|
// 后台同步事件
|
||
|
|
self.addEventListener('sync', (event) => {
|
||
|
|
console.log('[SW] Background sync event:', event.tag)
|
||
|
|
|
||
|
|
if (event.tag === 'background-sync') {
|
||
|
|
event.waitUntil(doBackgroundSync())
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// 执行后台同步
|
||
|
|
async function doBackgroundSync() {
|
||
|
|
try {
|
||
|
|
// 这里可以执行需要在网络恢复时同步的任务
|
||
|
|
console.log('[SW] Performing background sync')
|
||
|
|
|
||
|
|
// 例如:同步离线时的数据
|
||
|
|
// await syncOfflineData()
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('[SW] Background sync failed:', error)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 推送通知事件
|
||
|
|
self.addEventListener('push', (event) => {
|
||
|
|
console.log('[SW] Push event received')
|
||
|
|
|
||
|
|
if (!event.data) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const options = event.data.json()
|
||
|
|
|
||
|
|
event.waitUntil(
|
||
|
|
self.registration.showNotification(options.title || '新消息', {
|
||
|
|
body: options.body || '您有新消息',
|
||
|
|
icon: options.icon || '/favicon.ico',
|
||
|
|
badge: options.badge || '/favicon.ico',
|
||
|
|
data: options.data || {},
|
||
|
|
actions: options.actions || []
|
||
|
|
})
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
// 通知点击事件
|
||
|
|
self.addEventListener('notificationclick', (event) => {
|
||
|
|
console.log('[SW] Notification click event')
|
||
|
|
|
||
|
|
event.notification.close()
|
||
|
|
|
||
|
|
// 处理通知点击
|
||
|
|
if (event.action) {
|
||
|
|
// 处理特定的操作按钮点击
|
||
|
|
handleNotificationAction(event.action, event.notification.data)
|
||
|
|
} else {
|
||
|
|
// 处理通知主体点击
|
||
|
|
handleNotificationClick(event.notification.data)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// 处理通知点击
|
||
|
|
function handleNotificationClick(data) {
|
||
|
|
// 打开应用或特定页面
|
||
|
|
event.waitUntil(
|
||
|
|
clients.openWindow(data.url || '/')
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 处理通知操作
|
||
|
|
function handleNotificationAction(action, data) {
|
||
|
|
// 根据不同的操作执行不同的逻辑
|
||
|
|
switch (action) {
|
||
|
|
case 'view':
|
||
|
|
clients.openWindow(data.url || '/')
|
||
|
|
break
|
||
|
|
case 'dismiss':
|
||
|
|
// 关闭通知,无需其他操作
|
||
|
|
break
|
||
|
|
default:
|
||
|
|
console.log('[SW] Unknown notification action:', action)
|
||
|
|
}
|
||
|
|
}
|