fix: resolve infinite WebSocket reconnection loop

The ui_server proxies WebSocket connections. It was accepting the client's WebSocket connection (ws.onopen triggered on the client), but if it couldn't connect to the backend (or if the backend disconnected), it would drop the client connection right away (ws.onclose triggered).

The issue was that reconnectAttempts was being reset to 0 inside the ws.onopen handler. Because the connection was briefly succeeding before failing, the reconnectAttempts counter was resetting to 0 on every attempt, completely circumventing the exponential backoff mechanism and causing a tight reconnection loop.

Modified the WebSocket logic across all relevant UI components to delay resetting reconnectAttempts = 0. Instead of resetting immediately upon the TCP socket opening, it now safely waits until a valid JSON payload {"type": "connected"} is successfully received from the backend.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-02-25 10:15:47 -03:00
parent bfc8f4da77
commit 7c1deca8ae
9 changed files with 53 additions and 19 deletions

View file

@ -1101,6 +1101,7 @@
const r = JSON.parse(e.data);
if (r.type === "connected") {
console.log("WebSocket welcome message:", r);
reconnectAttempts = 0;
return;
}
if (r.bot_id) {
@ -1154,7 +1155,6 @@
ws.readyState,
);
updateConnectionStatus("connected");
reconnectAttempts = 0;
hasReceivedInitialMessage = false;
};
ws.onclose = function (e) {

View file

@ -1322,7 +1322,6 @@
"WebSocket connected for attendant:",
currentAttendantId,
);
reconnectAttempts = 0;
showToast(
"Connected to notification service",
"success",
@ -1367,6 +1366,7 @@
switch (msgType) {
case "connected":
console.log("WebSocket connected:", data.message);
reconnectAttempts = 0;
break;
case "new_conversation":
showToast("New conversation in queue", "info");

View file

@ -886,7 +886,6 @@
ws.onopen = function () {
clearTimeout(connectionTimeout);
console.log("WebSocket connected to:", url);
reconnectAttempts = 0;
disconnectNotified = false;
updateConnectionStatus("connected");
};
@ -897,7 +896,10 @@
console.log("Chat WebSocket received:", data);
// Ignore connection confirmation
if (data.type === "connected") return;
if (data.type === "connected") {
reconnectAttempts = 0;
return;
}
// Process system events (theme changes, etc)
if (data.event) {

View file

@ -14,6 +14,7 @@
<!-- Local JS requirements per AGENTS.md / UI.md -->
<script src="/suite/js/vendor/htmx.min.js"></script>
<script src="/suite/js/window-manager.js"></script>
<script src="/suite/js/theme-manager.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
@ -214,9 +215,12 @@
<div id="taskbar-apps">
<!-- Taskbar items populated automatically by window-manager.js -->
</div>
<div class="toolbar-time">
<div id="clock-time">00:00</div>
<div id="clock-date">01/01/2026</div>
<div class="toolbar-time" style="display: flex; align-items: center; gap: 15px;">
<div id="themeSelectorContainer"></div>
<div style="text-align: right;">
<div id="clock-time">00:00</div>
<div id="clock-date">01/01/2026</div>
</div>
</div>
</footer>
</div>
@ -231,6 +235,11 @@
} else {
console.error("WindowManager class not loaded from window-manager.js");
}
// Initialize ThemeManager
if (typeof window.ThemeManager !== 'undefined') {
window.ThemeManager.init();
}
});
// Listen to HTMX afterRequest event
@ -248,6 +257,15 @@
window.wm.open(appId, title, htmlContent);
}
}
// Ensure Theme dropdown is re-injected if wiped
if (window.ThemeManager) {
const container = document.getElementById('themeSelectorContainer');
if (container && !container.hasChildNodes()) {
// Quick and dirty way to re-init
window.ThemeManager.init();
}
}
});
// Simple Clock implementation matching the screenshot bottom right corner

View file

@ -192,7 +192,6 @@
document.body.addEventListener("htmx:wsOpen", () => {
updateConnectionStatus("connected");
reconnectAttempts = 0;
});
document.body.addEventListener("htmx:wsClose", () => {
@ -205,6 +204,10 @@
function handleWebSocketMessage(message) {
const messageType = message.type || message.event;
if (messageType === "connected") {
reconnectAttempts = 0;
}
// Debug logging
console.log("handleWebSocketMessage called with:", { messageType, message });

View file

@ -173,7 +173,8 @@ const ThemeManager = (() => {
}
function updateDropdown() {
// Dropdown removed
const select = document.getElementById("themeDropdown");
if (select) select.value = currentThemeId;
}
function createDropdown() {
@ -214,9 +215,12 @@ const ThemeManager = (() => {
currentThemeId = saved;
loadTheme(saved);
// Dropdown injection removed
// const container = document.getElementById("themeSelectorContainer");
// if (container) container.appendChild(createDropdown());
// Dropdown injection restored for the window manager
const container = document.getElementById("themeSelectorContainer");
if (container) {
container.innerHTML = '';
container.appendChild(createDropdown());
}
console.log("✓ Theme Manager initialized");
}

View file

@ -875,7 +875,6 @@
ws.onopen = function () {
console.log("WebSocket connected");
reconnectAttempts = 0;
disconnectNotified = false;
updateConnectionStatus("connected");
};
@ -886,7 +885,10 @@
console.log("Chat WebSocket received:", data);
// Ignore connection confirmation
if (data.type === "connected") return;
if (data.type === "connected") {
reconnectAttempts = 0;
return;
}
// Process system events (theme changes, etc)
if (data.event) {

View file

@ -177,9 +177,12 @@
<div id="taskbar-apps">
<!-- Taskbar items populated automatically by window-manager.js -->
</div>
<div class="toolbar-time">
<div id="clock-time">00:00</div>
<div id="clock-date">01/01/2026</div>
<div class="toolbar-time" style="display: flex; align-items: center; gap: 15px;">
<div id="themeSelectorContainer"></div>
<div style="text-align: right;">
<div id="clock-time">00:00</div>
<div id="clock-date">01/01/2026</div>
</div>
</div>
</footer>
</div>

View file

@ -875,7 +875,6 @@
ws.onopen = function () {
console.log("WebSocket connected");
reconnectAttempts = 0;
disconnectNotified = false;
updateConnectionStatus("connected");
};
@ -886,7 +885,10 @@
console.log("Chat WebSocket received:", data);
// Ignore connection confirmation
if (data.type === "connected") return;
if (data.type === "connected") {
reconnectAttempts = 0;
return;
}
// Process system events (theme changes, etc)
if (data.event) {