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 { 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 Paper from "@mui/material/Paper";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
@ -44,14 +45,9 @@ const loadAudio = (src: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Table: React.FC = () => {
|
const Table: React.FC = () => {
|
||||||
console.log("Table component rendered");
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [gameId, setGameId] = useState<string | undefined>(params.gameId ? (params.gameId as string) : undefined);
|
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 [name, setName] = useState<string>("");
|
||||||
const [error, setError] = useState<string | undefined>(undefined);
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
const [warning, setWarning] = 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 [houseRulesActive, setHouseRulesActive] = useState<boolean>(false);
|
||||||
const [winnerDismissed, setWinnerDismissed] = useState<boolean>(false);
|
const [winnerDismissed, setWinnerDismissed] = useState<boolean>(false);
|
||||||
const [global, setGlobal] = useState<Record<string, unknown>>({});
|
const [global, setGlobal] = useState<Record<string, unknown>>({});
|
||||||
const [count, setCount] = useState<number>(0);
|
|
||||||
const [audio, setAudio] = useState<boolean>(
|
const [audio, setAudio] = useState<boolean>(
|
||||||
localStorage.getItem("audio") ? JSON.parse(localStorage.getItem("audio") as string) : false
|
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 fields = ["id", "state", "color", "name", "private", "dice", "turn"];
|
||||||
|
|
||||||
const onWsOpen = (event: Event) => {
|
const loc = window.location;
|
||||||
console.log(`ws: open`);
|
const protocol = loc.protocol === "https:" ? "wss" : "ws";
|
||||||
console.log("WebSocket opened, sending game-update and get");
|
const socketUrl = gameId ? `${protocol}://${loc.host}${base}/api/v1/games/ws/${gameId}` : null;
|
||||||
setError("");
|
|
||||||
|
|
||||||
setConnection(ws);
|
const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(socketUrl, {
|
||||||
const sock = event.target as WebSocket;
|
shouldReconnect: (closeEvent) => true,
|
||||||
sock.send(JSON.stringify({ type: "game-update" }));
|
reconnectInterval: 5000,
|
||||||
sock.send(JSON.stringify({ type: "get", fields }));
|
onOpen: () => {
|
||||||
};
|
console.log(`ws: open`);
|
||||||
|
setError("");
|
||||||
const onWsMessage = (event: MessageEvent) => {
|
sendJsonMessage({ type: "game-update" });
|
||||||
const data = JSON.parse(event.data as string);
|
sendJsonMessage({ type: "get", fields });
|
||||||
switch (data.type) {
|
},
|
||||||
case "error":
|
onError: (err) => {
|
||||||
console.error(`App - error`, data.error);
|
console.log("WebSocket error", err);
|
||||||
setError(data.error);
|
const error = `Connection to Ketr Ketran game server failed! Connection attempt will be retried every 5 seconds.`;
|
||||||
break;
|
setError(error);
|
||||||
case "warning":
|
setGlobal(Object.assign({}, global, { ws: undefined }));
|
||||||
console.warn(`App - warning`, data.warning);
|
},
|
||||||
setWarning(data.warning);
|
onClose: () => {
|
||||||
setTimeout(() => {
|
console.log("WebSocket closed");
|
||||||
setWarning("");
|
const error = `Connection to Ketr Ketran game was lost. Attempting to reconnect...`;
|
||||||
}, 3000);
|
setError(error);
|
||||||
break;
|
setGlobal(Object.assign({}, global, { ws: undefined }));
|
||||||
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 sendUpdate = (update: unknown) => {
|
const sendUpdate = (update: unknown) => {
|
||||||
if (ws) ws.send(JSON.stringify(update));
|
sendJsonMessage(update);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cbResetConnection = useCallback(() => {
|
useEffect(() => {
|
||||||
let timer: number | null = null;
|
if (lastJsonMessage) {
|
||||||
function reset() {
|
const data = lastJsonMessage as any;
|
||||||
timer = null;
|
switch (data.type) {
|
||||||
setRetryConnection(true);
|
case "error":
|
||||||
}
|
console.error(`App - error`, data.error);
|
||||||
return () => {
|
setError(data.error);
|
||||||
if (timer) {
|
break;
|
||||||
clearTimeout(timer);
|
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);
|
}
|
||||||
};
|
}, [lastJsonMessage]);
|
||||||
}, [setRetryConnection]);
|
|
||||||
|
|
||||||
const resetConnection = cbResetConnection();
|
const globalValue = useMemo(() => ({
|
||||||
|
ws: readyState === ReadyState.OPEN ? {} : undefined,
|
||||||
if (global.ws !== connection || global.name !== name || global.gameId !== gameId) {
|
name,
|
||||||
setGlobal({
|
gameId,
|
||||||
ws: connection,
|
}), [readyState, name, gameId]);
|
||||||
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]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setGlobal(globalValue);
|
||||||
|
}, [globalValue, setGlobal]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Table useEffect for POST running, gameId =", gameId);
|
||||||
if (gameId) {
|
if (gameId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -242,6 +206,7 @@ const Table: React.FC = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
console.log("POST fetch response status:", res.status);
|
||||||
if (res.status >= 400) {
|
if (res.status >= 400) {
|
||||||
const error =
|
const error =
|
||||||
`Unable to connect to Ketr Ketran game server! ` + `Try refreshing your browser in a few seconds.`;
|
`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();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then((update) => {
|
.then((update) => {
|
||||||
|
console.log("POST fetch response data:", update);
|
||||||
if (update.id !== gameId) {
|
if (update.id !== gameId) {
|
||||||
navigate(`/${update.id}`);
|
navigate(`/${update.id}`);
|
||||||
setGameId(update.id);
|
setGameId(update.id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error("POST fetch error:", error);
|
||||||
});
|
});
|
||||||
}, [gameId, setGameId]);
|
}, [gameId, setGameId]);
|
||||||
|
|
||||||
useEffect(() => {
|
// WebSocket logic moved to useWebSocket
|
||||||
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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state === "volcano") {
|
if (state === "volcano") {
|
||||||
@ -576,6 +482,7 @@ const App: React.FC = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
console.log("GET fetch response status:", res.status);
|
||||||
if (res.status >= 400) {
|
if (res.status >= 400) {
|
||||||
const error =
|
const error =
|
||||||
`Unable to connect to Ketr Ketran game server! ` + `Try refreshing your browser in a few seconds.`;
|
`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();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
console.log("GET fetch response data:", data);
|
||||||
setPlayerId(data.player);
|
setPlayerId(data.player);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch((error) => {
|
||||||
|
console.error("GET fetch error:", error);
|
||||||
|
});
|
||||||
}, [playerId, setPlayerId]);
|
}, [playerId, setPlayerId]);
|
||||||
|
|
||||||
if (!playerId) {
|
if (!playerId) {
|
||||||
@ -596,8 +506,8 @@ const App: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Router basename={base}>
|
<Router basename={base}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<Table />} path="/:gameId" />
|
|
||||||
<Route element={<Table />} path="/" />
|
<Route element={<Table />} path="/" />
|
||||||
|
<Route element={<Table />} path="/:gameId" />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,6 @@ import Videocam from "@mui/icons-material/Videocam";
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import useWebSocket, { ReadyState } from "react-use-websocket";
|
import useWebSocket, { ReadyState } from "react-use-websocket";
|
||||||
import { Session } from "./GlobalContext";
|
|
||||||
import WebRTCStatus from "./WebRTCStatus";
|
import WebRTCStatus from "./WebRTCStatus";
|
||||||
import Moveable from "react-moveable";
|
import Moveable from "react-moveable";
|
||||||
import { flushSync } from "react-dom";
|
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.
|
// 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
|
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 ---------- */
|
/* ---------- Synthetic Tracks Helpers ---------- */
|
||||||
|
|
||||||
// Helper to hash a string to a color
|
// Helper to hash a string to a color
|
||||||
@ -1701,7 +1709,7 @@ const MediaControl: React.FC<MediaControlProps> = ({
|
|||||||
snapThreshold={5}
|
snapThreshold={5}
|
||||||
origin={false}
|
origin={false}
|
||||||
edge
|
edge
|
||||||
onDragStart={(e) => {
|
onDragStart={(e: any) => {
|
||||||
const controls = containerRef.current?.querySelector(".Controls");
|
const controls = containerRef.current?.querySelector(".Controls");
|
||||||
const target = e.inputEvent?.target as HTMLElement;
|
const target = e.inputEvent?.target as HTMLElement;
|
||||||
if (controls && target && (target.closest("button") || target.closest(".MuiIconButton-root"))) {
|
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;
|
let userDB: any, gameDB: any;
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
console.log(`${req.method} ${req.url}`);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
/* App is behind an nginx proxy which we trust, so use the remote address
|
/* App is behind an nginx proxy which we trust, so use the remote address
|
||||||
|
Loading…
x
Reference in New Issue
Block a user