1
0

Rewriting socket code

This commit is contained in:
James Ketr 2025-09-27 14:25:30 -07:00
parent 3c2c92eb79
commit d4f34cd43f
3 changed files with 132 additions and 209 deletions

View File

@ -1,5 +1,6 @@
import React, { useState, useCallback, useEffect, useRef } from "react";
import React, { useState, useCallback, useEffect, useRef, useMemo } from "react";
import { BrowserRouter as Router, Route, Routes, useParams, useNavigate } from "react-router-dom";
import useWebSocket, { ReadyState } from "react-use-websocket";
import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";
@ -44,14 +45,9 @@ const loadAudio = (src: string) => {
};
const Table: React.FC = () => {
console.log("Table component rendered");
const params = useParams();
const navigate = useNavigate();
const [gameId, setGameId] = useState<string | undefined>(params.gameId ? (params.gameId as string) : undefined);
const [ws, setWs] = useState<WebSocket | undefined>(undefined); /* tracks full websocket lifetime */
const [connection, setConnection] = useState<WebSocket | undefined>(undefined); /* set after ws is in OPEN */
const [retryConnection, setRetryConnection] =
useState<boolean>(true); /* set when connection should be re-established */
const [name, setName] = useState<string>("");
const [error, setError] = useState<string | undefined>(undefined);
const [warning, setWarning] = useState<string | undefined>(undefined);
@ -71,7 +67,6 @@ const Table: React.FC = () => {
const [houseRulesActive, setHouseRulesActive] = useState<boolean>(false);
const [winnerDismissed, setWinnerDismissed] = useState<boolean>(false);
const [global, setGlobal] = useState<Record<string, unknown>>({});
const [count, setCount] = useState<number>(0);
const [audio, setAudio] = useState<boolean>(
localStorage.getItem("audio") ? JSON.parse(localStorage.getItem("audio") as string) : false
);
@ -83,19 +78,40 @@ const Table: React.FC = () => {
);
const fields = ["id", "state", "color", "name", "private", "dice", "turn"];
const onWsOpen = (event: Event) => {
console.log(`ws: open`);
console.log("WebSocket opened, sending game-update and get");
setError("");
const loc = window.location;
const protocol = loc.protocol === "https:" ? "wss" : "ws";
const socketUrl = gameId ? `${protocol}://${loc.host}${base}/api/v1/games/ws/${gameId}` : null;
setConnection(ws);
const sock = event.target as WebSocket;
sock.send(JSON.stringify({ type: "game-update" }));
sock.send(JSON.stringify({ type: "get", fields }));
const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(socketUrl, {
shouldReconnect: (closeEvent) => true,
reconnectInterval: 5000,
onOpen: () => {
console.log(`ws: open`);
setError("");
sendJsonMessage({ type: "game-update" });
sendJsonMessage({ type: "get", fields });
},
onError: (err) => {
console.log("WebSocket error", err);
const error = `Connection to Ketr Ketran game server failed! Connection attempt will be retried every 5 seconds.`;
setError(error);
setGlobal(Object.assign({}, global, { ws: undefined }));
},
onClose: () => {
console.log("WebSocket closed");
const error = `Connection to Ketr Ketran game was lost. Attempting to reconnect...`;
setError(error);
setGlobal(Object.assign({}, global, { ws: undefined }));
},
});
const sendUpdate = (update: unknown) => {
sendJsonMessage(update);
};
const onWsMessage = (event: MessageEvent) => {
const data = JSON.parse(event.data as string);
useEffect(() => {
if (lastJsonMessage) {
const data = lastJsonMessage as any;
switch (data.type) {
case "error":
console.error(`App - error`, data.error);
@ -161,73 +177,21 @@ const Table: React.FC = () => {
default:
break;
}
};
const sendUpdate = (update: unknown) => {
if (ws) ws.send(JSON.stringify(update));
};
const cbResetConnection = useCallback(() => {
let timer: number | null = null;
function reset() {
timer = null;
setRetryConnection(true);
}
return () => {
if (timer) {
clearTimeout(timer);
}
timer = window.setTimeout(reset, 5000);
};
}, [setRetryConnection]);
}, [lastJsonMessage]);
const resetConnection = cbResetConnection();
if (global.ws !== connection || global.name !== name || global.gameId !== gameId) {
setGlobal({
ws: connection,
const globalValue = useMemo(() => ({
ws: readyState === ReadyState.OPEN ? {} : undefined,
name,
gameId,
});
}
const onWsError = () => {
const error =
`Connection to Ketr Ketran game server failed! ` + `Connection attempt will be retried every 5 seconds.`;
setError(error);
setGlobal(Object.assign({}, global, { ws: undefined }));
setWs(undefined); /* clear the socket */
setConnection(undefined); /* clear the connection */
resetConnection();
};
const onWsClose = () => {
const error = `Connection to Ketr Ketran game was lost. ` + `Attempting to reconnect...`;
setError(error);
setGlobal(Object.assign({}, global, { ws: undefined }));
setWs(undefined); /* clear the socket */
setConnection(undefined); /* clear the connection */
resetConnection();
};
const refWsOpen = useRef<(e: Event) => void>(() => {});
useEffect(() => {
refWsOpen.current = onWsOpen;
}, [onWsOpen]);
const refWsMessage = useRef<(e: MessageEvent) => void>(() => {});
useEffect(() => {
refWsMessage.current = onWsMessage;
}, [onWsMessage]);
const refWsClose = useRef<(e: CloseEvent) => void>(() => {});
useEffect(() => {
refWsClose.current = onWsClose;
}, [onWsClose]);
const refWsError = useRef<(e: Event) => void>(() => {});
useEffect(() => {
refWsError.current = onWsError;
}, [onWsError]);
}), [readyState, name, gameId]);
useEffect(() => {
setGlobal(globalValue);
}, [globalValue, setGlobal]);
useEffect(() => {
console.log("Table useEffect for POST running, gameId =", gameId);
if (gameId) {
return;
}
@ -242,6 +206,7 @@ const Table: React.FC = () => {
},
})
.then((res) => {
console.log("POST fetch response status:", res.status);
if (res.status >= 400) {
const error =
`Unable to connect to Ketr Ketran game server! ` + `Try refreshing your browser in a few seconds.`;
@ -251,77 +216,18 @@ const Table: React.FC = () => {
return res.json();
})
.then((update) => {
console.log("POST fetch response data:", update);
if (update.id !== gameId) {
navigate(`/${update.id}`);
setGameId(update.id);
}
})
.catch((error) => {
console.error(error);
console.error("POST fetch error:", error);
});
}, [gameId, setGameId]);
useEffect(() => {
if (!gameId) {
return;
}
const unbind = () => {
console.log(`table - unbind`);
};
if (!ws && !connection && retryConnection) {
const loc = window.location;
let new_uri = "";
if (loc.protocol === "https:") {
new_uri = "wss";
} else {
new_uri = "ws";
}
new_uri = `${new_uri}://${loc.host}${base}/api/v1/games/ws/${gameId}?${count}`;
setWs(new WebSocket(new_uri));
setConnection(undefined);
setRetryConnection(false);
setCount(count + 1);
return unbind;
}
if (!ws) {
return unbind;
}
const cbOpen = (e: Event) => refWsOpen.current(e);
const cbMessage = (e: MessageEvent) => refWsMessage.current(e);
const cbClose = (e: CloseEvent) => refWsClose.current(e);
const cbError = (e: Event) => refWsError.current(e);
ws.addEventListener("open", cbOpen);
ws.addEventListener("close", cbClose);
ws.addEventListener("error", cbError);
ws.addEventListener("message", cbMessage);
return () => {
unbind();
ws.removeEventListener("open", cbOpen);
ws.removeEventListener("close", cbClose);
ws.removeEventListener("error", cbError);
ws.removeEventListener("message", cbMessage);
};
}, [
ws,
setWs,
connection,
setConnection,
retryConnection,
setRetryConnection,
gameId,
refWsOpen,
refWsMessage,
refWsClose,
refWsError,
count,
setCount,
]);
// WebSocket logic moved to useWebSocket
useEffect(() => {
if (state === "volcano") {
@ -576,6 +482,7 @@ const App: React.FC = () => {
},
})
.then((res) => {
console.log("GET fetch response status:", res.status);
if (res.status >= 400) {
const error =
`Unable to connect to Ketr Ketran game server! ` + `Try refreshing your browser in a few seconds.`;
@ -584,9 +491,12 @@ const App: React.FC = () => {
return res.json();
})
.then((data) => {
console.log("GET fetch response data:", data);
setPlayerId(data.player);
})
.catch(() => {});
.catch((error) => {
console.error("GET fetch error:", error);
});
}, [playerId, setPlayerId]);
if (!playerId) {
@ -596,8 +506,8 @@ const App: React.FC = () => {
return (
<Router basename={base}>
<Routes>
<Route element={<Table />} path="/:gameId" />
<Route element={<Table />} path="/" />
<Route element={<Table />} path="/:gameId" />
</Routes>
</Router>
);

View File

@ -9,7 +9,6 @@ import Videocam from "@mui/icons-material/Videocam";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import useWebSocket, { ReadyState } from "react-use-websocket";
import { Session } from "./GlobalContext";
import WebRTCStatus from "./WebRTCStatus";
import Moveable from "react-moveable";
import { flushSync } from "react-dom";
@ -18,6 +17,15 @@ const debug = true;
// When true, do not send host candidates to the signaling server. Keeps TURN relays preferred.
const FILTER_HOST_CANDIDATES = false; // Temporarily disabled to test direct connections
type Session = {
session_id: string;
peer_name: string;
has_media?: boolean; // Whether this user provides audio/video streams
attributes?: Record<string, any>;
name: string;
id: string;
};
/* ---------- Synthetic Tracks Helpers ---------- */
// Helper to hash a string to a color
@ -1701,7 +1709,7 @@ const MediaControl: React.FC<MediaControlProps> = ({
snapThreshold={5}
origin={false}
edge
onDragStart={(e) => {
onDragStart={(e: any) => {
const controls = containerRef.current?.querySelector(".Controls");
const target = e.inputEvent?.target as HTMLElement;
if (controls && target && (target.closest("button") || target.closest(".MuiIconButton-root"))) {

View File

@ -28,6 +28,11 @@ console.log("Hosting server from: " + basePath);
let userDB: any, gameDB: any;
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
app.use(bodyParser.json());
/* App is behind an nginx proxy which we trust, so use the remote address