From 3874519a8772d556f7755190dc60c9f0ff8a7115 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Sat, 27 Sep 2025 15:21:51 -0700 Subject: [PATCH] Migrating to useWebSocket --- client/src/Actions.tsx | 23 ++++----- client/src/Activities.tsx | 16 +++---- client/src/App.tsx | 15 ++++-- client/src/Board.tsx | 16 +++---- client/src/Chat.tsx | 16 +++---- client/src/ChooseCard.tsx | 30 +++++------- client/src/GameOrder.tsx | 16 +++---- client/src/GlobalContext.ts | 3 +- client/src/Hand.tsx | 16 +++---- client/src/HouseRules.tsx | 92 ++++++++++++++++-------------------- client/src/Placard.tsx | 6 +-- client/src/PlayerList.tsx | 14 +++--- client/src/PlayersStatus.tsx | 16 +++---- client/src/SelectPlayer.tsx | 28 +++++------ client/src/Trade.tsx | 32 ++++++------- client/src/ViewCard.tsx | 30 +++++------- client/src/Winner.tsx | 26 +++++----- 17 files changed, 179 insertions(+), 216 deletions(-) diff --git a/client/src/Actions.tsx b/client/src/Actions.tsx index 534c8a9..b671f39 100644 --- a/client/src/Actions.tsx +++ b/client/src/Actions.tsx @@ -11,6 +11,7 @@ type LocalGlobalContext = { ws?: WebSocket | null; gameId?: string | null; name?: string | undefined; + sendJsonMessage?: (message: any) => void; }; type PrivateData = { @@ -105,31 +106,31 @@ const Actions: React.FC = ({ refWsMessage.current = onWsMessage; }); useEffect(() => { - if (!ws) { + if (!ctx.ws) { return; } const cbMessage = (e: MessageEvent) => refWsMessage.current(e); - ws.addEventListener("message", cbMessage as EventListener); + ctx.ws.addEventListener("message", cbMessage as EventListener); return () => { - ws.removeEventListener("message", cbMessage as EventListener); + ctx.ws.removeEventListener("message", cbMessage as EventListener); }; - }, [ws, refWsMessage]); + }, [ctx.ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!ctx.sendJsonMessage) { return; } - ws.send(JSON.stringify({ type: "get", fields })); - }, [ws, fields]); + ctx.sendJsonMessage({ type: "get", fields }); + }, [ctx.sendJsonMessage, fields]); const sendMessage = useCallback( (data: Record) => { - if (!ws) { - console.warn(`No socket`); + if (!ctx.sendJsonMessage) { + console.warn(`No sendJsonMessage`); } else { - ws.send(JSON.stringify(data)); + ctx.sendJsonMessage(data); } }, - [ws] + [ctx.sendJsonMessage] ); const buildClicked = () => { diff --git a/client/src/Activities.tsx b/client/src/Activities.tsx index 74bc83c..6a0f480 100644 --- a/client/src/Activities.tsx +++ b/client/src/Activities.tsx @@ -87,7 +87,7 @@ const Activity: React.FC = ({ keep, activity }) => { }; const Activities: React.FC = () => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [activities, setActivities] = useState([]); const [turn, setTurn] = useState(undefined); const [color, setColor] = useState(undefined); @@ -169,16 +169,14 @@ const Activities: React.FC = () => { }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); if (!timestamp) { return <>; diff --git a/client/src/App.tsx b/client/src/App.tsx index a0d02f7..5823bde 100755 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -82,7 +82,7 @@ const Table: React.FC = () => { const protocol = loc.protocol === "https:" ? "wss" : "ws"; const socketUrl = gameId ? `${protocol}://${loc.host}${base}/api/v1/games/ws/${gameId}` : null; - const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(socketUrl, { + const { sendJsonMessage, lastJsonMessage, readyState, getWebSocket } = useWebSocket(socketUrl, { shouldReconnect: (closeEvent) => true, reconnectInterval: 5000, onOpen: () => { @@ -181,10 +181,11 @@ const Table: React.FC = () => { }, [lastJsonMessage]); const globalValue = useMemo(() => ({ - ws: readyState === ReadyState.OPEN ? {} : undefined, + ws: getWebSocket(), name, gameId, - }), [readyState, name, gameId]); + sendJsonMessage, + }), [getWebSocket, name, gameId, sendJsonMessage]); useEffect(() => { setGlobal(globalValue); @@ -504,7 +505,13 @@ const App: React.FC = () => { } return ( - + } path="/" /> } path="/:gameId" /> diff --git a/client/src/Board.tsx b/client/src/Board.tsx index d60c7ec..9e7ea61 100644 --- a/client/src/Board.tsx +++ b/client/src/Board.tsx @@ -95,7 +95,7 @@ const clearTooltip = () => { }; const Board: React.FC = ({ animations }) => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const board = useRef(); const [transform, setTransform] = useState(1); const [pipElements, setPipElements] = useState([]); @@ -252,16 +252,14 @@ const Board: React.FC = ({ animations }) => { }; }, [ws]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); useEffect(() => { const boardBox = board.current.querySelector(".BoardBox"); diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index fb34913..76fe1fb 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -34,7 +34,7 @@ const Chat: React.FC = () => { return () => clearInterval(timer); }, []); - const { ws, name } = useContext(GlobalContext); + const { ws, name, sendJsonMessage } = useContext(GlobalContext); const fields = useMemo(() => ["chat", "startTime"], []); const onWsMessage = (event: MessageEvent) => { const data = JSON.parse(event.data); @@ -71,13 +71,11 @@ const Chat: React.FC = () => { if (!ws) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [ws, fields, sendJsonMessage]); const chatKeyPress = useCallback( (event: React.KeyboardEvent) => { @@ -87,7 +85,7 @@ const Chat: React.FC = () => { } if (ws) { - ws.send(JSON.stringify({ type: "chat", message: (event.target as HTMLInputElement).value })); + sendJsonMessage({ type: "chat", message: (event.target as HTMLInputElement).value }); (event.target as HTMLInputElement).value = ""; } } diff --git a/client/src/ChooseCard.tsx b/client/src/ChooseCard.tsx index 84f654f..118ff75 100644 --- a/client/src/ChooseCard.tsx +++ b/client/src/ChooseCard.tsx @@ -12,7 +12,7 @@ import { GlobalContext } from "./GlobalContext"; /* eslint-disable @typescript-eslint/no-explicit-any */ const ChooseCard: React.FC = () => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [turn, setTurn] = useState(undefined); const [color, setColor] = useState(undefined); const [state, setState] = useState(undefined); @@ -53,26 +53,22 @@ const ChooseCard: React.FC = () => { }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); const selectResources = useCallback(() => { - if (!ws) return; - ws.send( - JSON.stringify({ - type: "select-resources", - cards, - }) - ); - }, [ws, cards]); + if (!sendJsonMessage) return; + sendJsonMessage({ + type: "select-resources", + cards, + }); + }, [sendJsonMessage, cards]); let count = 0; if (turn && turn.actions && turn.actions.indexOf("select-resources") !== -1) { diff --git a/client/src/GameOrder.tsx b/client/src/GameOrder.tsx index e488021..44cd845 100644 --- a/client/src/GameOrder.tsx +++ b/client/src/GameOrder.tsx @@ -21,7 +21,7 @@ interface PlayerItem { } const GameOrder: React.FC = () => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [players, setPlayers] = useState<{ [key: string]: any }>({}); const [color, setColor] = useState(undefined); const fields = useMemo(() => ["players", "color"], []); @@ -57,16 +57,14 @@ const GameOrder: React.FC = () => { }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); const sendMessage = (data: any) => { ws!.send(JSON.stringify(data)); diff --git a/client/src/GlobalContext.ts b/client/src/GlobalContext.ts index b5058f1..29ecc4c 100644 --- a/client/src/GlobalContext.ts +++ b/client/src/GlobalContext.ts @@ -2,8 +2,9 @@ import { createContext } from 'react'; export type GlobalContextType = { gameId?: string | undefined; - ws?: WebSocket | undefined; + ws?: WebSocket | null | undefined; name?: string; + sendJsonMessage?: (message: any) => void; chat?: Array; }; diff --git a/client/src/Hand.tsx b/client/src/Hand.tsx index 1f6275e..55a83ee 100644 --- a/client/src/Hand.tsx +++ b/client/src/Hand.tsx @@ -34,7 +34,7 @@ interface HandProps { } const Hand: React.FC = ({ buildActive, setBuildActive, setCardActive }) => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [priv, setPriv] = useState(undefined); const [color, setColor] = useState(undefined); const [turn, setTurn] = useState(undefined); @@ -95,16 +95,14 @@ const Hand: React.FC = ({ buildActive, setBuildActive, setCardActive }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); useEffect(() => { if (!priv) { diff --git a/client/src/HouseRules.tsx b/client/src/HouseRules.tsx index 1a7f249..2bfec53 100644 --- a/client/src/HouseRules.tsx +++ b/client/src/HouseRules.tsx @@ -44,14 +44,14 @@ const categoryImages: { [key: string]: string } = { /* eslint-disable @typescript-eslint/no-explicit-any */ interface VolcanoProps { - ws: WebSocket | null; + sendJsonMessage: (message: any) => void; rules: any; field: string; disabled: boolean; } /* Volcano based on https://www.ultraboardgames.com/catan/the-volcano.php */ -const Volcano: React.FC = ({ ws, rules, field, disabled }) => { +const Volcano: React.FC = ({ sendJsonMessage, rules, field, disabled }) => { const init = Math.random() > 0.5 ? Math.floor(8 + Math.random() * 5) /* Do not include 7 */ @@ -79,33 +79,29 @@ const Volcano: React.FC = ({ ws, rules, field, disabled }) => { update = true; } - if (update && ws) { - ws.send( - JSON.stringify({ - type: "rules", - rules: rules, - }) - ); + if (update && sendJsonMessage) { + sendJsonMessage({ + type: "rules", + rules: rules, + }); } } - }, [rules, field, init, ws]); + }, [rules, field, init, sendJsonMessage]); const toggleGold = () => { - if (!ws) return; + if (!sendJsonMessage) return; rules[field].gold = !gold; rules[field].number = number; setGold(rules[field].gold); - ws.send( - JSON.stringify({ - type: "rules", - rules: rules, - }) - ); + sendJsonMessage({ + type: "rules", + rules: rules, + }); }; const update = (delta: number) => { - if (!ws) return; + if (!sendJsonMessage) return; let value = number + delta; if (value < 2 || value > 12) { return; @@ -117,12 +113,10 @@ const Volcano: React.FC = ({ ws, rules, field, disabled }) => { setNumber(value); rules[field].gold = gold; rules[field].number = value; - ws.send( - JSON.stringify({ - type: "rules", - rules: rules, - }) - ); + sendJsonMessage({ + type: "rules", + rules: rules, + }); }; return ( @@ -218,12 +212,12 @@ const Volcano: React.FC = ({ ws, rules, field, disabled }) => { }; interface VictoryPointsProps { - ws: WebSocket | null; + sendJsonMessage: (message: any) => void; rules: any; field: string; } -const VictoryPoints: React.FC = ({ ws, rules, field }) => { +const VictoryPoints: React.FC = ({ sendJsonMessage, rules, field }) => { const minVP = 10; const [points, setPoints] = useState(rules[field].points || minVP); console.log(`house-rules - ${field} - `, rules[field]); @@ -239,7 +233,7 @@ const VictoryPoints: React.FC = ({ ws, rules, field }) => { } const update = (value: number) => { - if (!ws) return; + if (!sendJsonMessage) return; const points = (rules[field].points || minVP) + value; if (points < minVP) { return; @@ -247,12 +241,10 @@ const VictoryPoints: React.FC = ({ ws, rules, field }) => { if (points !== rules[field].points) { setPoints(points); rules[field].points = points; - ws.send( - JSON.stringify({ - type: "rules", - rules: rules, - }) - ); + sendJsonMessage({ + type: "rules", + rules: rules, + }); } }; @@ -279,7 +271,7 @@ const HouseRules: React.FC = ({ houseRulesActive, setHouseRulesActive, }) => { - const { ws, name } = useContext(GlobalContext); + const { ws, name, sendJsonMessage } = useContext(GlobalContext); const [rules, setRules] = useState({}); const [state, setState] = useState({}); const [gameState, setGameState] = useState(""); @@ -316,16 +308,14 @@ const HouseRules: React.FC = ({ }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); const dismissClicked = useCallback(() => { setHouseRulesActive(false); @@ -333,19 +323,17 @@ const HouseRules: React.FC = ({ const setRule = useCallback( (event: React.ChangeEvent, key: string) => { - if (!ws) return; + if (!sendJsonMessage) return; const checked = event.target.checked; console.log(`house-rules - set rule ${key} to ${checked}`); rules[key].enabled = checked; setRules({ ...rules }); - ws.send( - JSON.stringify({ - type: "rules", - rules: rules, - }) - ); + sendJsonMessage({ + type: "rules", + rules: rules, + }); }, - [rules, ws] + [rules, sendJsonMessage] ); const ruleList = useMemo( @@ -359,7 +347,7 @@ const HouseRules: React.FC = ({ defaultChecked: false, element: ( = ({ category: "rules", defaultChecked: false, element: ( - + ), }, { diff --git a/client/src/Placard.tsx b/client/src/Placard.tsx index be6a0ad..851d0bf 100644 --- a/client/src/Placard.tsx +++ b/client/src/Placard.tsx @@ -16,12 +16,12 @@ type PlacardProps = { }; const Placard: React.FC = ({ type, disabled, count, buildActive, setBuildActive, className, sx }) => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const sendMessage = useCallback( (data: Record) => { - ws.send(JSON.stringify(data)); + sendJsonMessage(data); }, - [ws] + [sendJsonMessage] ); const dismissClicked = () => { diff --git a/client/src/PlayerList.tsx b/client/src/PlayerList.tsx index dda2dee..0abe8fa 100644 --- a/client/src/PlayerList.tsx +++ b/client/src/PlayerList.tsx @@ -11,7 +11,7 @@ import { GlobalContext } from "./GlobalContext"; /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ const PlayerList: React.FC = () => { - const { ws, name } = useContext(GlobalContext); + const { ws, name, sendJsonMessage } = useContext(GlobalContext); const [players, setPlayers] = useState<{ [key: string]: any }>({}); const [unselected, setUneslected] = useState([]); const [state, setState] = useState("lobby"); @@ -69,15 +69,13 @@ const PlayerList: React.FC = () => { }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields: ["state", "players", "unselected"], - }) - ); + sendJsonMessage({ + type: "get", + fields: ["state", "players", "unselected"], + }); }, [ws]); const toggleSelected = (key: string) => { diff --git a/client/src/PlayersStatus.tsx b/client/src/PlayersStatus.tsx index 1a0180c..35bfa20 100644 --- a/client/src/PlayersStatus.tsx +++ b/client/src/PlayersStatus.tsx @@ -133,7 +133,7 @@ interface PlayersStatusProps { } const PlayersStatus: React.FC = ({ active }) => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [players, setPlayers] = useState(undefined); const [color, setColor] = useState(undefined); const [largestArmy, setLargestArmy] = useState(undefined); @@ -184,16 +184,14 @@ const PlayersStatus: React.FC = ({ active }) => { }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); if (!players) { return <>; diff --git a/client/src/SelectPlayer.tsx b/client/src/SelectPlayer.tsx index 451a023..40dd09f 100644 --- a/client/src/SelectPlayer.tsx +++ b/client/src/SelectPlayer.tsx @@ -11,7 +11,7 @@ import { GlobalContext } from "./GlobalContext"; /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ const SelectPlayer: React.FC = () => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [turn, setTurn] = useState(undefined); const [color, setColor] = useState(undefined); const fields = useMemo(() => ["turn", "color"], []); @@ -47,27 +47,23 @@ const SelectPlayer: React.FC = () => { }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); const playerClick = useCallback( (event: React.MouseEvent) => { - ws!.send( - JSON.stringify({ - type: "steal-resource", - color: event.currentTarget.getAttribute("data-color"), - }) - ); + sendJsonMessage({ + type: "steal-resource", + color: event.currentTarget.getAttribute("data-color"), + }); }, - [ws] + [sendJsonMessage] ); if (!color || !turn || turn.color !== color || !turn.limits || !turn.limits.players) { diff --git a/client/src/Trade.tsx b/client/src/Trade.tsx index 8e1d22c..f17ff6b 100644 --- a/client/src/Trade.tsx +++ b/client/src/Trade.tsx @@ -43,7 +43,7 @@ const empty: Resources = { }; const Trade: React.FC = () => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [gives, setGives] = useState(Object.assign({}, empty)); const [gets, setGets] = useState(Object.assign({}, empty)); const [turn, setTurn] = useState(undefined); @@ -90,16 +90,14 @@ const Trade: React.FC = () => { }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); const transfer = useCallback( (type: string, direction: string) => { if (direction === "give") { @@ -168,17 +166,15 @@ const Trade: React.FC = () => { const sendTrade = useCallback( (action: string, offer: any) => { - if (ws) { - ws.send( - JSON.stringify({ - type: "trade", - action, - offer, - }) - ); + if (sendJsonMessage) { + sendJsonMessage({ + type: "trade", + action, + offer, + }); } }, - [ws] + [sendJsonMessage] ); useEffect(() => { diff --git a/client/src/ViewCard.tsx b/client/src/ViewCard.tsx index 075672a..4c010da 100644 --- a/client/src/ViewCard.tsx +++ b/client/src/ViewCard.tsx @@ -16,7 +16,7 @@ interface ViewCardProps { } const ViewCard: React.FC = ({ cardActive, setCardActive }) => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [priv, setPriv] = useState(undefined); const [turns, setTurns] = useState(0); const [rules, setRules] = useState({}); @@ -55,28 +55,24 @@ const ViewCard: React.FC = ({ cardActive, setCardActive }) => { }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); const playCard = useCallback(() => { - if (ws) { - ws.send( - JSON.stringify({ - type: "play-card", - card: cardActive, - }) - ); + if (sendJsonMessage) { + sendJsonMessage({ + type: "play-card", + card: cardActive, + }); } setCardActive(undefined); - }, [ws, cardActive, setCardActive]); + }, [sendJsonMessage, cardActive, setCardActive]); const close = () => { setCardActive(undefined); diff --git a/client/src/Winner.tsx b/client/src/Winner.tsx index 473714a..b343619 100644 --- a/client/src/Winner.tsx +++ b/client/src/Winner.tsx @@ -17,7 +17,7 @@ interface WinnerProps { /* eslint-disable @typescript-eslint/no-explicit-any */ const Winner: React.FC = ({ winnerDismissed, setWinnerDismissed }) => { - const { ws } = useContext(GlobalContext); + const { ws, sendJsonMessage } = useContext(GlobalContext); const [winner, setWinner] = useState(undefined); const [state, setState] = useState(undefined); const fields = useMemo(() => ["winner", "state"], []); @@ -56,27 +56,23 @@ const Winner: React.FC = ({ winnerDismissed, setWinnerDismissed }) }; }, [ws, refWsMessage]); useEffect(() => { - if (!ws) { + if (!sendJsonMessage) { return; } - ws.send( - JSON.stringify({ - type: "get", - fields, - }) - ); - }, [ws, fields]); + sendJsonMessage({ + type: "get", + fields, + }); + }, [sendJsonMessage, fields]); const quitClicked = useCallback(() => { if (!winnerDismissed) { setWinnerDismissed(true); - ws.send( - JSON.stringify({ - type: "goto-lobby", - }) - ); + sendJsonMessage({ + type: "goto-lobby", + }); } - }, [ws, winnerDismissed, setWinnerDismissed]); + }, [sendJsonMessage, winnerDismissed, setWinnerDismissed]); if (!winner || winnerDismissed) { return <>;