From c5ab0a2cc64def16ca9a9212e6fe720130d7b970 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Jul 2025 14:32:55 +0000 Subject: [PATCH 1/2] Refactor IndexedDB sync mechanism with dedicated store and improved handling Co-authored-by: jxxghp --- src/service-worker.ts | 105 +++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/src/service-worker.ts b/src/service-worker.ts index ab261b06..562351b1 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -80,40 +80,67 @@ async function setStoredUnreadCount(count: number): Promise { // 简单的IndexedDB包装器 async function openDB(): Promise { + // Bump the version to add the new "sync" store while keeping existing data intact return new Promise((resolve, reject) => { - const request = indexedDB.open('mp_badge_db', 1) + const request = indexedDB.open('mp_badge_db', 2) + request.onerror = () => reject(request.error) request.onsuccess = () => resolve(request.result) + request.onupgradeneeded = event => { const db = (event.target as IDBOpenDBRequest).result + + // Badge store (existing) if (!db.objectStoreNames.contains('badge')) { db.createObjectStore('badge') } + + // Dedicated store for offline-sync items + if (!db.objectStoreNames.contains('sync')) { + db.createObjectStore('sync') + } } }) } // 获取IndexedDB中的数据 -async function get(key: string): Promise { +async function get(key: string, storeName: string = 'badge'): Promise { const db = await openDB() return new Promise((resolve, reject) => { - const transaction = db.transaction(['badge'], 'readonly') - const store = transaction.objectStore('badge') + const tx = db.transaction([storeName], 'readonly') + const store = tx.objectStore(storeName) const request = store.get(key) + request.onerror = () => reject(request.error) request.onsuccess = () => resolve(request.result) }) } // 保存数据到IndexedDB -async function set(key: string, value: any): Promise { +async function set(key: string, value: any, storeName: string = 'badge'): Promise { const db = await openDB() return new Promise((resolve, reject) => { - const transaction = db.transaction(['badge'], 'readwrite') - const store = transaction.objectStore('badge') - const request = store.put(value, key) - request.onerror = () => reject(request.error) - request.onsuccess = () => resolve() + const tx = db.transaction([storeName], 'readwrite') + const store = tx.objectStore(storeName) + + store.put(value, key) + + tx.oncomplete = () => resolve() + tx.onerror = () => reject(tx.error) + }) +} + +// 删除IndexedDB中的数据(确保事务完成) +async function del(key: string, storeName: string = 'badge'): Promise { + const db = await openDB() + return new Promise((resolve, reject) => { + const tx = db.transaction([storeName], 'readwrite') + const store = tx.objectStore(storeName) + + store.delete(key) + + tx.oncomplete = () => resolve() + tx.onerror = () => reject(tx.error) }) } @@ -363,7 +390,7 @@ const syncQueue: Array<{ // 添加请求到同步队列 async function addToSyncQueue(request: Request) { - const id = `sync-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + const id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` const url = request.url const method = request.method @@ -384,8 +411,8 @@ async function addToSyncQueue(request: Request) { timestamp: Date.now(), } - // 保存到IndexedDB - await set(`sync-${id}`, syncItem) + // 保存到IndexedDB (使用专用的 "sync" store) + await set(id, syncItem, 'sync') syncQueue.push(syncItem) // 注册后台同步 @@ -397,23 +424,20 @@ async function addToSyncQueue(request: Request) { // 执行同步队列中的请求 async function processSyncQueue() { const db = await openDB() - const transaction = db.transaction(['badge'], 'readonly') - const store = transaction.objectStore('badge') - const request = store.getAllKeys() - - const keys = await new Promise((resolve, reject) => { - request.onsuccess = () => resolve(request.result) - request.onerror = () => reject(request.error) + + // 使用专用的 "sync" store,并开启 readwrite 事务(便于后续删除) + const tx = db.transaction(['sync'], 'readwrite') + const store = tx.objectStore('sync') + + const items: Array = await new Promise((resolve, reject) => { + const req = store.getAll() + req.onsuccess = () => resolve(req.result) + req.onerror = () => reject(req.error) }) - - // 过滤出同步项 - const syncKeys = keys.filter(key => String(key).startsWith('sync-')) - - for (const key of syncKeys) { + + for (const syncItem of items) { + const key = syncItem.id try { - const syncItem = await get(String(key)) - if (!syncItem) continue - // 构建请求 const init: RequestInit = { method: syncItem.method, @@ -421,20 +445,18 @@ async function processSyncQueue() { 'Content-Type': 'application/json', }, } - + if (syncItem.data) { init.body = syncItem.data } - + // 发送请求 const response = await fetch(syncItem.url, init) - + if (response.ok) { - // 成功后删除同步项 - const deleteTransaction = db.transaction(['badge'], 'readwrite') - const deleteStore = deleteTransaction.objectStore('badge') - await deleteStore.delete(key) - + // 成功后删除同步项,并等待事务完成 + await del(key, 'sync') + // 通知客户端同步成功 const clients = await self.clients.matchAll() clients.forEach(client => { @@ -449,13 +471,10 @@ async function processSyncQueue() { } } catch (error) { console.error('Sync failed for item:', key, error) - - // 检查是否超过24小时,如果是则删除 - const syncItem = await get(String(key)) - if (syncItem && Date.now() - syncItem.timestamp > 24 * 60 * 60 * 1000) { - const deleteTransaction = db.transaction(['badge'], 'readwrite') - const deleteStore = deleteTransaction.objectStore('badge') - await deleteStore.delete(key) + + // 如果该同步项已存在超过 24 小时,则将其丢弃 + if (Date.now() - syncItem.timestamp > 24 * 60 * 60 * 1000) { + await del(key, 'sync') } } } From abda382b9626b092c485979d3fa98e29981e721d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Jul 2025 14:35:25 +0000 Subject: [PATCH 2/2] Refactor service worker types and extract type definitions Co-authored-by: jxxghp --- src/service-worker.ts | 24 ++----------------- src/types/service-worker-sync.d.ts | 38 ++++++++++++++++++++++++++++++ src/types/workbox-precaching.d.ts | 23 ++++++++++++++++++ 3 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 src/types/service-worker-sync.d.ts create mode 100644 src/types/workbox-precaching.d.ts diff --git a/src/service-worker.ts b/src/service-worker.ts index 562351b1..4ab29004 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -1,28 +1,8 @@ import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching' // Service Worker 类型声明 -declare let self: ServiceWorkerGlobalScope - -// 扩展ServiceWorkerRegistration类型以支持sync -interface SyncManager { - register(tag: string): Promise -} - -interface ServiceWorkerRegistration { - readonly sync: SyncManager -} - -// 扩展ExtendableEvent以支持sync事件 -interface SyncEvent extends ExtendableEvent { - readonly tag: string - readonly lastChance: boolean -} - -// 扩展ServiceWorkerGlobalScope事件映射 -declare global { - interface ServiceWorkerGlobalScopeEventMap { - 'sync': SyncEvent - } +declare let self: ServiceWorkerGlobalScope & { + __WB_MANIFEST: Array<{ url: string; revision?: string }> } // 缓存版本控制 diff --git a/src/types/service-worker-sync.d.ts b/src/types/service-worker-sync.d.ts new file mode 100644 index 00000000..efa74235 --- /dev/null +++ b/src/types/service-worker-sync.d.ts @@ -0,0 +1,38 @@ +export {} + +declare global { + /** + * Background SyncManager interface as per the Web Background Sync API. + */ + interface SyncManager { + /** + * Registers a one-off sync event with the provided tag. + */ + register(tag: string): Promise + } + + /** + * Extension of ServiceWorkerRegistration to include the SyncManager. + */ + interface ServiceWorkerRegistration { + /** + * The SyncManager for background sync operations. + */ + readonly sync: SyncManager + } + + /** + * The event fired when a background sync is triggered. + */ + interface SyncEvent extends ExtendableEvent { + readonly tag: string + readonly lastChance: boolean + } + + /** + * Extend ServiceWorkerGlobalScope event map to include the sync event type. + */ + interface ServiceWorkerGlobalScopeEventMap { + 'sync': SyncEvent + } +} \ No newline at end of file diff --git a/src/types/workbox-precaching.d.ts b/src/types/workbox-precaching.d.ts new file mode 100644 index 00000000..19268a1e --- /dev/null +++ b/src/types/workbox-precaching.d.ts @@ -0,0 +1,23 @@ +// Type definitions for workbox-precaching runtime use in service worker +// Simplified subset needed by this project + +declare module 'workbox-precaching' { + /** + * A manifest entry generated by Workbox build tools. + */ + export interface ManifestEntry { + url: string + revision?: string + } + + /** + * Removes outdated precaches created by older versions of Workbox. + */ + export function cleanupOutdatedCaches(): void + + /** + * Adds the supplied manifest entries to the precache list and sets up the + * appropriate route so that they are served from the cache. + */ + export function precacheAndRoute(entries: ManifestEntry[]): void +} \ No newline at end of file