Files
battleship/data/app.js
T

104 lines
3.3 KiB
JavaScript

(() => {
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();
})();