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');
}
}