Files
stream-overlay/overlay/common.js
T
2026-06-24 13:00:51 +02:00

132 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* common.js Stato condiviso tra i widget overlay via WebSocket
*
* Ogni widget si connette al server Express+WS e ascolta gli aggiornamenti.
* Il pannello di controllo invia comandi/aggiornamenti, il server li
* propaga a tutti i widget connessi.
*/
// ---- Config ----
const WS_URL = (() => {
const loc = window.location;
const protocol = loc.protocol === 'https:' ? 'wss:' : 'ws:';
return `${protocol}//${loc.host}/ws`;
})();
let ws = null;
let currentState = {};
let subscribers = [];
let reconnectTimer = null;
let connectAttempts = 0;
// ---- WebSocket connection ----
function connect() {
if (ws && (ws.readyState === 0 || ws.readyState === 1)) return;
ws = new WebSocket(WS_URL);
ws.onopen = () => {
console.log('🟢 WS connected');
connectAttempts = 0;
};
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'STATE_UPDATE' && msg.state) {
currentState = msg.state;
notifySubscribers();
}
} catch (err) {
console.error('❌ WS message error:', err);
}
};
ws.onclose = () => {
console.log('🔴 WS disconnected, reconnecting...');
scheduleReconnect();
};
ws.onerror = (err) => {
console.error('❌ WS error:', err);
ws.close();
};
}
function scheduleReconnect() {
if (reconnectTimer) clearTimeout(reconnectTimer);
const delay = Math.min(1000 * 2 ** connectAttempts, 10000);
connectAttempts++;
reconnectTimer = setTimeout(connect, delay);
}
// ---- Subscribe to state updates ----
/**
* Si iscrive agli aggiornamenti di stato.
* @param {(state: object) => void} callback
* @returns {() => void} unsubscribe function
*/
function onStateUpdate(callback) {
subscribers.push(callback);
// Chiamata immediata con stato corrente (se già presente)
if (Object.keys(currentState).length > 0) {
callback({ ...currentState });
}
return () => {
subscribers = subscribers.filter((cb) => cb !== callback);
};
}
function notifySubscribers() {
const state = { ...currentState };
for (const cb of subscribers) {
try { cb(state); } catch (e) { console.error(e); }
}
}
/**
* Invia un comando al server (ADD_POINT, RESET_GAME, RESET_MATCH, SET_DEUCE, SET_SERVER)
*/
function sendCommand(command, player = null) {
const msg = { type: 'COMMAND', command };
if (player !== null) msg.player = player;
sendMessage(msg);
}
/**
* Invia un aggiornamento parziale di stato al server
*/
function sendStateUpdate(partial) {
sendMessage({ type: 'STATE_UPDATE', state: partial });
}
function sendMessage(msg) {
if (ws && ws.readyState === 1) {
ws.send(JSON.stringify(msg));
} else {
console.warn('⚠️ WS not connected, cannot send');
}
}
/**
* Utility: mappa punti → label tennis
*/
function pointLabel(state) {
// Tiebreak: mostra punti numerici
if (state.tiebreak) {
return [String(state.tiebreakPoints[0]), String(state.tiebreakPoints[1])];
}
const p1 = state.points[0];
const p2 = state.points[1];
if (state.deuce) {
if (state.advantage === 1) return ['AD', '40'];
if (state.advantage === 2) return ['40', 'AD'];
return ['40', '40'];
}
const map = ['0', '15', '30', '40'];
return [map[p1] || '40', map[p2] || '40'];
}
// ---- Auto-connect ----
connect();