diff --git a/changelog.md b/changelog.md index d81d13d..1861986 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ # Changelog +## 2026-03-03 - 8.4.2 - fix(ts_web_inject) +improve ReloadChecker resilience and TypedSocket handling + +- Added retry counters and limits for service worker subscription (MAX_SW_RETRIES) and traffic logging setup (MAX_TRAFFIC_LOGGING_RETRIES) to avoid infinite retry loops +- Made TypedSocket connection non-blocking by connecting in the background (createClient().then(...).catch(...)) so HTTP polling isn't blocked +- Introduced typedsocketConnected flag to track WS state and avoid double-connects; early-return if typedsocket exists +- Adjusted connection lifecycle handling: only trigger reload check when reconnect completes; set backendConnectionLost/state appropriately +- Adaptive polling interval: poll every 5s when WebSocket is not connected, otherwise 120s +- Added safer failure handling for TypedSocket connection with warning logs on errors +- Simplified/changed several logger levels/messages (info -> ok/warn) and removed some noisy info logs + ## 2026-03-03 - 8.4.1 - fix(statuspill) wait for document.body before appending status pill when script loads before is parsed; defer via DOMContentLoaded or requestAnimationFrame diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 1d67c9e..4f77c48 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: '8.4.1', + version: '8.4.2', 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_inject/index.ts b/ts_web_inject/index.ts index dba5151..d195b56 100644 --- a/ts_web_inject/index.ts +++ b/ts_web_inject/index.ts @@ -1,7 +1,7 @@ import * as plugins from './typedserver_web.plugins.js'; import * as interfaces from '../dist_ts_interfaces/index.js'; import { logger } from './typedserver_web.logger.js'; -logger.log('info', `TypedServer-Devtools initialized!`); +// TypedServer-Devtools loaded import { TypedserverStatusPill } from './typedserver_web.statuspill.js'; @@ -51,6 +51,9 @@ export class ReloadChecker { window.location.reload(); } + private swRetryCount = 0; + private static readonly MAX_SW_RETRIES = 5; + /** * Subscribe to service worker status updates */ @@ -67,13 +70,12 @@ export class ReloadChecker { timestamp: status.timestamp, }); }); - logger.log('info', 'Subscribed to service worker status updates'); + logger.log('ok', 'Subscribed to service worker status updates'); // Get initial SW status this.fetchServiceWorkerStatus(); - } else { - logger.log('note', 'Service worker client not available yet, will retry...'); - // Retry after a delay + } else if (this.swRetryCount < ReloadChecker.MAX_SW_RETRIES) { + this.swRetryCount++; setTimeout(() => this.subscribeToServiceWorker(), 2000); } } @@ -109,7 +111,6 @@ export class ReloadChecker { * starts the reload checker */ public async performHttpRequest() { - logger.log('info', 'performing http check...'); (await this.store.get(this.storeKey)) ? null : await this.store.set(this.storeKey, globalThis.typedserver.lastReload); @@ -201,22 +202,29 @@ export class ReloadChecker { } } - public async connectTypedsocket() { - if (!this.typedsocket) { - this.typedrouter.addTypedHandler( - new plugins.typedrequest.TypedHandler('pushLatestServerChangeTime', async (dataArg) => { - this.checkReload(dataArg.time); - return {}; - }) - ); - this.typedsocket = await plugins.typedsocket.TypedSocket.createClient( - this.typedrouter, - plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl() - ); + private typedsocketConnected = false; + + public connectTypedsocket() { + if (this.typedsocket) return; + + this.typedrouter.addTypedHandler( + new plugins.typedrequest.TypedHandler('pushLatestServerChangeTime', async (dataArg) => { + this.checkReload(dataArg.time); + return {}; + }) + ); + + // Connect in the background — never block the HTTP polling loop + plugins.typedsocket.TypedSocket.createClient( + this.typedrouter, + plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl() + ).then(async (socket) => { + this.typedsocket = socket; + this.typedsocketConnected = true; await this.typedsocket.setTag('typedserver_frontend', {}); this.typedsocket.statusSubject.subscribe(async (statusArg) => { - console.log(`typedsocket status: ${statusArg}`); if (statusArg === 'disconnected' || statusArg === 'reconnecting') { + this.typedsocketConnected = false; this.backendConnectionLost = true; this.statusPill.updateStatus({ source: 'backend', @@ -225,35 +233,39 @@ export class ReloadChecker { persist: true, timestamp: Date.now(), }); - } else if (statusArg === 'connected' && this.backendConnectionLost) { - this.backendConnectionLost = false; - this.statusPill.updateStatus({ - source: 'backend', - type: 'connected', - message: 'TypedSocket connected', - persist: false, - timestamp: Date.now(), - }); - // lets check if a reload is necessary - const getLatestServerChangeTime = - this.typedsocket.createTypedRequest( - 'getLatestServerChangeTime' - ); - const response = await getLatestServerChangeTime.fire({}); - this.checkReload(response.time); + } else if (statusArg === 'connected') { + this.typedsocketConnected = true; + if (this.backendConnectionLost) { + this.backendConnectionLost = false; + this.statusPill.updateStatus({ + source: 'backend', + type: 'connected', + message: 'TypedSocket connected', + persist: false, + timestamp: Date.now(), + }); + // lets check if a reload is necessary + const getLatestServerChangeTime = + this.typedsocket.createTypedRequest( + 'getLatestServerChangeTime' + ); + const response = await getLatestServerChangeTime.fire({}); + this.checkReload(response.time); + } } }); - logger.log('success', `ReloadChecker connected through typedsocket!`); + logger.log('ok', `ReloadChecker connected via TypedSocket`); // Enable traffic logging for sw-dash this.enableTrafficLogging(); - } + }).catch((err) => { + logger.log('warn', `TypedSocket connection failed: ${err}`); + }); } public started = false; public async start() { this.started = true; - logger.log('info', `starting ReloadChecker...`); // Subscribe to service worker status updates this.subscribeToServiceWorker(); @@ -261,11 +273,12 @@ export class ReloadChecker { while (this.started) { const response = await this.performHttpRequest(); if (response?.status === 200) { - logger.log('info', `ReloadChecker reached backend!`); await this.checkReload(parseInt(await response.text())); - await this.connectTypedsocket(); + this.connectTypedsocket(); } - await plugins.smartdelay.delayFor(120000); + // Poll more frequently when WebSocket isn't connected (fallback detection) + const pollInterval = this.typedsocketConnected ? 120000 : 5000; + await plugins.smartdelay.delayFor(pollInterval); } } @@ -282,20 +295,24 @@ export class ReloadChecker { } } + private trafficLoggingRetryCount = 0; + private static readonly MAX_TRAFFIC_LOGGING_RETRIES = 5; + /** * Enable TypedRequest traffic logging to the service worker * Sets up global hooks on TypedRouter to capture all request/response traffic */ public enableTrafficLogging(): void { if (this.trafficLoggingEnabled) { - logger.log('note', 'Traffic logging already enabled'); return; } // Check if service worker client is available if (!globalThis.globalSw?.actionManager) { - logger.log('note', 'Service worker client not available, will retry traffic logging setup...'); - setTimeout(() => this.enableTrafficLogging(), 2000); + if (this.trafficLoggingRetryCount < ReloadChecker.MAX_TRAFFIC_LOGGING_RETRIES) { + this.trafficLoggingRetryCount++; + setTimeout(() => this.enableTrafficLogging(), 2000); + } return; } @@ -320,7 +337,7 @@ export class ReloadChecker { }); this.trafficLoggingEnabled = true; - logger.log('success', 'TypedRequest traffic logging enabled'); + logger.log('ok', 'Traffic logging enabled'); } }