fix: resolve terminal WebSocket auth failures and improve reconnection
Deploy Dashboard (Dev) / deploy-dev (push) Failing after 1m8s

- Remove insecure query token parameter from backend (cookie-only auth)
- Add detailed JWT error logging (expired, invalid, missing tokens)
- Force token refresh before first WebSocket connection
- Stop reconnection after 3 consecutive auth failures
- Increase ping interval from 12s to 30s (reduce traffic)
- Increase pong timeout from 5s to 10s (handle network latency)
- Show attempt counter in reconnection error messages

Root cause: Frontend was reconnecting with 6-day-old expired token,
causing 403 rejections. Now ensures fresh cookies before connecting.
This commit is contained in:
Gan, Jimmy
2026-03-13 22:26:22 +08:00
parent 00b5d362e7
commit 7b17a53cab
2 changed files with 42 additions and 8 deletions
+28 -5
View File
@@ -196,8 +196,11 @@
let reconnecting = false;
let authRecoveryInFlight = false;
let reconnectAttempts = 0;
let consecutiveAuthFailures = 0;
const MAX_RECONNECT_DELAY = 30000;
const PONG_TIMEOUT = 5000; // 5s to receive pong
const MAX_AUTH_FAILURES = 3;
const PING_INTERVAL = 30000; // 30s between pings
const PONG_TIMEOUT = 10000; // 10s to receive pong
function clearHeartbeat() {
if (heartbeat) {
@@ -263,12 +266,18 @@
}
async function connect(forceRefresh = false) {
if (forceRefresh) {
// Always refresh token before first connection to ensure fresh cookies
const d = tabData.get(tabId);
const isFirstConnect = !d?.hasConnectedOnce;
if (forceRefresh || isFirstConnect) {
const refreshed = await tryRefreshSession();
if (!refreshed) {
handleAuthExpired();
return null;
}
// Mark that we've connected once with fresh token
if (d) d.hasConnectedOnce = true;
}
const ws = new WebSocket(
@@ -278,6 +287,7 @@
ws.onopen = () => {
reconnectAttempts = 0;
consecutiveAuthFailures = 0; // Reset auth failure counter on successful connection
updateTab(tabId, {
connected: true,
error: "",
@@ -295,7 +305,7 @@
}
}, PONG_TIMEOUT);
}
}, 12000);
}, PING_INTERVAL);
const d = tabData.get(tabId);
if (d) {
d.ws = ws;
@@ -335,7 +345,18 @@
const reason = e.reason || `Connection closed (code ${e.code})`;
const d = tabData.get(tabId);
if (d) d.ws = null;
if (e.code === 1008 && reason === "Unauthorized") {
// Handle auth failures (code 1008)
if (e.code === 1008) {
consecutiveAuthFailures++;
// Stop after MAX_AUTH_FAILURES consecutive auth failures
if (consecutiveAuthFailures >= MAX_AUTH_FAILURES) {
handleAuthExpired(`Session expired after ${MAX_AUTH_FAILURES} attempts — please refresh page`);
return;
}
// Try to refresh session
if (!closedManually && !authRecoveryInFlight) {
authRecoveryInFlight = true;
const refreshed = await tryRefreshSession();
@@ -344,7 +365,7 @@
reconnecting = true;
updateTab(tabId, {
connected: false,
error: "Refreshing session...",
error: `Refreshing session (attempt ${consecutiveAuthFailures}/${MAX_AUTH_FAILURES})...`,
statusBanner: tab.persistent ? "Reconnecting to persistent session..." : "",
});
term.write(`\r\n\x1b[90m[Refreshing session...]\x1b[0m`);
@@ -355,6 +376,8 @@
handleAuthExpired("Session expired — please log in again");
return;
}
// Non-auth failures: continue with normal reconnection
if (!closedManually) scheduleReconnect(reason);
else {
clearHeartbeat();