From c5c40e78f9fad41b62ca6ea33be039d6a2ee03aa Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 4 Dec 2025 19:25:55 +0000 Subject: [PATCH] feat(serviceworker): Add TypedRequest traffic monitoring and SW dashboard Requests panel --- changelog.md | 14 + ts/00_commitinfo_data.ts | 2 +- ts/controllers/controller.builtin.ts | 59 ++ ts_interfaces/serviceworker.ts | 129 +++- ts_swdash/sw-dash-app.ts | 25 +- ts_swdash/sw-dash-requests.ts | 636 ++++++++++++++++++ ts_web_inject/index.ts | 52 ++ ts_web_serviceworker/classes.backend.ts | 54 ++ ts_web_serviceworker/classes.cachemanager.ts | 23 + ts_web_serviceworker/classes.dashboard.ts | 91 ++- .../classes.requestlogstore.ts | 190 ++++++ .../classes.actionmanager.ts | 84 +++ 12 files changed, 1337 insertions(+), 22 deletions(-) create mode 100644 ts_swdash/sw-dash-requests.ts create mode 100644 ts_web_serviceworker/classes.requestlogstore.ts diff --git a/changelog.md b/changelog.md index 703a4ef..52e8b99 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,19 @@ # Changelog +## 2025-12-04 - 7.8.0 - feat(serviceworker) +Add TypedRequest traffic monitoring and SW dashboard 'Requests' panel + +- Add TypedRequest traffic monitoring interfaces and shared SW dashboard HTML (SW_DASH_HTML) to ts_interfaces/serviceworker.ts. +- Introduce RequestLogStore (ts_web_serviceworker/classes.requestlogstore.ts) to collect, persist in-memory, and compute stats for TypedRequest traffic (logs, counts, methods, averages). +- Add service worker backend handlers to receive and broadcast TypedRequest logs and to expose endpoints: serviceworker_typedRequestLog, serviceworker_getTypedRequestLogs, serviceworker_getTypedRequestStats, serviceworker_clearTypedRequestLogs. +- Expose HTTP routes and fallback behaviors in the server built-in controller to serve the SW dashboard (GET /sw-dash and /sw-dash/bundle.js) and to return sensible 503 placeholders when the SW is not active. +- Extend the service worker CacheManager and DashboardGenerator to serve TypedRequest-related endpoints (/sw-dash/requests, /sw-dash/requests/stats, /sw-dash/requests/methods) and to integrate RequestLogStore data into the dashboard APIs. +- Add a Lit-based dashboard component sw-dash-requests (ts_swdash/sw-dash-requests.ts) and integrate it into the main sw-dash-app UI to display live TypedRequest traffic with filtering, payload toggles, pagination and clear logs action. +- Enable client-side traffic logging from the injected reload checker (ts_web_inject/index.ts) by setting global TypedRouter hooks that send log entries to the service worker, with safeguards to avoid logging the logging requests themselves. +- Add action manager utilities (ts_web_serviceworker_client/classes.actionmanager.ts) to log TypedRequest entries to the SW, query logs and stats, clear logs, and subscribe to real-time TypedRequest broadcasts. +- Refactor Dashboard HTML generation to use the shared SW_DASH_HTML constant so server and service worker serve the same UI shell. +- Integrate broadcasting of TypedRequest log events from service worker backend to connected clients so the SW dashboard updates in real time. + ## 2025-12-04 - 7.7.1 - fix(web_serviceworker) Standardize DeesComms message format in service worker backend diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 2e13ef9..546aa1d 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.7.1', + version: '7.8.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/controllers/controller.builtin.ts b/ts/controllers/controller.builtin.ts index a5d89dd..aeb22a2 100644 --- a/ts/controllers/controller.builtin.ts +++ b/ts/controllers/controller.builtin.ts @@ -124,6 +124,65 @@ export class BuiltInRoutesController { }); } + @plugins.smartserve.Get('/sw-dash') + async getSwDash(ctx: plugins.smartserve.IRequestContext): Promise { + // Import shared HTML from interfaces + const { SW_DASH_HTML } = await import('../../dist_ts_interfaces/serviceworker.js'); + return new Response(SW_DASH_HTML, { + status: 200, + headers: { 'Content-Type': 'text/html' }, + }); + } + + // SW-dash data routes - return empty/unavailable when SW isn't active + @plugins.smartserve.Get('/sw-dash/metrics') + async getSwDashMetrics(): Promise { + return new Response(JSON.stringify({ error: 'Service worker not active', data: null }), { + status: 503, + headers: { 'Content-Type': 'application/json' }, + }); + } + + @plugins.smartserve.Get('/sw-dash/resources') + async getSwDashResources(): Promise { + return new Response(JSON.stringify({ error: 'Service worker not active', resources: [], domains: [], contentTypes: [], resourceCount: 0 }), { + status: 503, + headers: { 'Content-Type': 'application/json' }, + }); + } + + @plugins.smartserve.Get('/sw-dash/events') + async getSwDashEvents(): Promise { + return new Response(JSON.stringify({ error: 'Service worker not active', events: [], total: 0 }), { + status: 503, + headers: { 'Content-Type': 'application/json' }, + }); + } + + @plugins.smartserve.Get('/sw-dash/events/count') + async getSwDashEventsCount(): Promise { + return new Response(JSON.stringify({ error: 'Service worker not active', count: 0 }), { + status: 503, + headers: { 'Content-Type': 'application/json' }, + }); + } + + @plugins.smartserve.Get('/sw-dash/cumulative-metrics') + async getSwDashCumulativeMetrics(): Promise { + return new Response(JSON.stringify({ error: 'Service worker not active', data: null }), { + status: 503, + headers: { 'Content-Type': 'application/json' }, + }); + } + + @plugins.smartserve.Get('/sw-dash/speedtest') + async getSwDashSpeedtest(): Promise { + return new Response(JSON.stringify({ error: 'Service worker not active - speedtest unavailable' }), { + status: 503, + headers: { 'Content-Type': 'application/json' }, + }); + } + @plugins.smartserve.Get('/sw-dash/bundle.js') async getSwDashBundle(ctx: plugins.smartserve.IRequestContext): Promise { try { diff --git a/ts_interfaces/serviceworker.ts b/ts_interfaces/serviceworker.ts index 19f6a8a..26e608c 100644 --- a/ts_interfaces/serviceworker.ts +++ b/ts_interfaces/serviceworker.ts @@ -509,4 +509,131 @@ export interface IMessage_Serviceworker_ResourceCached cached: boolean; }; response: {}; -} \ No newline at end of file +} + +// ============================= +// TypedRequest Traffic Monitoring +// ============================= + +/** + * Log entry for TypedRequest traffic monitoring + */ +export interface ITypedRequestLogEntry { + correlationId: string; + method: string; + direction: 'outgoing' | 'incoming'; + phase: 'request' | 'response'; + timestamp: number; + durationMs?: number; + payload: any; + error?: string; +} + +/** + * Statistics for TypedRequest traffic + */ +export interface ITypedRequestStats { + totalRequests: number; + totalResponses: number; + methodCounts: Record; + errorCount: number; + avgDurationMs: number; +} + +/** + * Message for logging TypedRequest traffic from client to SW + */ +export interface IMessage_Serviceworker_TypedRequestLog + extends plugins.typedrequestInterfaces.implementsTR< + plugins.typedrequestInterfaces.ITypedRequest, + IMessage_Serviceworker_TypedRequestLog + > { + method: 'serviceworker_typedRequestLog'; + request: ITypedRequestLogEntry; + response: {}; +} + +/** + * Push notification when a TypedRequest is logged + */ +export interface IMessage_Serviceworker_TypedRequestLogged + extends plugins.typedrequestInterfaces.implementsTR< + plugins.typedrequestInterfaces.ITypedRequest, + IMessage_Serviceworker_TypedRequestLogged + > { + method: 'serviceworker_typedRequestLogged'; + request: ITypedRequestLogEntry; + response: {}; +} + +/** + * Request to get TypedRequest logs + */ +export interface IRequest_Serviceworker_GetTypedRequestLogs + extends plugins.typedrequestInterfaces.implementsTR< + plugins.typedrequestInterfaces.ITypedRequest, + IRequest_Serviceworker_GetTypedRequestLogs + > { + method: 'serviceworker_getTypedRequestLogs'; + request: { + limit?: number; + method?: string; + since?: number; + }; + response: { + logs: ITypedRequestLogEntry[]; + totalCount: number; + }; +} + +/** + * Request to get TypedRequest traffic statistics + */ +export interface IRequest_Serviceworker_GetTypedRequestStats + extends plugins.typedrequestInterfaces.implementsTR< + plugins.typedrequestInterfaces.ITypedRequest, + IRequest_Serviceworker_GetTypedRequestStats + > { + method: 'serviceworker_getTypedRequestStats'; + request: {}; + response: ITypedRequestStats; +} + +/** + * Request to clear TypedRequest logs + */ +export interface IRequest_Serviceworker_ClearTypedRequestLogs + extends plugins.typedrequestInterfaces.implementsTR< + plugins.typedrequestInterfaces.ITypedRequest, + IRequest_Serviceworker_ClearTypedRequestLogs + > { + method: 'serviceworker_clearTypedRequestLogs'; + request: {}; + response: { + success: boolean; + }; +} + +// ============================= +// Shared Constants +// ============================= + +/** + * HTML shell for the SW Dashboard - shared between server and service worker + */ +export const SW_DASH_HTML = ` + + + + + SW Dashboard + + + + + + +`; \ No newline at end of file diff --git a/ts_swdash/sw-dash-app.ts b/ts_swdash/sw-dash-app.ts index 509825a..c82a2de 100644 --- a/ts_swdash/sw-dash-app.ts +++ b/ts_swdash/sw-dash-app.ts @@ -13,9 +13,10 @@ import './sw-dash-urls.js'; import './sw-dash-domains.js'; import './sw-dash-types.js'; import './sw-dash-events.js'; +import './sw-dash-requests.js'; import './sw-dash-table.js'; -type ViewType = 'overview' | 'urls' | 'domains' | 'types' | 'events'; +type ViewType = 'overview' | 'urls' | 'domains' | 'types' | 'events' | 'requests'; interface IResourceData { resources: ICachedResource[]; @@ -297,6 +298,20 @@ export class SwDashApp extends LitElement { return {}; } ); + + // Handle TypedRequest logged push updates - dispatch to requests component + this.comms.createTypedHandler( + 'serviceworker_typedRequestLogged', + async (entry) => { + // Dispatch custom event for sw-dash-requests component + this.dispatchEvent(new CustomEvent('typedrequest-logged', { + detail: entry, + bubbles: true, + composed: true, + })); + return {}; + } + ); } /** @@ -389,6 +404,10 @@ export class SwDashApp extends LitElement { class="nav-tab ${this.currentView === 'events' ? 'active' : ''}" @click="${() => this.setView('events')}" >Events +
@@ -414,6 +433,10 @@ export class SwDashApp extends LitElement {
+ +
+ +