feat(web_serviceworker): Add advanced service worker subsystems: cache deduplication, metrics, update & network managers, event bus and dashboard

This commit is contained in:
2025-12-04 11:25:56 +00:00
parent 30126f716e
commit c263b0608c
3 changed files with 64 additions and 8 deletions

View File

@@ -1,5 +1,19 @@
# Changelog
## 2025-12-04 - 6.3.0 - feat(web_serviceworker)
Add advanced service worker subsystems: cache deduplication, metrics, update & network managers, event bus and dashboard
- CacheManager: request deduplication for concurrent fetches, safer caching (preserve CORS headers), periodic in-flight cleanup and full cache cleaning API
- Fetch handling: improved handling for same-origin vs cross-origin requests, more robust 500 debug responses when upstream fetch fails
- UpdateManager: rate-limited update checks, offline grace period, debounced update and cache revalidation tasks, forceUpdate logic and persisted version/cache timestamps
- NetworkManager: online/offline detection, retry/backoff, request timeouts and more resilient makeRequest implementation
- EventBus: singleton pub/sub with history, once/onMany/onAll helpers and convenience emitters for cache/network/update events
- MetricsCollector: comprehensive metrics for cache, network, updates and connections with helper methods and JSON/HTML dashboard endpoints (/sw-dash, /sw-dash/metrics)
- ErrorHandler & ServiceWorkerError: structured error types, severity, context, history and helper APIs for consistent error reporting
- ServiceWorker & backend: improved install/activate flows, clients.claim(), cache cleaning on activation, backend APIs to purge cache and trigger reloads/notifications
- TypedServer / servertools: addRoute path pattern parsing (named params & wildcards), safer HTML injection for reload script, TypedRequest controller and service worker route helpers
- Various safety and compatibility improvements (response cloning, header normalization, cache-control decisions, and fallback behaviors)
## 2025-12-04 - 6.2.0 - feat(web_serviceworker)
Add service-worker dashboard and request deduplication; improve caching, metrics and error handling

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@api.global/typedserver',
version: '6.2.0',
version: '6.3.0',
description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.'
}

View File

@@ -1,6 +1,7 @@
import * as plugins from './plugins.js';
import * as interfaces from '../dist_ts_interfaces/index.js';
import { logger } from './logging.js';
import { getMetricsCollector } from './classes.metrics.js';
// Add type definitions for ServiceWorker APIs
declare global {
@@ -41,11 +42,15 @@ declare global {
*/
export class ServiceworkerBackend {
public deesComms = new plugins.deesComms.DeesComms();
private swSelf: ServiceWorkerGlobalScope;
private clientUpdateInterval: ReturnType<typeof setInterval> | null = null;
constructor(optionsArg: {
self: any;
purgeCache: (reqArg: interfaces.serviceworker.IRequest_PurgeServiceWorkerCache['request']) => Promise<interfaces.serviceworker.IRequest_PurgeServiceWorkerCache['response']>;
}) {
this.swSelf = optionsArg.self as unknown as ServiceWorkerGlobalScope;
const metrics = getMetricsCollector();
// lets handle wakestuff
optionsArg.self.addEventListener('message', (event) => {
@@ -53,16 +58,51 @@ export class ServiceworkerBackend {
console.log('sw-backend: got wake up call');
}
});
this.deesComms.createTypedHandler<interfaces.serviceworker.IRequest_Client_Serviceworker_ConnectionPolling>('broadcastConnectionPolling', async reqArg => {
// Record connection attempt
metrics.recordConnectionAttempt();
metrics.recordConnectionSuccess();
// Update connected clients count
await this.updateConnectedClientsCount();
return {
serviceworkerId: '123'
};
})
});
this.deesComms.createTypedHandler<interfaces.serviceworker.IRequest_PurgeServiceWorkerCache>('purgeServiceWorkerCache', async reqArg => {
console.log(`Executing purge cache in serviceworker backend.`)
return await optionsArg.purgeCache?.(reqArg);
});
// Periodically update connected clients count
this.startClientCountUpdates();
}
/**
* Start periodic updates of connected client count
*/
private startClientCountUpdates(): void {
// Update immediately
this.updateConnectedClientsCount();
// Then update every 5 seconds
this.clientUpdateInterval = setInterval(() => {
this.updateConnectedClientsCount();
}, 5000);
}
/**
* Update the connected clients count using the Clients API
*/
private async updateConnectedClientsCount(): Promise<void> {
try {
const clients = await this.swSelf.clients.matchAll({ type: 'window' });
const metrics = getMetricsCollector();
metrics.setConnectedClients(clients.length);
} catch (error) {
logger.log('warn', `Failed to update connected clients count: ${error}`);
}
}
/**
@@ -71,7 +111,7 @@ export class ServiceworkerBackend {
public async triggerReloadAll() {
try {
logger.log('info', 'Triggering reload for all clients due to new version');
// Send update message via DeesComms
// This will be picked up by clients that have registered a handler for 'serviceworker_newVersion'
await this.deesComms.postMessage({
@@ -79,13 +119,15 @@ export class ServiceworkerBackend {
request: {},
messageId: `sw_update_${Date.now()}`
});
// As a fallback, also use the clients API to reload clients that might not catch the broadcast
// We need to type-cast self since TypeScript doesn't recognize ServiceWorker API
const swSelf = self as unknown as ServiceWorkerGlobalScope;
const clients = await swSelf.clients.matchAll({ type: 'window' });
const clients = await this.swSelf.clients.matchAll({ type: 'window' });
logger.log('info', `Found ${clients.length} clients to reload`);
// Update metrics with current client count
const metrics = getMetricsCollector();
metrics.setConnectedClients(clients.length);
for (const client of clients) {
if ('navigate' in client) {
// For modern browsers, navigate to the same URL to trigger reload