Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e41ab63e4 | |||
| 7003d3fbf9 | |||
| e80619bac6 | |||
| e290744451 | |||
| c5c45f668f | |||
| aa748e0d82 |
25
changelog.md
25
changelog.md
@@ -1,5 +1,30 @@
|
||||
# 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
|
||||
|
||||
- Guard against missing document.body to avoid errors when the script runs before the <body> is parsed
|
||||
- Retry showing on DOMContentLoaded if the document is still loading
|
||||
- Fallback to requestAnimationFrame to schedule the show on the next frame if DOM is already parsed
|
||||
|
||||
## 2026-02-24 - 8.4.0 - feat(utilityservers)
|
||||
add injectReload and noCache options and enable dev features by default
|
||||
|
||||
- Adds optional configuration properties 'injectReload' and 'noCache' to the website server options interface.
|
||||
- Dev features (injectReload and noCache) are no longer only enabled when serveDir is set; they now default to true when not explicitly provided.
|
||||
- This changes default runtime behavior: live-reload injection and disabled browser caching may be enabled for servers that previously did not have them — consumers should set options explicitly to preserve previous behavior.
|
||||
|
||||
## 2026-02-24 - 8.3.1 - fix(typedserver)
|
||||
no changes detected — no version bump needed
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@api.global/typedserver",
|
||||
"version": "8.3.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.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@api.global/typedserver',
|
||||
version: '8.3.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.'
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ export interface IUtilityWebsiteServerConstructorOptions {
|
||||
adsTxt?: string[];
|
||||
/** Response compression configuration (default: enabled with brotli + gzip) */
|
||||
compression?: plugins.smartserve.ICompressionConfig | boolean;
|
||||
/** Disable browser caching (default: true when serveDir is set) */
|
||||
noCache?: boolean;
|
||||
/** Inject live-reload devtools script into HTML (default: true when serveDir is set) */
|
||||
injectReload?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,10 +65,10 @@ export class UtilityWebsiteServer {
|
||||
domain: this.options.domain,
|
||||
port,
|
||||
|
||||
// Development features (only when serving from filesystem)
|
||||
injectReload: !!this.options.serveDir,
|
||||
// Development features
|
||||
injectReload: this.options.injectReload ?? true,
|
||||
watch: !!this.options.serveDir,
|
||||
noCache: !!this.options.serveDir,
|
||||
noCache: this.options.noCache ?? true,
|
||||
|
||||
// SPA support (enabled by default for modern web apps)
|
||||
spaFallback: this.options.spaFallback ?? true,
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,6 +311,15 @@ export class TypedserverStatusPill extends LitElement {
|
||||
*/
|
||||
public show(): void {
|
||||
if (!this.appended) {
|
||||
if (!document.body) {
|
||||
// Script loaded before <body> was parsed (async module) — wait for DOM
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => this.show(), { once: true });
|
||||
} else {
|
||||
requestAnimationFrame(() => this.show());
|
||||
}
|
||||
return;
|
||||
}
|
||||
document.body.appendChild(this);
|
||||
this.appended = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user