diff --git a/changelog.md b/changelog.md index 47c0610..dd01df1 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2025-12-04 - 6.2.0 - feat(web_serviceworker) +Add service-worker dashboard and request deduplication; improve caching, metrics and error handling + +- Add DashboardGenerator to serve an interactive terminal-style dashboard at /sw-dash and a metrics JSON endpoint at /sw-dash/metrics +- Introduce request deduplication in CacheManager to coalesce concurrent network fetches and avoid duplicate requests +- Add periodic cleanup for in-flight request tracking to prevent unbounded memory growth +- Improve caching flow: preserve response headers (excluding cache-control headers), ensure CORS headers and Cross-Origin-Resource-Policy, and store response bodies as blobs to avoid locked stream issues +- Provide clearer 500 error HTML responses for failed fetches to aid debugging +- Integrate metrics and event emissions for network and cache operations (record request success/failure, cache hits/misses, and emit corresponding events) + ## 2025-12-04 - 6.1.0 - feat(web_serviceworker) Enhance service worker subsystem: add metrics, event bus, error handling, config and caching/update improvements; make client connection & polling robust diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index cb18c85..afffeb9 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: '6.1.0', + version: '6.2.0', 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 df8c40e..6e9f6f6 100644 --- a/ts_web_serviceworker/classes.cachemanager.ts +++ b/ts_web_serviceworker/classes.cachemanager.ts @@ -5,6 +5,7 @@ import { ServiceWorker } from './classes.serviceworker.js'; import { getMetricsCollector } from './classes.metrics.js'; import { getEventBus, ServiceWorkerEvent } from './classes.eventbus.js'; import { getErrorHandler, ServiceWorkerErrorType } from './classes.errorhandler.js'; +import { getDashboardGenerator } from './classes.dashboard.js'; export class CacheManager { public losslessServiceWorkerRef: ServiceWorker; @@ -203,6 +204,18 @@ export class CacheManager { const originalRequest: Request = fetchEventArg.request; const parsedUrl = new URL(originalRequest.url); + // Handle dashboard routes - serve directly from service worker + if (parsedUrl.pathname === '/sw-dash' || parsedUrl.pathname === '/sw-dash/') { + const dashboard = getDashboardGenerator(); + fetchEventArg.respondWith(Promise.resolve(dashboard.serveDashboard())); + return; + } + if (parsedUrl.pathname === '/sw-dash/metrics') { + const dashboard = getDashboardGenerator(); + fetchEventArg.respondWith(Promise.resolve(dashboard.serveMetrics())); + return; + } + // Block requests that we don't want the service worker to handle. if ( parsedUrl.hostname.includes('paddle.com') || diff --git a/ts_web_serviceworker/classes.dashboard.ts b/ts_web_serviceworker/classes.dashboard.ts new file mode 100644 index 0000000..2e56673 --- /dev/null +++ b/ts_web_serviceworker/classes.dashboard.ts @@ -0,0 +1,565 @@ +import { getMetricsCollector, type IServiceWorkerMetrics } from './classes.metrics.js'; + +/** + * Dashboard generator that creates a terminal-like metrics display + * served directly from the service worker + */ +export class DashboardGenerator { + /** + * Serves the dashboard HTML page + */ + public serveDashboard(): Response { + return new Response(this.generateDashboardHtml(), { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }); + } + + /** + * Serves the metrics JSON endpoint + */ + public serveMetrics(): Response { + return new Response(this.generateMetricsJson(), { + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + }, + }); + } + + /** + * Generates JSON metrics response + */ + public generateMetricsJson(): string { + const metrics = getMetricsCollector(); + return JSON.stringify({ + ...metrics.getMetrics(), + cacheHitRate: metrics.getCacheHitRate(), + networkSuccessRate: metrics.getNetworkSuccessRate(), + summary: metrics.getSummary(), + }); + } + + /** + * Generates the complete HTML dashboard page with terminal-like styling + */ + public generateDashboardHtml(): string { + const metrics = getMetricsCollector(); + const data = metrics.getMetrics(); + const hitRate = metrics.getCacheHitRate(); + const successRate = metrics.getNetworkSuccessRate(); + + return ` + +
+ + +