diff --git a/admin-web/index.html b/admin-web/index.html index a14ccf2..5ffcabd 100644 --- a/admin-web/index.html +++ b/admin-web/index.html @@ -81,6 +81,11 @@ confirm: { open: false, title: "", message: "" }, _heroPollTimer: null, _heroPollUntil: null, + _heroLiveWs: null, + _heroLiveWsHeroId: null, + _heroLiveWsStatus: "disconnected", + _heroLiveWsError: "", + _heroLiveWsLastAt: null, }; state._confirmAction = null; @@ -568,7 +573,21 @@ state._heroPollTimer = null; state._heroPollUntil = null; } + function stopHeroLiveWS() { + if (state._heroLiveWs) { + try { state._heroLiveWs.close(); } catch (err) {} + } + state._heroLiveWs = null; + state._heroLiveWsHeroId = null; + state._heroLiveWsStatus = "disconnected"; + state._heroLiveWsError = ""; + state._heroLiveWsLastAt = null; + render(); + } function startHeroMovementPoll(durationSec = 55) { + if (state._heroLiveWs && state._heroLiveWs.readyState === WebSocket.OPEN) { + return; + } stopHeroMovementPoll(); state._heroPollUntil = Date.now() + durationSec * 1000; state._heroPollTimer = setInterval(async () => { @@ -586,6 +605,55 @@ }, 1000); render(); } + function connectHeroLiveWS() { + if (!state.selectedHeroId) { setMessage("Select hero first"); return; } + if (!state.auth.username || !state.auth.password) { setMessage("Set admin credentials first"); return; } + stopHeroMovementPoll(); + stopHeroLiveWS(); + const proto = location.protocol === "https:" ? "wss" : "ws"; + const auth = btoa(`${state.auth.username}:${state.auth.password}`); + const url = `${proto}://${location.host}/admin-ws/hero/${state.selectedHeroId}?auth=${encodeURIComponent(auth)}`; + const ws = new WebSocket(url); + state._heroLiveWs = ws; + state._heroLiveWsHeroId = state.selectedHeroId; + state._heroLiveWsStatus = "connecting"; + state._heroLiveWsError = ""; + state._heroLiveWsLastAt = null; + render(); + ws.onopen = () => { + state._heroLiveWsStatus = "connected"; + render(); + }; + ws.onclose = () => { + state._heroLiveWsStatus = "disconnected"; + render(); + }; + ws.onerror = () => { + state._heroLiveWsStatus = "error"; + state._heroLiveWsError = "WebSocket error"; + render(); + }; + ws.onmessage = (evt) => { + try { + const data = JSON.parse(evt.data); + if (data && data.error) { + state._heroLiveWsStatus = "error"; + state._heroLiveWsError = String(data.error); + render(); + return; + } + if (data && data.id && state.selectedHeroId === data.id) { + state.selectedHero = data; + state._heroLiveWsLastAt = Date.now(); + render(); + } + } catch (err) { + state._heroLiveWsStatus = "error"; + state._heroLiveWsError = "Failed to parse WS payload"; + render(); + } + }; + } function formatRemainingMs(ms) { if (ms == null || !Number.isFinite(ms)) return "—"; if (ms <= 0) return "истекло"; @@ -637,9 +705,28 @@ } return `
Endpoint: /admin-ws/hero/{heroId} (BasicAuth from saved credentials).
+