(() => { const target = document.querySelector('#health'); const token = localStorage.getItem('battleship.sessionToken') || ''; let socket; let lastVersion = 0; let retryIndex = 0; let retryTimer; let pollTimer; let heartbeatTimer; const labels = { uptime_ms: 'Время работы (мс)', wifi_state: 'Wi-Fi', rssi_dbm: 'RSSI (дБм)', free_heap_bytes: 'Свободная память (байт)', min_free_heap_bytes: 'Мин. свободная память (байт)', build_version: 'Версия сборки' }; function render(health) { target.replaceChildren(); for (const [key, label] of Object.entries(labels)) { const term = document.createElement('dt'); const value = document.createElement('dd'); term.textContent = label; value.textContent = health[key] ?? 'недоступно'; target.append(term, value); } } async function refresh() { try { const response = await fetch('/api/health', { cache: 'no-store' }); if (!response.ok) throw new Error(`HTTP ${response.status}`); render(await response.json()); } catch (error) { target.textContent = `Не удалось получить состояние: ${error.message}`; } } async function pollState() { try { const response = await fetch(`/api/state?version=${lastVersion}`, { cache: 'no-store', headers: token ? { 'X-Session-Token': token } : {} }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const state = await response.json(); acceptState(state); } catch (_) {} } function beginPolling() { if (!pollTimer) pollTimer = window.setInterval(pollState, 2000); pollState(); } function stopPolling() { if (pollTimer) window.clearInterval(pollTimer); pollTimer = undefined; } function stopHeartbeat() { if (heartbeatTimer) window.clearInterval(heartbeatTimer); heartbeatTimer = undefined; } function acceptState(state) { if (lastVersion && state.version > lastVersion + 1) pollState(); lastVersion = state.version; document.title = `Морской бой — версия ${state.version}`; } function connectSocket() { if (!token) { beginPolling(); return; } const scheme = location.protocol === 'https:' ? 'wss' : 'ws'; socket = new WebSocket(`${scheme}://${location.host}/ws`); socket.onopen = () => { socket.send(JSON.stringify({ type: 'hello', token, version: lastVersion })); }; socket.onmessage = event => { try { const message = JSON.parse(event.data); if (message.type !== 'state') return; acceptState(message); retryIndex = 0; stopPolling(); if (!heartbeatTimer) { heartbeatTimer = window.setInterval(() => { if (socket?.readyState === WebSocket.OPEN) socket.send('{"type":"ping"}'); }, 15000); } } catch (_) { socket.close(); } }; socket.onclose = () => { stopHeartbeat(); beginPolling(); const delays = [1000, 2000, 5000, 10000]; const delay = delays[Math.min(retryIndex++, delays.length - 1)]; retryTimer = window.setTimeout(connectSocket, delay); }; socket.onerror = () => socket.close(); } refresh(); window.setInterval(refresh, 5000); connectSocket(); })();