// Service Worker - 纯 JavaScript 版本 // 定时检查间隔(毫秒) const CHECK_INTERVAL = 30 * 1000; // 30秒检查一次(更频繁) // 已发送的通知标签集合(避免重复发送) const sentNotifications = new Set(); // 从 IndexedDB 加载提醒事件 async function loadEvents() { try { const db = await openDB(); return new Promise((resolve, reject) => { const tx = db.transaction('events', 'readonly'); const store = tx.objectStore('events'); const request = store.getAll(); request.onerror = () => reject(request.error); request.onsuccess = () => resolve(request.result); }); } catch { return []; } } // 打开 IndexedDB function openDB() { return new Promise((resolve, reject) => { const request = indexedDB.open('qia-notifications', 1); request.onerror = () => reject(request.error); request.onsuccess = () => resolve(request.result); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains('events')) { db.createObjectStore('events', { keyPath: 'id' }); } if (!db.objectStoreNames.contains('sent-notifications')) { db.createObjectStore('sent-notifications', { keyPath: 'tag' }); } }; }); } // 检查是否有提醒时间到达 function checkReminders(events) { const now = new Date(); const notifications = []; for (const event of events) { if (event.type !== 'reminder') continue; if (event.is_completed) continue; if (!event.reminder_times || event.reminder_times.length === 0) continue; for (const reminderTime of event.reminder_times) { const rt = new Date(reminderTime); // 检查是否在最近3分钟内(宽限期,处理后台运行不稳定的情况) const diffMs = now.getTime() - rt.getTime(); const diffMinutes = diffMs / (1000 * 60); // 跳过还未到的提醒 if (diffMinutes < 0) continue; // 如果在3分钟宽限期内,且还没发送过通知 if (diffMinutes < 3) { const tag = `reminder-${event.id}-${reminderTime}`; // 避免重复发送同一通知 if (!sentNotifications.has(tag)) { sentNotifications.add(tag); notifications.push({ id: `${event.id}-${Date.now()}`, title: event.title, body: event.content || getDefaultBodyText(event.date), tag, data: { eventId: event.id, reminderTime }, timestamp: now.getTime(), }); console.log('SW: 找到到期提醒:', event.title, '- 差值(分钟):', diffMinutes.toFixed(2)); } } } } return notifications; } // 获取默认通知正文 function getDefaultBodyText(dateStr) { const date = new Date(dateStr); return date.toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false, }); } // 显示浏览器通知 async function showNotification(notification) { if (Notification.permission !== 'granted') return; try { await self.registration.showNotification(notification.title, { body: notification.body, icon: '/favicon.png', badge: '/favicon.png', tag: notification.tag, data: notification.data, requireInteraction: true, actions: [ { action: 'view', title: '查看' }, { action: 'dismiss', title: '关闭' }, ], }); console.log('SW: 通知已发送:', notification.title); } catch (error) { console.error('SW: Failed to show notification:', error); } } // 主检查函数 async function performCheck() { try { const events = await loadEvents(); const notifications = checkReminders(events); if (notifications.length > 0) { console.log('SW: 检查提醒,已加载事件数:', events.length, '将发送通知数:', notifications.length); for (const notification of notifications) { await showNotification(notification); } } } catch (error) { console.error('SW: Reminder check failed:', error); } } // 定时检查 let checkTimer = null; function startPeriodicCheck() { if (checkTimer) clearInterval(checkTimer); // 立即执行一次 performCheck(); // 然后定期执行 checkTimer = self.setInterval(performCheck, CHECK_INTERVAL); console.log('SW: 定时检查已启动,间隔', CHECK_INTERVAL / 1000, '秒'); } function stopPeriodicCheck() { if (checkTimer) { clearInterval(checkTimer); checkTimer = null; } } // 消息处理 self.addEventListener('message', (event) => { console.log('SW: 收到消息', event.data); if (event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } if (event.data.type === 'START_CHECK') { startPeriodicCheck(); } if (event.data.type === 'STOP_CHECK') { stopPeriodicCheck(); } if (event.data.type === 'UPDATE_EVENTS') { // 事件更新,保存到 IndexedDB 并立即检查 saveEventsToDB(event.data.events).then(() => { console.log('SW: 数据已更新,立即检查...'); performCheck(); }); } if (event.data.type === 'TRIGGER_CHECK') { console.log('SW: 收到 TRIGGER_CHECK,手动执行检查'); performCheck(); } if (event.data.type === 'GET_SENT_TAGS') { if (event.ports && event.ports[0]) { event.ports[0].postMessage({ type: 'SENT_TAGS', tags: Array.from(sentNotifications) }); } } if (event.data.type === 'CLEAR_SENT_NOTIFICATIONS') { sentNotifications.clear(); console.log('SW: 已清除已发送通知记录'); } }); // 保存事件到 IndexedDB async function saveEventsToDB(events) { try { const db = await openDB(); const tx = db.transaction('events', 'readwrite'); const store = tx.objectStore('events'); await store.clear(); for (const event of events) { await store.put(event); } console.log('SW: 已保存', events.length, '个事件到 IndexedDB'); } catch (error) { console.error('SW: Failed to save events:', error); } } // 安装 self.addEventListener('install', (event) => { console.log('SW: Installing...'); event.waitUntil(self.skipWaiting()); }); // 激活 self.addEventListener('activate', (event) => { console.log('SW: Activating...'); event.waitUntil( self.clients.claim().then(() => { console.log('SW: 已获取控制权,启动检查...'); startPeriodicCheck(); }) ); }); // 通知点击处理 self.addEventListener('notificationclick', (event) => { event.notification.close(); if (event.action === 'view') { event.waitUntil( self.clients.matchAll({ type: 'window' }).then((clientList) => { for (const client of clientList) { if (client.url.includes(self.registration.scope) && 'focus' in client) { return client.focus(); } } if (self.clients.openWindow) { return self.clients.openWindow('/'); } }) ); } }); // 定期清理过期的 sentNotifications(保留24小时) setInterval(() => { const now = Date.now(); // 简单清理:只保留最近的通知记录 if (sentNotifications.size > 1000) { const iterator = sentNotifications.keys(); while (sentNotifications.size > 500) { sentNotifications.delete(iterator.next().value); } console.log('SW: 已清理过期的通知记录'); } }, 60 * 60 * 1000); // 每小时清理一次 // 启动定时检查 startPeriodicCheck();