let socket; let heartbeatInterval; function connect() { // Use wss:// for secure connections (in production) or ws:// for local const protocol = window.location.protocol === "https:" ? "wss" : "ws"; socket = new WebSocket(`${protocol}://${window.location.host}`); socket.onopen = () => { console.log("WebSocket connection established."); // Start a heartbeat to keep the connection alive clearInterval(heartbeatInterval); // Clear previous interval if it exists heartbeatInterval = setInterval(() => { if (socket.readyState === WebSocket.OPEN) { // Sending a simple ping message. The server's 'ws' library will handle this // automatically to keep the connection from being marked as idle by proxies. socket.send(JSON.stringify({ type: "ping" })); } }, 30000); // every 30 seconds }; socket.onmessage = (event) => { // The server's 'ws' library might automatically respond to pings with pongs. // We can safely ignore them, along with our own pings if they get echoed. if (event.data.includes("ping") || event.data.includes("pong")) { return; } const data = JSON.parse(event.data); // --- This is our client-side message router --- switch (data.type) { case "activityLogUpdate": handleActivityLogUpdate(data.payload); break; case "systemResourcesUpdate": handleSystemResourceUpdate(data.payload); break; // Add more cases here for other real-time updates default: console.log("Received unknown message type:", data.type); } }; socket.onclose = () => { console.log( "WebSocket connection closed. Attempting to reconnect in 3 seconds...", ); clearInterval(heartbeatInterval); // Stop the heartbeat when the connection is lost // **This is the key part:** Wait 3 seconds, then try to connect again. setTimeout(connect, 3000); }; socket.onerror = (error) => { console.error("WebSocket Error:", error); // The 'onclose' event will fire automatically after an error, // which will then trigger our reconnection logic. socket.close(); // Ensure the socket is closed to trigger onclose }; } function handleActivityLogUpdate(payload) { const activityLog = document.getElementById("activity-log"); if (!activityLog || !payload.message) return; const newLogItem = document.createElement("p"); const styledMessage = payload.message.replace( /(\[[^\]]+\])/, '$1', ); newLogItem.innerHTML = styledMessage; activityLog.prepend(newLogItem); while (activityLog.children.length > 20) { activityLog.lastChild.remove(); } } function handleSystemResourceUpdate(payload) { // Conceptual: Find the memory percentage element and update its text and style const memPercentage = document.querySelector(".metric-header strong"); // Simplified selector const memProgressBar = document.querySelector(".progress-bar.memory"); // Simplified selector if (memPercentage) memPercentage.textContent = `${payload.memory.percentage}%`; if (memProgressBar) memProgressBar.style.width = `${payload.memory.percentage}%`; console.log("System resources updated!", payload); } // --- Initial Connection --- connect();