Improved handling ws connection and redirect

This commit is contained in:
aronmal 2023-06-11 00:37:12 +02:00
parent 53a07b21b0
commit 0317a3343c
Signed by: aronmal
GPG key ID: 816B7707426FC612
10 changed files with 100 additions and 50 deletions

View file

@ -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 (
<div className="mx-32 flex flex-col self-stretch rounded-3xl bg-gray-400">
<div className="flex items-center justify-between border-b-2 border-slate-900">
<Icon src="speech_bubble.png">Chat</Icon>
<h1 className="font-farro text-5xl font-medium">
{launching ? (
<WithDots>{"Game is starting in " + launchTime}</WithDots>
<WithDots>
{launchTime < 0
? "Game starts"
: "Game is starting in " + launchTime}
</WithDots>
) : (
<>
Game-PIN:{" "}
{"Game-PIN: "}
{isConnected ? (
<span className="underline">{payload?.gamePin ?? "----"}</span>
) : (
@ -114,8 +127,8 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
disabled={launching}
onClick={() => {
leave(async () => {
await router.push("/")
reset()
await router.push("/")
})
}}
>

View file

@ -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()

View file

@ -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 & Action>()(
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) => {

View file

@ -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 }
}

View file

@ -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 {

View file

@ -3,4 +3,5 @@ import { io } from "socket.io-client"
export const socket: cSocket = io({
path: "/api/ws",
autoConnect: false,
})

View file

@ -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"],
})
}

View file

@ -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)
})
})

View file

@ -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<typeof GameStateSchema>}`

View file

@ -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)