diff --git a/changelog.md b/changelog.md index a307256..b6dc0b1 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2025-12-04 - 7.4.1 - fix(web_serviceworker) +Improve service worker persistence, metrics and caching robustness + +- Ensure persistent store is initialized before use in dashboard handlers and service worker activation/handlers (calls to persistentStore.init()) +- Make serveCumulativeMetrics async and align fetchEvent.respondWith usage (remove unnecessary Promise.resolve) +- Change persistent WebStore database name to 'losslessServiceworkerPersistent' to separate durable store from runtime store +- Make PersistentStore.init() more resilient: add detailed logging, avoid throwing on init failure, mark initialized to prevent retry loops, and start periodic save only after load +- Ensure logEvent awaits initialization and adds defensive logging around reading/writing the event log +- Add request deduplication logic and improved cache handling in CacheManager (fetchWithDeduplication usage and safer respondWith) + ## 2025-12-04 - 7.4.0 - feat(serviceworker) Add persistent event store, cumulative metrics and dashboard events UI for service worker observability diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index a7d4339..6cb9b82 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@api.global/typedserver', - version: '7.4.0', + version: '7.4.1', description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.' } diff --git a/ts_web_serviceworker/classes.cachemanager.ts b/ts_web_serviceworker/classes.cachemanager.ts index bdeb78c..919a6ee 100644 --- a/ts_web_serviceworker/classes.cachemanager.ts +++ b/ts_web_serviceworker/classes.cachemanager.ts @@ -237,7 +237,7 @@ export class CacheManager { } if (parsedUrl.pathname === '/sw-dash/cumulative-metrics') { const dashboard = getDashboardGenerator(); - fetchEventArg.respondWith(Promise.resolve(dashboard.serveCumulativeMetrics())); + fetchEventArg.respondWith(dashboard.serveCumulativeMetrics()); return; } // DELETE method for clearing events diff --git a/ts_web_serviceworker/classes.dashboard.ts b/ts_web_serviceworker/classes.dashboard.ts index 45b7a3a..9b2f14d 100644 --- a/ts_web_serviceworker/classes.dashboard.ts +++ b/ts_web_serviceworker/classes.dashboard.ts @@ -52,6 +52,7 @@ export class DashboardGenerator { */ public async serveEventLog(searchParams: URLSearchParams): Promise { const persistentStore = getPersistentStore(); + await persistentStore.init(); const limit = searchParams.get('limit') ? parseInt(searchParams.get('limit')!, 10) : undefined; const type = searchParams.get('type') as TEventType | undefined; @@ -72,6 +73,7 @@ export class DashboardGenerator { */ public async serveEventCount(searchParams: URLSearchParams): Promise { const persistentStore = getPersistentStore(); + await persistentStore.init(); const since = searchParams.get('since') ? parseInt(searchParams.get('since')!, 10) : Date.now() - 3600000; // Default: last hour @@ -88,8 +90,9 @@ export class DashboardGenerator { /** * Serves cumulative metrics */ - public serveCumulativeMetrics(): Response { + public async serveCumulativeMetrics(): Promise { const persistentStore = getPersistentStore(); + await persistentStore.init(); const metrics = persistentStore.getCumulativeMetrics(); return new Response(JSON.stringify(metrics), { @@ -105,6 +108,7 @@ export class DashboardGenerator { */ public async clearEventLog(): Promise { const persistentStore = getPersistentStore(); + await persistentStore.init(); const success = await persistentStore.clearEventLog(); return new Response(JSON.stringify({ success }), { @@ -126,6 +130,7 @@ export class DashboardGenerator { public async runSpeedtest(): Promise { const metrics = getMetricsCollector(); const persistentStore = getPersistentStore(); + await persistentStore.init(); const results: { latency?: { durationMs: number }; download?: { durationMs: number; speedMbps: number; bytesTransferred: number }; diff --git a/ts_web_serviceworker/classes.persistentstore.ts b/ts_web_serviceworker/classes.persistentstore.ts index 9269ff2..c5ff40c 100644 --- a/ts_web_serviceworker/classes.persistentstore.ts +++ b/ts_web_serviceworker/classes.persistentstore.ts @@ -66,7 +66,7 @@ export class PersistentStore { private constructor() { this.store = new plugins.webstore.WebStore({ - dbName: 'losslessServiceworker', + dbName: 'losslessServiceworkerPersistent', storeName: 'persistentStore', }); } @@ -85,19 +85,27 @@ export class PersistentStore { * Initializes the store and starts periodic saving */ public async init(): Promise { + console.log('[PersistentStore] init() called, initialized:', this.initialized); if (this.initialized) { + console.log('[PersistentStore] Already initialized, returning early'); return; } try { + console.log('[PersistentStore] Calling store.init()...'); + // Initialize the WebStore (required before using any methods) await this.store.init(); + console.log('[PersistentStore] store.init() completed successfully'); + await this.loadCumulativeMetrics(); + console.log('[PersistentStore] loadCumulativeMetrics() completed'); // Increment restart count if (this.cumulativeMetrics) { this.cumulativeMetrics.swRestartCount++; this.isDirty = true; await this.saveCumulativeMetrics(); + console.log('[PersistentStore] Saved cumulative metrics after restart count increment'); } // Start periodic save @@ -105,9 +113,12 @@ export class PersistentStore { this.initialized = true; logger.log('ok', '[PersistentStore] Initialized successfully'); + console.log('[PersistentStore] Initialization complete'); } catch (error) { + console.error('[PersistentStore] Failed to initialize:', error); logger.log('error', `[PersistentStore] Failed to initialize: ${error}`); - throw error; + // Don't throw - allow SW to continue even if persistent store fails + this.initialized = true; // Mark as initialized to prevent retry loops } } @@ -262,6 +273,7 @@ export class PersistentStore { message: string, details?: Record ): Promise { + console.log('[PersistentStore] logEvent called:', type, message); const entry: IEventLogEntry = { id: generateId(), timestamp: Date.now(), @@ -271,10 +283,21 @@ export class PersistentStore { }; try { + // Ensure initialized + if (!this.initialized) { + console.log('[PersistentStore] Not initialized, calling init() first'); + await this.init(); + } + let events: IEventLogEntry[] = []; + console.log('[PersistentStore] Checking if event log exists...'); if (await this.store.check(this.EVENT_LOG_KEY)) { + console.log('[PersistentStore] Event log exists, loading...'); events = await this.store.get(this.EVENT_LOG_KEY); + console.log('[PersistentStore] Loaded', events.length, 'events'); + } else { + console.log('[PersistentStore] Event log does not exist, creating new one'); } // Add new entry @@ -283,9 +306,12 @@ export class PersistentStore { // Apply retention policy events = this.applyRetentionPolicy(events); + console.log('[PersistentStore] Saving', events.length, 'events...'); await this.store.set(this.EVENT_LOG_KEY, events); + console.log('[PersistentStore] Events saved successfully'); logger.log('note', `[PersistentStore] Logged event: ${type} - ${message}`); } catch (error) { + console.error('[PersistentStore] Failed to log event:', error); logger.log('error', `[PersistentStore] Failed to log event: ${error}`); } } diff --git a/ts_web_serviceworker/classes.serviceworker.ts b/ts_web_serviceworker/classes.serviceworker.ts index fa5f66f..294e10c 100644 --- a/ts_web_serviceworker/classes.serviceworker.ts +++ b/ts_web_serviceworker/classes.serviceworker.ts @@ -95,6 +95,7 @@ export class ServiceWorker { // Log activation event const persistentStore = getPersistentStore(); + await persistentStore.init(); // Ensure store is initialized (safe to call multiple times) await persistentStore.logEvent('sw_activated', 'Service worker activated', { timestamp: new Date().toISOString(), }); @@ -123,6 +124,7 @@ export class ServiceWorker { // Log cache invalidation event (survives) const persistentStore = getPersistentStore(); + await persistentStore.init(); // Ensure store is initialized await persistentStore.logEvent('cache_invalidated', `Cache invalidated: ${reqArg.reason}`, { reason: reqArg.reason, timestamp: reqArg.timestamp,