feat: enhance WebSocket connection handling with reconnect logic and state management
All checks were successful
CI / test (push) Successful in 6s
All checks were successful
CI / test (push) Successful in 6s
This commit is contained in:
42
src/ui.py
42
src/ui.py
@@ -61,8 +61,10 @@ def monitoring_page_html() -> str:
|
|||||||
token: localStorage.getItem("screenjob_token") || "",
|
token: localStorage.getItem("screenjob_token") || "",
|
||||||
jobs: [],
|
jobs: [],
|
||||||
selectedJobId: null,
|
selectedJobId: null,
|
||||||
ws: null
|
ws: null,
|
||||||
|
wsReconnectTimer: null
|
||||||
};
|
};
|
||||||
|
const manuallyClosedSockets = new WeakSet();
|
||||||
tokenInput.value = state.token;
|
tokenInput.value = state.token;
|
||||||
|
|
||||||
function authHeaders() {
|
function authHeaders() {
|
||||||
@@ -119,15 +121,26 @@ def monitoring_page_html() -> str:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pushEventLine(obj) {
|
function pushEventLine(obj) {
|
||||||
|
if (!obj || !obj.job_id || !obj.event_type) return;
|
||||||
const line = document.createElement("div");
|
const line = document.createElement("div");
|
||||||
line.className = "border-b border-slate-800 pb-1";
|
line.className = "border-b border-slate-800 pb-1";
|
||||||
line.textContent = `[${obj.ts}] ${obj.job_id} step=${obj.step} ${obj.event_type} ${JSON.stringify(obj.payload || {})}`;
|
const ts = obj.ts || "-";
|
||||||
|
const step = (obj.step ?? "-");
|
||||||
|
line.textContent = `[${ts}] ${obj.job_id} step=${step} ${obj.event_type} ${JSON.stringify(obj.payload || {})}`;
|
||||||
eventsEl.prepend(line);
|
eventsEl.prepend(line);
|
||||||
while (eventsEl.childNodes.length > 400) {
|
while (eventsEl.childNodes.length > 400) {
|
||||||
eventsEl.removeChild(eventsEl.lastChild);
|
eventsEl.removeChild(eventsEl.lastChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleWsReconnect() {
|
||||||
|
if (state.wsReconnectTimer || !state.token) return;
|
||||||
|
state.wsReconnectTimer = setTimeout(() => {
|
||||||
|
state.wsReconnectTimer = null;
|
||||||
|
connectWs();
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
|
||||||
function updateLatestVisualFromEvent(ev) {
|
function updateLatestVisualFromEvent(ev) {
|
||||||
if (!ev || ev.event_type !== "visual_update") return;
|
if (!ev || ev.event_type !== "visual_update") return;
|
||||||
if (!state.selectedJobId || ev.job_id !== state.selectedJobId) return;
|
if (!state.selectedJobId || ev.job_id !== state.selectedJobId) return;
|
||||||
@@ -164,16 +177,17 @@ def monitoring_page_html() -> str:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connectWs() {
|
function connectWs() {
|
||||||
if (state.ws) {
|
|
||||||
try { state.ws.close(); } catch (_) {}
|
|
||||||
}
|
|
||||||
if (!state.token) return;
|
if (!state.token) return;
|
||||||
|
if (state.ws && (state.ws.readyState === WebSocket.OPEN || state.ws.readyState === WebSocket.CONNECTING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const scheme = location.protocol === "https:" ? "wss" : "ws";
|
const scheme = location.protocol === "https:" ? "wss" : "ws";
|
||||||
const ws = new WebSocket(`${scheme}://${location.host}/ws?token=${encodeURIComponent(state.token)}`);
|
const ws = new WebSocket(`${scheme}://${location.host}/ws?token=${encodeURIComponent(state.token)}`);
|
||||||
state.ws = ws;
|
state.ws = ws;
|
||||||
ws.onmessage = async (event) => {
|
ws.onmessage = async (event) => {
|
||||||
try {
|
try {
|
||||||
const payload = JSON.parse(event.data);
|
const payload = JSON.parse(event.data);
|
||||||
|
if (!payload || payload.event_type === "connected") return;
|
||||||
pushEventLine(payload);
|
pushEventLine(payload);
|
||||||
updateLatestVisualFromEvent(payload);
|
updateLatestVisualFromEvent(payload);
|
||||||
if (!state.selectedJobId || payload.job_id === state.selectedJobId) {
|
if (!state.selectedJobId || payload.job_id === state.selectedJobId) {
|
||||||
@@ -185,7 +199,14 @@ def monitoring_page_html() -> str:
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ws.onclose = () => setTimeout(connectWs, 1200);
|
ws.onclose = () => {
|
||||||
|
if (state.ws === ws) state.ws = null;
|
||||||
|
if (manuallyClosedSockets.has(ws)) {
|
||||||
|
manuallyClosedSockets.delete(ws);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduleWsReconnect();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fullRefresh() {
|
async function fullRefresh() {
|
||||||
@@ -197,6 +218,15 @@ def monitoring_page_html() -> str:
|
|||||||
async function connect() {
|
async function connect() {
|
||||||
state.token = tokenInput.value.trim();
|
state.token = tokenInput.value.trim();
|
||||||
localStorage.setItem("screenjob_token", state.token);
|
localStorage.setItem("screenjob_token", state.token);
|
||||||
|
if (state.ws) {
|
||||||
|
manuallyClosedSockets.add(state.ws);
|
||||||
|
try { state.ws.close(); } catch (_) {}
|
||||||
|
state.ws = null;
|
||||||
|
}
|
||||||
|
if (state.wsReconnectTimer) {
|
||||||
|
clearTimeout(state.wsReconnectTimer);
|
||||||
|
state.wsReconnectTimer = null;
|
||||||
|
}
|
||||||
await fullRefresh();
|
await fullRefresh();
|
||||||
connectWs();
|
connectWs();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user