fix(web_serviceworker): Improve service worker persistence, metrics and caching robustness
This commit is contained in:
10
changelog.md
10
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
|
||||
|
||||
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -52,6 +52,7 @@ export class DashboardGenerator {
|
||||
*/
|
||||
public async serveEventLog(searchParams: URLSearchParams): Promise<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
const metrics = getMetricsCollector();
|
||||
const persistentStore = getPersistentStore();
|
||||
await persistentStore.init();
|
||||
const results: {
|
||||
latency?: { durationMs: number };
|
||||
download?: { durationMs: number; speedMbps: number; bytesTransferred: number };
|
||||
|
||||
@@ -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<void> {
|
||||
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<string, any>
|
||||
): Promise<void> {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user