/** * 天工智能体平台 — Service Worker * * - 离线缓存:静态资源 + API 响应 * - 推送通知 * - 安装后激活 */ const CACHE_NAME = 'aiagent-v1.0.0'; const RUNTIME_CACHE = 'aiagent-runtime'; const PRECACHE_URLS = [ '/', '/index.html', '/manifest.json', ]; // ─── Install: 预缓存关键资源 ─────────────────────────────────── self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(PRECACHE_URLS); }).then(() => self.skipWaiting()) ); }); // ─── Activate: 清理旧缓存 ────────────────────────────────────── self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((names) => { return Promise.all( names .filter((n) => n !== CACHE_NAME && n !== RUNTIME_CACHE) .map((n) => caches.delete(n)) ); }).then(() => self.clients.claim()) ); }); // ─── Fetch: 网络优先 + 缓存回退 ──────────────────────────────── self.addEventListener('fetch', (event) => { const { request } = event; if (request.method !== 'GET') return; // API 请求:网络优先 if (request.url.includes('/api/')) { event.respondWith( fetch(request) .then((response) => { const clone = response.clone(); caches.open(RUNTIME_CACHE).then((cache) => { cache.put(request, clone); }); return response; }) .catch(() => caches.match(request)) ); return; } // 静态资源:缓存优先 event.respondWith( caches.match(request).then((cached) => { const fetched = fetch(request).then((response) => { const clone = response.clone(); caches.open(RUNTIME_CACHE).then((cache) => { cache.put(request, clone); }); return response; }); return cached || fetched; }) ); }); // ─── Push: 浏览器推送通知 ────────────────────────────────────── self.addEventListener('push', (event) => { if (!event.data) return; const data = event.data.json().catch(() => ({ title: '天工智能体', body: event.data.text() })); const options = { body: data.body || '', icon: '/icons/icon-192.png', badge: '/icons/icon-192.png', data: data.data || {}, vibrate: [200, 100, 200], requireInteraction: data.requireInteraction || false, }; event.waitUntil( self.registration.showNotification(data.title || '天工智能体', options) ); }); // ─── Notification Click ───────────────────────────────────────── self.addEventListener('notificationclick', (event) => { event.notification.close(); const url = event.notification.data?.url || '/'; event.waitUntil( self.clients.matchAll({ type: 'window' }).then((clients) => { for (const client of clients) { if (client.url.includes(url) && 'focus' in client) { return client.focus(); } } return self.clients.openWindow(url); }) ); });