From 0317a3343cbfff3120313d17bf9f508419be085d Mon Sep 17 00:00:00 2001 From: aronmal Date: Sun, 11 Jun 2023 00:37:12 +0200 Subject: [PATCH] Improved handling ws connection and redirect --- leaky-ships/components/Lobby/LobbyFrame.tsx | 25 ++++++++--- leaky-ships/hooks/useDraw.ts | 4 +- leaky-ships/hooks/useGameProps.ts | 12 ++++++ leaky-ships/hooks/useSocket.ts | 47 ++++++++++++++------- leaky-ships/interfaces/NextApiSocket.ts | 8 ++-- leaky-ships/lib/socket.ts | 1 + leaky-ships/pages/api/game/join.ts | 2 +- leaky-ships/pages/api/ws.ts | 44 ++++++++++--------- leaky-ships/prisma/generated/zod/index.ts | 2 +- leaky-ships/prisma/schema.prisma | 5 ++- 10 files changed, 100 insertions(+), 50 deletions(-) diff --git a/leaky-ships/components/Lobby/LobbyFrame.tsx b/leaky-ships/components/Lobby/LobbyFrame.tsx index 6c15900..c488b59 100644 --- a/leaky-ships/components/Lobby/LobbyFrame.tsx +++ b/leaky-ships/components/Lobby/LobbyFrame.tsx @@ -47,13 +47,13 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) { ) useEffect(() => { - if (!launching || launchTime >= 1) return - router.push("/gamefield") + if (!launching || launchTime > 0) return + socket.emit("starting") }, [launching, launchTime, router]) useEffect(() => { if (!launching) return setLaunchTime(3) - if (launchTime === 0) return + if (launchTime < 0) return const timeout = setTimeout(() => { setLaunchTime((e) => e - 1) @@ -67,16 +67,29 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) { socket.emit("update", full) }, [full, payload?.game?.id, isConnected]) + useEffect(() => { + if ( + typeof payload?.game?.state !== "string" || + payload?.game?.state === "lobby" + ) + return + router.push("gamefield") + }) + return (
Chat

{launching ? ( - {"Game is starting in " + launchTime} + + {launchTime < 0 + ? "Game starts" + : "Game is starting in " + launchTime} + ) : ( <> - Game-PIN:{" "} + {"Game-PIN: "} {isConnected ? ( {payload?.gamePin ?? "----"} ) : ( @@ -114,8 +127,8 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) { disabled={launching} onClick={() => { leave(async () => { - await router.push("/") reset() + await router.push("/") }) }} > diff --git a/leaky-ships/hooks/useDraw.ts b/leaky-ships/hooks/useDraw.ts index 5c5b978..fbb249b 100644 --- a/leaky-ships/hooks/useDraw.ts +++ b/leaky-ships/hooks/useDraw.ts @@ -1,4 +1,4 @@ -import { Draw, DrawLineProps, Point } from "../interfaces/frontend" +import { Draw, Point } from "../interfaces/frontend" import { useDrawProps } from "./useDrawProps" import { socket } from "@lib/socket" import { useEffect, useRef, useState } from "react" @@ -108,7 +108,7 @@ export const useDraw = () => { drawLine({ prevPoint, currentPoint, ctx, color }) }) - socket.on("clear", clear) + socket.on("canvas-clear", clear) return () => { socket.removeAllListeners() diff --git a/leaky-ships/hooks/useGameProps.ts b/leaky-ships/hooks/useGameProps.ts index cf6d7f2..c50e27d 100644 --- a/leaky-ships/hooks/useGameProps.ts +++ b/leaky-ships/hooks/useGameProps.ts @@ -45,6 +45,7 @@ export type Action = { full: (newProps: GamePropsSchema) => void leave: (cb: () => void) => void setIsReady: (payload: { i: number; isReady: boolean }) => void + starting: () => void setIsConnected: (payload: { i: number; isConnected: boolean }) => void reset: () => void } @@ -159,6 +160,17 @@ export const useGameProps = create()( state.userStates[i].isConnected = true }) ), + starting: () => + set( + produce((state: State) => { + if (state.payload?.game?.state !== "lobby") return + state.payload.game.state = "starting" + state.userStates = state.userStates.map((e) => ({ + ...e, + isReady: false, + })) + }) + ), setIsConnected: ({ i, isConnected }) => set( produce((state: State) => { diff --git a/leaky-ships/hooks/useSocket.ts b/leaky-ships/hooks/useSocket.ts index d5e2f3e..73eaf9b 100644 --- a/leaky-ships/hooks/useSocket.ts +++ b/leaky-ships/hooks/useSocket.ts @@ -1,5 +1,7 @@ +import { isAuthenticated } from "../pages/start" import { useGameProps } from "./useGameProps" import { socket } from "@lib/socket" +import { GamePropsSchema } from "@lib/zodSchemas" import status from "http-status" import { useSession } from "next-auth/react" import { useRouter } from "next/router" @@ -16,6 +18,7 @@ function useSocket() { setSetting, full, setIsReady, + starting, setIsConnected, } = useGameProps() const { data: session } = useSession() @@ -27,6 +30,10 @@ function useSocket() { if (!isIndex) return { i: undefined, isIndex } return { i, isIndex } }, [payload?.users, session?.user?.id]) + const isConnected = useMemo( + () => (isIndex ? userStates[i].isConnected : isConnectedState), + [i, isConnectedState, isIndex, userStates] + ) useEffect(() => { if (!isIndex) return @@ -37,8 +44,6 @@ function useSocket() { }, [i, isConnectedState, isIndex, setIsConnected]) useEffect(() => { - if (!session?.user.id) return - socket.on("connect", () => { console.log("connected") toast.dismiss("connect_error") @@ -73,7 +78,7 @@ function useSocket() { socket.on("playerEvent", (event) => { const { type, i } = event let message: string - console.log(type) + console.log("playerEvent", type) switch (type) { case "disconnect": setIsConnected({ @@ -115,6 +120,8 @@ function useSocket() { socket.on("isReady", setIsReady) + socket.on("starting", starting) + socket.on("disconnect", () => { console.log("disconnect") setIsConnectedState(false) @@ -125,27 +132,37 @@ function useSocket() { } }, [ full, + payload?.game?.id, router, session?.user.id, setIsConnected, setIsReady, setPlayer, setSetting, + starting, userStates, ]) - // useEffect(() => { - // if (!isConnected) return - // let count = 0 - // const interval = setInterval(() => { - // const start = Date.now() - // socket.volatile.emit("ping", ++count, (count) => { - // const duration = Date.now() - start - // console.log("ping", count, duration) - // }) - // }, 5000) - // return () => clearInterval(interval) - // }, [isConnected]) + useEffect(() => { + if (!payload?.game?.id) { + socket.disconnect() + fetch("/api/game/running", { + method: "GET", + }) + .then(isAuthenticated) + .then((game) => GamePropsSchema.parse(game)) + .then((res) => full(res)) + .catch() + return + } + if (isConnected) return + socket.connect() + const start = Date.now() + socket.volatile.emit("ping", () => { + const duration = Date.now() - start + console.log("ping", duration) + }) + }, [full, isConnected, payload?.game?.id]) return { isConnected: isIndex ? userStates[i].isConnected : isConnectedState } } diff --git a/leaky-ships/interfaces/NextApiSocket.ts b/leaky-ships/interfaces/NextApiSocket.ts index 269d545..42a147c 100644 --- a/leaky-ships/interfaces/NextApiSocket.ts +++ b/leaky-ships/interfaces/NextApiSocket.ts @@ -47,20 +47,22 @@ export interface ServerToClientEvents { "get-canvas-state": () => void "canvas-state-from-server": (state: string, userIndex: number) => void "draw-line": (props: DrawLineProps, userIndex: number) => void - clear: () => void + "canvas-clear": () => void + starting: () => void } export interface ClientToServerEvents { update: (callback: (game: GamePropsSchema) => void) => void isReady: (isReady: boolean) => void isConnected: (isReady: boolean) => void - ping: (count: number, callback: (count: number) => void) => void + ping: (callback: () => void) => void join: (withAck: (ack: boolean) => void) => void gameSetting: (payload: GameSettings, callback: (hash: string) => void) => void leave: (withAck: (ack: boolean) => void) => void "canvas-state": (state: string) => void "draw-line": (props: DrawLineProps) => void - clear: () => void + "canvas-clear": () => void + starting: () => void } interface InterServerEvents { diff --git a/leaky-ships/lib/socket.ts b/leaky-ships/lib/socket.ts index bf08521..cd10517 100644 --- a/leaky-ships/lib/socket.ts +++ b/leaky-ships/lib/socket.ts @@ -3,4 +3,5 @@ import { io } from "socket.io-client" export const socket: cSocket = io({ path: "/api/ws", + autoConnect: false, }) diff --git a/leaky-ships/pages/api/game/join.ts b/leaky-ships/pages/api/game/join.ts index dcd7307..4c5b267 100644 --- a/leaky-ships/pages/api/game/join.ts +++ b/leaky-ships/pages/api/game/join.ts @@ -55,7 +55,7 @@ export default async function join( if (games.length) { return sendResponse(req, res, { message: "Spieler ist bereits in Spiel!", - statusCode: 409, + redirectUrl: "/api/game/running", type: ["infoCyan"], }) } diff --git a/leaky-ships/pages/api/ws.ts b/leaky-ships/pages/api/ws.ts index a8a6b5c..c8c620a 100644 --- a/leaky-ships/pages/api/ws.ts +++ b/leaky-ships/pages/api/ws.ts @@ -2,7 +2,6 @@ import { NextApiResponseWithSocket, sServer, } from "../../interfaces/NextApiSocket" -import { DrawLineProps } from "../../interfaces/frontend" import { composeBody, gameSelects, @@ -108,9 +107,7 @@ const SocketHandler = async ( socket.to(game.id).emit("gameSetting", payload, hash) }) - socket.on("ping", (count, callback) => { - callback(count) - }) + socket.on("ping", (callback) => callback()) socket.on("leave", async (cb) => { if (!socket.data.gameId || !socket.data.user?.id) return cb(false) @@ -192,23 +189,31 @@ const SocketHandler = async ( .emit("canvas-state-from-server", state, socket.data.index) }) - socket.on( - "draw-line", - ({ prevPoint, currentPoint, color }: DrawLineProps) => { - if (!socket.data.gameId || !socket.data.index) return - socket - .to(socket.data.gameId) - .emit( - "draw-line", - { prevPoint, currentPoint, color }, - socket.data.index - ) - } - ) + socket.on("draw-line", ({ prevPoint, currentPoint, color }) => { + if (!socket.data.gameId || !socket.data.index) return + socket + .to(socket.data.gameId) + .emit( + "draw-line", + { prevPoint, currentPoint, color }, + socket.data.index + ) + }) - socket.on("clear", () => { + socket.on("canvas-clear", () => { if (!socket.data.gameId) return - socket.to(socket.data.gameId).emit("clear") + socket.to(socket.data.gameId).emit("canvas-clear") + }) + + socket.on("starting", async () => { + if (socket.data.index !== 0 || !socket.data.gameId) return + await prisma.game.update({ + where: { id: socket.data.gameId }, + data: { + state: "starting", + }, + }) + io.to(socket.data.gameId).emit("starting") }) socket.on("disconnecting", async () => { @@ -225,7 +230,6 @@ const SocketHandler = async ( }) socket.on("disconnect", () => { - // socket.rooms.size === 0 logging("Disconnect: " + socket.id, ["debug"], socket.request) }) }) diff --git a/leaky-ships/prisma/generated/zod/index.ts b/leaky-ships/prisma/generated/zod/index.ts index 54f56d5..679fa70 100644 --- a/leaky-ships/prisma/generated/zod/index.ts +++ b/leaky-ships/prisma/generated/zod/index.ts @@ -32,7 +32,7 @@ export const User_GameScalarFieldEnumSchema = z.enum(['id','createdAt','gameId', export const VerificationTokenScalarFieldEnumSchema = z.enum(['identifier','token','expires']); -export const GameStateSchema = z.enum(['launching','running','ended']); +export const GameStateSchema = z.enum(['lobby','starting','running','ended']); export type GameStateType = `${z.infer}` diff --git a/leaky-ships/prisma/schema.prisma b/leaky-ships/prisma/schema.prisma index 98c1c1a..0d572c9 100644 --- a/leaky-ships/prisma/schema.prisma +++ b/leaky-ships/prisma/schema.prisma @@ -69,7 +69,8 @@ model VerificationToken { } enum GameState { - launching + lobby + starting running ended } @@ -78,7 +79,7 @@ model Game { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - state GameState @default(launching) + state GameState @default(lobby) allowSpectators Boolean @default(true) allowSpecials Boolean @default(true) allowChat Boolean @default(true)