barcode gamings

This commit is contained in:
legop3
2026-06-18 22:01:10 -04:00
parent d3765b7d8f
commit c598e25019
9 changed files with 417 additions and 54 deletions
+84 -6
View File
@@ -5,10 +5,14 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSocket } from '../context/SocketContext.jsx';
const STATE_STALE_MS = 10 * 1000;
const RESUBSCRIBE_MS = 5 * 1000;
const EMPTY_BARCODE_GAME_STATE = {
activeGameId: null,
games: [],
activeGame: null,
participants: [],
counters: {
objects: [],
rovers: [],
@@ -23,6 +27,7 @@ function normalizeState(payload = {}) {
...EMPTY_BARCODE_GAME_STATE,
...(payload && typeof payload === 'object' ? payload : {}),
games: Array.isArray(payload?.games) ? payload.games : [],
participants: Array.isArray(payload?.participants) ? payload.participants : [],
counters: {
...EMPTY_BARCODE_GAME_STATE.counters,
...(payload?.counters && typeof payload.counters === 'object' ? payload.counters : {}),
@@ -34,20 +39,92 @@ function normalizeState(payload = {}) {
export default function useBarcodeGameState() {
const socket = useSocket();
const [state, setState] = useState(EMPTY_BARCODE_GAME_STATE);
const [connectionState, setConnectionState] = useState({
connected: Boolean(socket.connected),
stale: true,
lastReceivedAt: null,
});
useEffect(() => {
let disposed = false;
let staleTimer = null;
let retryTimer = null;
let lastReceivedAt = 0;
function handleState(payload = {}) {
if (disposed) return;
lastReceivedAt = Date.now();
setState(normalizeState(payload));
setConnectionState({
connected: Boolean(socket.connected),
stale: false,
lastReceivedAt,
});
}
function subscribeToGameState() {
// Socket.io rooms are server-side state, so a reconnect needs a fresh
// subscribe packet. Retrying the same idempotent subscribe is cheap and
// prevents the scanner page from getting stuck with old game text after a
// transient Wi-Fi or server restart.
socket.emit('barcodeGame:subscribe', {}, (response = {}) => {
if (response.state) {
handleState(response.state);
return;
}
setConnectionState((previous) => ({
...previous,
connected: Boolean(socket.connected),
stale: !lastReceivedAt,
}));
});
}
function handleConnect() {
setConnectionState((previous) => ({
...previous,
connected: true,
stale: !lastReceivedAt,
}));
subscribeToGameState();
}
function handleDisconnect() {
setConnectionState((previous) => ({
...previous,
connected: false,
stale: true,
}));
}
socket.emit('barcodeGame:subscribe', {}, (response = {}) => {
if (response.state) {
handleState(response.state);
}
});
socket.on('barcodeGame:state', handleState);
socket.on('connect', handleConnect);
socket.on('disconnect', handleDisconnect);
subscribeToGameState();
staleTimer = window.setInterval(() => {
const isStale = !lastReceivedAt || Date.now() - lastReceivedAt > STATE_STALE_MS;
setConnectionState((previous) => ({
...previous,
connected: Boolean(socket.connected),
stale: isStale,
}));
}, 1000);
retryTimer = window.setInterval(() => {
if (!socket.connected) return;
if (!lastReceivedAt || Date.now() - lastReceivedAt > STATE_STALE_MS) {
subscribeToGameState();
}
}, RESUBSCRIBE_MS);
return () => {
disposed = true;
window.clearInterval(staleTimer);
window.clearInterval(retryTimer);
socket.off('barcodeGame:state', handleState);
socket.off('connect', handleConnect);
socket.off('disconnect', handleDisconnect);
};
}, [socket]);
@@ -71,8 +148,9 @@ export default function useBarcodeGameState() {
return useMemo(
() => ({
state,
connectionState,
voteForGame,
}),
[state, voteForGame],
[connectionState, state, voteForGame],
);
}