feat: implement WebSocket synchronization and HTTP recovery
This commit is contained in:
+37
-7
@@ -1,10 +1,12 @@
|
||||
(() => {
|
||||
const target = document.querySelector('#health');
|
||||
const role = new URLSearchParams(location.search).get('role') || 'spectator';
|
||||
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: 'Мин. свободная память (байт)',
|
||||
@@ -34,10 +36,12 @@
|
||||
|
||||
async function pollState() {
|
||||
try {
|
||||
const response = await fetch(`/api/state?role=${encodeURIComponent(role)}`, { cache: 'no-store' });
|
||||
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();
|
||||
document.title = `Морской бой — версия ${state.version}`;
|
||||
acceptState(state);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -51,14 +55,40 @@
|
||||
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() {
|
||||
socket = new WebSocket(`ws://${location.host}/ws?role=${encodeURIComponent(role)}`);
|
||||
socket.onopen = () => { retryIndex = 0; stopPolling(); };
|
||||
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 => {
|
||||
const state = JSON.parse(event.data);
|
||||
document.title = `Морской бой — версия ${state.version}`;
|
||||
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)];
|
||||
|
||||
Reference in New Issue
Block a user