fix(ts_web_inject): improve ReloadChecker resilience and TypedSocket handling
This commit is contained in:
11
changelog.md
11
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 <body> is parsed; defer via DOMContentLoaded or requestAnimationFrame
|
||||
|
||||
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
@@ -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<interfaces.IReq_PushLatestServerChangeTime>(
|
||||
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<interfaces.IReq_PushLatestServerChangeTime>(
|
||||
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<interfaces.IReq_GetLatestServerChangeTime>(
|
||||
'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<interfaces.IReq_GetLatestServerChangeTime>(
|
||||
'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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user