From 8c57670742d142d47beb06494c582dbda08cc6a6 Mon Sep 17 00:00:00 2001 From: Ralex91 <95540504+Ralex91@users.noreply.github.com> Date: Sat, 3 Feb 2024 00:14:04 +0100 Subject: [PATCH] Add Kick player & Validators --- package-lock.json | 30 ++++++++++++++++++++ package.json | 1 + socket/package-lock.json | 40 ++++++++++++++++++++++++++- socket/package.json | 3 +- socket/src/index.js | 2 +- socket/src/roles/player.js | 36 ++++++++++++++++++------ socket/src/utils/cooldown.js | 2 +- socket/src/validator.js | 12 ++++++++ src/components/Toaster.jsx | 26 +++++++++++++++++ src/components/game/GameWrapper.jsx | 13 ++++++++- src/components/game/join/Room.jsx | 2 +- src/components/game/join/Username.jsx | 23 ++++++++++----- src/components/game/states/Room.jsx | 5 ++++ src/pages/_app.js | 2 ++ src/pages/game.jsx | 13 +++++---- src/pages/index.js | 7 +++-- src/pages/managerold.jsx | 2 +- src/validator.js | 0 18 files changed, 188 insertions(+), 31 deletions(-) create mode 100644 socket/src/validator.js create mode 100644 src/components/Toaster.jsx delete mode 100644 src/validator.js diff --git a/package-lock.json b/package-lock.json index 1912f20..0a9421f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "next": "14.1.0", "react": "^18", "react-dom": "^18", + "react-hot-toast": "^2.4.1", "socket.io": "^4.7.4", "socket.io-client": "^4.7.4" }, @@ -1222,6 +1223,12 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -2303,6 +2310,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3820,6 +3835,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 2eda4a8..646951a 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "next": "14.1.0", "react": "^18", "react-dom": "^18", + "react-hot-toast": "^2.4.1", "socket.io": "^4.7.4", "socket.io-client": "^4.7.4" }, diff --git a/socket/package-lock.json b/socket/package-lock.json index f0e358b..254df4b 100644 --- a/socket/package-lock.json +++ b/socket/package-lock.json @@ -7,7 +7,8 @@ "": { "version": "0.1.0", "dependencies": { - "socket.io": "^4.7.4" + "socket.io": "^4.7.4", + "yup": "^1.3.3" } }, "node_modules/@socket.io/component-emitter": { @@ -160,6 +161,11 @@ "node": ">=0.10.0" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "node_modules/socket.io": { "version": "4.7.4", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.4.tgz", @@ -197,6 +203,27 @@ "node": ">=10.0.0" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -229,6 +256,17 @@ "optional": true } } + }, + "node_modules/yup": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.3.3.tgz", + "integrity": "sha512-v8QwZSsHH2K3/G9WSkp6mZKO+hugKT1EmnMqLNUcfu51HU9MDyhlETT/JgtzprnrnQHPWsjc6MUDMBp/l9fNnw==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } } } } diff --git a/socket/package.json b/socket/package.json index e0a0c91..90fe8e9 100644 --- a/socket/package.json +++ b/socket/package.json @@ -6,6 +6,7 @@ "start": "node ./src/index.js" }, "dependencies": { - "socket.io": "^4.7.4" + "socket.io": "^4.7.4", + "yup": "^1.3.3" } } diff --git a/socket/src/index.js b/socket/src/index.js index 3114155..1323832 100644 --- a/socket/src/index.js +++ b/socket/src/index.js @@ -29,7 +29,7 @@ io.on("connection", (socket) => { Manager.createRoom(gameState, io, socket), ) socket.on("manager:kickPlayer", (playerId) => - Manager.kickPlayer(gameState, socket, io, playerId), + Manager.kickPlayer(gameState, io, socket, playerId), ) socket.on("manager:startGame", () => Manager.startGame(gameState, io, socket)) diff --git a/socket/src/roles/player.js b/socket/src/roles/player.js index 3218c65..5b7c20f 100644 --- a/socket/src/roles/player.js +++ b/socket/src/roles/player.js @@ -1,23 +1,43 @@ import convertTimeToPoint from "../utils/convertTimeToPoint.js" import { abortCooldown } from "../utils/cooldown.js" +import { inviteCodeValidator, usernameValidator } from "../validator.js" const Player = { - checkRoom: (game, io, socket, roomId) => { - if (!game.room || roomId !== game.room) { - io.to(socket.id).emit("game:errorMessage", "Room not found") - console.log("zaza") + checkRoom: async (game, io, socket, roomId) => { + try { + await inviteCodeValidator.validate(roomId) + } catch (error) { + socket.emit("game:errorMessage", error.errors[0]) return } - io.to(socket.id).emit("game:successRoom", roomId) + if (!game.room || roomId !== game.room) { + socket.emit("game:errorMessage", "Room not found") + return + } + + socket.emit("game:successRoom", roomId) }, - join: (game, io, socket, player) => { - if (!player.room || !player.room || game.started) { + join: async (game, io, socket, player) => { + try { + await usernameValidator.validate(player.username) + } catch (error) { + socket.emit("game:errorMessage", error.errors[0]) return } - console.log("joined", player) + if (!game.room || player.room !== game.room) { + socket.emit("game:errorMessage", "Room not found") + return + } + + if (game.started) { + socket.emit("game:errorMessage", "Game already started") + return + } + + console.log("New Player", player) socket.join(player.room) diff --git a/socket/src/utils/cooldown.js b/socket/src/utils/cooldown.js index de2dcd3..2a55cdf 100644 --- a/socket/src/utils/cooldown.js +++ b/socket/src/utils/cooldown.js @@ -23,4 +23,4 @@ export const cooldown = (ms, io, room) => { }) } -export const sleep = (sec) => new Promise((r) => setTimeout(r, sec * 1000)) \ No newline at end of file +export const sleep = (sec) => new Promise((r) => setTimeout(r, sec * 1000)) diff --git a/socket/src/validator.js b/socket/src/validator.js new file mode 100644 index 0000000..2c6f0dc --- /dev/null +++ b/socket/src/validator.js @@ -0,0 +1,12 @@ +import yup from "yup" + +export const usernameValidator = yup + .string() + .required("Username is required") + .min(4, "Username cannot be less than 4 characters") + .max(20, "Username cannot exceed 20 characters") + +export const inviteCodeValidator = yup + .string() + .required("Invite code is required") + .length(6, "Invalid invite code") diff --git a/src/components/Toaster.jsx b/src/components/Toaster.jsx new file mode 100644 index 0000000..1d14263 --- /dev/null +++ b/src/components/Toaster.jsx @@ -0,0 +1,26 @@ +import clsx from "clsx" +import { Toaster as ToasterRaw, ToastBar } from "react-hot-toast" + +const Toaster = () => ( + + {(t) => ( + + {({ icon, message }) => ( + <> + {icon} + {message} + + )} + + )} + +) + +export default Toaster diff --git a/src/components/game/GameWrapper.jsx b/src/components/game/GameWrapper.jsx index 9b7de9e..7bdb0dd 100644 --- a/src/components/game/GameWrapper.jsx +++ b/src/components/game/GameWrapper.jsx @@ -4,14 +4,24 @@ import background from "@/assets/background.webp" import { usePlayerContext } from "@/context/player" import { useSocketContext } from "@/context/socket" import { useEffect, useState } from "react" +import { useRouter } from "next/router" export default function GameWrapper({ children, textNext, onNext, manager }) { const { socket } = useSocketContext() - const { player } = usePlayerContext() + const { player, dispatch } = usePlayerContext() + const router = useRouter() const [questionState, setQuestionState] = useState() useEffect(() => { + socket.on("game:kick", () => { + dispatch({ + type: "LOGOUT", + }) + + router.replace("/") + }) + socket.on("game:updateQuestion", ({ current, total }) => { setQuestionState({ current, @@ -20,6 +30,7 @@ export default function GameWrapper({ children, textNext, onNext, manager }) { }) return () => { + socket.off("game:kick") socket.off("game:updateQuestion") } }, []) diff --git a/src/components/game/join/Room.jsx b/src/components/game/join/Room.jsx index ac89326..d1cd28c 100644 --- a/src/components/game/join/Room.jsx +++ b/src/components/game/join/Room.jsx @@ -2,7 +2,7 @@ import { usePlayerContext } from "@/context/player" import Form from "@/components/Form" import Button from "@/components/Button" import Input from "@/components/Input" -import { useEffect, useMemo, useState } from "react" +import { useEffect, useState } from "react" import { socket } from "@/context/socket" export default function Room() { diff --git a/src/components/game/join/Username.jsx b/src/components/game/join/Username.jsx index c7ec880..b82fc58 100644 --- a/src/components/game/join/Username.jsx +++ b/src/components/game/join/Username.jsx @@ -2,7 +2,7 @@ import { usePlayerContext } from "@/context/player" import Form from "@/components/Form" import Button from "@/components/Button" import Input from "@/components/Input" -import { useState } from "react" +import { useEffect, useState } from "react" import { useSocketContext } from "@/context/socket" import { useRouter } from "next/router" @@ -14,14 +14,23 @@ export default function Username() { const [username, setUsername] = useState("") const handleJoin = () => { - dispatch({ - type: "LOGIN", - payload: username, + socket.emit("player:join", { username: username, room: player.room }) + } + + useEffect(() => { + socket.on("game:successJoin", () => { + dispatch({ + type: "LOGIN", + payload: username, + }) + + router.replace("/game") }) - socket.emit("player:join", { username: username, room: player.room }) - router.push("/game") - } + return () => { + socket.off("game:successJoin") + } + }, [username]) return ( <> diff --git a/src/components/game/states/Room.jsx b/src/components/game/states/Room.jsx index c46b1bf..01bb275 100644 --- a/src/components/game/states/Room.jsx +++ b/src/components/game/states/Room.jsx @@ -10,8 +10,13 @@ export default function Room({ data: { text, inviteCode } }) { setPlayerList([...playerList, player]) }) + socket.on("manager:playerKicked", (playerId) => { + setPlayerList(playerList.filter((p) => p.id !== playerId)) + }) + return () => { socket.off("manager:newPlayer") + socket.off("manager:playerKicked") } }, [playerList]) diff --git a/src/pages/_app.js b/src/pages/_app.js index 53f9f48..f0129a7 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -1,3 +1,4 @@ +import Toaster from "@/components/Toaster" import { PlayerContextProvider } from "@/context/player" import { SocketContextProvider } from "@/context/socket" import "@/styles/globals.css" @@ -26,6 +27,7 @@ export default function App({ Component, pageProps }) { + ) } diff --git a/src/pages/game.jsx b/src/pages/game.jsx index 6dd24e3..a8f4205 100644 --- a/src/pages/game.jsx +++ b/src/pages/game.jsx @@ -9,7 +9,7 @@ import Start from "@/components/game/states/Start" import { usePlayerContext } from "@/context/player" import { useSocketContext } from "@/context/socket" import { useRouter } from "next/router" -import { createElement, useMemo, useState } from "react" +import { createElement, useEffect, useState } from "react" const gameStateComponent = { SELECT_ANSWER: Answers, @@ -26,10 +26,11 @@ export default function Game() { const { socket } = useSocketContext() const { player } = usePlayerContext() - if (!player) { - //router.push("/") - return - } + useEffect(() => { + if (!player) { + router.replace("/") + } + }, []) const [state, setState] = useState({ status: { @@ -42,7 +43,7 @@ export default function Game() { }, }) - useMemo(() => { + useEffect(() => { socket.on("game:status", (status) => { setState({ ...state, diff --git a/src/pages/index.js b/src/pages/index.js index e233b43..54b63ef 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -4,12 +4,13 @@ import Form from "@/components/Form" import Button from "@/components/Button" import Input from "@/components/Input" import logo from "@/assets/logo.svg" -import { useEffect, useMemo, useState } from "react" +import { useEffect, useState } from "react" import Loader from "@/components/Loader" import { usePlayerContext } from "@/context/player" import Room from "@/components/game/join/Room" import Username from "@/components/game/join/Username" import { useSocketContext } from "@/context/socket" +import toast from "react-hot-toast" export default function Home() { const [loading, setLoading] = useState(false) @@ -17,9 +18,9 @@ export default function Home() { const { player, dispatch } = usePlayerContext() const { socket } = useSocketContext() - useMemo(() => { + useEffect(() => { socket.on("game:errorMessage", (message) => { - console.log(message) + toast.error(message) }) return () => { diff --git a/src/pages/managerold.jsx b/src/pages/managerold.jsx index b1a1599..6d12aba 100644 --- a/src/pages/managerold.jsx +++ b/src/pages/managerold.jsx @@ -4,7 +4,7 @@ import Leaderboard from "@/components/game/states/Leaderboard" import Question from "@/components/game/states/Question" import Wait from "@/components/game/states/Wait" import { useSocketContext } from "@/context/socket" -import { createElement, useEffect, useMemo, useState } from "react" +import { createElement, useEffect, useState } from "react" export default function Manager({ children }) { const { socket } = useSocketContext() diff --git a/src/validator.js b/src/validator.js deleted file mode 100644 index e69de29..0000000