net: prioritize close-event reconnect with 2s delay and 3 retry cap
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// Maintainer-controlled web client version.
|
// Maintainer-controlled web client version.
|
||||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||||
window.CHGRID_WEB_VERSION = "2026.02.22 R165";
|
window.CHGRID_WEB_VERSION = "2026.02.22 R166";
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ const MIC_CALIBRATION_ACTIVE_RMS_THRESHOLD = 0.003;
|
|||||||
const MIC_INPUT_GAIN_SCALE_MULTIPLIER = 2;
|
const MIC_INPUT_GAIN_SCALE_MULTIPLIER = 2;
|
||||||
const MIC_INPUT_GAIN_STEP = 0.05;
|
const MIC_INPUT_GAIN_STEP = 0.05;
|
||||||
const HEARTBEAT_INTERVAL_MS = 10_000;
|
const HEARTBEAT_INTERVAL_MS = 10_000;
|
||||||
|
const RECONNECT_DELAY_MS = 2_000;
|
||||||
|
const RECONNECT_MAX_ATTEMPTS = 3;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -512,6 +514,10 @@ function handleSignalingStatus(message: string): void {
|
|||||||
if (message === 'Connected.') {
|
if (message === 'Connected.') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (message === 'Disconnected.' && state.running && !reconnectInFlight) {
|
||||||
|
void reconnectAfterSocketClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
pushChatMessage(message);
|
pushChatMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1109,16 +1115,37 @@ function startHeartbeat(): void {
|
|||||||
|
|
||||||
/** Performs one reconnect attempt when heartbeat timeout indicates stale signaling. */
|
/** Performs one reconnect attempt when heartbeat timeout indicates stale signaling. */
|
||||||
async function reconnectAfterHeartbeatTimeout(): Promise<void> {
|
async function reconnectAfterHeartbeatTimeout(): Promise<void> {
|
||||||
|
await reconnectWithRetry('heartbeat');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs immediate reconnect when websocket closes unexpectedly. */
|
||||||
|
async function reconnectAfterSocketClose(): Promise<void> {
|
||||||
|
await reconnectWithRetry('socketClose');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reconnects after disconnect with delay and bounded retry attempts. */
|
||||||
|
async function reconnectWithRetry(reason: 'heartbeat' | 'socketClose'): Promise<void> {
|
||||||
if (reconnectInFlight || !state.running) return;
|
if (reconnectInFlight || !state.running) return;
|
||||||
reconnectInFlight = true;
|
reconnectInFlight = true;
|
||||||
stopHeartbeat();
|
stopHeartbeat();
|
||||||
pushChatMessage('Connection stale. Reconnecting...');
|
if (reason === 'heartbeat') {
|
||||||
disconnect();
|
pushChatMessage('Connection stale. Reconnecting...');
|
||||||
try {
|
|
||||||
await connect();
|
|
||||||
} finally {
|
|
||||||
reconnectInFlight = false;
|
|
||||||
}
|
}
|
||||||
|
disconnect();
|
||||||
|
for (let attempt = 1; attempt <= RECONNECT_MAX_ATTEMPTS; attempt += 1) {
|
||||||
|
await new Promise((resolve) => window.setTimeout(resolve, RECONNECT_DELAY_MS));
|
||||||
|
await connect();
|
||||||
|
if (state.running) {
|
||||||
|
reconnectInFlight = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (attempt < RECONNECT_MAX_ATTEMPTS) {
|
||||||
|
pushChatMessage(`Reconnect attempt ${attempt} failed. Retrying...`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pushChatMessage('Reconnect failed after 3 attempts. Press Connect to retry.');
|
||||||
|
audio.sfxUiCancel();
|
||||||
|
reconnectInFlight = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Builds dependencies shared by connect/disconnect flow helpers. */
|
/** Builds dependencies shared by connect/disconnect flow helpers. */
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ This is a behavior guide for packet semantics beyond raw schemas.
|
|||||||
|
|
||||||
- Client sends automatic heartbeat `ping` packets every 10 seconds while connected.
|
- Client sends automatic heartbeat `ping` packets every 10 seconds while connected.
|
||||||
- Heartbeat pings use negative `clientSentAt` ids and are internal (not user-visible ping status).
|
- Heartbeat pings use negative `clientSentAt` ids and are internal (not user-visible ping status).
|
||||||
- If a heartbeat `pong` is missed for one interval (10 seconds), client force-disconnects and reconnects.
|
- If websocket close is observed unexpectedly, client starts reconnect flow.
|
||||||
|
- If a heartbeat `pong` is missed for one interval (10 seconds), client also starts reconnect flow.
|
||||||
|
- Reconnect flow waits 2 seconds and retries up to 3 times before stopping.
|
||||||
- After reconnect, if `welcome.serverInfo.instanceId` changed, client announces `Server restarted.`
|
- After reconnect, if `welcome.serverInfo.instanceId` changed, client announces `Server restarted.`
|
||||||
- Client emits `Connected. Version <version>.` after each `welcome`.
|
- Client emits `Connected. Version <version>.` after each `welcome`.
|
||||||
- If `welcome.serverInfo.version` differs from running client version, client auto-reloads.
|
- If `welcome.serverInfo.version` differs from running client version, client auto-reloads.
|
||||||
|
|||||||
@@ -47,8 +47,10 @@ Core incoming message effects:
|
|||||||
|
|
||||||
## Stale Connection Recovery
|
## Stale Connection Recovery
|
||||||
|
|
||||||
- While running, client sends heartbeat `ping` every 10 seconds.
|
- If websocket closes unexpectedly, client starts reconnect flow immediately.
|
||||||
- If one heartbeat `pong` is missed (10-second interval), client auto-reconnects.
|
- While running, client also sends heartbeat `ping` every 10 seconds (fallback for silent half-open cases).
|
||||||
|
- If one heartbeat `pong` is missed (10-second interval), client starts reconnect flow.
|
||||||
|
- Reconnect flow waits 2 seconds and retries up to 3 times.
|
||||||
- If reconnect lands on a different `welcome.serverInfo.instanceId`, client announces server restart.
|
- If reconnect lands on a different `welcome.serverInfo.instanceId`, client announces server restart.
|
||||||
- Connect/reconnect status message is emitted from `welcome` and includes server version.
|
- Connect/reconnect status message is emitted from `welcome` and includes server version.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user