Rewriting socket code
This commit is contained in:
parent
3c2c92eb79
commit
d4f34cd43f
@ -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,151 +78,120 @@ 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 onWsMessage = (event: MessageEvent) => {
|
||||
const data = JSON.parse(event.data as string);
|
||||
switch (data.type) {
|
||||
case "error":
|
||||
console.error(`App - error`, data.error);
|
||||
setError(data.error);
|
||||
break;
|
||||
case "warning":
|
||||
console.warn(`App - warning`, data.warning);
|
||||
setWarning(data.warning);
|
||||
setTimeout(() => {
|
||||
setWarning("");
|
||||
}, 3000);
|
||||
break;
|
||||
case "game-update":
|
||||
console.log("Received game-update:", data.update);
|
||||
if (!loaded) {
|
||||
setLoaded(true);
|
||||
console.log("App: setLoaded to true");
|
||||
}
|
||||
console.log(`app - message - ${data.type}`, data.update);
|
||||
|
||||
if ("private" in data.update && !equal(priv, data.update.private)) {
|
||||
const priv = data.update.private;
|
||||
if (priv.name !== name) {
|
||||
setName(priv.name);
|
||||
console.log("App: setName from priv.name =", priv.name);
|
||||
}
|
||||
if (priv.color !== color) {
|
||||
setColor(priv.color);
|
||||
}
|
||||
setPriv(priv);
|
||||
}
|
||||
|
||||
if ("name" in data.update) {
|
||||
if (data.update.name) {
|
||||
setName(data.update.name);
|
||||
console.log("App: setName from data.update.name =", data.update.name);
|
||||
} else {
|
||||
console.log("App: data.update.name is empty");
|
||||
setWarning("");
|
||||
setError("");
|
||||
setPriv(undefined);
|
||||
}
|
||||
}
|
||||
if ("id" in data.update && data.update.id !== gameId) {
|
||||
setGameId(data.update.id);
|
||||
}
|
||||
if ("state" in data.update && data.update.state !== state) {
|
||||
if (data.update.state !== "winner" && winnerDismissed) {
|
||||
setWinnerDismissed(false);
|
||||
}
|
||||
setState(data.update.state);
|
||||
}
|
||||
if ("dice" in data.update && !equal(data.update.dice, dice)) {
|
||||
setDice(data.update.dice);
|
||||
}
|
||||
if ("turn" in data.update && !equal(data.update.turn, turn)) {
|
||||
setTurn(data.update.turn);
|
||||
}
|
||||
if ("color" in data.update && data.update.color !== color) {
|
||||
setColor(data.update.color);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
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) => {
|
||||
if (ws) ws.send(JSON.stringify(update));
|
||||
sendJsonMessage(update);
|
||||
};
|
||||
|
||||
const cbResetConnection = useCallback(() => {
|
||||
let timer: number | null = null;
|
||||
function reset() {
|
||||
timer = null;
|
||||
setRetryConnection(true);
|
||||
}
|
||||
return () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
useEffect(() => {
|
||||
if (lastJsonMessage) {
|
||||
const data = lastJsonMessage as any;
|
||||
switch (data.type) {
|
||||
case "error":
|
||||
console.error(`App - error`, data.error);
|
||||
setError(data.error);
|
||||
break;
|
||||
case "warning":
|
||||
console.warn(`App - warning`, data.warning);
|
||||
setWarning(data.warning);
|
||||
setTimeout(() => {
|
||||
setWarning("");
|
||||
}, 3000);
|
||||
break;
|
||||
case "game-update":
|
||||
console.log("Received game-update:", data.update);
|
||||
if (!loaded) {
|
||||
setLoaded(true);
|
||||
console.log("App: setLoaded to true");
|
||||
}
|
||||
console.log(`app - message - ${data.type}`, data.update);
|
||||
|
||||
if ("private" in data.update && !equal(priv, data.update.private)) {
|
||||
const priv = data.update.private;
|
||||
if (priv.name !== name) {
|
||||
setName(priv.name);
|
||||
console.log("App: setName from priv.name =", priv.name);
|
||||
}
|
||||
if (priv.color !== color) {
|
||||
setColor(priv.color);
|
||||
}
|
||||
setPriv(priv);
|
||||
}
|
||||
|
||||
if ("name" in data.update) {
|
||||
if (data.update.name) {
|
||||
setName(data.update.name);
|
||||
console.log("App: setName from data.update.name =", data.update.name);
|
||||
} else {
|
||||
console.log("App: data.update.name is empty");
|
||||
setWarning("");
|
||||
setError("");
|
||||
setPriv(undefined);
|
||||
}
|
||||
}
|
||||
if ("id" in data.update && data.update.id !== gameId) {
|
||||
setGameId(data.update.id);
|
||||
}
|
||||
if ("state" in data.update && data.update.state !== state) {
|
||||
if (data.update.state !== "winner" && winnerDismissed) {
|
||||
setWinnerDismissed(false);
|
||||
}
|
||||
setState(data.update.state);
|
||||
}
|
||||
if ("dice" in data.update && !equal(data.update.dice, dice)) {
|
||||
setDice(data.update.dice);
|
||||
}
|
||||
if ("turn" in data.update && !equal(data.update.turn, turn)) {
|
||||
setTurn(data.update.turn);
|
||||
}
|
||||
if ("color" in data.update && data.update.color !== color) {
|
||||
setColor(data.update.color);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
timer = window.setTimeout(reset, 5000);
|
||||
};
|
||||
}, [setRetryConnection]);
|
||||
}
|
||||
}, [lastJsonMessage]);
|
||||
|
||||
const resetConnection = cbResetConnection();
|
||||
|
||||
if (global.ws !== connection || global.name !== name || global.gameId !== gameId) {
|
||||
setGlobal({
|
||||
ws: connection,
|
||||
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]);
|
||||
const globalValue = useMemo(() => ({
|
||||
ws: readyState === ReadyState.OPEN ? {} : undefined,
|
||||
name,
|
||||
gameId,
|
||||
}), [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>
|
||||
);
|
||||
|
@ -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"))) {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user